-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Improve errors on module: node12 and extensionless relative imports #46486
Conversation
@@ -3503,6 +3518,18 @@ namespace ts { | |||
hasJsonModuleEmitEnabled(compilerOptions)) { | |||
error(errorNode, Diagnostics.Cannot_find_module_0_Consider_using_resolveJsonModule_to_import_module_with_json_extension, moduleReference); | |||
} | |||
else if (isESMFile && resolutionIsNode12OrHigher && isExtensionlessRelativePathImport) { | |||
const absoluteRef = getNormalizedAbsolutePath(moduleReference, getDirectoryPath(currentSourceFile.path)); | |||
const suggestedExt = suggestedExtensions.find(([actualExt, _importExt]) => host.fileExists(absoluteRef + actualExt))?.[1]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This probes the FS - do we have any concerns there from a performance standpoint?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally the answer is “no” when we’re about to emit an error, but it is kind of notable that nowhere else in the checker uses fileExists
. You could use host.getSourceFile
instead which would (for all of our own TypeCheckerHost implementations) only see if there was a file by that name we already loaded for some reason. This will often be sufficient given a project’s default includes
glob, but won’t work in general, so it kind of depends on whether we want this message to be a sure thing or a medium-effort heuristic. I kind of lean toward host.fileExists
is already there, so it’s probably ok to use...
On the other hand, this error message could be a pretty hot path if you take a big codebase and flip it from --module commonjs
to node12
before updating anything. But doing this work at some point seems unavoidable if we want to give the better error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this something we can move into the program construction phase then? Do we ever build errors there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're going to probe a bunch of paths in the same directory, it can be faster to use readdir and then compare against the results yourself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's some danger that people will see this thousands of times when they first change the setting, but I'm a little reluctant to "optimize" it without some way to establish that the change helped. Probably better to keep it simple until we measure a problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this something we can move into the program construction phase then? Do we ever build errors there?
After thinking about this more, I think maybe the ideal implementation would probe the filesystem during module resolution—when ESM-mode module resolution fails, it could try CJS-mode resolution and attach the result to the ResolvedModuleWithFailedLookupLocations
result. The checker would then call getResolvedModuleWithFailedLookupLocationsFromCache
(already on program but needs to be added to TypeCheckerHost
) when it sees an unresolved import to check and see if CJS-mode resolution would have worked, and if so, exactly what file it would have resolved to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably worth noting that the fileExists
check may not even be sufficient in the presence of export (or import) maps - if you wanna know if the specifier-with-some-specific-extension would have resolved, you pretty much need to rerun the whole resolution process now. Precaching some extra specifier resolutions could work, but it certainly wouldn't be free to do (especially given that all of .js
, .cjs
, and .mjs
specifiers would need testing).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think @andrewbranch's suggestion is a good one for suggesting extensions. I don't think I could get this ready for the RC, though. Does it still make sense to include this PR as is? I think the error message without the suggestion is going to remain as is, so that should go in. I think it would only make sense to remove the extension suggestion message/check if we have performance concerns over calling fileExists
. Is that the case?
@@ -64,27 +64,27 @@ tests/cases/conformance/node/allowJs/index.js(59,22): error TS1471: Module './su | |||
tests/cases/conformance/node/allowJs/index.js(60,1): error TS8002: 'import ... =' can only be used in TypeScript files. | |||
tests/cases/conformance/node/allowJs/index.js(60,22): error TS1471: Module './subfolder2/another/index' cannot be imported using this construct. The specifier only resolves to an ES module, which cannot be imported synchronously. Use dynamic import instead. | |||
tests/cases/conformance/node/allowJs/index.js(74,21): error TS2307: Cannot find module './' or its corresponding type declarations. | |||
tests/cases/conformance/node/allowJs/index.js(75,21): error TS2307: Cannot find module './index' or its corresponding type declarations. | |||
tests/cases/conformance/node/allowJs/index.js(76,21): error TS2307: Cannot find module './subfolder' or its corresponding type declarations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is interesting - technically we'd want to tell people that they have to import from index.js
. It's not a big deal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My comment at #46486 (comment) would fix this. It’s probably not worth blocking over if we don’t have time to try this before the RC, but we should at least follow up in 4.6 to try it.
tests/cases/conformance/node/allowJs/index.cjs(84,21): error TS2307: Cannot find module './subfolder2/another/' or its corresponding type declarations. | ||
tests/cases/conformance/node/allowJs/index.cjs(85,21): error TS2307: Cannot find module './subfolder2/another/index' or its corresponding type declarations. | ||
tests/cases/conformance/node/allowJs/index.cjs(76,21): error TS2835: Relative import paths need explicit file extensions in ES modules when '--moduleResolution' is 'node12' or 'nodenext'. Did you mean './index.mjs'? | ||
tests/cases/conformance/node/allowJs/index.cjs(77,21): error TS2834: Relative import paths need explicit file extensions in ES modules when '--moduleResolution' is 'node12' or 'nodenext'. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The other common case is an import like this one - where a full index.js
is potentially missing, and not just the extension. Might be worth specializing the error for that case, too.
I added new test cases to cover every combination of import type + CJS/ESM file I could think of. Note: I still have to update the old test baselines to reflect the new error messages; CI will fail until then. |
// but other errors should not assume that they are allowed | ||
import { foo } from "./foo"; // should error, should not ask for extension | ||
~~~~~~~ | ||
!!! error TS2307: Cannot find module './foo' or its corresponding type declarations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For future reference, I think it's a bit weird that the error message here is TS2307 "Cannot find module", instead of something like TS1471: "Module '{0}' cannot be imported using this construct".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think during 4.6 we might try to replace this with some targeted fallback-to-CJS-resolution attempts in the module resolver itself, but this is a big improvement and is worth shipping in 4.5 to help users get a feel for how to use node12
/nodenext
.
…icrosoft#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
Fixes #46152