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:
parent
2debe9c8dd
commit
464ee9155e
15 changed files with 311 additions and 49 deletions
|
@ -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),
|
||||
|
|
|
@ -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,26 +723,30 @@ delete Object.prototype.__proto__;
|
|||
fileName: arg,
|
||||
}
|
||||
: arg;
|
||||
if (fileReference.fileName.startsWith("npm:")) {
|
||||
/** @type {[string, ts.Extension | null] | undefined} */
|
||||
const resolved = ops.op_resolve(
|
||||
containingFilePath,
|
||||
[
|
||||
[
|
||||
return [
|
||||
fileReference.resolutionMode == null
|
||||
? isCjs
|
||||
: fileReference.resolutionMode === ts.ModuleKind.CommonJS,
|
||||
fileReference.fileName,
|
||||
],
|
||||
],
|
||||
)?.[0];
|
||||
if (resolved && resolved[1]) {
|
||||
];
|
||||
});
|
||||
|
||||
/** @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: resolved[0],
|
||||
// todo(dsherret): we should probably be setting this
|
||||
isExternalLibraryImport: undefined,
|
||||
resolvedFileName,
|
||||
extension,
|
||||
isExternalLibraryImport: false,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
|
@ -749,19 +754,19 @@ delete Object.prototype.__proto__;
|
|||
resolvedTypeReferenceDirective: undefined,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return ts.resolveTypeReferenceDirective(
|
||||
fileReference.fileName,
|
||||
});
|
||||
|
||||
if (logDebug) {
|
||||
debug(
|
||||
"resolveTypeReferenceDirectiveReferences ",
|
||||
typeDirectiveReferences,
|
||||
containingFilePath,
|
||||
options,
|
||||
host,
|
||||
redirectedReference,
|
||||
undefined,
|
||||
containingSourceFile?.impliedNodeFormat ??
|
||||
fileReference.resolutionMode,
|
||||
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",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
3
tests/registry/npm/@denotest/augments-global/1.0.0/import-meta.d.ts
vendored
Normal file
3
tests/registry/npm/@denotest/augments-global/1.0.0/import-meta.d.ts
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
/// <reference types="./real-import-meta.d.ts" />
|
||||
|
||||
export type Foo = number;
|
|
@ -1,5 +1,13 @@
|
|||
{
|
||||
"name": "@denotest/augments-global",
|
||||
"version": "1.0.0",
|
||||
"types": "./index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.d.ts"
|
||||
},
|
||||
"./import-meta": {
|
||||
"types": "./import-meta.d.ts"
|
||||
}
|
||||
}
|
||||
}
|
8
tests/registry/npm/@denotest/augments-global/1.0.0/real-import-meta.d.ts
vendored
Normal file
8
tests/registry/npm/@denotest/augments-global/1.0.0/real-import-meta.d.ts
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
interface ImportMetaEnv {
|
||||
TEST: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
env: ImportMetaEnv;
|
||||
bar: number;
|
||||
}
|
53
tests/specs/check/import_meta_no_errors/__test__.jsonc
Normal file
53
tests/specs/check/import_meta_no_errors/__test__.jsonc
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
5
tests/specs/check/import_meta_no_errors/deno.json
Normal file
5
tests/specs/check/import_meta_no_errors/deno.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"imports": {
|
||||
"@types/node": "npm:@types/node@*"
|
||||
}
|
||||
}
|
3
tests/specs/check/import_meta_no_errors/main.ts
Normal file
3
tests/specs/check/import_meta_no_errors/main.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
/// <reference types="@types/node" />
|
||||
|
||||
const _foo = import.meta.dirname;
|
|
@ -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));
|
53
tests/specs/check/type_reference_import_meta/__test__.jsonc
Normal file
53
tests/specs/check/type_reference_import_meta/__test__.jsonc
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
6
tests/specs/check/type_reference_import_meta/deno.json
Normal file
6
tests/specs/check/type_reference_import_meta/deno.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"imports": {
|
||||
"@denotest/augments-global": "npm:@denotest/augments-global@1"
|
||||
},
|
||||
"compilerOptions": { "types": ["./types.d.ts"] }
|
||||
}
|
3
tests/specs/check/type_reference_import_meta/main.ts
Normal file
3
tests/specs/check/type_reference_import_meta/main.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
const test = import.meta.env.TEST;
|
||||
const bar = import.meta.bar;
|
||||
console.log(test, bar);
|
|
@ -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));
|
1
tests/specs/check/type_reference_import_meta/types.d.ts
vendored
Normal file
1
tests/specs/check/type_reference_import_meta/types.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/// <reference types="@denotest/augments-global/import-meta" />
|
Loading…
Add table
Reference in a new issue