Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consistently avoid module resolution errors when using getSymbolAtLocation #58668

Merged
42 changes: 22 additions & 20 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2763,7 +2763,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const moduleNotFoundError = !(moduleName.parent.parent.flags & NodeFlags.Ambient)
? Diagnostics.Invalid_module_name_in_augmentation_module_0_cannot_be_found
: undefined;
let mainModule = resolveExternalModuleNameWorker(moduleName, moduleName, moduleNotFoundError, /*isForAugmentation*/ true);
let mainModule = resolveExternalModuleNameWorker(moduleName, moduleName, moduleNotFoundError, /*ignoreErrors*/ false, /*isForAugmentation*/ true);
if (!mainModule) {
return;
}
Expand Down Expand Up @@ -4521,17 +4521,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const errorMessage = isClassic ?
Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_nodenext_or_to_add_aliases_to_the_paths_option
: Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations;
return resolveExternalModuleNameWorker(location, moduleReferenceExpression, ignoreErrors ? undefined : errorMessage);
return resolveExternalModuleNameWorker(location, moduleReferenceExpression, ignoreErrors ? undefined : errorMessage, ignoreErrors);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the core of the fix. It propagates the ignoreErrors and the intention to ignore them is highlighted in the comment here:
https://github.dev/microsoft/TypeScript/blob/af3a61fe4487a92d59f9479aa4249d897b91af14/src/compiler/checker.ts#L1654-L1658

}

function resolveExternalModuleNameWorker(location: Node, moduleReferenceExpression: Expression, moduleNotFoundError: DiagnosticMessage | undefined, isForAugmentation = false): Symbol | undefined {
function resolveExternalModuleNameWorker(location: Node, moduleReferenceExpression: Expression, moduleNotFoundError: DiagnosticMessage | undefined, ignoreErrors = false, isForAugmentation = false): Symbol | undefined {
return isStringLiteralLike(moduleReferenceExpression)
? resolveExternalModule(location, moduleReferenceExpression.text, moduleNotFoundError, moduleReferenceExpression, isForAugmentation)
? resolveExternalModule(location, moduleReferenceExpression.text, moduleNotFoundError, !ignoreErrors ? moduleReferenceExpression : undefined, isForAugmentation)
: undefined;
}

function resolveExternalModule(location: Node, moduleReference: string, moduleNotFoundError: DiagnosticMessage | undefined, errorNode: Node, isForAugmentation = false): Symbol | undefined {
if (startsWith(moduleReference, "@types/")) {
function resolveExternalModule(location: Node, moduleReference: string, moduleNotFoundError: DiagnosticMessage | undefined, errorNode: Node | undefined, isForAugmentation = false): Symbol | undefined {
if (errorNode && startsWith(moduleReference, "@types/")) {
const diag = Diagnostics.Cannot_import_type_declaration_files_Consider_importing_0_instead_of_1;
const withoutAtTypePrefix = removePrefix(moduleReference, "@types/");
error(errorNode, diag, withoutAtTypePrefix, moduleReference);
Expand All @@ -4557,7 +4557,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
: host.getDefaultResolutionModeForFile(currentSourceFile);
const moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions);
const resolvedModule = host.getResolvedModule(currentSourceFile, moduleReference, mode)?.resolvedModule;
const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule, currentSourceFile);
const resolutionDiagnostic = errorNode && resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule, currentSourceFile);
const sourceFile = resolvedModule
&& (!resolutionDiagnostic || resolutionDiagnostic === Diagnostics.Module_0_was_resolved_to_1_but_jsx_is_not_set)
&& host.getSourceFile(resolvedModule.resolvedFileName);
Expand All @@ -4568,9 +4568,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

if (resolvedModule.resolvedUsingTsExtension && isDeclarationFileName(moduleReference)) {
const importOrExport = findAncestor(location, isImportDeclaration)?.importClause ||
const importOrExport = findAncestor(location, isImportDeclaration) ||
findAncestor(location, or(isImportEqualsDeclaration, isExportDeclaration));
if (importOrExport && !importOrExport.isTypeOnly || findAncestor(location, isImportCall)) {
if (errorNode && importOrExport && !(isImportDeclaration(importOrExport) ? importOrExport.importClause : importOrExport)?.isTypeOnly || findAncestor(location, isImportCall)) {
error(
errorNode,
Diagnostics.A_declaration_file_cannot_be_imported_without_import_type_Did_you_mean_to_import_an_implementation_file_0_instead,
Expand All @@ -4579,19 +4579,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
else if (resolvedModule.resolvedUsingTsExtension && !shouldAllowImportingTsExtension(compilerOptions, currentSourceFile.fileName)) {
const importOrExport = findAncestor(location, isImportDeclaration)?.importClause ||
const importOrExport = findAncestor(location, isImportDeclaration) ||
findAncestor(location, or(isImportEqualsDeclaration, isExportDeclaration));
if (!(importOrExport?.isTypeOnly || findAncestor(location, isImportTypeNode))) {
if (errorNode && !(importOrExport && (isImportDeclaration(importOrExport) ? importOrExport.importClause : importOrExport)?.isTypeOnly || findAncestor(location, isImportTypeNode))) {
const tsExtension = Debug.checkDefined(tryExtractTSExtension(moduleReference));
error(errorNode, Diagnostics.An_import_path_can_only_end_with_a_0_extension_when_allowImportingTsExtensions_is_enabled, tsExtension);
}
}

if (sourceFile.symbol) {
if (resolvedModule.isExternalLibraryImport && !resolutionExtensionIsTSOrJson(resolvedModule.extension)) {
if (errorNode && resolvedModule.isExternalLibraryImport && !resolutionExtensionIsTSOrJson(resolvedModule.extension)) {
errorOnImplicitAnyModule(/*isError*/ false, errorNode, currentSourceFile, mode, resolvedModule, moduleReference);
}
if (moduleResolutionKind === ModuleResolutionKind.Node16 || moduleResolutionKind === ModuleResolutionKind.NodeNext) {
if (errorNode && (moduleResolutionKind === ModuleResolutionKind.Node16 || moduleResolutionKind === ModuleResolutionKind.NodeNext)) {
const isSyncImport = (currentSourceFile.impliedNodeFormat === ModuleKind.CommonJS && !findAncestor(location, isImportCall)) || !!findAncestor(location, isImportEqualsDeclaration);
const overrideHost = findAncestor(location, l => isImportTypeNode(l) || isExportDeclaration(l) || isImportDeclaration(l)) as ImportTypeNode | ImportDeclaration | ExportDeclaration | undefined;
// An override clause will take effect for type-only imports and import types, and allows importing the types across formats, regardless of
Expand Down Expand Up @@ -4656,7 +4656,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// merged symbol is module declaration symbol combined with all augmentations
return getMergedSymbol(sourceFile.symbol);
}
if (moduleNotFoundError) {
if (errorNode && moduleNotFoundError) {
// report errors only if it was requested
error(errorNode, Diagnostics.File_0_is_not_a_module, sourceFile.fileName);
}
Expand All @@ -4678,6 +4678,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}

if (!errorNode) {
return undefined;
}

// May be an untyped module. If so, ignore resolutionDiagnostic.
if (resolvedModule && !resolutionExtensionIsTSOrJson(resolvedModule.extension) && resolutionDiagnostic === undefined || resolutionDiagnostic === Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type) {
if (isForAugmentation) {
Expand Down Expand Up @@ -32781,7 +32785,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
? Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_nodenext_or_to_add_aliases_to_the_paths_option
: Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations;
const specifier = getJSXRuntimeImportSpecifier(file, runtimeImportSpecifier);
const mod = resolveExternalModule(specifier || location!, runtimeImportSpecifier, errorMessage, location!);
const mod = resolveExternalModule(specifier || location!, runtimeImportSpecifier, errorMessage, location);
const result = mod && mod !== unknownSymbol ? getMergedSymbol(resolveSymbol(mod)) : undefined;
if (links) {
links.jsxImplicitImportContainer = result || false;
Expand Down Expand Up @@ -46808,6 +46812,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
if (checkExternalImportOrExportDeclaration(node)) {
const importClause = node.importClause;
const moduleExisted = resolveExternalModuleName(node, node.moduleSpecifier);
Andarist marked this conversation as resolved.
Show resolved Hide resolved
if (importClause && !checkGrammarImportClause(importClause)) {
if (importClause.name) {
checkImportBinding(importClause);
Expand All @@ -46820,11 +46825,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportStar);
}
}
else {
const moduleExisted = resolveExternalModuleName(node, node.moduleSpecifier);
if (moduleExisted) {
forEach(importClause.namedBindings.elements, checkImportBinding);
}
else if (moduleExisted) {
forEach(importClause.namedBindings.elements, checkImportBinding);
}
}
}
Expand Down
7 changes: 5 additions & 2 deletions src/testRunner/unittests/programApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,14 +216,17 @@ describe("unittests:: programApi:: Program.getTypeChecker / Program.getSemanticD
const sourceFile = program.getSourceFile("main.ts")!;
const typeChecker = program.getTypeChecker();
typeChecker.getSymbolAtLocation((sourceFile.statements[0] as ts.ImportDeclaration).moduleSpecifier);
assert.isEmpty(program.getSemanticDiagnostics());
const diagnostics = program.getSemanticDiagnostics();
assert.equal(diagnostics.length, 1);
assert.equal(diagnostics[0].code, ts.Diagnostics.File_0_is_not_a_module.code);
assert.equal(diagnostics[0].messageText, "File '/module.d.ts' is not a module.");
});
});

describe("unittests:: programApi:: CompilerOptions relative paths", () => {
it("resolves relative paths by getCurrentDirectory", () => {
const main = new documents.TextDocument("/main.ts", 'import "module";');
const mod = new documents.TextDocument("/lib/module.ts", "declare const foo: any;");
const mod = new documents.TextDocument("/lib/module.ts", "export declare const foo: any;");

const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main, mod], cwd: "/" });
const program = ts.createProgram(["./main.ts"], {
Expand Down
54 changes: 54 additions & 0 deletions src/testRunner/unittests/tsc/composite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,58 @@ describe("unittests:: tsc:: composite::", () => {
},
],
});

verifyTsc({
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

those are tests related to the referenced issue

scenario: "composite",
subScenario: "synthetic jsx import of ESM module from CJS module no crash no jsx element",
fs: () =>
loadProjectFromFiles({
"/src/main.ts": "export default 42;",
"/tsconfig.json": jsonToReadableText({
compilerOptions: {
composite: true,
module: "Node16",
jsx: "react-jsx",
jsxImportSource: "solid-js",
},
}),
"/node_modules/solid-js/package.json": jsonToReadableText({
name: "solid-js",
type: "module",
}),
"/node_modules/solid-js/jsx-runtime.d.ts": Utils.dedent`
export namespace JSX {
type IntrinsicElements = { div: {}; };
}
`,
}),
commandLineArgs: [],
});

verifyTsc({
scenario: "composite",
subScenario: "synthetic jsx import of ESM module from CJS module error on jsx element",
fs: () =>
loadProjectFromFiles({
"/src/main.tsx": "export default <div/>;",
"/tsconfig.json": jsonToReadableText({
compilerOptions: {
composite: true,
module: "Node16",
jsx: "react-jsx",
jsxImportSource: "solid-js",
},
}),
"/node_modules/solid-js/package.json": jsonToReadableText({
name: "solid-js",
type: "module",
}),
"/node_modules/solid-js/jsx-runtime.d.ts": Utils.dedent`
export namespace JSX {
type IntrinsicElements = { div: {}; };
}
`,
}),
commandLineArgs: [],
});
});
2 changes: 1 addition & 1 deletion src/testRunner/unittests/tscWatch/programUpdates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2129,7 +2129,7 @@ import { x } from "../b";`,
sys: () => {
const module1: File = {
path: `/user/username/projects/myproject/a.ts`,
content: ``,
content: `export {};`,
};
const module2: File = {
path: `/user/username/projects/myproject/b.ts`,
Expand Down
20 changes: 14 additions & 6 deletions tests/baselines/reference/allowsImportingTsExtension.errors.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
b.ts(2,16): error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.
b.ts(3,30): error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.
b.ts(5,25): error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.
b.ts(3,8): error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.
b.ts(4,30): error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.
b.ts(6,25): error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.
c.ts(2,16): error TS2846: A declaration file cannot be imported without 'import type'. Did you mean to import an implementation file './a.js' instead?
c.ts(3,30): error TS2846: A declaration file cannot be imported without 'import type'. Did you mean to import an implementation file './a.js' instead?
c.ts(5,25): error TS2846: A declaration file cannot be imported without 'import type'. Did you mean to import an implementation file './a.js' instead?
c.ts(3,8): error TS2846: A declaration file cannot be imported without 'import type'. Did you mean to import an implementation file './a.js' instead?
c.ts(4,30): error TS2846: A declaration file cannot be imported without 'import type'. Did you mean to import an implementation file './a.js' instead?
c.ts(6,25): error TS2846: A declaration file cannot be imported without 'import type'. Did you mean to import an implementation file './a.js' instead?


==== a.ts (0 errors) ====
Expand All @@ -12,10 +14,13 @@ c.ts(5,25): error TS2846: A declaration file cannot be imported without 'import
==== a.d.ts (0 errors) ====
export class A {}

==== b.ts (3 errors) ====
==== b.ts (4 errors) ====
import type { A } from "./a.ts"; // ok
import {} from "./a.ts"; // error
~~~~~~~~
!!! error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.
import "./a.ts"; // error
~~~~~~~~
!!! error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.
import { type A as _A } from "./a.ts"; // error
~~~~~~~~
Expand All @@ -25,10 +30,13 @@ c.ts(5,25): error TS2846: A declaration file cannot be imported without 'import
~~~~~~~~
!!! error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.

==== c.ts (3 errors) ====
==== c.ts (4 errors) ====
import type { A } from "./a.d.ts"; // ok
import {} from "./a.d.ts"; // error
~~~~~~~~~~
!!! error TS2846: A declaration file cannot be imported without 'import type'. Did you mean to import an implementation file './a.js' instead?
import "./a.d.ts"; // error
~~~~~~~~~~
!!! error TS2846: A declaration file cannot be imported without 'import type'. Did you mean to import an implementation file './a.js' instead?
import { type A as _A } from "./a.d.ts"; // error
~~~~~~~~~~
Expand Down
6 changes: 4 additions & 2 deletions tests/baselines/reference/allowsImportingTsExtension.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ export class A {}
//// [b.ts]
import type { A } from "./a.ts"; // ok
import {} from "./a.ts"; // error
import "./a.ts"; // error
import { type A as _A } from "./a.ts"; // error
type __A = import("./a.ts").A; // ok
const aPromise = import("./a.ts"); // error

//// [c.ts]
import type { A } from "./a.d.ts"; // ok
import {} from "./a.d.ts"; // error
import "./a.d.ts"; // error
import { type A as _A } from "./a.d.ts"; // error
type __A = import("./a.d.ts").A; // ok
const aPromise = import("./a.d.ts"); // error
Expand All @@ -25,8 +27,8 @@ const aPromise = import("./a.d.ts"); // error
export class A {
}
//// [b.js]
import "./a.ts"; // error
const aPromise = import("./a.ts"); // error
export {};
//// [c.js]
import "./a.d.ts"; // error
const aPromise = import("./a.d.ts"); // error
export {};
14 changes: 8 additions & 6 deletions tests/baselines/reference/allowsImportingTsExtension.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,34 @@ import type { A } from "./a.ts"; // ok
>A : Symbol(A, Decl(b.ts, 0, 13))

import {} from "./a.ts"; // error
import "./a.ts"; // error
import { type A as _A } from "./a.ts"; // error
>A : Symbol(A, Decl(a.ts, 0, 0))
>_A : Symbol(_A, Decl(b.ts, 2, 8))
>_A : Symbol(_A, Decl(b.ts, 3, 8))

type __A = import("./a.ts").A; // ok
>__A : Symbol(__A, Decl(b.ts, 2, 38))
>__A : Symbol(__A, Decl(b.ts, 3, 38))
>A : Symbol(A, Decl(a.ts, 0, 0))

const aPromise = import("./a.ts"); // error
>aPromise : Symbol(aPromise, Decl(b.ts, 4, 5))
>aPromise : Symbol(aPromise, Decl(b.ts, 5, 5))
>"./a.ts" : Symbol("a", Decl(a.ts, 0, 0))

=== c.ts ===
import type { A } from "./a.d.ts"; // ok
>A : Symbol(A, Decl(c.ts, 0, 13))

import {} from "./a.d.ts"; // error
import "./a.d.ts"; // error
import { type A as _A } from "./a.d.ts"; // error
>A : Symbol(A, Decl(a.ts, 0, 0))
>_A : Symbol(_A, Decl(c.ts, 2, 8))
>_A : Symbol(_A, Decl(c.ts, 3, 8))

type __A = import("./a.d.ts").A; // ok
>__A : Symbol(__A, Decl(c.ts, 2, 40))
>__A : Symbol(__A, Decl(c.ts, 3, 40))
>A : Symbol(A, Decl(a.ts, 0, 0))

const aPromise = import("./a.d.ts"); // error
>aPromise : Symbol(aPromise, Decl(c.ts, 4, 5))
>aPromise : Symbol(aPromise, Decl(c.ts, 5, 5))
>"./a.d.ts" : Symbol("a", Decl(a.ts, 0, 0))

2 changes: 2 additions & 0 deletions tests/baselines/reference/allowsImportingTsExtension.types
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { A } from "./a.ts"; // ok
> : ^

import {} from "./a.ts"; // error
import "./a.ts"; // error
import { type A as _A } from "./a.ts"; // error
>A : typeof A
> : ^^^^^^^^
Expand All @@ -40,6 +41,7 @@ import type { A } from "./a.d.ts"; // ok
> : ^

import {} from "./a.d.ts"; // error
import "./a.d.ts"; // error
import { type A as _A } from "./a.d.ts"; // error
>A : typeof A
> : ^^^^^^^^
Expand Down
Loading