1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-21 04:52:26 -05:00

fix(check/lsp): fix bugs with tsc type resolution, allow npm packages to augment ImportMeta (#27690)

Fixes #26224.
Fixes #27042.

There were three bugs here:
- we were only resolving `/// <reference types` directives starting with
`npm:`, which meant we failed to resolve bare specifiers (this broke the
`/// <reference types="vite/client">` directive in most of the vite
templates)
- the `$node_modules` workaround caused us to fail to read files for
tsc. For instance tsc would construct new paths based on specifiers
containing `$node_modules`, and since we hadn't created those we weren't
mapping them back to the original (this broke some type resolution
within `vite/client`)
- our separation of `ImportMeta` across node and deno globals in tsc
meant that npm packages couldn't augment `ImportMeta` (this broke
`vite/client`'s augmentation to add `import.meta.env` and others)


After this, the only remaining issue in the vanilla vite template is our
error on `/vite.svg` (which is an ambient module), and I'll look into
that next.
This commit is contained in:
Nathan Whitaker 2025-01-16 11:20:04 -08:00 committed by GitHub
parent 2debe9c8dd
commit 464ee9155e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 311 additions and 49 deletions

View file

@ -4341,7 +4341,9 @@ impl TscSpecifierMap {
if let Some(specifier) = self.normalized_specifiers.get(original) { if let Some(specifier) = self.normalized_specifiers.get(original) {
return Ok(specifier.clone()); 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) { let specifier = match ModuleSpecifier::parse(&specifier_str) {
Ok(s) => s, Ok(s) => s,
Err(err) => return Err(err), Err(err) => return Err(err),

View file

@ -500,6 +500,8 @@ delete Object.prototype.__proto__;
// Microsoft/TypeScript#26825 but that doesn't seem to be working here, // Microsoft/TypeScript#26825 but that doesn't seem to be working here,
// so we will ignore complaints about this compiler setting. // so we will ignore complaints about this compiler setting.
5070, 5070,
// TS6053: File '{0}' not found.
6053,
// TS7016: Could not find a declaration file for module '...'. '...' // TS7016: Could not find a declaration file for module '...'. '...'
// implicitly has an 'any' type. This is due to `allowJs` being off by // implicitly has an 'any' type. This is due to `allowJs` being off by
// default but importing of a JavaScript module. // default but importing of a JavaScript module.
@ -705,15 +707,14 @@ delete Object.prototype.__proto__;
resolveTypeReferenceDirectiveReferences( resolveTypeReferenceDirectiveReferences(
typeDirectiveReferences, typeDirectiveReferences,
containingFilePath, containingFilePath,
redirectedReference, _redirectedReference,
options, options,
containingSourceFile, containingSourceFile,
_reusedNames, _reusedNames,
) { ) {
const isCjs = const isCjs =
containingSourceFile?.impliedNodeFormat === ts.ModuleKind.CommonJS; containingSourceFile?.impliedNodeFormat === ts.ModuleKind.CommonJS;
/** @type {Array<ts.ResolvedTypeReferenceDirectiveWithFailedLookupLocations>} */ const toResolve = typeDirectiveReferences.map((arg) => {
const result = typeDirectiveReferences.map((arg) => {
/** @type {ts.FileReference} */ /** @type {ts.FileReference} */
const fileReference = typeof arg === "string" const fileReference = typeof arg === "string"
? { ? {
@ -722,46 +723,50 @@ delete Object.prototype.__proto__;
fileName: arg, fileName: arg,
} }
: arg; : arg;
if (fileReference.fileName.startsWith("npm:")) { return [
/** @type {[string, ts.Extension | null] | undefined} */ fileReference.resolutionMode == null
const resolved = ops.op_resolve( ? isCjs
containingFilePath, : fileReference.resolutionMode === ts.ModuleKind.CommonJS,
[ fileReference.fileName,
[ ];
fileReference.resolutionMode == null });
? isCjs
: fileReference.resolutionMode === ts.ModuleKind.CommonJS, /** @type {Array<[string, ts.Extension | null] | undefined>} */
fileReference.fileName, const resolved = ops.op_resolve(
], containingFilePath,
], toResolve,
)?.[0]; );
if (resolved && resolved[1]) {
return { /** @type {Array<ts.ResolvedTypeReferenceDirectiveWithFailedLookupLocations>} */
resolvedTypeReferenceDirective: { const result = resolved.map((item) => {
primary: true, if (item && item[1]) {
resolvedFileName: resolved[0], const [resolvedFileName, extension] = item;
// todo(dsherret): we should probably be setting this return {
isExternalLibraryImport: undefined, resolvedTypeReferenceDirective: {
}, primary: true,
}; resolvedFileName,
} else { extension,
return { isExternalLibraryImport: false,
resolvedTypeReferenceDirective: undefined, },
}; };
}
} else { } else {
return ts.resolveTypeReferenceDirective( return {
fileReference.fileName, resolvedTypeReferenceDirective: undefined,
containingFilePath, };
options,
host,
redirectedReference,
undefined,
containingSourceFile?.impliedNodeFormat ??
fileReference.resolutionMode,
);
} }
}); });
if (logDebug) {
debug(
"resolveTypeReferenceDirectiveReferences ",
typeDirectiveReferences,
containingFilePath,
options,
containingSourceFile?.fileName,
" => ",
result,
);
}
return result; return result;
}, },
resolveModuleNameLiterals( resolveModuleNameLiterals(
@ -1116,6 +1121,36 @@ delete Object.prototype.__proto__;
if (IGNORED_DIAGNOSTICS.includes(diagnostic.code)) { if (IGNORED_DIAGNOSTICS.includes(diagnostic.code)) {
return false; 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 // make the diagnostic for using an `export =` in an es module a warning
if (diagnostic.code === 1203) { if (diagnostic.code === 1203) {
diagnostic.category = ts.DiagnosticCategory.Warning; diagnostic.category = ts.DiagnosticCategory.Warning;
@ -1410,7 +1445,6 @@ delete Object.prototype.__proto__;
"ErrorConstructor", "ErrorConstructor",
"gc", "gc",
"Global", "Global",
"ImportMeta",
"localStorage", "localStorage",
"queueMicrotask", "queueMicrotask",
"RequestInit", "RequestInit",

View file

@ -17326,12 +17326,7 @@ fn compiler_options_types() {
let mut deno_json = deno_json.clone(); let mut deno_json = deno_json.clone();
deno_json["nodeModulesDir"] = json!(node_modules_dir); deno_json["nodeModulesDir"] = json!(node_modules_dir);
temp.write("deno.json", deno_json.to_string()); temp.write("deno.json", deno_json.to_string());
context context.run_deno("install");
.new_command()
.args("install")
.run()
.skip_output_check()
.assert_exit_code(0);
client.did_change_watched_files(json!({ client.did_change_watched_files(json!({
"changes": [{ "changes": [{
"uri": temp.url().join("deno.json").unwrap(), "uri": temp.url().join("deno.json").unwrap(),
@ -17345,3 +17340,75 @@ fn compiler_options_types() {
client.did_close_file(&source); 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
/// <reference types="./real-import-meta.d.ts" />
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#"
/// <reference types="@denotest/augments-global/import-meta" />
"#,
);
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);
}
}

View file

@ -0,0 +1,3 @@
/// <reference types="./real-import-meta.d.ts" />
export type Foo = number;

View file

@ -1,5 +1,13 @@
{ {
"name": "@denotest/augments-global", "name": "@denotest/augments-global",
"version": "1.0.0", "version": "1.0.0",
"types": "./index.d.ts" "types": "./index.d.ts",
"exports": {
".": {
"types": "./index.d.ts"
},
"./import-meta": {
"types": "./import-meta.d.ts"
}
}
} }

View file

@ -0,0 +1,8 @@
interface ImportMetaEnv {
TEST: string;
}
interface ImportMeta {
env: ImportMetaEnv;
bar: number;
}

View file

@ -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"
}
]
}
}
}

View file

@ -0,0 +1,5 @@
{
"imports": {
"@types/node": "npm:@types/node@*"
}
}

View file

@ -0,0 +1,3 @@
/// <reference types="@types/node" />
const _foo = import.meta.dirname;

View file

@ -0,0 +1,8 @@
if (Deno.args.length !== 1) {
console.error("Usage: set_node_modules_dir.ts <setting>");
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));

View file

@ -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"
}
]
}
}
}

View file

@ -0,0 +1,6 @@
{
"imports": {
"@denotest/augments-global": "npm:@denotest/augments-global@1"
},
"compilerOptions": { "types": ["./types.d.ts"] }
}

View file

@ -0,0 +1,3 @@
const test = import.meta.env.TEST;
const bar = import.meta.bar;
console.log(test, bar);

View file

@ -0,0 +1,8 @@
if (Deno.args.length !== 1) {
console.error("Usage: set_node_modules_dir.ts <setting>");
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));

View file

@ -0,0 +1 @@
/// <reference types="@denotest/augments-global/import-meta" />