@taiga-ui/eslint-plugin-experience-next 0.503.0 → 0.504.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/index.d.ts +2 -1
- package/index.esm.js +237 -208
- package/package.json +1 -1
- package/rules/recommended/import-integrity.d.ts +2 -1
package/README.md
CHANGED
|
@@ -80,7 +80,7 @@ from third-party plugins. The exact severities and file globs live in
|
|
|
80
80
|
| [flat-exports](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/flat-exports.md) | Spread nested arrays when exporting Angular entity collections | | 🔧 | |
|
|
81
81
|
| [host-attributes-sort](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/host-attributes-sort.md) | Sort Angular host metadata attributes using configurable attribute groups | ✅ | 🔧 | |
|
|
82
82
|
| [html-logical-properties](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/html-logical-properties.md) | Enforce logical CSS properties over directional ones in Angular template style bindings | ✅ | 🔧 | |
|
|
83
|
-
| [import-integrity](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/import-integrity.md) | Fast import default, namespace, cycle, duplicate, named-as-default,
|
|
83
|
+
| [import-integrity](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/import-integrity.md) | Fast import default, namespace, cycle, duplicate, named-as-default, self-import, and path checks | ✅ | 🔧 | |
|
|
84
84
|
| [injection-token-description](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/injection-token-description.md) | Require `InjectionToken` descriptions to include the token name | ✅ | 🔧 | |
|
|
85
85
|
| [no-commonjs-import-patterns](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-commonjs-import-patterns.md) | Disallow legacy CommonJS interop import patterns | ✅ | | |
|
|
86
86
|
| [no-deep-imports](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-deep-imports.md) | Disables deep imports of Taiga UI packages | ✅ | 🔧 | |
|
package/index.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ declare const plugin: {
|
|
|
38
38
|
'html-logical-properties': import("eslint").Rule.RuleModule & {
|
|
39
39
|
name: string;
|
|
40
40
|
};
|
|
41
|
-
'import-integrity': import("@typescript-eslint/utils/ts-eslint").RuleModule<"duplicateImport" | "importCycle" | "missingDefaultExport" | "namedAsDefault" | "namedAsDefaultMember" | "selfImport" | "unknownNamespaceMember", [({
|
|
41
|
+
'import-integrity': import("@typescript-eslint/utils/ts-eslint").RuleModule<"duplicateImport" | "importCycle" | "missingDefaultExport" | "namedAsDefault" | "namedAsDefaultMember" | "selfImport" | "unknownNamespaceMember" | "uselessPathSegments", [({
|
|
42
42
|
checkCycles?: boolean;
|
|
43
43
|
checkDefaultImports?: boolean;
|
|
44
44
|
checkDuplicateImports?: boolean;
|
|
@@ -46,6 +46,7 @@ declare const plugin: {
|
|
|
46
46
|
checkNamedAsDefaultMembers?: boolean;
|
|
47
47
|
checkNamespaceMembers?: boolean;
|
|
48
48
|
checkSelfImports?: boolean;
|
|
49
|
+
checkUselessPathSegments?: boolean;
|
|
49
50
|
ignoreExternalDefaultImports?: boolean;
|
|
50
51
|
} | undefined)?], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
51
52
|
name: string;
|
package/index.esm.js
CHANGED
|
@@ -890,7 +890,7 @@ var recommended = defineConfig([
|
|
|
890
890
|
'import/no-named-as-default-member': 'off', // Covered by @taiga-ui/experience-next/import-integrity.
|
|
891
891
|
'import/no-self-import': 'off', // Covered by @taiga-ui/experience-next/import-integrity.
|
|
892
892
|
'import/no-unresolved': 'off',
|
|
893
|
-
'import/no-useless-path-segments':
|
|
893
|
+
'import/no-useless-path-segments': 'off', // Covered by @taiga-ui/experience-next/import-integrity.
|
|
894
894
|
'import/no-webpack-loader-syntax': 'error',
|
|
895
895
|
'lines-around-comment': [
|
|
896
896
|
'error',
|
|
@@ -247754,9 +247754,37 @@ function resolveModuleFileName(program, containingFile, moduleSpecifier) {
|
|
|
247754
247754
|
}
|
|
247755
247755
|
return resolved.resolvedFileName;
|
|
247756
247756
|
}
|
|
247757
|
-
function
|
|
247757
|
+
function splitModuleSpecifierQuery(moduleSpecifier) {
|
|
247758
247758
|
const queryIndex = moduleSpecifier.indexOf('?');
|
|
247759
|
-
return queryIndex === -1
|
|
247759
|
+
return queryIndex === -1
|
|
247760
|
+
? { path: moduleSpecifier, query: '' }
|
|
247761
|
+
: {
|
|
247762
|
+
path: moduleSpecifier.slice(0, queryIndex),
|
|
247763
|
+
query: moduleSpecifier.slice(queryIndex),
|
|
247764
|
+
};
|
|
247765
|
+
}
|
|
247766
|
+
function getModuleSpecifierPath(moduleSpecifier) {
|
|
247767
|
+
return splitModuleSpecifierQuery(moduleSpecifier).path;
|
|
247768
|
+
}
|
|
247769
|
+
function toRelativeImportPath(relativePath) {
|
|
247770
|
+
const stripped = relativePath.replaceAll(/\/$/g, '');
|
|
247771
|
+
return /^\.{1,2}$|^\.{1,2}\//.test(stripped) ? stripped : `./${stripped}`;
|
|
247772
|
+
}
|
|
247773
|
+
function normalizeImportPath(moduleSpecifierPath) {
|
|
247774
|
+
return toRelativeImportPath(path.posix.normalize(moduleSpecifierPath));
|
|
247775
|
+
}
|
|
247776
|
+
function countRelativeParents(pathSegments) {
|
|
247777
|
+
return pathSegments.filter((segment) => segment === '..').length;
|
|
247778
|
+
}
|
|
247779
|
+
function isIndexModulePath(moduleSpecifierPath) {
|
|
247780
|
+
return /(?:^|\/)index(?:\.[cm]?[jt]sx?)?$/.test(moduleSpecifierPath);
|
|
247781
|
+
}
|
|
247782
|
+
function quoteModuleSpecifier(source, moduleSpecifier) {
|
|
247783
|
+
const quote = source.raw.startsWith('"') ? '"' : "'";
|
|
247784
|
+
const escaped = moduleSpecifier
|
|
247785
|
+
.replaceAll('\\', '\\\\')
|
|
247786
|
+
.replaceAll(quote, `\\${quote}`);
|
|
247787
|
+
return `${quote}${escaped}${quote}`;
|
|
247760
247788
|
}
|
|
247761
247789
|
function resolveModuleKey(program, containingFile, moduleSpecifier, canonicalFileName) {
|
|
247762
247790
|
const moduleSpecifierPath = getModuleSpecifierPath(moduleSpecifier);
|
|
@@ -247860,149 +247888,14 @@ function getRuntimeModuleSpecifier(statement) {
|
|
|
247860
247888
|
}
|
|
247861
247889
|
return null;
|
|
247862
247890
|
}
|
|
247863
|
-
function buildDependenciesByFileName(program, canonicalFileName) {
|
|
247864
|
-
const sourceFiles = program.getSourceFiles().filter(isProjectCodeFile);
|
|
247865
|
-
const projectFileNames = new Set(sourceFiles.map((sourceFile) => canonicalFileName(sourceFile.fileName)));
|
|
247866
|
-
const dependenciesByFileName = new Map();
|
|
247867
|
-
const displayFileNameByFileName = new Map();
|
|
247868
|
-
for (const sourceFile of sourceFiles) {
|
|
247869
|
-
const fileName = canonicalFileName(sourceFile.fileName);
|
|
247870
|
-
const edges = [];
|
|
247871
|
-
displayFileNameByFileName.set(fileName, sourceFile.fileName);
|
|
247872
|
-
for (const statement of sourceFile.statements) {
|
|
247873
|
-
const moduleSpecifier = getRuntimeModuleSpecifier(statement);
|
|
247874
|
-
if (!moduleSpecifier) {
|
|
247875
|
-
continue;
|
|
247876
|
-
}
|
|
247877
|
-
const resolvedFileName = resolveModuleFileName(program, sourceFile.fileName, moduleSpecifier);
|
|
247878
|
-
if (!resolvedFileName) {
|
|
247879
|
-
continue;
|
|
247880
|
-
}
|
|
247881
|
-
const targetFileName = canonicalFileName(resolvedFileName);
|
|
247882
|
-
if (!projectFileNames.has(targetFileName)) {
|
|
247883
|
-
continue;
|
|
247884
|
-
}
|
|
247885
|
-
edges.push({
|
|
247886
|
-
isImport: ts.isImportDeclaration(statement),
|
|
247887
|
-
moduleSpecifier,
|
|
247888
|
-
targetFileName,
|
|
247889
|
-
});
|
|
247890
|
-
}
|
|
247891
|
-
dependenciesByFileName.set(fileName, edges);
|
|
247892
|
-
}
|
|
247893
|
-
return { dependenciesByFileName, displayFileNameByFileName };
|
|
247894
|
-
}
|
|
247895
|
-
function findStronglyConnectedComponents(dependenciesByFileName) {
|
|
247896
|
-
let nextComponentId = 0;
|
|
247897
|
-
let nextIndex = 0;
|
|
247898
|
-
const componentIdByFileName = new Map();
|
|
247899
|
-
const componentSizeById = new Map();
|
|
247900
|
-
const nodeStateByFileName = new Map();
|
|
247901
|
-
const stack = [];
|
|
247902
|
-
function visit(fileName) {
|
|
247903
|
-
const state = {
|
|
247904
|
-
index: nextIndex,
|
|
247905
|
-
lowLink: nextIndex,
|
|
247906
|
-
onStack: true,
|
|
247907
|
-
};
|
|
247908
|
-
nextIndex += 1;
|
|
247909
|
-
nodeStateByFileName.set(fileName, state);
|
|
247910
|
-
stack.push(fileName);
|
|
247911
|
-
for (const edge of dependenciesByFileName.get(fileName) ?? []) {
|
|
247912
|
-
const targetState = nodeStateByFileName.get(edge.targetFileName);
|
|
247913
|
-
if (!targetState) {
|
|
247914
|
-
visit(edge.targetFileName);
|
|
247915
|
-
const visitedTargetState = nodeStateByFileName.get(edge.targetFileName);
|
|
247916
|
-
if (visitedTargetState) {
|
|
247917
|
-
state.lowLink = Math.min(state.lowLink, visitedTargetState.lowLink);
|
|
247918
|
-
}
|
|
247919
|
-
continue;
|
|
247920
|
-
}
|
|
247921
|
-
if (targetState.onStack) {
|
|
247922
|
-
state.lowLink = Math.min(state.lowLink, targetState.index);
|
|
247923
|
-
}
|
|
247924
|
-
}
|
|
247925
|
-
if (state.lowLink !== state.index) {
|
|
247926
|
-
return;
|
|
247927
|
-
}
|
|
247928
|
-
const componentId = nextComponentId;
|
|
247929
|
-
let componentSize = 0;
|
|
247930
|
-
let shouldPop = stack.length > 0;
|
|
247931
|
-
nextComponentId += 1;
|
|
247932
|
-
while (shouldPop) {
|
|
247933
|
-
const memberFileName = stack.pop();
|
|
247934
|
-
if (!memberFileName) {
|
|
247935
|
-
shouldPop = false;
|
|
247936
|
-
continue;
|
|
247937
|
-
}
|
|
247938
|
-
const memberState = nodeStateByFileName.get(memberFileName);
|
|
247939
|
-
if (memberState) {
|
|
247940
|
-
memberState.onStack = false;
|
|
247941
|
-
}
|
|
247942
|
-
componentSize += 1;
|
|
247943
|
-
componentIdByFileName.set(memberFileName, componentId);
|
|
247944
|
-
shouldPop = stack.length > 0 && memberFileName !== fileName;
|
|
247945
|
-
}
|
|
247946
|
-
componentSizeById.set(componentId, componentSize);
|
|
247947
|
-
}
|
|
247948
|
-
for (const fileName of dependenciesByFileName.keys()) {
|
|
247949
|
-
if (!nodeStateByFileName.has(fileName)) {
|
|
247950
|
-
visit(fileName);
|
|
247951
|
-
}
|
|
247952
|
-
}
|
|
247953
|
-
return { componentIdByFileName, componentSizeById };
|
|
247954
|
-
}
|
|
247955
|
-
function collectSelfCycles(dependenciesByFileName) {
|
|
247956
|
-
const selfCycleFileNames = new Set();
|
|
247957
|
-
for (const [fileName, edges] of dependenciesByFileName) {
|
|
247958
|
-
if (edges.some((edge) => edge.targetFileName === fileName)) {
|
|
247959
|
-
selfCycleFileNames.add(fileName);
|
|
247960
|
-
}
|
|
247961
|
-
}
|
|
247962
|
-
return selfCycleFileNames;
|
|
247963
|
-
}
|
|
247964
|
-
function buildSccHasImportEdge(dependenciesByFileName, componentIdByFileName) {
|
|
247965
|
-
const result = new Map();
|
|
247966
|
-
for (const [fileName, edges] of dependenciesByFileName) {
|
|
247967
|
-
const sourceComponentId = componentIdByFileName.get(fileName);
|
|
247968
|
-
if (sourceComponentId === undefined || result.get(sourceComponentId) === true) {
|
|
247969
|
-
continue;
|
|
247970
|
-
}
|
|
247971
|
-
for (const edge of edges) {
|
|
247972
|
-
if (edge.isImport &&
|
|
247973
|
-
componentIdByFileName.get(edge.targetFileName) === sourceComponentId) {
|
|
247974
|
-
result.set(sourceComponentId, true);
|
|
247975
|
-
break;
|
|
247976
|
-
}
|
|
247977
|
-
}
|
|
247978
|
-
}
|
|
247979
|
-
return result;
|
|
247980
|
-
}
|
|
247981
|
-
function buildBarrelFileNames(dependenciesByFileName) {
|
|
247982
|
-
const result = new Set();
|
|
247983
|
-
for (const [fileName, edges] of dependenciesByFileName) {
|
|
247984
|
-
if (edges.some((edge) => !edge.isImport)) {
|
|
247985
|
-
result.add(fileName);
|
|
247986
|
-
}
|
|
247987
|
-
}
|
|
247988
|
-
return result;
|
|
247989
|
-
}
|
|
247990
247891
|
function getImportGraph(program) {
|
|
247991
247892
|
const cached = importGraphCacheByProgram.get(program);
|
|
247992
247893
|
if (cached) {
|
|
247993
247894
|
return cached;
|
|
247994
247895
|
}
|
|
247995
|
-
const canonicalFileName = createCanonicalFileName();
|
|
247996
|
-
const { dependenciesByFileName, displayFileNameByFileName } = buildDependenciesByFileName(program, canonicalFileName);
|
|
247997
|
-
const { componentIdByFileName, componentSizeById } = findStronglyConnectedComponents(dependenciesByFileName);
|
|
247998
247896
|
const cache = {
|
|
247999
|
-
|
|
248000
|
-
|
|
248001
|
-
componentSizeById,
|
|
248002
|
-
dependenciesByFileName,
|
|
248003
|
-
displayFileNameByFileName,
|
|
248004
|
-
sccHasImportEdgeById: buildSccHasImportEdge(dependenciesByFileName, componentIdByFileName),
|
|
248005
|
-
selfCycleFileNames: collectSelfCycles(dependenciesByFileName),
|
|
247897
|
+
dependenciesByFileName: new Map(),
|
|
247898
|
+
displayFileNameByFileName: new Map(),
|
|
248006
247899
|
};
|
|
248007
247900
|
importGraphCacheByProgram.set(program, cache);
|
|
248008
247901
|
return cache;
|
|
@@ -248092,76 +247985,136 @@ function getImportOrReExportModuleSpecifier(node) {
|
|
|
248092
247985
|
}
|
|
248093
247986
|
return typeof node.source.value === 'string' ? node.source.value : null;
|
|
248094
247987
|
}
|
|
248095
|
-
function
|
|
248096
|
-
const
|
|
248097
|
-
|
|
248098
|
-
|
|
247988
|
+
function getProjectSourceFile(program, fileName) {
|
|
247989
|
+
const sourceFile = getSourceFileByFileName(program, fileName);
|
|
247990
|
+
return sourceFile && isProjectCodeFile(sourceFile) ? sourceFile : null;
|
|
247991
|
+
}
|
|
247992
|
+
function getDependenciesForFile(program, graph, fileName, canonicalFileName) {
|
|
247993
|
+
const cached = graph.dependenciesByFileName.get(fileName);
|
|
247994
|
+
if (cached) {
|
|
247995
|
+
return cached;
|
|
248099
247996
|
}
|
|
248100
|
-
const
|
|
248101
|
-
|
|
248102
|
-
|
|
247997
|
+
const sourceFile = getProjectSourceFile(program, fileName);
|
|
247998
|
+
const edges = [];
|
|
247999
|
+
graph.dependenciesByFileName.set(fileName, edges);
|
|
248000
|
+
if (!sourceFile) {
|
|
248001
|
+
return edges;
|
|
248002
|
+
}
|
|
248003
|
+
graph.displayFileNameByFileName.set(fileName, sourceFile.fileName);
|
|
248004
|
+
for (const statement of sourceFile.statements) {
|
|
248005
|
+
const moduleSpecifier = getRuntimeModuleSpecifier(statement);
|
|
248006
|
+
if (!moduleSpecifier) {
|
|
248103
248007
|
continue;
|
|
248104
248008
|
}
|
|
248105
|
-
const
|
|
248106
|
-
|
|
248107
|
-
|
|
248108
|
-
|
|
248109
|
-
|
|
248110
|
-
|
|
248009
|
+
const resolvedFileName = resolveModuleFileName(program, sourceFile.fileName, moduleSpecifier);
|
|
248010
|
+
if (!resolvedFileName) {
|
|
248011
|
+
continue;
|
|
248012
|
+
}
|
|
248013
|
+
const targetSourceFile = getProjectSourceFile(program, resolvedFileName);
|
|
248014
|
+
if (!targetSourceFile) {
|
|
248015
|
+
continue;
|
|
248111
248016
|
}
|
|
248017
|
+
const targetFileName = canonicalFileName(targetSourceFile.fileName);
|
|
248018
|
+
graph.displayFileNameByFileName.set(targetFileName, targetSourceFile.fileName);
|
|
248019
|
+
edges.push({
|
|
248020
|
+
isImport: ts.isImportDeclaration(statement),
|
|
248021
|
+
moduleSpecifier,
|
|
248022
|
+
targetFileName,
|
|
248023
|
+
});
|
|
248112
248024
|
}
|
|
248113
|
-
return
|
|
248025
|
+
return edges;
|
|
248026
|
+
}
|
|
248027
|
+
function fileHasReExportEdges(program, graph, fileName, canonicalFileName) {
|
|
248028
|
+
return getDependenciesForFile(program, graph, fileName, canonicalFileName).some((edge) => !edge.isImport);
|
|
248029
|
+
}
|
|
248030
|
+
function getCycleStateKey(fileName, hasImportEdge) {
|
|
248031
|
+
return `${hasImportEdge ? '1' : '0'}:${fileName}`;
|
|
248032
|
+
}
|
|
248033
|
+
function reconstructSearchPath(stateKey, previousStateKeyByStateKey, fileNameByStateKey) {
|
|
248034
|
+
const pathSegments = [];
|
|
248035
|
+
let currentStateKey = stateKey;
|
|
248036
|
+
while (currentStateKey !== null) {
|
|
248037
|
+
const fileName = fileNameByStateKey.get(currentStateKey);
|
|
248038
|
+
if (!fileName) {
|
|
248039
|
+
return [];
|
|
248040
|
+
}
|
|
248041
|
+
pathSegments.push(fileName);
|
|
248042
|
+
const previousStateKey = previousStateKeyByStateKey.get(currentStateKey);
|
|
248043
|
+
if (previousStateKey === undefined) {
|
|
248044
|
+
return [];
|
|
248045
|
+
}
|
|
248046
|
+
currentStateKey = previousStateKey;
|
|
248047
|
+
}
|
|
248048
|
+
return pathSegments.reverse();
|
|
248114
248049
|
}
|
|
248115
|
-
function
|
|
248116
|
-
const queue = [
|
|
248117
|
-
const
|
|
248118
|
-
|
|
248119
|
-
|
|
248120
|
-
|
|
248050
|
+
function findCyclePathFromEdge(program, graph, currentFileName, edge, canonicalFileName) {
|
|
248051
|
+
const queue = [{ fileName: edge.targetFileName, hasImportEdge: edge.isImport }];
|
|
248052
|
+
const startStateKey = getCycleStateKey(edge.targetFileName, edge.isImport);
|
|
248053
|
+
const previousStateKeyByStateKey = new Map([
|
|
248054
|
+
[startStateKey, null],
|
|
248055
|
+
]);
|
|
248056
|
+
const fileNameByStateKey = new Map([
|
|
248057
|
+
[startStateKey, edge.targetFileName],
|
|
248058
|
+
]);
|
|
248059
|
+
let firstResult = null;
|
|
248060
|
+
for (const state of queue) {
|
|
248061
|
+
const stateKey = getCycleStateKey(state.fileName, state.hasImportEdge);
|
|
248062
|
+
if (state.fileName === currentFileName) {
|
|
248063
|
+
const path = reconstructSearchPath(stateKey, previousStateKeyByStateKey, fileNameByStateKey);
|
|
248064
|
+
if (path.length === 0) {
|
|
248065
|
+
continue;
|
|
248066
|
+
}
|
|
248067
|
+
const result = {
|
|
248068
|
+
cyclePath: [currentFileName, ...path],
|
|
248069
|
+
hasImportEdge: state.hasImportEdge,
|
|
248070
|
+
targetFileName: edge.targetFileName,
|
|
248071
|
+
};
|
|
248072
|
+
if (result.hasImportEdge) {
|
|
248073
|
+
return result;
|
|
248074
|
+
}
|
|
248075
|
+
firstResult ??= result;
|
|
248121
248076
|
continue;
|
|
248122
248077
|
}
|
|
248123
|
-
for (const
|
|
248124
|
-
|
|
248125
|
-
|
|
248078
|
+
for (const nextEdge of getDependenciesForFile(program, graph, state.fileName, canonicalFileName)) {
|
|
248079
|
+
const nextState = {
|
|
248080
|
+
fileName: nextEdge.targetFileName,
|
|
248081
|
+
hasImportEdge: state.hasImportEdge || nextEdge.isImport,
|
|
248082
|
+
};
|
|
248083
|
+
const nextStateKey = getCycleStateKey(nextState.fileName, nextState.hasImportEdge);
|
|
248084
|
+
if (previousStateKeyByStateKey.has(nextStateKey)) {
|
|
248126
248085
|
continue;
|
|
248127
248086
|
}
|
|
248128
|
-
|
|
248129
|
-
|
|
248087
|
+
previousStateKeyByStateKey.set(nextStateKey, stateKey);
|
|
248088
|
+
fileNameByStateKey.set(nextStateKey, nextState.fileName);
|
|
248089
|
+
queue.push(nextState);
|
|
248130
248090
|
}
|
|
248131
248091
|
}
|
|
248132
|
-
|
|
248133
|
-
|
|
248134
|
-
|
|
248135
|
-
|
|
248136
|
-
|
|
248137
|
-
|
|
248138
|
-
|
|
248139
|
-
|
|
248140
|
-
|
|
248141
|
-
|
|
248092
|
+
return firstResult;
|
|
248093
|
+
}
|
|
248094
|
+
function findCyclePathForSpecifier(program, graph, currentFileName, moduleSpecifier, canonicalFileName) {
|
|
248095
|
+
let firstResult = null;
|
|
248096
|
+
for (const edge of getDependenciesForFile(program, graph, currentFileName, canonicalFileName)) {
|
|
248097
|
+
if (edge.moduleSpecifier !== moduleSpecifier) {
|
|
248098
|
+
continue;
|
|
248099
|
+
}
|
|
248100
|
+
const result = findCyclePathFromEdge(program, graph, currentFileName, edge, canonicalFileName);
|
|
248101
|
+
if (!result) {
|
|
248102
|
+
continue;
|
|
248103
|
+
}
|
|
248104
|
+
if (result.hasImportEdge) {
|
|
248105
|
+
return result;
|
|
248142
248106
|
}
|
|
248143
|
-
|
|
248107
|
+
firstResult ??= result;
|
|
248144
248108
|
}
|
|
248145
|
-
return
|
|
248109
|
+
return firstResult;
|
|
248146
248110
|
}
|
|
248147
248111
|
function formatFileName(graph, fileName, cwd) {
|
|
248148
248112
|
const displayFileName = graph.displayFileNameByFileName.get(fileName) ?? fileName;
|
|
248149
248113
|
const relativeFileName = normalizeSlashes(path.relative(cwd, displayFileName));
|
|
248150
248114
|
return relativeFileName || normalizeSlashes(path.basename(displayFileName));
|
|
248151
248115
|
}
|
|
248152
|
-
function formatCyclePath(graph,
|
|
248153
|
-
|
|
248154
|
-
if (componentId === undefined || currentFileName === targetFileName) {
|
|
248155
|
-
return [currentFileName, targetFileName]
|
|
248156
|
-
.map((fileName) => formatFileName(graph, fileName, cwd))
|
|
248157
|
-
.join(' -> ');
|
|
248158
|
-
}
|
|
248159
|
-
return [
|
|
248160
|
-
currentFileName,
|
|
248161
|
-
...findPathWithinComponent(graph, targetFileName, currentFileName, componentId),
|
|
248162
|
-
]
|
|
248163
|
-
.map((fileName) => formatFileName(graph, fileName, cwd))
|
|
248164
|
-
.join(' -> ');
|
|
248116
|
+
function formatCyclePath(graph, cyclePath, cwd) {
|
|
248117
|
+
return cyclePath.map((fileName) => formatFileName(graph, fileName, cwd)).join(' -> ');
|
|
248165
248118
|
}
|
|
248166
248119
|
function getAliasedSymbolIfNeeded(checker, symbol) {
|
|
248167
248120
|
return (symbol.flags & ts.SymbolFlags.Alias) === 0
|
|
@@ -248306,6 +248259,7 @@ const rule$K = createRule({
|
|
|
248306
248259
|
const checkNamedAsDefaultMembers = context.options[0]?.checkNamedAsDefaultMembers ?? true;
|
|
248307
248260
|
const checkNamespaceMembers = context.options[0]?.checkNamespaceMembers ?? true;
|
|
248308
248261
|
const checkSelfImports = context.options[0]?.checkSelfImports ?? true;
|
|
248262
|
+
const shouldCheckUselessPathSegments = context.options[0]?.checkUselessPathSegments ?? true;
|
|
248309
248263
|
const ignoreExternalDefaultImports = context.options[0]?.ignoreExternalDefaultImports ?? true;
|
|
248310
248264
|
const canonicalFileName = createCanonicalFileName();
|
|
248311
248265
|
const currentFileName = canonicalFileName(context.filename);
|
|
@@ -248435,10 +248389,82 @@ const rule$K = createRule({
|
|
|
248435
248389
|
node,
|
|
248436
248390
|
});
|
|
248437
248391
|
}
|
|
248438
|
-
function
|
|
248439
|
-
|
|
248392
|
+
function getNormalizedPathReplacement(moduleSpecifierPath, canonicalResolvedFileName) {
|
|
248393
|
+
const normalizedPath = normalizeImportPath(moduleSpecifierPath);
|
|
248394
|
+
if (normalizedPath === moduleSpecifierPath) {
|
|
248395
|
+
return null;
|
|
248396
|
+
}
|
|
248397
|
+
const normalizedResolved = resolveModule(tsProgram, context.filename, normalizedPath);
|
|
248398
|
+
return normalizedResolved &&
|
|
248399
|
+
canonicalFileName(normalizedResolved.resolvedFileName) ===
|
|
248400
|
+
canonicalResolvedFileName
|
|
248401
|
+
? normalizedPath
|
|
248402
|
+
: null;
|
|
248403
|
+
}
|
|
248404
|
+
function getIndexPathReplacement(moduleSpecifierPath, canonicalResolvedFileName) {
|
|
248405
|
+
if (!isIndexModulePath(moduleSpecifierPath)) {
|
|
248406
|
+
return null;
|
|
248407
|
+
}
|
|
248408
|
+
const parentDirectory = path.posix.dirname(moduleSpecifierPath);
|
|
248409
|
+
if (parentDirectory === '.' || parentDirectory === '..') {
|
|
248410
|
+
return parentDirectory;
|
|
248411
|
+
}
|
|
248412
|
+
const parentResolved = resolveModule(tsProgram, context.filename, parentDirectory);
|
|
248413
|
+
return parentResolved &&
|
|
248414
|
+
canonicalFileName(parentResolved.resolvedFileName) !==
|
|
248415
|
+
canonicalResolvedFileName
|
|
248416
|
+
? `${parentDirectory}/`
|
|
248417
|
+
: parentDirectory;
|
|
248418
|
+
}
|
|
248419
|
+
function getExcessParentPathReplacement(moduleSpecifierPath, resolvedFileName) {
|
|
248420
|
+
if (moduleSpecifierPath.startsWith('./')) {
|
|
248421
|
+
return null;
|
|
248422
|
+
}
|
|
248423
|
+
const expectedPath = computeRelativeImportPath(context.filename, resolvedFileName);
|
|
248424
|
+
const importSegments = moduleSpecifierPath.replace(/^\.\//, '').split('/');
|
|
248425
|
+
const importParentCount = countRelativeParents(importSegments);
|
|
248426
|
+
const expectedParentCount = countRelativeParents(expectedPath.split('/'));
|
|
248427
|
+
if (importParentCount <= expectedParentCount ||
|
|
248428
|
+
expectedPath === moduleSpecifierPath) {
|
|
248429
|
+
return null;
|
|
248430
|
+
}
|
|
248431
|
+
return expectedPath;
|
|
248432
|
+
}
|
|
248433
|
+
function getUselessPathSegmentsReplacement(moduleSpecifierPath) {
|
|
248434
|
+
const resolved = resolveModule(tsProgram, context.filename, moduleSpecifierPath);
|
|
248435
|
+
if (!resolved) {
|
|
248436
|
+
return null;
|
|
248437
|
+
}
|
|
248438
|
+
const canonicalResolvedFileName = canonicalFileName(resolved.resolvedFileName);
|
|
248439
|
+
return (getNormalizedPathReplacement(moduleSpecifierPath, canonicalResolvedFileName) ??
|
|
248440
|
+
getIndexPathReplacement(moduleSpecifierPath, canonicalResolvedFileName) ??
|
|
248441
|
+
getExcessParentPathReplacement(moduleSpecifierPath, resolved.resolvedFileName));
|
|
248442
|
+
}
|
|
248443
|
+
function checkUselessPathSegments(source) {
|
|
248444
|
+
if (!shouldCheckUselessPathSegments || !source.value.startsWith('.')) {
|
|
248445
|
+
return;
|
|
248446
|
+
}
|
|
248447
|
+
const parts = splitModuleSpecifierQuery(source.value);
|
|
248448
|
+
const proposedPath = getUselessPathSegmentsReplacement(parts.path);
|
|
248449
|
+
if (!proposedPath) {
|
|
248450
|
+
return;
|
|
248451
|
+
}
|
|
248452
|
+
const proposedModuleSpecifier = `${proposedPath}${parts.query}`;
|
|
248453
|
+
context.report({
|
|
248454
|
+
data: {
|
|
248455
|
+
moduleSpecifier: source.value,
|
|
248456
|
+
proposedPath: proposedModuleSpecifier,
|
|
248457
|
+
},
|
|
248458
|
+
fix: (fixer) => fixer.replaceText(source, quoteModuleSpecifier(source, proposedModuleSpecifier)),
|
|
248459
|
+
messageId: 'uselessPathSegments',
|
|
248460
|
+
node: source,
|
|
248461
|
+
});
|
|
248462
|
+
}
|
|
248463
|
+
function checkDeclarationModuleSpecifier(node) {
|
|
248464
|
+
if (!isStringLiteral(node.source)) {
|
|
248440
248465
|
return;
|
|
248441
248466
|
}
|
|
248467
|
+
checkUselessPathSegments(node.source);
|
|
248442
248468
|
checkSelfImport(node.source.value, node.source);
|
|
248443
248469
|
}
|
|
248444
248470
|
function checkRequireSelfImport(node) {
|
|
@@ -248454,12 +248480,12 @@ const rule$K = createRule({
|
|
|
248454
248480
|
}
|
|
248455
248481
|
checkSelfImport(moduleSpecifier.value, moduleSpecifier);
|
|
248456
248482
|
}
|
|
248457
|
-
function
|
|
248483
|
+
function checkDynamicImport(node) {
|
|
248458
248484
|
const { source } = node;
|
|
248459
|
-
if (source
|
|
248460
|
-
typeof source.value !== 'string') {
|
|
248485
|
+
if (!isStringLiteral(source)) {
|
|
248461
248486
|
return;
|
|
248462
248487
|
}
|
|
248488
|
+
checkUselessPathSegments(source);
|
|
248463
248489
|
checkSelfImport(source.value, source);
|
|
248464
248490
|
}
|
|
248465
248491
|
function checkImportCycle(node) {
|
|
@@ -248475,11 +248501,11 @@ const rule$K = createRule({
|
|
|
248475
248501
|
return;
|
|
248476
248502
|
}
|
|
248477
248503
|
const graph = getImportGraph(tsProgram);
|
|
248478
|
-
const
|
|
248479
|
-
if (!
|
|
248504
|
+
const cycle = findCyclePathForSpecifier(tsProgram, graph, currentFileName, moduleSpecifier, canonicalFileName);
|
|
248505
|
+
if (!cycle) {
|
|
248480
248506
|
return;
|
|
248481
248507
|
}
|
|
248482
|
-
const cyclePath = formatCyclePath(graph,
|
|
248508
|
+
const cyclePath = formatCyclePath(graph, cycle.cyclePath, context.cwd);
|
|
248483
248509
|
if (node.type === dist$3.AST_NODE_TYPES.ImportDeclaration) {
|
|
248484
248510
|
// Compute the redirect replacement eagerly so we can decide whether
|
|
248485
248511
|
// to suppress. If a redirect to the direct source file is possible,
|
|
@@ -248501,12 +248527,10 @@ const rule$K = createRule({
|
|
|
248501
248527
|
}
|
|
248502
248528
|
else {
|
|
248503
248529
|
// For re-export nodes (export * from / export {x} from), suppress the
|
|
248504
|
-
// error when the
|
|
248530
|
+
// error when the reachable cycle contains an ImportDeclaration edge.
|
|
248505
248531
|
// That ImportDeclaration will be reported (and fixed) directly, so
|
|
248506
248532
|
// reporting the barrel re-export would create duplicate, unfixable noise.
|
|
248507
|
-
|
|
248508
|
-
if (componentId !== undefined &&
|
|
248509
|
-
graph.sccHasImportEdgeById.get(componentId) === true) {
|
|
248533
|
+
if (cycle.hasImportEdge) {
|
|
248510
248534
|
return;
|
|
248511
248535
|
}
|
|
248512
248536
|
context.report({
|
|
@@ -248528,7 +248552,7 @@ const rule$K = createRule({
|
|
|
248528
248552
|
// Not a barrel (has no re-export edges) — symbols are defined locally,
|
|
248529
248553
|
// so there is no shorter direct-source path to redirect to.
|
|
248530
248554
|
if (!canonicalBarrelFileName ||
|
|
248531
|
-
!getImportGraph(tsProgram)
|
|
248555
|
+
!fileHasReExportEdges(tsProgram, getImportGraph(tsProgram), canonicalBarrelFileName, canonicalFileName)) {
|
|
248532
248556
|
return null;
|
|
248533
248557
|
}
|
|
248534
248558
|
const specifiersBySourceFile = new Map();
|
|
@@ -248794,21 +248818,21 @@ const rule$K = createRule({
|
|
|
248794
248818
|
CallExpression: checkRequireSelfImport,
|
|
248795
248819
|
ExportAllDeclaration(node) {
|
|
248796
248820
|
checkImportCycle(node);
|
|
248797
|
-
|
|
248821
|
+
checkDeclarationModuleSpecifier(node);
|
|
248798
248822
|
},
|
|
248799
248823
|
ExportNamedDeclaration(node) {
|
|
248800
248824
|
checkImportCycle(node);
|
|
248801
|
-
|
|
248825
|
+
checkDeclarationModuleSpecifier(node);
|
|
248802
248826
|
checkNamedAsDefaultExport(node);
|
|
248803
248827
|
},
|
|
248804
248828
|
ImportDeclaration(node) {
|
|
248805
248829
|
checkImportCycle(node);
|
|
248806
|
-
|
|
248830
|
+
checkDeclarationModuleSpecifier(node);
|
|
248807
248831
|
collectDuplicateImport(node);
|
|
248808
248832
|
checkDefaultImport(node);
|
|
248809
248833
|
collectNamespaceImport(node);
|
|
248810
248834
|
},
|
|
248811
|
-
ImportExpression:
|
|
248835
|
+
ImportExpression: checkDynamicImport,
|
|
248812
248836
|
MemberExpression(node) {
|
|
248813
248837
|
checkNamedAsDefaultMemberExpression(node);
|
|
248814
248838
|
checkNamespaceMember(node);
|
|
@@ -248819,7 +248843,7 @@ const rule$K = createRule({
|
|
|
248819
248843
|
},
|
|
248820
248844
|
meta: {
|
|
248821
248845
|
docs: {
|
|
248822
|
-
description: 'Fast replacement for import/default, import/namespace, import/no-cycle, import/no-duplicates, import/no-named-as-default, import/no-named-as-default-member,
|
|
248846
|
+
description: 'Fast replacement for import/default, import/namespace, import/no-cycle, import/no-duplicates, import/no-named-as-default, import/no-named-as-default-member, import/no-self-import, and import/no-useless-path-segments checks',
|
|
248823
248847
|
},
|
|
248824
248848
|
fixable: 'code',
|
|
248825
248849
|
messages: {
|
|
@@ -248830,6 +248854,7 @@ const rule$K = createRule({
|
|
|
248830
248854
|
namedAsDefaultMember: 'Default import "{{defaultName}}" from "{{moduleSpecifier}}" also has a named export "{{memberName}}". Use a named import instead.',
|
|
248831
248855
|
selfImport: 'Module imports itself.',
|
|
248832
248856
|
unknownNamespaceMember: 'Namespace import "{{namespaceName}}" from "{{moduleSpecifier}}" has no exported member "{{memberName}}".',
|
|
248857
|
+
uselessPathSegments: 'Useless path segments for "{{moduleSpecifier}}", should be "{{proposedPath}}".',
|
|
248833
248858
|
},
|
|
248834
248859
|
schema: [
|
|
248835
248860
|
{
|
|
@@ -248863,6 +248888,10 @@ const rule$K = createRule({
|
|
|
248863
248888
|
description: 'Report imports, re-exports, dynamic imports, and require() calls that resolve to the current file. Defaults to true.',
|
|
248864
248889
|
type: 'boolean',
|
|
248865
248890
|
},
|
|
248891
|
+
checkUselessPathSegments: {
|
|
248892
|
+
description: 'Report relative imports, re-exports, and dynamic imports with unnecessary path segments or /index suffixes. Defaults to true.',
|
|
248893
|
+
type: 'boolean',
|
|
248894
|
+
},
|
|
248866
248895
|
ignoreExternalDefaultImports: {
|
|
248867
248896
|
description: 'Skip default import checks for modules resolved from external libraries. Defaults to true.',
|
|
248868
248897
|
type: 'boolean',
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type TSESLint } from '@typescript-eslint/utils';
|
|
2
|
-
type MessageId = 'duplicateImport' | 'importCycle' | 'missingDefaultExport' | 'namedAsDefault' | 'namedAsDefaultMember' | 'selfImport' | 'unknownNamespaceMember';
|
|
2
|
+
type MessageId = 'duplicateImport' | 'importCycle' | 'missingDefaultExport' | 'namedAsDefault' | 'namedAsDefaultMember' | 'selfImport' | 'unknownNamespaceMember' | 'uselessPathSegments';
|
|
3
3
|
type Options = [
|
|
4
4
|
{
|
|
5
5
|
checkCycles?: boolean;
|
|
@@ -9,6 +9,7 @@ type Options = [
|
|
|
9
9
|
checkNamedAsDefaultMembers?: boolean;
|
|
10
10
|
checkNamespaceMembers?: boolean;
|
|
11
11
|
checkSelfImports?: boolean;
|
|
12
|
+
checkUselessPathSegments?: boolean;
|
|
12
13
|
ignoreExternalDefaultImports?: boolean;
|
|
13
14
|
}?
|
|
14
15
|
];
|