-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Fix the interaction between absolute path imports and module roots #2343
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
Conversation
@@ -133,7 +133,19 @@ public ModuleLoader( | |||
} | |||
|
|||
public void setPackageJsonMainEntries(Map<String, String> packageJsonMainEntries) { |
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 add a javadoc comment to this method saying what the keys and values of packageJsonMainEntries represent?
for (Map.Entry<String, String> packageJsonMainEntry : packageJsonMainEntries.entrySet()) { | ||
String entryKey = packageJsonMainEntry.getKey(); | ||
|
||
// For node_modules paths only, remove any leading slash |
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 expand this comment to explain why it's important to remove the leading slash only for node modules?
|
||
// For node_modules paths only, remove any leading slash | ||
if (entryKey.startsWith("/node_modules")) { | ||
entryKey = new InputPathData(packageJsonMainEntry.getKey()).stripLeadingSlash(); |
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.
nit: why not just pass entryKey
instead of calling getKey()
again?
private final String[] browserFileExtensionsToSearch = {""}; | ||
private final String[] nodeFilesToSearch = { | ||
MODULE_SLASH + "package.json", MODULE_SLASH + "index.js", MODULE_SLASH + "index.json" | ||
}; |
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.
These should really be "true" constants.
Could you move their definitions outside of ModulePath so you can make the private static final
and ALL_CAPS?
return !(isAbsoluteIdentifier(this.path) || isRelativeIdentifier(this.path)); | ||
} | ||
|
||
String ensureLeadingSlash() { |
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 method names don't make it clear to me what they do, and I see that sometimes you just need the string but other times you actually need a new InputPathData object with a modified path.
How about this?
InputPathData toRelativePath()
InputPathData toAbsolutePath()
Just add .path
to calls when you only need the string.
Or have toRelativePathString() versions if you prefer.
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 struggle I have is one of terminology. Within this file:
- isAbsoluteIdentifier('/foo') => true
- isAbsoluteIdentifier('./foo') => false
- isAbsoluteIdentifier('foo') => false
- isRelativeIdentifier('/foo') => false
- isRelativeIdentifier('./foo') => true
- isRelativeIdentifier('foo') => false
I don't know how to label paths that do not begin with either a '.' or '/' as they are neither relative or absolute.
If it's a module path foo/bar.js
can either be relative to the compilation root (LEGACY mode), looked up from the node module registry (NODE mode) or invalid (BROWSER mode).
If it's an input path, foo/bar.js
is relative to either the compilation or module roots.
It's all horribly confusing.
Iterable<String> names, ImmutableList<String> roots) { | ||
HashSet<String> resolved = new HashSet<>(); | ||
private static ImmutableMap<String, InputPathData> resolvePaths( | ||
Iterable<String> names, ImmutableList<InputPathData> roots) { |
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.
nit: Why not Iterable<InputPathData> roots
?
private static ImmutableSet<String> resolvePaths( | ||
Iterable<String> names, ImmutableList<String> roots) { | ||
HashSet<String> resolved = new HashSet<>(); | ||
private static ImmutableMap<String, InputPathData> resolvePaths( |
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 add a Javadoc comment explaining what the keys and values of the return value represent?
HashSet<String> resolved = new HashSet<>(); | ||
private static ImmutableMap<String, InputPathData> resolvePaths( | ||
Iterable<String> names, ImmutableList<InputPathData> roots) { | ||
Map<String, InputPathData> resolved = new HashMap<>(); |
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.
nit: use ImmutableMap.builder()
private static ImmutableSortedMap<String, ImmutableSet<String>> buildRegistry( | ||
ImmutableSet<String> modulePaths) { | ||
SortedMap<String, Set<String>> registry = | ||
private static ImmutableSortedMap<String, ImmutableMap<String, InputPathData>> buildRegistry( |
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.
Please expand the JavaDoc to define the meaning of the keys and values of both the input and output.
@@ -564,4 +607,30 @@ public String apply(DependencyInfo info) { | |||
*/ | |||
NODE | |||
} | |||
|
|||
private static class InputPathData { |
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.
Need javadoc for this.
1a6c9d9
to
9a2df3a
Compare
@brad4d I took a hard run through and reworked this PR. I was able to drastically simplify things and hopefully the entire file is now much easier to read and follow. |
@@ -232,6 +248,14 @@ public ModulePath resolveJsModule( | |||
|
|||
@Nullable | |||
private String resolveJsModuleFile(String moduleAddress) { | |||
String[] fileExtensionsToSearch; |
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.
How about passing the file extensions to search into this method instead?
I'd prefer to keep checking of ResolutionMode to only the one switch statement, even if it means creating some custom-named functions for particular code paths.
Such functions make it easier to know the context when you're reading.
if (loadAddress != null) { | ||
return loadAddress; | ||
return removeAbsoluteIdentifierIndicator(loadAddress); |
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 add a comment to say why you remove the absolute identifier indicator here?
Maybe in javadoc on the method.
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.
Apparently there wasn't a good reason ...
if (!modulePaths.contains(resolved) && errorHandler != null) { | ||
errorHandler.report( | ||
CheckLevel.WARNING, JSError.make(sourcename, lineno, colno, LOAD_WARNING, moduleName)); | ||
String resolved = resolveJsModuleFile(moduleName); //locateNoCheck(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.
should probably remove the //locateNoCheck
comment.
5235847
to
d44dd2b
Compare
d44dd2b
to
96e5df8
Compare
@brad4d Fixed. |
@@ -388,38 +417,66 @@ public static boolean isPathIdentifier(String name) { | |||
return name.contains(MODULE_SLASH); | |||
} | |||
|
|||
/** Removes the leading slash from absolute identifiers */ | |||
public static String removeAbsoluteIdentifierIndicator(String name) { |
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 isn't called anywhere.
} | ||
|
||
/** Converts paths which don't start with either a '.' or '/' to absolute paths */ | ||
public static String toAbsoluteIdentifier(String name) { |
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.
Why is this public?
/** Converts paths which don't start with either a '.' or '/' to absolute paths */ | ||
public static String toAbsoluteIdentifier(String name) { | ||
if (name.length() == 0 || isAbsoluteIdentifier(name) || isRelativeIdentifier(name)) { | ||
return name; |
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.
Wait, this can return a relative identifier?
That's just misleading.
I saw your post regarding troublesome terminology, though I cannot find it now.
I propose this.
- "absolute" means "starts with '/'"
- "relative" means "starts with './'"
- "ambiguous" means everything else.
How about we detect ambiguous paths ASAP and either report an error or explicitly force them to be relative or absolute at that point?
I'd rather see some duplicated logic that makes it clear what is happening than a "fix it if it is broken, otherwise leave it alone" method like this one.
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.
How about we detect ambiguous paths ASAP and either report an error or explicitly force them to be relative or absolute at that point?
I think ambiguous paths (as you labeled them) are used widely inside google. For instance, this path is ambiguous:
import {foo} from 'path/to/bar'
I can force them to be absolute if you are willing to try landing 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.
FYI that path means two completely different things depending on the resolution mode. This has been a source of great confusion to all parties.
import {foo} from 'path/to/bar'
In LEGACY
mode, this path is absolute.
In NODE
mode, this path is looked up from the registry.
In BROWSER
mode, this path is invalid.
@ChadKillingsworth |
@brad4d I reworked the resolution algorithms into independent classes. |
54b1353
to
b6767f9
Compare
b6767f9
to
c175780
Compare
Looking very good. |
Had to fix some internal stuff to know about the new files. |
Got a couple-dozen failures when testing all the things. |
I would actually expect a few project fixes to be required by this change - but it is definitely worth the trouble. |
None of the breaks appear to be real. |
Just to be clear, you don't have to push changes like formatting changes back to the Github PR for MOE to work. |
String scriptAddress, String moduleAddress, String sourcename, int lineno, int colno) { | ||
|
||
if (ModuleLoader.isAmbiguousIdentifier(moduleAddress)) { | ||
if (errorHandler != null) { |
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 we forbid null for errorHandler and just make the creator pass in a no-op Errorhandler if they don't care about errors?
} | ||
|
||
Map<String, String> getPackageJsonMainEntries() { | ||
return null; |
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 we return an empty map instead of null so this method doesn't have to be @Nullable
?
@ChadKillingsworth I've also passed along 2 requests for changes in a github code review. Regarding testing we are looking great. |
@MatrixFrog |
@brad4d So the 64million dollar question... Do I need to squash my commits when I push next? |
…e nullable. Returns an empty map from getPackageJsonMainEntries so it does not return null.
@ChadKillingsworth In case you're interested, I'm basically working like this. cd $my_github_clone
git pull $chads_pr_branch
git format-patch -stdout >patchfile
patch-munge <patchfile >patchfile.munge # secret sauce to fix paths, etc.
cd $my_internal_repo_clone
git am patchfile.munge Then the reverse to go back out to you. |
Awesome. I made the requested changes. Should be good-to-go. |
I've imported your latest change and hit "the submit button". |
Closes google#2343 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=152771274
The interaction between the
--js_module_root
flag and absolute importsrequire('/foo/bar.js')
results in the module loader unable to locate the module. This applies equally to all 3 module resolution modes and both CommonJS and ES6 modules.Fixing this without breaking existing functionality has proven challenging. I'm hopeful this PR actually accomplishes it.
Given a module root of
base/
, the following modules should properly resolve:base/mod1.js
base/app.js