1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-20 20:42:19 -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) {
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),

View file

@ -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<ts.ResolvedTypeReferenceDirectiveWithFailedLookupLocations>} */
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<ts.ResolvedTypeReferenceDirectiveWithFailedLookupLocations>} */
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",

View file

@ -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
/// <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",
"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" />