          sym-ns-str (namespace sym)]
 
      (if (= "js" sym-ns-str)
-       (do (when (contains? locals (-> sym name symbol))
-             (ana/warning :js-shadowed-by-local env {:name sym}))
-
+       ;; js/... vars
+       (let [symn (-> sym name symbol)
+             shadowed-by-local (handle-symbol-local symn (get locals symn))]
+         (cond
+           (some? shadowed-by-local)
+           (do (ana/warning :js-shadowed-by-local env {:name sym})
+               (assoc shadowed-by-local :op :local))
+
+           :else
            ;; always record all fully qualified js/foo.bar calls
            ;; except for the code emitted by cljs.core/exists?
            ;; (exists? some.foo/bar)
            ;; checks js/some, js/some.foo, js/some.foo.bar
-           (when-not (-> sym meta ::ana/no-resolve)
-             (let [[global & props]
-                   (str/split (name sym) #"\.")]
-
-               ;; do not record access to
-               ;; js/goog.string.format
-               ;; js/cljs.core.assoc
-               ;; just in case someone does that, we won't need externs for those
-               (when-not (contains? known-safe-js-globals global)
-                 (shadow-js-access-global current-ns global)
-                 (when (seq props)
-                   (doseq [prop props]
-                     (shadow-js-access-property current-ns prop))))))
-
-           {:name sym
-            :ns 'js
-            :tag 'js
-            :ret-tag 'js})
-
+           (do (when-not (-> sym meta ::ana/no-resolve)
+                 (let [[global & props]
+                       (str/split (name sym) #"\.")]
+
+                   ;; do not record access to
+                   ;; js/goog.string.format
+                   ;; js/cljs.core.assoc
+                   ;; just in case someone does that, we won't need externs for those
+                   (when-not (contains? known-safe-js-globals global)
+                     (shadow-js-access-global current-ns global)
+                     (when (seq props)
+                       (doseq [prop props]
+                         (shadow-js-access-property current-ns prop))))))
+
+               {:op :js-var
+                :name sym
+                :ns 'js
+                :tag 'js
+                :ret-tag 'js})))
+
+       ;; none js/... vars
        (let [s (str sym)
-             lb (get locals sym)
+             lb (handle-symbol-local sym (get locals sym))
              current-ns-info (ana/gets @env/*compiler* ::ana/namespaces current-ns)]
 
          (cond
-           (some? lb) lb
+           (some? lb) (assoc lb :op :local)
 
            (some? sym-ns-str)
            (let [ns sym-ns-str

           (let [idx (.indexOf s ".")
                 prefix (symbol (subs s 0 idx))
                 suffix (subs s (inc idx))]
             (if-some [lb (get locals prefix)]
               {:name (symbol (str (:name lb)) suffix)}
               (if-some [full-ns (ana/gets current-ns-info :imports prefix)]
                 {:name (symbol (str full-ns) suffix)}
                 (if-some [info (ana/gets current-ns-info :defs prefix)]
                   (merge info
                     {:name (symbol (str current-ns) (str sym))
                      :ns current-ns})
                   (merge (ana/gets @env/*compiler* ::ana/namespaces prefix :defs (symbol suffix))
                     {:name (if (= "" prefix) (symbol suffix) (symbol (str prefix) suffix))
                      :ns prefix})))))

           (some? (ana/gets current-ns-info :uses sym))

                 (confirm env current-ns sym))
               (resolve-cljs-var current-ns sym)
               )))))))

(defn shadow-resolve-var-checked
  ([env sym]
   {:post [(contains? % :op)]}
   (shadow-resolve-var env sym nil))
  ([env sym confirm]
   {:post [(contains? % :op)]}
   (shadow-resolve-var env sym confirm)))

(defn infer-externs-dot
  [{:keys [form form-meta method field target-tag env prop tag] :as ast}
