Improve errors on module: node12 and extensionless relative imports (#46486)

* add new error + suggestions

* push down assert defined

* remove comment

* fix esm module import detection, update baselines

* add and update new tests

* fix review comments

* remove renamed baseline references

* update node modules test baselines

* fix error message, add new tests

* update old tests with new error message
This commit is contained in:
Gabriela Araujo Britto
2021-10-29 10:25:03 -07:00
committed by GitHub
parent f1a69ec93e
commit 9cdbb7248b
47 changed files with 854 additions and 404 deletions

View File

@@ -1016,6 +1016,22 @@ namespace ts {
const builtinGlobals = createSymbolTable();
builtinGlobals.set(undefinedSymbol.escapedName, undefinedSymbol);
// Extensions suggested for path imports when module resolution is node12 or higher.
// The first element of each tuple is the extension a file has.
// The second element of each tuple is the extension that should be used in a path import.
// e.g. if we want to import file `foo.mts`, we should write `import {} from "./foo.mjs".
const suggestedExtensions: [string, string][] = [
[".mts", ".mjs"],
[".ts", ".js"],
[".cts", ".cjs"],
[".mjs", ".mjs"],
[".js", ".js"],
[".cjs", ".cjs"],
[".tsx", compilerOptions.jsx === JsxEmit.Preserve ? ".jsx" : ".js"],
[".jsx", ".jsx"],
[".json", ".json"],
];
initializeTypeChecker();
return checker;
@@ -3443,7 +3459,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 resolvedModule = getResolvedModule(currentSourceFile, moduleReference, mode)!; // TODO: GH#18217
const resolvedModule = getResolvedModule(currentSourceFile, moduleReference, mode);
const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule);
const sourceFile = resolvedModule && !resolutionDiagnostic && host.getSourceFile(resolvedModule.resolvedFileName);
if (sourceFile) {
@@ -3489,10 +3505,10 @@ namespace ts {
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) {
const diag = Diagnostics.Invalid_module_name_in_augmentation_Module_0_resolves_to_an_untyped_module_at_1_which_cannot_be_augmented;
error(errorNode, diag, moduleReference, resolvedModule.resolvedFileName);
error(errorNode, diag, moduleReference, resolvedModule!.resolvedFileName);
}
else {
errorOnImplicitAnyModule(/*isError*/ noImplicitAny && !!moduleNotFoundError, errorNode, resolvedModule, moduleReference);
errorOnImplicitAnyModule(/*isError*/ noImplicitAny && !!moduleNotFoundError, errorNode, resolvedModule!, moduleReference);
}
// Failed imports and untyped modules are both treated in an untyped manner; only difference is whether we give a diagnostic first.
return undefined;
@@ -3513,6 +3529,10 @@ namespace ts {
}
else {
const tsExtension = tryExtractTSExtension(moduleReference);
const isExtensionlessRelativePathImport = pathIsRelative(moduleReference) && !hasExtension(moduleReference);
const moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions);
const resolutionIsNode12OrNext = moduleResolutionKind === ModuleResolutionKind.Node12 ||
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);
@@ -3532,6 +3552,18 @@ namespace ts {
hasJsonModuleEmitEnabled(compilerOptions)) {
error(errorNode, Diagnostics.Cannot_find_module_0_Consider_using_resolveJsonModule_to_import_module_with_json_extension, moduleReference);
}
else if (mode === ModuleKind.ESNext && resolutionIsNode12OrNext && isExtensionlessRelativePathImport) {
const absoluteRef = getNormalizedAbsolutePath(moduleReference, getDirectoryPath(currentSourceFile.path));
const suggestedExt = suggestedExtensions.find(([actualExt, _importExt]) => host.fileExists(absoluteRef + actualExt))?.[1];
if (suggestedExt) {
error(errorNode,
Diagnostics.Relative_import_paths_need_explicit_file_extensions_in_EcmaScript_imports_when_moduleResolution_is_node12_or_nodenext_Did_you_mean_0,
moduleReference + suggestedExt);
}
else {
error(errorNode, Diagnostics.Relative_import_paths_need_explicit_file_extensions_in_EcmaScript_imports_when_moduleResolution_is_node12_or_nodenext_Consider_adding_an_extension_to_the_import_path);
}
}
else {
error(errorNode, moduleNotFoundError, moduleReference);
}

View File

@@ -3345,6 +3345,14 @@
"category": "Error",
"code": 2833
},
"Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node12' or 'nodenext'. Consider adding an extension to the import path.": {
"category": "Error",
"code": 2834
},
"Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node12' or 'nodenext'. Did you mean '{0}'?": {
"category": "Error",
"code": 2835
},
"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",

View File

@@ -5992,7 +5992,7 @@ namespace ts {
export enum ModuleResolutionKind {
Classic = 1,
NodeJs = 2,
// Starting with node12, node's module resolver has significant departures from tranditional cjs resolution
// Starting with node12, node's module resolver has significant departures from traditional cjs resolution
// to better support ecmascript modules and their use within node - more features are still being added, so
// we can expect it to change over time, and as such, offer both a `NodeNext` moving resolution target, and a `Node12`
// version-anchored resolution target