-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
feat(linter): support path wildcards in enforce-module-boundaries autofix #18316
feat(linter): support path wildcards in enforce-module-boundaries autofix #18316
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
@@ -358,14 +358,20 @@ export default createESLintRule<Options, MessageIds>({ | |||
dirname(fileName), | |||
dirname(importPath) | |||
); | |||
const maybeTagRelative = (path: string): 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.
Why was this needed? Wasn't relative(...)
above already returning a relative 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.
it's relative but when there's only one level of relativity (./
), relative() would cut ./
making i.e. expected ./my/module
into my/module
: it goes into imports like this then import { some } from 'my/module'
which makes resolver think it's an alias or a node lib, whereas what we really need is import { some } from './my/module'
to reflect that we import relatively to the same dir.
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.
Wow, that's unexpected behavior of that function. Thanks for the clarification
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.
Can we name this something like ensureRelativeForm
or something along those lines. This name doesn't really explain what the function does.
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.
ensureRelativeForm
sounds good, I didn't come up with a better name. Renamed!
@@ -81,6 +115,29 @@ function hasMemberExport(exportedMember, filePath) { | |||
} | |||
|
|||
export function getRelativeImportPath(exportedMember, filePath, basePath) { | |||
const TSX_SUFFIX = '.tsx'; |
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 prefer to have these explicitly used. It makes code shorter and more readable as it doesn't encapsulate the extension.
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.
Got it. I'll change it back soon. Although I think that encapsulating extension doesn't harm readability, I respect the project code guidelines.
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.
Inlined
return getOneOrNone( | ||
Object.entries(paths) | ||
.map(([src, dests]) => { | ||
const isWildcard = src.endsWith(WILDCARD_TS_SPECIAL_CASE); |
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.
Again, please use *
directly. It makes the code more readable.
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.
Inlined
@@ -27,9 +27,43 @@ function tryReadBaseJson() { | |||
*/ | |||
export function getBarrelEntryPointByImportScope( |
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.
Can we make this simpler?
The getOneOrNone
seems to be doing a bit more than that since it also returns the result of the function. Additionally, if we are using generics, then they should extend from something other than any
.
We want to collect all the paths where the alias is a wildcard and matches the import, and then append the delta to the mapped path.
Something like this would work:
const result = new Set<string>; // set ensures there are no duplicates
Object.entries(paths).forEach((alias, targets) => {
if (!alias.endsWith('*')) {
return;
}
const strippedAlias = alias.slice(0, -1); // remove asterisk
if (!importScope.startsWith(strippedAlias)) {
return;
}
const dynamicPart = importScope.slice(strippedAlias.length);
targets.forEach(target => {
result.add(target.replace('*', dynamicPart)); // add interpolated value
})
})
return Array.from(result);
I think this is cleaner and more readable, but most importantly - using primitive functions over higher-order functions (map, filter, flat) improves performance significantly.
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.
Used your implementation and validated it against the older code. Looks correct. I took liberty to convert .forEach(
into for ... of ...
to be in line with removing higher-order functions from the computation.
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 .forEach
is not a higher-order function and is slightly faster than for...of
and comparable with plain for
. But this is not a deal breaker so let's just leave it.
const JS_SUFFIX = '.js'; | ||
const stats = lstatSync(filePath, { throwIfNoEntry: false }); // TODO nodejs bug: can return undefined despite contract https://nodejs.org/api/fs.html#fslstatsyncpath-options | ||
if (!stats) { | ||
const suffixesToTry = [TSX_SUFFIX, TS_SUFFIX, JSX_SUFFIX, JS_SUFFIX]; |
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 replace this recursive logic with:
if (lstatSync(filePath).isDirectory()) {
// existing unchanged code
} else if (!lstatSync(filePath, { throwIfNoEntry: false })){ // not folder, but not full file either
// try to find an extension that exists
const ext = ['.ts', '.tsx', '.js', '.jsx'].find(ext => lstatSync(filePath + ext, { throwIfNoEntry: false }));
if (ext) {
filePath += ext;
}
}
// the rest of the logic unchanged
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.
Replaced
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 reasoning is correct, but code is unnecessarily complex. Check my comments and try to make it simpler.
Thank you for your suggestions @meeroslav , appreciated it! I've updated the PR accordingly. |
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.
Looks good! Thank you for the PR and for making the necessary changes.
This pull request has already been merged/closed. If you experience issues related to these changes, please open a new issue referencing this pull request. |
…ofix
What it solves: supports wildcards from https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping in enforce-module-boundaries
--fix
Original discussion and bug report https://nrwlcommunity.slack.com/archives/CMFKWPU6Q/p1690292877751699
Current Behavior
only basic case seems to be handled, the one that is generated by default generators
Expected Behavior
More advanced aliasing https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping, including
paths: {"@currents/graph-internals/*": ["libs/graph-internals/src/lib/*"],}
, is desirableRelated Issue(s)
None