diff --git a/cli/jsr.rs b/cli/jsr.rs index c4cb87dbd9..bdfba7f103 100644 --- a/cli/jsr.rs +++ b/cli/jsr.rs @@ -111,12 +111,32 @@ impl JsrCacheResolver { ) -> Option { let info = self.package_version_info(nv)?; let path = path.strip_prefix("./").unwrap_or(path); + let mut sloppy_fallback = None; for (export, path_) in info.exports() { - if path_.strip_prefix("./").unwrap_or(path_) == path { + let path_ = path_.strip_prefix("./").unwrap_or(path_); + if path_ == path { return Some(export.strip_prefix("./").unwrap_or(export).to_string()); } + // TSC in some cases will suggest a `.js` import path for a `.d.ts` source + // file. + if sloppy_fallback.is_none() { + let path = path + .strip_suffix(".js") + .or_else(|| path.strip_suffix(".mjs")) + .or_else(|| path.strip_suffix(".cjs")) + .unwrap_or(path); + let path_ = path_ + .strip_suffix(".d.ts") + .or_else(|| path_.strip_suffix(".d.mts")) + .or_else(|| path_.strip_suffix(".d.cts")) + .unwrap_or(path_); + if path_ == path { + sloppy_fallback = + Some(export.strip_prefix("./").unwrap_or(export).to_string()); + } + } } - None + sloppy_fallback } pub fn lookup_req_for_nv(&self, nv: &PackageNv) -> Option { diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs index 23b6bb0999..133f5f0acf 100644 --- a/cli/lsp/analysis.rs +++ b/cli/lsp/analysis.rs @@ -544,7 +544,10 @@ fn fix_ts_import_action( action: &tsc::CodeFixAction, import_mapper: &TsResponseImportMapper, ) -> Result { - if action.fix_name == "import" { + if matches!( + action.fix_name.as_str(), + "import" | "fixMissingFunctionDeclaration" + ) { let change = action .changes .first() diff --git a/tests/integration/lsp_tests.rs b/tests/integration/lsp_tests.rs index 3473248996..ed95541d2e 100644 --- a/tests/integration/lsp_tests.rs +++ b/tests/integration/lsp_tests.rs @@ -5364,6 +5364,126 @@ fn lsp_jsr_auto_import_completion_import_map() { client.shutdown(); } +#[test] +fn lsp_jsr_code_action_missing_declaration() { + let context = TestContextBuilder::new() + .use_http_server() + .use_temp_cwd() + .build(); + let temp_dir = context.temp_dir(); + let file = source_file( + temp_dir.path().join("file.ts"), + r#" + import { someFunction } from "jsr:@denotest/types-file"; + assertReturnType(someFunction()); + "#, + ); + let mut client = context.new_lsp_command().build(); + client.initialize_default(); + client.write_request( + "workspace/executeCommand", + json!({ + "command": "deno.cache", + "arguments": [[], file.uri()], + }), + ); + client.did_open_file(&file); + let res = client.write_request( + "textDocument/codeAction", + json!({ + "textDocument": { + "uri": file.uri(), + }, + "range": { + "start": { "line": 2, "character": 6 }, + "end": { "line": 2, "character": 22 }, + }, + "context": { + "diagnostics": [ + { + "range": { + "start": { "line": 2, "character": 6 }, + "end": { "line": 2, "character": 22 }, + }, + "severity": 8, + "code": 2304, + "source": "deno-ts", + "message": "Cannot find name 'assertReturnType'.", + "relatedInformation": [], + }, + ], + "only": ["quickfix"], + }, + }), + ); + assert_eq!( + res, + json!([ + { + "title": "Add missing function declaration 'assertReturnType'", + "kind": "quickfix", + "diagnostics": [ + { + "range": { + "start": { + "line": 2, + "character": 6, + }, + "end": { + "line": 2, + "character": 22, + }, + }, + "severity": 8, + "code": 2304, + "source": "deno-ts", + "message": "Cannot find name 'assertReturnType'.", + "relatedInformation": [], + }, + ], + "edit": { + "documentChanges": [ + { + "textDocument": { + "uri": file.uri(), + "version": 1, + }, + "edits": [ + { + "range": { + "start": { + "line": 1, + "character": 6, + }, + "end": { + "line": 1, + "character": 6, + }, + }, + "newText": "import { ReturnType } from \"jsr:@denotest/types-file/types\";\n", + }, + { + "range": { + "start": { + "line": 3, + "character": 0, + }, + "end": { + "line": 3, + "character": 0, + }, + }, + "newText": "\n function assertReturnType(arg0: ReturnType) {\n throw new Error(\"Function not implemented.\");\n }\n", + }, + ], + }, + ], + }, + }, + ]) + ); +} + #[test] fn lsp_code_actions_deno_cache_npm() { let context = TestContextBuilder::new().use_temp_cwd().build(); diff --git a/tests/registry/jsr/@denotest/types-file/1.0.0/mod.ts b/tests/registry/jsr/@denotest/types-file/1.0.0/mod.ts new file mode 100644 index 0000000000..4d0d73bb51 --- /dev/null +++ b/tests/registry/jsr/@denotest/types-file/1.0.0/mod.ts @@ -0,0 +1,5 @@ +import type { ReturnType } from "./types.d.ts"; + +export function someFunction(): ReturnType { + return {}; +} diff --git a/tests/registry/jsr/@denotest/types-file/1.0.0/types.d.ts b/tests/registry/jsr/@denotest/types-file/1.0.0/types.d.ts new file mode 100644 index 0000000000..dabbb6a374 --- /dev/null +++ b/tests/registry/jsr/@denotest/types-file/1.0.0/types.d.ts @@ -0,0 +1 @@ +export interface ReturnType {} diff --git a/tests/registry/jsr/@denotest/types-file/1.0.0_meta.json b/tests/registry/jsr/@denotest/types-file/1.0.0_meta.json new file mode 100644 index 0000000000..99ba71b5f2 --- /dev/null +++ b/tests/registry/jsr/@denotest/types-file/1.0.0_meta.json @@ -0,0 +1,28 @@ +{ + "exports": { + ".": "./mod.ts", + "./types": "./types.d.ts" + }, + "moduleGraph1": { + "/types.d.ts": {}, + "/mod.ts": { + "dependencies": [ + { + "type": "static", + "kind": "importType", + "specifier": "./types.d.ts", + "specifierRange": [ + [ + 0, + 42 + ], + [ + 0, + 46 + ] + ] + } + ] + } + } +} diff --git a/tests/registry/jsr/@denotest/types-file/meta.json b/tests/registry/jsr/@denotest/types-file/meta.json new file mode 100644 index 0000000000..02601e4d0d --- /dev/null +++ b/tests/registry/jsr/@denotest/types-file/meta.json @@ -0,0 +1,5 @@ +{ + "versions": { + "1.0.0": {} + } +}