-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
match leading slash imports with path mappings - fixes #13730 #27980
Conversation
src/compiler/moduleNameResolver.ts
Outdated
@@ -1433,3 +1444,9 @@ namespace ts { | |||
return value !== undefined ? { value } : undefined; | |||
} | |||
} | |||
/* @internal */ | |||
namespace ts { | |||
export function startsWithSlash(name: string) { |
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.
Since this function is not used anywhere else, you could avoid exporting it
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.
Great tip. I tried that first, but I had some trouble which seemed to be caused by the /* internal */
comment, it could not resolve CharacterCodes
. I just tried to move it again, and now it works. My knowledge of TypeScript is very low, so I will adjust it.
Question for the maintainers: do you guys want me to squash the commits once the code has stabilized? |
@sheetalkamat I have made some changes:
|
There is one potential problem left. When thinking about this problem it is important to remember that the function Before this change, all With this change, all This is not necessarily a problem. However, in some code bases there is 'invisible' coupling between concepts. The coupling for this case would be the assumption that As far as I can tell (by tracing back the introduction of path mapping and the introduction of root path's as 'relative') any coupling that exist would be accidental. I can also imagine that if a developer used Looking at the uses of
My advise would be to create a separate issue to track down the uses of
|
2c8642e
to
82e9ef7
Compare
I have squashed the commits. |
@sheetalkamat What's next? |
if (!baseUrl) { | ||
return undefined; | ||
} | ||
if (state.traceEnabled) { | ||
trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName); | ||
} | ||
if (paths) { |
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 this should be pulled out into separate function instead of inlining. Its easier to read.
We also need trace above this line about using baseUrl in that.
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 this should be pulled out into separate function instead of inlining.
I thought about that. I however couldn't think of a name. The name tryLoadModuleUsingPaths
is already taken. What do you suggest?
We also need trace above this line about using baseUrl in that.
The trace baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1
is not exactly correct for what is happening. And since we have the trace paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0
I thought that would be enough. But if you really want it there I could of course add it back in.
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.
Yes please add that back in there since its used in there anyways.
src/compiler/moduleNameResolver.ts
Outdated
function isEligibleForPathMapping(moduleName: string, paths: MapLike<string[]>): boolean { | ||
if (isRootedDiskPath(moduleName) || isUrl(moduleName)) { | ||
// check for potential alias otherwise { '*': './src/*' } will match | ||
const root = moduleName.slice(0, getRootLength(moduleName)); |
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.
You don't need all these checks. The path mapping needs to be done if: moduleName is not relative path or it is absolute path.
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.
Did you read the comment?
It would break backwards compatibility and existing configurations.
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.
@DanielRosenwasser isn't this change expected breaking change ?
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.
Yes, I thought that was the point of this PR in the first place. What exactly are the extra checks meant to protect against?
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.
Root (or url) imports failing (resolving to another file) in existing projects failing because they have a *
mapping.
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.
As mentioned this is desired to be a breaking change. Please remove these extra checks
const resolved = tryLoadModuleUsingPaths(extensions, moduleName, baseUrl, paths, loader, /*onlyRecordFailures*/ false, state); | ||
if (resolved) return resolved.value; | ||
} | ||
|
||
if (!isExternalModuleNameRelative(moduleName)) { |
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.
Also needs some tests to ensure that caching is working as intended (and hasn't changed)
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.
Could you point me in the right direction? I am unsure what would be needed.
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 are tests in src/testRunner/moduleResolution.ts
that verify caching is working as expected.
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.
@sheetalkamat Can you please explain how I can run the unit tests? Thank you
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.
@sheetalkamat I figured it out, they are run by default
Also need changes in |
What kind of inputs would this fail on? |
More than failing of completions, we need to make sure the path completions are correct when there is path mapping. |
a361e89
to
340bb87
Compare
@sheetalkamat I have updated the code to reflect your comments (apart from the ones that still have questions). I also squashed my commits again and rebased on master. Could you please take another look? |
src/compiler/moduleNameResolver.ts
Outdated
if (isRootedDiskPath(moduleName) || isUrl(moduleName)) { | ||
// check for potential alias otherwise { '*': './src/*' } will match | ||
const root = moduleName.slice(0, getRootLength(moduleName)); | ||
const potentialAlias = getOwnKeys(paths).find(path => path.startsWith(root)); |
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.
find
and startsWith
are ES2015 and we support ES5 environments, so use the helper functions from core.ts
src/compiler/moduleNameResolver.ts
Outdated
// check for potential alias otherwise { '*': './src/*' } will match | ||
const root = moduleName.slice(0, getRootLength(moduleName)); | ||
const potentialAlias = getOwnKeys(paths).find(path => path.startsWith(root)); | ||
return Boolean(potentialAlias); |
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.
!!
340bb87
to
aadfa61
Compare
src/services/stringCompletions.ts
Outdated
if (compilerOptions.rootDirs) { | ||
return getCompletionEntriesForDirectoryFragmentWithRootDirs( | ||
compilerOptions.rootDirs, literalValue, scriptDirectory, extensionOptions, compilerOptions, host, scriptPath); | ||
if (isRootedDiskPath(literalValue) || isUrl(literalValue)) { |
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.
@andy-ms can you please review completions change
src/services/stringCompletions.ts
Outdated
compilerOptions.rootDirs, literalValue, scriptDirectory, extensionOptions, compilerOptions, host, scriptPath); | ||
if (isRootedDiskPath(literalValue) || isUrl(literalValue)) { | ||
if (compilerOptions.baseUrl) { | ||
return getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, compilerOptions, host, typeChecker); |
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 two getCompletionEntriesForNonRelativeModules
calls are identical, as are the two getCompletionEntriesForRelativeModules
calls. This could be a single conditional instead of several nested ones.
return isPathRelativeToScript(literalValue) || !compilerOptions.baseUrl && (isRootedDiskPath(literalValue) || isUrl(literalValue))
? getCompletionEntriesForRelativeModules(literalValue, scriptDirectory, compilerOptions, host, scriptPath)
: getCompletionEntriesForNonRelativeModules(literalValue, scriptDirectory, compilerOptions, host, typeChecker);
aadfa61
to
1a9c209
Compare
@sheetalkamat I have made the required changes. |
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.
What is the change in tests/cases/user/TypeScript-Node-Starter/TypeScript-Node-Starter ?? Please revert it. Thanks
@sheetalkamat Those are really difficult to revert -- it happens when the submodule is updated by git. I think it's fine because we regularly update these anyway. |
@EECOLOR thank you for contribution. This should be available in tonight's nightly build. |
@sheetalkamat thank you and your team for your help |
@sheetalkamat did something go wrong with the nightly build? |
@weswigham @DanielRosenwasser would know. |
Looks like for some reason the nightly publish job didn't get scheduled until I just went to go check on the task. Likely an azure devops issue - since I believe it just triggered, we'll probably publish a nightly in about 30 minutes. |
Checklist:
master
branchjake runtests
locallyAbout the code changes: I can imagine you would like to have the code structured differently or have different traces. I just looked at what currently been implemented and took the most straightforward approach making the changes easy to diff. Feel free to make suggestions.
About the tests: I am missing information to provide the correct amount of tests, or actually I don't know what your policy is. I think the tests I added should cover all of the code I added and prevent future breakage.
About performance: I tried to minimize the performance impact. I am not sure how often this code is called in situations where high performance is required. That said, I noticed a lot of places where performance could be improved, so I am not sure how much you guys want to focus on that.
About caching: I noticed a few places where
isExternalModuleNameRelative
is used in relation to caching. We might want to add the 'is potentially an alias import' there as well. This however requires some extra thought. An example of such a place:Fixes #13730