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) {
|
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),
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
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",
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
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