Repository: denoland/deno
Pull request: fix(lsp): do not show import-map-remap diagnostic when referrer and target are within the entry base

For example, if someone has `"$src/": "./src"` in their import map, this will no longer show a diagnostic when someone imports `"./a.ts"` from ./src/b.ts`

Closes https://github.com/denoland/deno/issues/17867

Changed files:
filename: cli/lsp/diagnostics.rs
patch: @@ -1749,6 +1749,39 @@ fn diagnose_dependency(
   dependency_key: &str,
   dependency: &deno_graph::Dependency,
 ) {
+  /// Given a specifier and a referring specifier, determine if a value in the
+  /// import map could be used as an import specifier that resolves using the
+  /// import map.
+  ///
+  /// This was inlined from the import_map crate in order to ignore more
+  /// entries.
+  fn import_map_lookup(
+    import_map: &ImportMap,
+    specifier: &Url,
+    referrer: &Url,
+  ) -> Option<String> {
+    let specifier_str = specifier.as_str();
+    for entry in import_map.entries_for_referrer(referrer) {
+      if let Some(address) = entry.value {
+        let address_str = address.as_str();
+        if referrer.as_str().starts_with(address_str) {
+          // ignore when the referrer has a common base with the
+          // import map entry (ex. `./src/a.ts` importing `./src/b.ts`
+          // and there's a `"$src/": "./src/"` import map entry)
+          continue;
+        }
+        if address_str == specifier_str {
+          return Some(entry.raw_key.to_string());
+        }
+        if address_str.ends_with('/') && specifier_str.starts_with(address_str)
+        {
+          return Some(specifier_str.replace(address_str, entry.raw_key));
+        }
+      }
+    }
+    None
+  }
+
   let referrer = referrer_doc.specifier();
   if snapshot.resolver.in_node_modules(referrer) {
     return; // ignore, surface typescript errors instead
@@ -1765,7 +1798,9 @@ fn diagnose_dependency(
       .ok()
       .or_else(|| dependency.maybe_type.ok());
     if let Some(resolved) = resolved {
-      if let Some(to) = import_map.lookup(&resolved.specifier, referrer) {
+      if let Some(to) =
+        import_map_lookup(import_map, &resolved.specifier, referrer)
+      {
         if dependency_key != to {
           diagnostics.push(
             DenoDiagnostic::ImportMapRemap {
@@ -2144,7 +2179,13 @@ let c: number = "a";
         ),
         (
           "a/file.ts",
-          "import { assert } from \"../std/assert/mod.ts\";\n\nassert();\n",
+          "import { assert } from \"../std/assert/mod.ts\";\n\nassert();\nexport function a() {}",
+          1,
+          LanguageId::TypeScript,
+        ),
+        (
+          "a/file2.ts",
+          "import { a } from './file.ts';\nconsole.log(a);\n",
           1,
           LanguageId::TypeScript,
         ),
@@ -2153,7 +2194,9 @@ let c: number = "a";
         "a/deno.json",
         r#"{
         "imports": {
-          "/~/std/": "../std/"
+          "/~/std/": "../std/",
+          "$@/": "./",
+          "$a/": "../a/"
         }
       }"#,
       )),
@@ -2162,7 +2205,7 @@ let c: number = "a";
     let config = mock_config();
     let token = CancellationToken::new();
     let actual = generate_all_deno_diagnostics(&snapshot, &config, token);
-    assert_eq!(actual.len(), 2);
+    assert_eq!(actual.len(), 3);
     for record in actual {
       let relative_specifier =
         temp_dir.url().make_relative(&record.specifier).unwrap();
@@ -2195,6 +2238,9 @@ let c: number = "a";
             }
           ])
         ),
+        "a/file2.ts" => {
+          assert_eq!(json!(record.versioned.diagnostics), json!([]))
+        }
         _ => unreachable!("unexpected specifier {}", record.specifier),
       }
     }