feat(linter): support path wildcards in enforce-module-boundaries autofix (#18316)
This commit is contained in:
parent
dcefa4a6ad
commit
9c7ded0b1a
@ -332,7 +332,7 @@ export default createESLintRule<Options, MessageIds>({
|
|||||||
fix(fixer) {
|
fix(fixer) {
|
||||||
// imp has form of @myorg/someproject/some/path
|
// imp has form of @myorg/someproject/some/path
|
||||||
const indexTsPaths = getBarrelEntryPointByImportScope(imp);
|
const indexTsPaths = getBarrelEntryPointByImportScope(imp);
|
||||||
if (indexTsPaths && indexTsPaths.length > 0) {
|
if (indexTsPaths.length > 0) {
|
||||||
const specifiers = (node as any).specifiers;
|
const specifiers = (node as any).specifiers;
|
||||||
if (!specifiers || specifiers.length === 0) {
|
if (!specifiers || specifiers.length === 0) {
|
||||||
return;
|
return;
|
||||||
@ -359,13 +359,22 @@ export default createESLintRule<Options, MessageIds>({
|
|||||||
dirname(importPath)
|
dirname(importPath)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// the string we receive from elsewhere might not have a leading './' here despite still being a relative path
|
||||||
|
// we'd like to ensure it's a normalized relative form starting from ./ or ../
|
||||||
|
const ensureRelativeForm = (path: string): string =>
|
||||||
|
path.startsWith('./') || path.startsWith('../')
|
||||||
|
? path
|
||||||
|
: `./${path}`;
|
||||||
|
|
||||||
// if the string is empty, it's the current file
|
// if the string is empty, it's the current file
|
||||||
const importPathResolved =
|
const importPathResolved =
|
||||||
relativePath === ''
|
relativePath === ''
|
||||||
? `./${basename(importPath)}`
|
? `./${basename(importPath)}`
|
||||||
: joinPathFragments(
|
: ensureRelativeForm(
|
||||||
|
joinPathFragments(
|
||||||
relativePath,
|
relativePath,
|
||||||
basename(importPath)
|
basename(importPath)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
importsToRemap.push({
|
importsToRemap.push({
|
||||||
|
|||||||
@ -27,9 +27,38 @@ function tryReadBaseJson() {
|
|||||||
*/
|
*/
|
||||||
export function getBarrelEntryPointByImportScope(
|
export function getBarrelEntryPointByImportScope(
|
||||||
importScope: string
|
importScope: string
|
||||||
): string[] | null {
|
): string[] {
|
||||||
|
const tryPaths = (
|
||||||
|
paths: Record<string, string[]>,
|
||||||
|
importScope: string
|
||||||
|
): string[] => {
|
||||||
|
// TODO check and warn that the entries of paths[importScope] have no wildcards; that'd be user misconfiguration
|
||||||
|
if (paths[importScope]) return paths[importScope];
|
||||||
|
|
||||||
|
// accommodate wildcards (it's not glob) https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping
|
||||||
|
const result = new Set<string>(); // set ensures there are no duplicates
|
||||||
|
|
||||||
|
for (const [alias, targets] of Object.entries(paths)) {
|
||||||
|
if (!alias.endsWith('*')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const strippedAlias = alias.slice(0, -1); // remove asterisk
|
||||||
|
if (!importScope.startsWith(strippedAlias)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const dynamicPart = importScope.slice(strippedAlias.length);
|
||||||
|
targets.forEach((target) => {
|
||||||
|
result.add(target.replace('*', dynamicPart)); // add interpolated value
|
||||||
|
});
|
||||||
|
// we found the entry for importScope; an import scope not supposed and has no sense having > 1 Aliases; TODO warn on duplicated entries
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(result);
|
||||||
|
};
|
||||||
const tsConfigBase = tryReadBaseJson();
|
const tsConfigBase = tryReadBaseJson();
|
||||||
return tsConfigBase?.compilerOptions?.paths[importScope] || null;
|
if (!tsConfigBase?.compilerOptions?.paths) return [];
|
||||||
|
return tryPaths(tsConfigBase.compilerOptions.paths, importScope);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBarrelEntryPointProjectNode(
|
export function getBarrelEntryPointProjectNode(
|
||||||
@ -90,7 +119,20 @@ export function getRelativeImportPath(exportedMember, filePath, basePath) {
|
|||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
|
!lstatSync(filePath, {
|
||||||
|
throwIfNoEntry: false,
|
||||||
|
}) /*not folder, but probably not full file with an extension 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;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const fileContent = readFileSync(filePath, 'utf8');
|
const fileContent = readFileSync(filePath, 'utf8');
|
||||||
|
|
||||||
// use the TypeScript AST to find the path to the file where exportedMember is defined
|
// use the TypeScript AST to find the path to the file where exportedMember is defined
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user