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

--moduleResolution minimal #50153

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
72 changes: 57 additions & 15 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3607,6 +3607,7 @@ namespace ts {
(isModuleDeclaration(location) ? location : location.parent && isModuleDeclaration(location.parent) && location.parent.name === location ? location.parent : undefined)?.name ||
(isLiteralImportTypeNode(location) ? location : undefined)?.argument.literal;
const mode = contextSpecifier && isStringLiteralLike(contextSpecifier) ? getModeForUsageLocation(currentSourceFile, contextSpecifier) : currentSourceFile.impliedNodeFormat;
const moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions);
const resolvedModule = getResolvedModule(currentSourceFile, moduleReference, mode);
const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule);
const sourceFile = resolvedModule
Expand All @@ -3617,11 +3618,43 @@ namespace ts {
if (resolutionDiagnostic) {
error(errorNode, resolutionDiagnostic, moduleReference, resolvedModule.resolvedFileName);
}

if (moduleResolutionKind === ModuleResolutionKind.Minimal && pathContainsNodeModules(moduleReference)) {
// A relative import into node_modules is a problem for a few reasons:
// 1. Portability - if the code gets published as a library, it will very likely break
// 2. It's unclear how we should resolve types. By typical relative import rules, we
// would ignore package.json fields that tell us where to find types and would have
// no special behavior linking up node_modules/@types with their implementations -
// we would only find .d.ts files as siblings of .js files. Any package that puts
// their types in a separate directory, or is typed by @types, would be broken.
// Some of these redirections would be safe to do, but others might reflect
// Node-specific resolution features that would only work with non-relative imports.
// The module resolver still returns a result, because it's possible for a module in
// node_modules to end up in the program, and module specifier generation assumes that it
// is always possible to generate a module specifier, even if it also generates a diagnostic.
error(errorNode, Diagnostics.Relative_imports_into_node_modules_are_not_allowed);
}
else if (resolvedModule.resolvedUsingTsExtension && isDeclarationFileName(moduleReference)) {
const importOrExport =
findAncestor(location, isImportDeclaration)?.importClause ||
findAncestor(location, or(isImportEqualsDeclaration, isExportDeclaration));
if (importOrExport && !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,
getSuggestedImportSource(Debug.checkDefined(tryExtractTSExtension(moduleReference))));
}
}
else if (resolvedModule.resolvedUsingTsExtension && !shouldAllowImportingTsExtension(compilerOptions, currentSourceFile.fileName)) {
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)) {
errorOnImplicitAnyModule(/*isError*/ false, errorNode, resolvedModule, moduleReference);
}
if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node16 || getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext) {
if (moduleResolutionKind === ModuleResolutionKind.Node16 || moduleResolutionKind === ModuleResolutionKind.NodeNext) {
const isSyncImport = (currentSourceFile.impliedNodeFormat === ModuleKind.CommonJS && !findAncestor(location, isImportCall)) || !!findAncestor(location, isImportEqualsDeclaration);
const overrideClauseHost = findAncestor(location, l => isImportTypeNode(l) || isExportDeclaration(l) || isImportDeclaration(l)) as ImportTypeNode | ImportDeclaration | ExportDeclaration | undefined;
const overrideClause = overrideClauseHost && isImportTypeNode(overrideClauseHost) ? overrideClauseHost.assertions?.assertClause : overrideClauseHost?.assertClause;
Expand Down Expand Up @@ -3729,25 +3762,17 @@ namespace ts {
else {
const tsExtension = tryExtractTSExtension(moduleReference);
const isExtensionlessRelativePathImport = pathIsRelative(moduleReference) && !hasExtension(moduleReference);
const moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions);
const resolutionIsNode16OrNext = moduleResolutionKind === ModuleResolutionKind.Node16 ||
moduleResolutionKind === ModuleResolutionKind.NodeNext;
if (tsExtension) {
const diag = Diagnostics.An_import_path_cannot_end_with_a_0_extension_Consider_importing_1_instead;
const importSourceWithoutExtension = removeExtension(moduleReference, tsExtension);
let replacedImportSource = importSourceWithoutExtension;
/**
* Direct users to import source with .js extension if outputting an ES module.
* @see https://github.com/microsoft/TypeScript/issues/42151
*/
if (moduleKind >= ModuleKind.ES2015) {
replacedImportSource += tsExtension === Extension.Mts ? ".mjs" : tsExtension === Extension.Cts ? ".cjs" : ".js";
}
error(errorNode, diag, tsExtension, replacedImportSource);
if (moduleResolutionKind === ModuleResolutionKind.Minimal && pathContainsNodeModules(moduleReference)) {
error(errorNode, Diagnostics.Relative_imports_into_node_modules_are_not_allowed);
}
else if (tsExtension) {
errorOnTSExtensionImport(tsExtension);
}
else if (!compilerOptions.resolveJsonModule &&
fileExtensionIs(moduleReference, Extension.Json) &&
getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Classic &&
moduleResolutionKind !== ModuleResolutionKind.Classic &&
hasJsonModuleEmitEnabled(compilerOptions)) {
error(errorNode, Diagnostics.Cannot_find_module_0_Consider_using_resolveJsonModule_to_import_module_with_json_extension, moduleReference);
}
Expand All @@ -3769,6 +3794,23 @@ namespace ts {
}
}
return undefined;

function errorOnTSExtensionImport(tsExtension: string) {
const diag = Diagnostics.An_import_path_cannot_end_with_a_0_extension_Consider_importing_1_instead;
error(errorNode, diag, tsExtension, getSuggestedImportSource(tsExtension));
}

function getSuggestedImportSource(tsExtension: string) {
const importSourceWithoutExtension = removeExtension(moduleReference, tsExtension);
/**
* Direct users to import source with .js extension if outputting an ES module.
* @see https://github.com/microsoft/TypeScript/issues/42151
*/
if (moduleKind >= ModuleKind.ES2015 || getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Minimal) {
return importSourceWithoutExtension + (tsExtension === Extension.Mts ? ".mjs" : tsExtension === Extension.Cts ? ".cjs" : ".js");
}
return importSourceWithoutExtension;
}
}

function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void {
Expand Down
9 changes: 9 additions & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,7 @@ namespace ts {
classic: ModuleResolutionKind.Classic,
node16: ModuleResolutionKind.Node16,
nodenext: ModuleResolutionKind.NodeNext,
minimal: ModuleResolutionKind.Minimal,
})),
affectsModuleResolution: true,
paramType: Diagnostics.STRATEGY,
Expand Down Expand Up @@ -955,6 +956,14 @@ namespace ts {
category: Diagnostics.Modules,
description: Diagnostics.List_of_file_name_suffixes_to_search_when_resolving_a_module,
},
{
name: "allowImportingTsExtensions",
type: "boolean",
affectsModuleResolution: true,
category: Diagnostics.Modules,
description: Diagnostics.Allow_imports_to_include_TypeScript_file_extensions_Requires_moduleResolution_minimal_and_either_noEmit_or_emitDeclarationOnly_to_be_set,
defaultValueDescription: false,
},

// Source Maps
{
Expand Down
24 changes: 20 additions & 4 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3567,6 +3567,14 @@
"category": "Error",
"code": 2845
},
"A declaration file cannot be imported without 'import type'. Did you mean to import an implementation file '{0}' instead?": {
"category": "Error",
"code": 2846
},
"Relative imports into 'node_modules' are not allowed.": {
"category": "Error",
"code": 2847
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down Expand Up @@ -4225,6 +4233,14 @@
"category": "Error",
"code": 5095
},
"Option 'allowImportingTsExtensions' can only be used when 'moduleResolution' is set to 'minimal' and either 'noEmit' or 'emitDeclarationOnly' is set.": {
"category": "Error",
"code": 5096
},
"An import path can only end with a '{0}' extension when 'allowImportingTsExtensions' is enabled.": {
"category": "Error",
"code": 5097
},

"Generates a sourcemap for each corresponding '.d.ts' file.": {
"category": "Message",
Expand Down Expand Up @@ -4447,10 +4463,6 @@
"category": "Message",
"code": 6066
},
"Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6).": {
"category": "Message",
"code": 6069
},
"Initializes a TypeScript project and creates a tsconfig.json file.": {
"category": "Message",
"code": 6070
Expand Down Expand Up @@ -5414,6 +5426,10 @@
"category": "Message",
"code": 6401
},
"Allow imports to include TypeScript file extensions. Requires '--moduleResolution minimal' and either '--noEmit' or '--emitDeclarationOnly' to be set.": {
"category": "Message",
"code": 6402
},

"The expected type comes from property '{0}' which is declared here on type '{1}'": {
"category": "Message",
Expand Down