Get packageId for relative import within a package#21130
Conversation
| } | ||
|
|
||
| function parseNodeModuleFromPath(path: string): { packageDirectory: string, subModuleName: string } | undefined { | ||
| const nodeModules = "/node_modules/"; |
There was a problem hiding this comment.
Is this case-sensitive even on Windows?
There was a problem hiding this comment.
Should be fixed if we normalize the path first.
|
|
||
| function indexOfNextSlash(s: string, pos: number): number { | ||
| const x = s.indexOf(directorySeparator, pos); | ||
| return x === -1 ? pos : x; |
There was a problem hiding this comment.
This special case makes sense for this application, but probably not in general. Would it make sense to move this helper into parseNodeModuleFromPath?
There was a problem hiding this comment.
I'm not a fan of moving things into closures if they don't need to close over any variables, but this could be named better.
|
|
||
| function removeExtensionAndIndex(fileName: string): string { | ||
| const noExtension = removeFileExtension(fileName); | ||
| return noExtension === "index" ? "" : removeSuffix(noExtension, "/index"); |
There was a problem hiding this comment.
Presumably, we won't reach here for (e.g.) "index.vb"?
There was a problem hiding this comment.
removeFileExtension only removes the supported file extensions (and also correctly removes all of .d.ts and not just .ts. Although it's also true that fileName shouldn't have a .vb extension since we'd never resolve a module to a .vb file.
|
@amcasey Good to merge? |
|
No objections here, but it might be nice to have a second set of eyes. |
aozgaa
left a comment
There was a problem hiding this comment.
Some variable naming suggestions and questions about valid inputs.
|
|
||
| function parseNodeModuleFromPath(path: string): { packageDirectory: string, subModuleName: string } | undefined { | ||
| path = normalizePath(path); | ||
| const nodeModules = "/node_modules/"; |
There was a problem hiding this comment.
maybe rename to nodeModulesPathPart and declare outside the function?
| const indexAfterNodeModules = idx + nodeModules.length; | ||
| let indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterNodeModules); | ||
| if (path.charCodeAt(indexAfterNodeModules) === CharacterCodes.at) { | ||
| indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterPackageName); |
There was a problem hiding this comment.
Would it make sense to get a path like /node_modules/@asdf? How should we handle such an input if so?
There was a problem hiding this comment.
A path should be a complete file path like /node_modules/@asdf/pkg/index.d.ts, not just /node_modules/@asdf.
There was a problem hiding this comment.
Are we concerned with malformed paths and packages? As a scenario, do we care if someone is mucking about and makes a malformed entry manually? I personally think tsserver shouldn't crash, and ideally we would surface a message to a user.
Do we have some way of handling situations like this gracefully?
There was a problem hiding this comment.
I personally think tsserver shouldn't crash
That's just, like, your opinion, man. 😜
But we shouldn't get a malformed path here because this is only called after a successful module resolution. And even if we did, I don't see a place where this function would crash.
| indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterPackageName); | ||
| } | ||
| const packageDirectory = path.slice(0, indexAfterPackageName); | ||
| const subModuleName = removeExtensionAndIndex(path.slice(indexAfterPackageName)); |
There was a problem hiding this comment.
From the caller loadModuleFromNodeModulesFolder and the use of getPackageJsonInfo, it looks like subModuleName should refer to submodule within a nested module (eg: "core" in "@angular/core"). Is this correct?
Should subModuleName be "" when we are handling a module without submodules?
There was a problem hiding this comment.
subModuleName is the name of a path within a package, so for @angular/core/foo.d.ts it would be "foo" and for @angular/core/index.d.ts it would be the empty string "".
| } | ||
|
|
||
| function moveToNextDirectorySeparatorIfAvailable(path: string, pos: number): number { | ||
| const indexOfSlash = path.indexOf(directorySeparator, pos); |
| return { packageDirectory, subModuleName }; | ||
| } | ||
|
|
||
| function moveToNextDirectorySeparatorIfAvailable(path: string, pos: number): number { |
There was a problem hiding this comment.
Should pos necessarily be the index of a slash? Could we rename the argument to slashIndex or add a comment if so?
| return indexOfSlash === -1 ? pos : indexOfSlash; | ||
| } | ||
|
|
||
| function removeExtensionAndIndex(fileName: string): string { |
There was a problem hiding this comment.
Do we have a convention where fileName is a path? If it someone were to input just the last part of a path here (eg: "index.d.ts") then the result we would get would be "index". Is that okay?
There was a problem hiding this comment.
I'll rename the parameter to path.
| const indexAfterNodeModules = idx + nodeModules.length; | ||
| let indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterNodeModules); | ||
| if (path.charCodeAt(indexAfterNodeModules) === CharacterCodes.at) { | ||
| indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterPackageName); |
There was a problem hiding this comment.
How does the method work with submodules under @types (eg: "@types/someModule/Submodule")? Or is that not a valid input here?
There was a problem hiding this comment.
In that case subModuleName would be "Submodule".
| /** | ||
| * packageDirectory is the directory of the package itself. | ||
| * subModuleName is the path within the package. | ||
| * For `foo/index.d.ts` this is { packageDirectory: "foo", subModuleName: "" }. |
There was a problem hiding this comment.
Should these examples start with "/node_modules/"?
095694a to
c7bde6f
Compare
|
|
||
| const indexAfterNodeModules = idx + nodeModulesPathPart.length; | ||
| let indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterNodeModules); | ||
| if (path.charCodeAt(indexAfterNodeModules) === CharacterCodes.at) { |
There was a problem hiding this comment.
can you add a test for scoped packages as well
| */ | ||
| function parseNodeModuleFromPath(path: string): { packageDirectory: string, subModuleName: string } | undefined { | ||
| path = normalizePath(path); | ||
| const idx = path.indexOf(nodeModulesPathPart); |
There was a problem hiding this comment.
should not this be lastIndexOf instead? you want the last node_modules in the list right?
| "File '/node_modules/foo/use.tsx' does not exist.", | ||
| "File '/node_modules/foo/use.d.ts' exist - use it as a name resolution result.", | ||
| "Resolving real path for '/node_modules/foo/use.d.ts', result '/node_modules/foo/use.d.ts'.", | ||
| "======== Module name 'foo/use' was successfully resolved to '/node_modules/foo/use.d.ts'. ========", |
There was a problem hiding this comment.
this is not related to this PR, but just a thought that came to me, we are not logging the packageid anywhere. we should probably to make these tests more statically verifiable
d5c4f2d to
3420bd6
Compare
Fixes #20945 (tested with the sample repository provided)
If we're loading relatively from a file, we will parse the result path to see if we're currently in a
node_modules; if so, we will look for a packageId for the current package.