@taiga-ui/eslint-plugin-experience-next 0.503.0 → 0.505.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 +238 -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',
|
|
@@ -1509,6 +1509,7 @@ var taigaSpecific = defineConfig([
|
|
|
1509
1509
|
{
|
|
1510
1510
|
files: pattern('**/*.html'),
|
|
1511
1511
|
ignores: [
|
|
1512
|
+
'**/proprietary/**',
|
|
1512
1513
|
// Angular ESLint virtual files for inline templates in spec/cy files
|
|
1513
1514
|
'**/*.spec.ts/**/*.html',
|
|
1514
1515
|
'**/*.cy.ts/**/*.html',
|
|
@@ -247754,9 +247755,37 @@ function resolveModuleFileName(program, containingFile, moduleSpecifier) {
|
|
|
247754
247755
|
}
|
|
247755
247756
|
return resolved.resolvedFileName;
|
|
247756
247757
|
}
|
|
247757
|
-
function
|
|
247758
|
+
function splitModuleSpecifierQuery(moduleSpecifier) {
|
|
247758
247759
|
const queryIndex = moduleSpecifier.indexOf('?');
|
|
247759
|
-
return queryIndex === -1
|
|
247760
|
+
return queryIndex === -1
|
|
247761
|
+
? { path: moduleSpecifier, query: '' }
|
|
247762
|
+
: {
|
|
247763
|
+
path: moduleSpecifier.slice(0, queryIndex),
|
|
247764
|
+
query: moduleSpecifier.slice(queryIndex),
|
|
247765
|
+
};
|
|
247766
|
+
}
|
|
247767
|
+
function getModuleSpecifierPath(moduleSpecifier) {
|
|
247768
|
+
return splitModuleSpecifierQuery(moduleSpecifier).path;
|
|
247769
|
+
}
|
|
247770
|
+
function toRelativeImportPath(relativePath) {
|
|
247771
|
+
const stripped = relativePath.replaceAll(/\/$/g, '');
|
|
247772
|
+
return /^\.{1,2}$|^\.{1,2}\//.test(stripped) ? stripped : `./${stripped}`;
|
|
247773
|
+
}
|
|
247774
|
+
function normalizeImportPath(moduleSpecifierPath) {
|
|
247775
|
+
return toRelativeImportPath(path.posix.normalize(moduleSpecifierPath));
|
|
247776
|
+
}
|
|
247777
|
+
function countRelativeParents(pathSegments) {
|
|
247778
|
+
return pathSegments.filter((segment) => segment === '..').length;
|
|
247779
|
+
}
|
|
247780
|
+
function isIndexModulePath(moduleSpecifierPath) {
|
|
247781
|
+
return /(?:^|\/)index(?:\.[cm]?[jt]sx?)?$/.test(moduleSpecifierPath);
|
|
247782
|
+
}
|
|
247783
|
+
function quoteModuleSpecifier(source, moduleSpecifier) {
|
|
247784
|
+
const quote = source.raw.startsWith('"') ? '"' : "'";
|
|
247785
|
+
const escaped = moduleSpecifier
|
|
247786
|
+
.replaceAll('\\', '\\\\')
|
|
247787
|
+
.replaceAll(quote, `\\${quote}`);
|
|
247788
|
+
return `${quote}${escaped}${quote}`;
|
|
247760
247789
|
}
|
|
247761
247790
|
function resolveModuleKey(program, containingFile, moduleSpecifier, canonicalFileName) {
|
|
247762
247791
|
const moduleSpecifierPath = getModuleSpecifierPath(moduleSpecifier);
|
|
@@ -247860,149 +247889,14 @@ function getRuntimeModuleSpecifier(statement) {
|
|
|
247860
247889
|
}
|
|
247861
247890
|
return null;
|
|
247862
247891
|
}
|
|
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
247892
|
function getImportGraph(program) {
|
|
247991
247893
|
const cached = importGraphCacheByProgram.get(program);
|
|
247992
247894
|
if (cached) {
|
|
247993
247895
|
return cached;
|
|
247994
247896
|
}
|
|
247995
|
-
const canonicalFileName = createCanonicalFileName();
|
|
247996
|
-
const { dependenciesByFileName, displayFileNameByFileName } = buildDependenciesByFileName(program, canonicalFileName);
|
|
247997
|
-
const { componentIdByFileName, componentSizeById } = findStronglyConnectedComponents(dependenciesByFileName);
|
|
247998
247897
|
const cache = {
|
|
247999
|
-
|
|
248000
|
-
|
|
248001
|
-
componentSizeById,
|
|
248002
|
-
dependenciesByFileName,
|
|
248003
|
-
displayFileNameByFileName,
|
|
248004
|
-
sccHasImportEdgeById: buildSccHasImportEdge(dependenciesByFileName, componentIdByFileName),
|
|
248005
|
-
selfCycleFileNames: collectSelfCycles(dependenciesByFileName),
|
|
247898
|
+
dependenciesByFileName: new Map(),
|
|
247899
|
+
displayFileNameByFileName: new Map(),
|
|
248006
247900
|
};
|
|
248007
247901
|
importGraphCacheByProgram.set(program, cache);
|
|
248008
247902
|
return cache;
|
|
@@ -248092,76 +247986,136 @@ function getImportOrReExportModuleSpecifier(node) {
|
|
|
248092
247986
|
}
|
|
248093
247987
|
return typeof node.source.value === 'string' ? node.source.value : null;
|
|
248094
247988
|
}
|
|
248095
|
-
function
|
|
248096
|
-
const
|
|
248097
|
-
|
|
248098
|
-
|
|
247989
|
+
function getProjectSourceFile(program, fileName) {
|
|
247990
|
+
const sourceFile = getSourceFileByFileName(program, fileName);
|
|
247991
|
+
return sourceFile && isProjectCodeFile(sourceFile) ? sourceFile : null;
|
|
247992
|
+
}
|
|
247993
|
+
function getDependenciesForFile(program, graph, fileName, canonicalFileName) {
|
|
247994
|
+
const cached = graph.dependenciesByFileName.get(fileName);
|
|
247995
|
+
if (cached) {
|
|
247996
|
+
return cached;
|
|
248099
247997
|
}
|
|
248100
|
-
const
|
|
248101
|
-
|
|
248102
|
-
|
|
247998
|
+
const sourceFile = getProjectSourceFile(program, fileName);
|
|
247999
|
+
const edges = [];
|
|
248000
|
+
graph.dependenciesByFileName.set(fileName, edges);
|
|
248001
|
+
if (!sourceFile) {
|
|
248002
|
+
return edges;
|
|
248003
|
+
}
|
|
248004
|
+
graph.displayFileNameByFileName.set(fileName, sourceFile.fileName);
|
|
248005
|
+
for (const statement of sourceFile.statements) {
|
|
248006
|
+
const moduleSpecifier = getRuntimeModuleSpecifier(statement);
|
|
248007
|
+
if (!moduleSpecifier) {
|
|
248103
248008
|
continue;
|
|
248104
248009
|
}
|
|
248105
|
-
const
|
|
248106
|
-
|
|
248107
|
-
|
|
248108
|
-
|
|
248109
|
-
|
|
248110
|
-
|
|
248010
|
+
const resolvedFileName = resolveModuleFileName(program, sourceFile.fileName, moduleSpecifier);
|
|
248011
|
+
if (!resolvedFileName) {
|
|
248012
|
+
continue;
|
|
248013
|
+
}
|
|
248014
|
+
const targetSourceFile = getProjectSourceFile(program, resolvedFileName);
|
|
248015
|
+
if (!targetSourceFile) {
|
|
248016
|
+
continue;
|
|
248111
248017
|
}
|
|
248018
|
+
const targetFileName = canonicalFileName(targetSourceFile.fileName);
|
|
248019
|
+
graph.displayFileNameByFileName.set(targetFileName, targetSourceFile.fileName);
|
|
248020
|
+
edges.push({
|
|
248021
|
+
isImport: ts.isImportDeclaration(statement),
|
|
248022
|
+
moduleSpecifier,
|
|
248023
|
+
targetFileName,
|
|
248024
|
+
});
|
|
248112
248025
|
}
|
|
248113
|
-
return
|
|
248026
|
+
return edges;
|
|
248027
|
+
}
|
|
248028
|
+
function fileHasReExportEdges(program, graph, fileName, canonicalFileName) {
|
|
248029
|
+
return getDependenciesForFile(program, graph, fileName, canonicalFileName).some((edge) => !edge.isImport);
|
|
248030
|
+
}
|
|
248031
|
+
function getCycleStateKey(fileName, hasImportEdge) {
|
|
248032
|
+
return `${hasImportEdge ? '1' : '0'}:${fileName}`;
|
|
248033
|
+
}
|
|
248034
|
+
function reconstructSearchPath(stateKey, previousStateKeyByStateKey, fileNameByStateKey) {
|
|
248035
|
+
const pathSegments = [];
|
|
248036
|
+
let currentStateKey = stateKey;
|
|
248037
|
+
while (currentStateKey !== null) {
|
|
248038
|
+
const fileName = fileNameByStateKey.get(currentStateKey);
|
|
248039
|
+
if (!fileName) {
|
|
248040
|
+
return [];
|
|
248041
|
+
}
|
|
248042
|
+
pathSegments.push(fileName);
|
|
248043
|
+
const previousStateKey = previousStateKeyByStateKey.get(currentStateKey);
|
|
248044
|
+
if (previousStateKey === undefined) {
|
|
248045
|
+
return [];
|
|
248046
|
+
}
|
|
248047
|
+
currentStateKey = previousStateKey;
|
|
248048
|
+
}
|
|
248049
|
+
return pathSegments.reverse();
|
|
248114
248050
|
}
|
|
248115
|
-
function
|
|
248116
|
-
const queue = [
|
|
248117
|
-
const
|
|
248118
|
-
|
|
248119
|
-
|
|
248120
|
-
|
|
248051
|
+
function findCyclePathFromEdge(program, graph, currentFileName, edge, canonicalFileName) {
|
|
248052
|
+
const queue = [{ fileName: edge.targetFileName, hasImportEdge: edge.isImport }];
|
|
248053
|
+
const startStateKey = getCycleStateKey(edge.targetFileName, edge.isImport);
|
|
248054
|
+
const previousStateKeyByStateKey = new Map([
|
|
248055
|
+
[startStateKey, null],
|
|
248056
|
+
]);
|
|
248057
|
+
const fileNameByStateKey = new Map([
|
|
248058
|
+
[startStateKey, edge.targetFileName],
|
|
248059
|
+
]);
|
|
248060
|
+
let firstResult = null;
|
|
248061
|
+
for (const state of queue) {
|
|
248062
|
+
const stateKey = getCycleStateKey(state.fileName, state.hasImportEdge);
|
|
248063
|
+
if (state.fileName === currentFileName) {
|
|
248064
|
+
const path = reconstructSearchPath(stateKey, previousStateKeyByStateKey, fileNameByStateKey);
|
|
248065
|
+
if (path.length === 0) {
|
|
248066
|
+
continue;
|
|
248067
|
+
}
|
|
248068
|
+
const result = {
|
|
248069
|
+
cyclePath: [currentFileName, ...path],
|
|
248070
|
+
hasImportEdge: state.hasImportEdge,
|
|
248071
|
+
targetFileName: edge.targetFileName,
|
|
248072
|
+
};
|
|
248073
|
+
if (result.hasImportEdge) {
|
|
248074
|
+
return result;
|
|
248075
|
+
}
|
|
248076
|
+
firstResult ??= result;
|
|
248121
248077
|
continue;
|
|
248122
248078
|
}
|
|
248123
|
-
for (const
|
|
248124
|
-
|
|
248125
|
-
|
|
248079
|
+
for (const nextEdge of getDependenciesForFile(program, graph, state.fileName, canonicalFileName)) {
|
|
248080
|
+
const nextState = {
|
|
248081
|
+
fileName: nextEdge.targetFileName,
|
|
248082
|
+
hasImportEdge: state.hasImportEdge || nextEdge.isImport,
|
|
248083
|
+
};
|
|
248084
|
+
const nextStateKey = getCycleStateKey(nextState.fileName, nextState.hasImportEdge);
|
|
248085
|
+
if (previousStateKeyByStateKey.has(nextStateKey)) {
|
|
248126
248086
|
continue;
|
|
248127
248087
|
}
|
|
248128
|
-
|
|
248129
|
-
|
|
248088
|
+
previousStateKeyByStateKey.set(nextStateKey, stateKey);
|
|
248089
|
+
fileNameByStateKey.set(nextStateKey, nextState.fileName);
|
|
248090
|
+
queue.push(nextState);
|
|
248130
248091
|
}
|
|
248131
248092
|
}
|
|
248132
|
-
|
|
248133
|
-
|
|
248134
|
-
|
|
248135
|
-
|
|
248136
|
-
|
|
248137
|
-
|
|
248138
|
-
|
|
248139
|
-
|
|
248140
|
-
|
|
248141
|
-
|
|
248093
|
+
return firstResult;
|
|
248094
|
+
}
|
|
248095
|
+
function findCyclePathForSpecifier(program, graph, currentFileName, moduleSpecifier, canonicalFileName) {
|
|
248096
|
+
let firstResult = null;
|
|
248097
|
+
for (const edge of getDependenciesForFile(program, graph, currentFileName, canonicalFileName)) {
|
|
248098
|
+
if (edge.moduleSpecifier !== moduleSpecifier) {
|
|
248099
|
+
continue;
|
|
248100
|
+
}
|
|
248101
|
+
const result = findCyclePathFromEdge(program, graph, currentFileName, edge, canonicalFileName);
|
|
248102
|
+
if (!result) {
|
|
248103
|
+
continue;
|
|
248104
|
+
}
|
|
248105
|
+
if (result.hasImportEdge) {
|
|
248106
|
+
return result;
|
|
248142
248107
|
}
|
|
248143
|
-
|
|
248108
|
+
firstResult ??= result;
|
|
248144
248109
|
}
|
|
248145
|
-
return
|
|
248110
|
+
return firstResult;
|
|
248146
248111
|
}
|
|
248147
248112
|
function formatFileName(graph, fileName, cwd) {
|
|
248148
248113
|
const displayFileName = graph.displayFileNameByFileName.get(fileName) ?? fileName;
|
|
248149
248114
|
const relativeFileName = normalizeSlashes(path.relative(cwd, displayFileName));
|
|
248150
248115
|
return relativeFileName || normalizeSlashes(path.basename(displayFileName));
|
|
248151
248116
|
}
|
|
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(' -> ');
|
|
248117
|
+
function formatCyclePath(graph, cyclePath, cwd) {
|
|
248118
|
+
return cyclePath.map((fileName) => formatFileName(graph, fileName, cwd)).join(' -> ');
|
|
248165
248119
|
}
|
|
248166
248120
|
function getAliasedSymbolIfNeeded(checker, symbol) {
|
|
248167
248121
|
return (symbol.flags & ts.SymbolFlags.Alias) === 0
|
|
@@ -248306,6 +248260,7 @@ const rule$K = createRule({
|
|
|
248306
248260
|
const checkNamedAsDefaultMembers = context.options[0]?.checkNamedAsDefaultMembers ?? true;
|
|
248307
248261
|
const checkNamespaceMembers = context.options[0]?.checkNamespaceMembers ?? true;
|
|
248308
248262
|
const checkSelfImports = context.options[0]?.checkSelfImports ?? true;
|
|
248263
|
+
const shouldCheckUselessPathSegments = context.options[0]?.checkUselessPathSegments ?? true;
|
|
248309
248264
|
const ignoreExternalDefaultImports = context.options[0]?.ignoreExternalDefaultImports ?? true;
|
|
248310
248265
|
const canonicalFileName = createCanonicalFileName();
|
|
248311
248266
|
const currentFileName = canonicalFileName(context.filename);
|
|
@@ -248435,10 +248390,82 @@ const rule$K = createRule({
|
|
|
248435
248390
|
node,
|
|
248436
248391
|
});
|
|
248437
248392
|
}
|
|
248438
|
-
function
|
|
248439
|
-
|
|
248393
|
+
function getNormalizedPathReplacement(moduleSpecifierPath, canonicalResolvedFileName) {
|
|
248394
|
+
const normalizedPath = normalizeImportPath(moduleSpecifierPath);
|
|
248395
|
+
if (normalizedPath === moduleSpecifierPath) {
|
|
248396
|
+
return null;
|
|
248397
|
+
}
|
|
248398
|
+
const normalizedResolved = resolveModule(tsProgram, context.filename, normalizedPath);
|
|
248399
|
+
return normalizedResolved &&
|
|
248400
|
+
canonicalFileName(normalizedResolved.resolvedFileName) ===
|
|
248401
|
+
canonicalResolvedFileName
|
|
248402
|
+
? normalizedPath
|
|
248403
|
+
: null;
|
|
248404
|
+
}
|
|
248405
|
+
function getIndexPathReplacement(moduleSpecifierPath, canonicalResolvedFileName) {
|
|
248406
|
+
if (!isIndexModulePath(moduleSpecifierPath)) {
|
|
248407
|
+
return null;
|
|
248408
|
+
}
|
|
248409
|
+
const parentDirectory = path.posix.dirname(moduleSpecifierPath);
|
|
248410
|
+
if (parentDirectory === '.' || parentDirectory === '..') {
|
|
248411
|
+
return parentDirectory;
|
|
248412
|
+
}
|
|
248413
|
+
const parentResolved = resolveModule(tsProgram, context.filename, parentDirectory);
|
|
248414
|
+
return parentResolved &&
|
|
248415
|
+
canonicalFileName(parentResolved.resolvedFileName) !==
|
|
248416
|
+
canonicalResolvedFileName
|
|
248417
|
+
? `${parentDirectory}/`
|
|
248418
|
+
: parentDirectory;
|
|
248419
|
+
}
|
|
248420
|
+
function getExcessParentPathReplacement(moduleSpecifierPath, resolvedFileName) {
|
|
248421
|
+
if (moduleSpecifierPath.startsWith('./')) {
|
|
248422
|
+
return null;
|
|
248423
|
+
}
|
|
248424
|
+
const expectedPath = computeRelativeImportPath(context.filename, resolvedFileName);
|
|
248425
|
+
const importSegments = moduleSpecifierPath.replace(/^\.\//, '').split('/');
|
|
248426
|
+
const importParentCount = countRelativeParents(importSegments);
|
|
248427
|
+
const expectedParentCount = countRelativeParents(expectedPath.split('/'));
|
|
248428
|
+
if (importParentCount <= expectedParentCount ||
|
|
248429
|
+
expectedPath === moduleSpecifierPath) {
|
|
248430
|
+
return null;
|
|
248431
|
+
}
|
|
248432
|
+
return expectedPath;
|
|
248433
|
+
}
|
|
248434
|
+
function getUselessPathSegmentsReplacement(moduleSpecifierPath) {
|
|
248435
|
+
const resolved = resolveModule(tsProgram, context.filename, moduleSpecifierPath);
|
|
248436
|
+
if (!resolved) {
|
|
248437
|
+
return null;
|
|
248438
|
+
}
|
|
248439
|
+
const canonicalResolvedFileName = canonicalFileName(resolved.resolvedFileName);
|
|
248440
|
+
return (getNormalizedPathReplacement(moduleSpecifierPath, canonicalResolvedFileName) ??
|
|
248441
|
+
getIndexPathReplacement(moduleSpecifierPath, canonicalResolvedFileName) ??
|
|
248442
|
+
getExcessParentPathReplacement(moduleSpecifierPath, resolved.resolvedFileName));
|
|
248443
|
+
}
|
|
248444
|
+
function checkUselessPathSegments(source) {
|
|
248445
|
+
if (!shouldCheckUselessPathSegments ||
|
|
248446
|
+
!source.value.startsWith('.') ||
|
|
248447
|
+
splitModuleSpecifierQuery(source.value).query) {
|
|
248448
|
+
return;
|
|
248449
|
+
}
|
|
248450
|
+
const proposedPath = getUselessPathSegmentsReplacement(source.value);
|
|
248451
|
+
if (!proposedPath) {
|
|
248452
|
+
return;
|
|
248453
|
+
}
|
|
248454
|
+
context.report({
|
|
248455
|
+
data: {
|
|
248456
|
+
moduleSpecifier: source.value,
|
|
248457
|
+
proposedPath,
|
|
248458
|
+
},
|
|
248459
|
+
fix: (fixer) => fixer.replaceText(source, quoteModuleSpecifier(source, proposedPath)),
|
|
248460
|
+
messageId: 'uselessPathSegments',
|
|
248461
|
+
node: source,
|
|
248462
|
+
});
|
|
248463
|
+
}
|
|
248464
|
+
function checkDeclarationModuleSpecifier(node) {
|
|
248465
|
+
if (!isStringLiteral(node.source)) {
|
|
248440
248466
|
return;
|
|
248441
248467
|
}
|
|
248468
|
+
checkUselessPathSegments(node.source);
|
|
248442
248469
|
checkSelfImport(node.source.value, node.source);
|
|
248443
248470
|
}
|
|
248444
248471
|
function checkRequireSelfImport(node) {
|
|
@@ -248454,12 +248481,12 @@ const rule$K = createRule({
|
|
|
248454
248481
|
}
|
|
248455
248482
|
checkSelfImport(moduleSpecifier.value, moduleSpecifier);
|
|
248456
248483
|
}
|
|
248457
|
-
function
|
|
248484
|
+
function checkDynamicImport(node) {
|
|
248458
248485
|
const { source } = node;
|
|
248459
|
-
if (source
|
|
248460
|
-
typeof source.value !== 'string') {
|
|
248486
|
+
if (!isStringLiteral(source)) {
|
|
248461
248487
|
return;
|
|
248462
248488
|
}
|
|
248489
|
+
checkUselessPathSegments(source);
|
|
248463
248490
|
checkSelfImport(source.value, source);
|
|
248464
248491
|
}
|
|
248465
248492
|
function checkImportCycle(node) {
|
|
@@ -248475,11 +248502,11 @@ const rule$K = createRule({
|
|
|
248475
248502
|
return;
|
|
248476
248503
|
}
|
|
248477
248504
|
const graph = getImportGraph(tsProgram);
|
|
248478
|
-
const
|
|
248479
|
-
if (!
|
|
248505
|
+
const cycle = findCyclePathForSpecifier(tsProgram, graph, currentFileName, moduleSpecifier, canonicalFileName);
|
|
248506
|
+
if (!cycle) {
|
|
248480
248507
|
return;
|
|
248481
248508
|
}
|
|
248482
|
-
const cyclePath = formatCyclePath(graph,
|
|
248509
|
+
const cyclePath = formatCyclePath(graph, cycle.cyclePath, context.cwd);
|
|
248483
248510
|
if (node.type === dist$3.AST_NODE_TYPES.ImportDeclaration) {
|
|
248484
248511
|
// Compute the redirect replacement eagerly so we can decide whether
|
|
248485
248512
|
// to suppress. If a redirect to the direct source file is possible,
|
|
@@ -248501,12 +248528,10 @@ const rule$K = createRule({
|
|
|
248501
248528
|
}
|
|
248502
248529
|
else {
|
|
248503
248530
|
// For re-export nodes (export * from / export {x} from), suppress the
|
|
248504
|
-
// error when the
|
|
248531
|
+
// error when the reachable cycle contains an ImportDeclaration edge.
|
|
248505
248532
|
// That ImportDeclaration will be reported (and fixed) directly, so
|
|
248506
248533
|
// reporting the barrel re-export would create duplicate, unfixable noise.
|
|
248507
|
-
|
|
248508
|
-
if (componentId !== undefined &&
|
|
248509
|
-
graph.sccHasImportEdgeById.get(componentId) === true) {
|
|
248534
|
+
if (cycle.hasImportEdge) {
|
|
248510
248535
|
return;
|
|
248511
248536
|
}
|
|
248512
248537
|
context.report({
|
|
@@ -248528,7 +248553,7 @@ const rule$K = createRule({
|
|
|
248528
248553
|
// Not a barrel (has no re-export edges) — symbols are defined locally,
|
|
248529
248554
|
// so there is no shorter direct-source path to redirect to.
|
|
248530
248555
|
if (!canonicalBarrelFileName ||
|
|
248531
|
-
!getImportGraph(tsProgram)
|
|
248556
|
+
!fileHasReExportEdges(tsProgram, getImportGraph(tsProgram), canonicalBarrelFileName, canonicalFileName)) {
|
|
248532
248557
|
return null;
|
|
248533
248558
|
}
|
|
248534
248559
|
const specifiersBySourceFile = new Map();
|
|
@@ -248794,21 +248819,21 @@ const rule$K = createRule({
|
|
|
248794
248819
|
CallExpression: checkRequireSelfImport,
|
|
248795
248820
|
ExportAllDeclaration(node) {
|
|
248796
248821
|
checkImportCycle(node);
|
|
248797
|
-
|
|
248822
|
+
checkDeclarationModuleSpecifier(node);
|
|
248798
248823
|
},
|
|
248799
248824
|
ExportNamedDeclaration(node) {
|
|
248800
248825
|
checkImportCycle(node);
|
|
248801
|
-
|
|
248826
|
+
checkDeclarationModuleSpecifier(node);
|
|
248802
248827
|
checkNamedAsDefaultExport(node);
|
|
248803
248828
|
},
|
|
248804
248829
|
ImportDeclaration(node) {
|
|
248805
248830
|
checkImportCycle(node);
|
|
248806
|
-
|
|
248831
|
+
checkDeclarationModuleSpecifier(node);
|
|
248807
248832
|
collectDuplicateImport(node);
|
|
248808
248833
|
checkDefaultImport(node);
|
|
248809
248834
|
collectNamespaceImport(node);
|
|
248810
248835
|
},
|
|
248811
|
-
ImportExpression:
|
|
248836
|
+
ImportExpression: checkDynamicImport,
|
|
248812
248837
|
MemberExpression(node) {
|
|
248813
248838
|
checkNamedAsDefaultMemberExpression(node);
|
|
248814
248839
|
checkNamespaceMember(node);
|
|
@@ -248819,7 +248844,7 @@ const rule$K = createRule({
|
|
|
248819
248844
|
},
|
|
248820
248845
|
meta: {
|
|
248821
248846
|
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,
|
|
248847
|
+
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
248848
|
},
|
|
248824
248849
|
fixable: 'code',
|
|
248825
248850
|
messages: {
|
|
@@ -248830,6 +248855,7 @@ const rule$K = createRule({
|
|
|
248830
248855
|
namedAsDefaultMember: 'Default import "{{defaultName}}" from "{{moduleSpecifier}}" also has a named export "{{memberName}}". Use a named import instead.',
|
|
248831
248856
|
selfImport: 'Module imports itself.',
|
|
248832
248857
|
unknownNamespaceMember: 'Namespace import "{{namespaceName}}" from "{{moduleSpecifier}}" has no exported member "{{memberName}}".',
|
|
248858
|
+
uselessPathSegments: 'Useless path segments for "{{moduleSpecifier}}", should be "{{proposedPath}}".',
|
|
248833
248859
|
},
|
|
248834
248860
|
schema: [
|
|
248835
248861
|
{
|
|
@@ -248863,6 +248889,10 @@ const rule$K = createRule({
|
|
|
248863
248889
|
description: 'Report imports, re-exports, dynamic imports, and require() calls that resolve to the current file. Defaults to true.',
|
|
248864
248890
|
type: 'boolean',
|
|
248865
248891
|
},
|
|
248892
|
+
checkUselessPathSegments: {
|
|
248893
|
+
description: 'Report relative imports, re-exports, and dynamic imports with unnecessary path segments or /index suffixes. Defaults to true.',
|
|
248894
|
+
type: 'boolean',
|
|
248895
|
+
},
|
|
248866
248896
|
ignoreExternalDefaultImports: {
|
|
248867
248897
|
description: 'Skip default import checks for modules resolved from external libraries. Defaults to true.',
|
|
248868
248898
|
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
|
];
|