diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index 09e11380ac..32352d9f26 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -4341,7 +4341,9 @@ impl TscSpecifierMap { if let Some(specifier) = self.normalized_specifiers.get(original) { return Ok(specifier.clone()); } - let specifier_str = original.replace(".d.ts.d.ts", ".d.ts"); + let specifier_str = original + .replace(".d.ts.d.ts", ".d.ts") + .replace("$node_modules", "node_modules"); let specifier = match ModuleSpecifier::parse(&specifier_str) { Ok(s) => s, Err(err) => return Err(err), diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js index b3279f54ac..65319211fb 100644 --- a/cli/tsc/99_main_compiler.js +++ b/cli/tsc/99_main_compiler.js @@ -500,6 +500,8 @@ delete Object.prototype.__proto__; // Microsoft/TypeScript#26825 but that doesn't seem to be working here, // so we will ignore complaints about this compiler setting. 5070, + // TS6053: File '{0}' not found. + 6053, // TS7016: Could not find a declaration file for module '...'. '...' // implicitly has an 'any' type. This is due to `allowJs` being off by // default but importing of a JavaScript module. @@ -705,15 +707,14 @@ delete Object.prototype.__proto__; resolveTypeReferenceDirectiveReferences( typeDirectiveReferences, containingFilePath, - redirectedReference, + _redirectedReference, options, containingSourceFile, _reusedNames, ) { const isCjs = containingSourceFile?.impliedNodeFormat === ts.ModuleKind.CommonJS; - /** @type {Array} */ - const result = typeDirectiveReferences.map((arg) => { + const toResolve = typeDirectiveReferences.map((arg) => { /** @type {ts.FileReference} */ const fileReference = typeof arg === "string" ? { @@ -722,46 +723,50 @@ delete Object.prototype.__proto__; fileName: arg, } : arg; - if (fileReference.fileName.startsWith("npm:")) { - /** @type {[string, ts.Extension | null] | undefined} */ - const resolved = ops.op_resolve( - containingFilePath, - [ - [ - fileReference.resolutionMode == null - ? isCjs - : fileReference.resolutionMode === ts.ModuleKind.CommonJS, - fileReference.fileName, - ], - ], - )?.[0]; - if (resolved && resolved[1]) { - return { - resolvedTypeReferenceDirective: { - primary: true, - resolvedFileName: resolved[0], - // todo(dsherret): we should probably be setting this - isExternalLibraryImport: undefined, - }, - }; - } else { - return { - resolvedTypeReferenceDirective: undefined, - }; - } + return [ + fileReference.resolutionMode == null + ? isCjs + : fileReference.resolutionMode === ts.ModuleKind.CommonJS, + fileReference.fileName, + ]; + }); + + /** @type {Array<[string, ts.Extension | null] | undefined>} */ + const resolved = ops.op_resolve( + containingFilePath, + toResolve, + ); + + /** @type {Array} */ + const result = resolved.map((item) => { + if (item && item[1]) { + const [resolvedFileName, extension] = item; + return { + resolvedTypeReferenceDirective: { + primary: true, + resolvedFileName, + extension, + isExternalLibraryImport: false, + }, + }; } else { - return ts.resolveTypeReferenceDirective( - fileReference.fileName, - containingFilePath, - options, - host, - redirectedReference, - undefined, - containingSourceFile?.impliedNodeFormat ?? - fileReference.resolutionMode, - ); + return { + resolvedTypeReferenceDirective: undefined, + }; } }); + + if (logDebug) { + debug( + "resolveTypeReferenceDirectiveReferences ", + typeDirectiveReferences, + containingFilePath, + options, + containingSourceFile?.fileName, + " => ", + result, + ); + } return result; }, resolveModuleNameLiterals( @@ -1116,6 +1121,36 @@ delete Object.prototype.__proto__; if (IGNORED_DIAGNOSTICS.includes(diagnostic.code)) { return false; } + + // ignore diagnostics resulting from the `ImportMeta` declaration in deno merging with + // the one in @types/node. the types of the filename and dirname properties are different, + // which causes tsc to error. + const importMetaFilenameDirnameModifiersRe = + /^All declarations of '(filename|dirname)'/; + const importMetaFilenameDirnameTypesRe = + /^Subsequent property declarations must have the same type.\s+Property '(filename|dirname)'/; + // Declarations of X must have identical modifiers. + if (diagnostic.code === 2687) { + if ( + typeof diagnostic.messageText === "string" && + (importMetaFilenameDirnameModifiersRe.test(diagnostic.messageText)) && + (diagnostic.file?.fileName.startsWith("asset:///") || + diagnostic.file?.fileName?.includes("@types/node")) + ) { + return false; + } + } + // Subsequent property declarations must have the same type. + if (diagnostic.code === 2717) { + if ( + typeof diagnostic.messageText === "string" && + (importMetaFilenameDirnameTypesRe.test(diagnostic.messageText)) && + (diagnostic.file?.fileName.startsWith("asset:///") || + diagnostic.file?.fileName?.includes("@types/node")) + ) { + return false; + } + } // make the diagnostic for using an `export =` in an es module a warning if (diagnostic.code === 1203) { diagnostic.category = ts.DiagnosticCategory.Warning; @@ -1410,7 +1445,6 @@ delete Object.prototype.__proto__; "ErrorConstructor", "gc", "Global", - "ImportMeta", "localStorage", "queueMicrotask", "RequestInit", diff --git a/tests/integration/lsp_tests.rs b/tests/integration/lsp_tests.rs index 3c1aa1c4ad..9607207163 100644 --- a/tests/integration/lsp_tests.rs +++ b/tests/integration/lsp_tests.rs @@ -17326,12 +17326,7 @@ fn compiler_options_types() { let mut deno_json = deno_json.clone(); deno_json["nodeModulesDir"] = json!(node_modules_dir); temp.write("deno.json", deno_json.to_string()); - context - .new_command() - .args("install") - .run() - .skip_output_check() - .assert_exit_code(0); + context.run_deno("install"); client.did_change_watched_files(json!({ "changes": [{ "uri": temp.url().join("deno.json").unwrap(), @@ -17345,3 +17340,75 @@ fn compiler_options_types() { client.did_close_file(&source); } } + +#[test] +fn type_reference_import_meta() { + let context = TestContextBuilder::for_npm().use_temp_cwd().build(); + let mut client = context.new_lsp_command().build(); + let temp = context.temp_dir(); + let temp_dir = temp.path(); + let source = source_file( + temp_dir.join("index.ts"), + r#" + const test = import.meta.env.TEST; + const bar = import.meta.bar; + console.log(test, bar); + "#, + ); + /* + tests type reference w/ bare specifier, type reference in an npm package, + and augmentation of `ImportMeta` (this combination modeled after the vanilla vite template, + which uses `vite/client`) + + @denotest/augments-global/import-meta: + ```dts + /// + + export type Foo = number; + ``` + + real-import-meta.d.ts: + ```dts + interface ImportMetaEnv { + TEST: string; + } + + interface ImportMeta { + env: ImportMetaEnv; + bar: number; + } + ``` + */ + temp.write( + "types.d.ts", + r#" + /// + "#, + ); + + let deno_json = json!({ + "imports": { + "@denotest/augments-global": "npm:@denotest/augments-global@1" + } + }); + temp.write("deno.json", deno_json.to_string()); + + client.initialize_default(); + + for node_modules_dir in ["none", "auto", "manual"] { + let mut deno_json = deno_json.clone(); + deno_json["nodeModulesDir"] = json!(node_modules_dir); + temp.write("deno.json", deno_json.to_string()); + context.run_deno("install"); + client.did_change_watched_files(json!({ + "changes": [{ + "uri": temp.url().join("deno.json").unwrap(), + "type": 2, + }], + })); + + let diagnostics = client.did_open_file(&source); + assert_eq!(diagnostics.all().len(), 0); + client.did_close_file(&source); + } +} diff --git a/tests/registry/npm/@denotest/augments-global/1.0.0/import-meta.d.ts b/tests/registry/npm/@denotest/augments-global/1.0.0/import-meta.d.ts new file mode 100644 index 0000000000..9dbe976c2f --- /dev/null +++ b/tests/registry/npm/@denotest/augments-global/1.0.0/import-meta.d.ts @@ -0,0 +1,3 @@ +/// + +export type Foo = number; \ No newline at end of file diff --git a/tests/registry/npm/@denotest/augments-global/1.0.0/package.json b/tests/registry/npm/@denotest/augments-global/1.0.0/package.json index 33f20414ab..a63e420d68 100644 --- a/tests/registry/npm/@denotest/augments-global/1.0.0/package.json +++ b/tests/registry/npm/@denotest/augments-global/1.0.0/package.json @@ -1,5 +1,13 @@ { "name": "@denotest/augments-global", "version": "1.0.0", - "types": "./index.d.ts" + "types": "./index.d.ts", + "exports": { + ".": { + "types": "./index.d.ts" + }, + "./import-meta": { + "types": "./import-meta.d.ts" + } + } } \ No newline at end of file diff --git a/tests/registry/npm/@denotest/augments-global/1.0.0/real-import-meta.d.ts b/tests/registry/npm/@denotest/augments-global/1.0.0/real-import-meta.d.ts new file mode 100644 index 0000000000..06875eeef3 --- /dev/null +++ b/tests/registry/npm/@denotest/augments-global/1.0.0/real-import-meta.d.ts @@ -0,0 +1,8 @@ +interface ImportMetaEnv { + TEST: string; +} + +interface ImportMeta { + env: ImportMetaEnv; + bar: number; +} \ No newline at end of file diff --git a/tests/specs/check/import_meta_no_errors/__test__.jsonc b/tests/specs/check/import_meta_no_errors/__test__.jsonc new file mode 100644 index 0000000000..e03edb297f --- /dev/null +++ b/tests/specs/check/import_meta_no_errors/__test__.jsonc @@ -0,0 +1,53 @@ +{ + "tempDir": true, + "tests": { + "node_modules_dir_none": { + "steps": [ + { + "args": "run -A ./set_node_modules_dir.ts none", + "output": "" + }, + { + "args": "install", + "output": "[WILDCARD]" + }, + { + "args": "check --all ./main.ts", + "output": "Check [WILDCARD]main.ts\n" + } + ] + }, + "node_modules_dir_auto": { + "steps": [ + { + "args": "run -A ./set_node_modules_dir.ts auto", + "output": "" + }, + { + "args": "install", + "output": "[WILDCARD]" + }, + { + "args": "check --all ./main.ts", + "output": "Check [WILDCARD]main.ts\n" + } + ] + }, + "node_modules_dir_manual": { + "steps": [ + { + "args": "run -A ./set_node_modules_dir.ts auto", + "output": "" + }, + { + "args": "install", + "output": "[WILDCARD]" + }, + { + "args": "check --all ./main.ts", + "output": "Check [WILDCARD]main.ts\n" + } + ] + } + } +} diff --git a/tests/specs/check/import_meta_no_errors/deno.json b/tests/specs/check/import_meta_no_errors/deno.json new file mode 100644 index 0000000000..4cc5c30d3a --- /dev/null +++ b/tests/specs/check/import_meta_no_errors/deno.json @@ -0,0 +1,5 @@ +{ + "imports": { + "@types/node": "npm:@types/node@*" + } +} diff --git a/tests/specs/check/import_meta_no_errors/main.ts b/tests/specs/check/import_meta_no_errors/main.ts new file mode 100644 index 0000000000..ff1b8e3629 --- /dev/null +++ b/tests/specs/check/import_meta_no_errors/main.ts @@ -0,0 +1,3 @@ +/// + +const _foo = import.meta.dirname; diff --git a/tests/specs/check/import_meta_no_errors/set_node_modules_dir.ts b/tests/specs/check/import_meta_no_errors/set_node_modules_dir.ts new file mode 100644 index 0000000000..656f215890 --- /dev/null +++ b/tests/specs/check/import_meta_no_errors/set_node_modules_dir.ts @@ -0,0 +1,8 @@ +if (Deno.args.length !== 1) { + console.error("Usage: set_node_modules_dir.ts "); + Deno.exit(1); +} +const setting = Deno.args[0].trim(); +const denoJson = JSON.parse(Deno.readTextFileSync("./deno.json")); +denoJson["nodeModulesDir"] = setting; +Deno.writeTextFileSync("./deno.json", JSON.stringify(denoJson, null, 2)); diff --git a/tests/specs/check/type_reference_import_meta/__test__.jsonc b/tests/specs/check/type_reference_import_meta/__test__.jsonc new file mode 100644 index 0000000000..f23081fef4 --- /dev/null +++ b/tests/specs/check/type_reference_import_meta/__test__.jsonc @@ -0,0 +1,53 @@ +{ + "tempDir": true, + "tests": { + "node_modules_dir_none": { + "steps": [ + { + "args": "run -A ./set_node_modules_dir.ts none", + "output": "" + }, + { + "args": "install", + "output": "[WILDCARD]" + }, + { + "args": "check ./main.ts", + "output": "Check [WILDCARD]main.ts\n" + } + ] + }, + "node_modules_dir_auto": { + "steps": [ + { + "args": "run -A ./set_node_modules_dir.ts auto", + "output": "" + }, + { + "args": "install", + "output": "[WILDCARD]" + }, + { + "args": "check ./main.ts", + "output": "Check [WILDCARD]main.ts\n" + } + ] + }, + "node_modules_dir_manual": { + "steps": [ + { + "args": "run -A ./set_node_modules_dir.ts auto", + "output": "" + }, + { + "args": "install", + "output": "[WILDCARD]" + }, + { + "args": "check ./main.ts", + "output": "Check [WILDCARD]main.ts\n" + } + ] + } + } +} diff --git a/tests/specs/check/type_reference_import_meta/deno.json b/tests/specs/check/type_reference_import_meta/deno.json new file mode 100644 index 0000000000..bea3f5b73d --- /dev/null +++ b/tests/specs/check/type_reference_import_meta/deno.json @@ -0,0 +1,6 @@ +{ + "imports": { + "@denotest/augments-global": "npm:@denotest/augments-global@1" + }, + "compilerOptions": { "types": ["./types.d.ts"] } +} diff --git a/tests/specs/check/type_reference_import_meta/main.ts b/tests/specs/check/type_reference_import_meta/main.ts new file mode 100644 index 0000000000..c0924e35ef --- /dev/null +++ b/tests/specs/check/type_reference_import_meta/main.ts @@ -0,0 +1,3 @@ +const test = import.meta.env.TEST; +const bar = import.meta.bar; +console.log(test, bar); diff --git a/tests/specs/check/type_reference_import_meta/set_node_modules_dir.ts b/tests/specs/check/type_reference_import_meta/set_node_modules_dir.ts new file mode 100644 index 0000000000..656f215890 --- /dev/null +++ b/tests/specs/check/type_reference_import_meta/set_node_modules_dir.ts @@ -0,0 +1,8 @@ +if (Deno.args.length !== 1) { + console.error("Usage: set_node_modules_dir.ts "); + Deno.exit(1); +} +const setting = Deno.args[0].trim(); +const denoJson = JSON.parse(Deno.readTextFileSync("./deno.json")); +denoJson["nodeModulesDir"] = setting; +Deno.writeTextFileSync("./deno.json", JSON.stringify(denoJson, null, 2)); diff --git a/tests/specs/check/type_reference_import_meta/types.d.ts b/tests/specs/check/type_reference_import_meta/types.d.ts new file mode 100644 index 0000000000..2df7d5371b --- /dev/null +++ b/tests/specs/check/type_reference_import_meta/types.d.ts @@ -0,0 +1 @@ +///