@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 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, and self-import checks | ✅ | 🔧 | |
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': ['error', { noUselessIndex: true }],
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 getModuleSpecifierPath(moduleSpecifier) {
247758
+ function splitModuleSpecifierQuery(moduleSpecifier) {
247758
247759
  const queryIndex = moduleSpecifier.indexOf('?');
247759
- return queryIndex === -1 ? moduleSpecifier : moduleSpecifier.slice(0, queryIndex);
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
- barrelFileNames: buildBarrelFileNames(dependenciesByFileName),
248000
- componentIdByFileName,
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 findCyclicTargetFileName(graph, currentFileName, moduleSpecifier) {
248096
- const currentComponentId = graph.componentIdByFileName.get(currentFileName);
248097
- if (currentComponentId === undefined) {
248098
- return null;
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 currentComponentSize = graph.componentSizeById.get(currentComponentId) ?? 0;
248101
- for (const edge of graph.dependenciesByFileName.get(currentFileName) ?? []) {
248102
- if (edge.moduleSpecifier !== moduleSpecifier) {
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 isSelfCycle = edge.targetFileName === currentFileName &&
248106
- graph.selfCycleFileNames.has(currentFileName);
248107
- const isSameComponentCycle = currentComponentSize > 1 &&
248108
- graph.componentIdByFileName.get(edge.targetFileName) === currentComponentId;
248109
- if (isSelfCycle || isSameComponentCycle) {
248110
- return edge.targetFileName;
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 null;
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 findPathWithinComponent(graph, startFileName, endFileName, componentId) {
248116
- const queue = [startFileName];
248117
- const previousFileName = new Map([[startFileName, null]]);
248118
- for (let index = 0; index < queue.length && !previousFileName.has(endFileName); index += 1) {
248119
- const currentFileName = queue[index];
248120
- if (!currentFileName) {
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 edge of graph.dependenciesByFileName.get(currentFileName) ?? []) {
248124
- if (graph.componentIdByFileName.get(edge.targetFileName) !== componentId ||
248125
- previousFileName.has(edge.targetFileName)) {
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
- previousFileName.set(edge.targetFileName, currentFileName);
248129
- queue.push(edge.targetFileName);
248088
+ previousStateKeyByStateKey.set(nextStateKey, stateKey);
248089
+ fileNameByStateKey.set(nextStateKey, nextState.fileName);
248090
+ queue.push(nextState);
248130
248091
  }
248131
248092
  }
248132
- if (!previousFileName.has(endFileName)) {
248133
- return [startFileName, endFileName];
248134
- }
248135
- const pathSegments = [];
248136
- let currentFileName = endFileName;
248137
- while (currentFileName !== null) {
248138
- pathSegments.push(currentFileName);
248139
- const nextFileName = previousFileName.get(currentFileName);
248140
- if (nextFileName === undefined) {
248141
- return [startFileName, endFileName];
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
- currentFileName = nextFileName;
248108
+ firstResult ??= result;
248144
248109
  }
248145
- return pathSegments.reverse();
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, currentFileName, targetFileName, cwd) {
248153
- const componentId = graph.componentIdByFileName.get(currentFileName);
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 checkDeclarationSelfImport(node) {
248439
- if (!node.source || typeof node.source.value !== 'string') {
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 checkDynamicSelfImport(node) {
248484
+ function checkDynamicImport(node) {
248458
248485
  const { source } = node;
248459
- if (source.type !== dist$3.AST_NODE_TYPES.Literal ||
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 targetFileName = findCyclicTargetFileName(graph, currentFileName, moduleSpecifier);
248479
- if (!targetFileName) {
248505
+ const cycle = findCyclePathForSpecifier(tsProgram, graph, currentFileName, moduleSpecifier, canonicalFileName);
248506
+ if (!cycle) {
248480
248507
  return;
248481
248508
  }
248482
- const cyclePath = formatCyclePath(graph, currentFileName, targetFileName, context.cwd);
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 same SCC already contains an ImportDeclaration edge.
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
- const componentId = graph.componentIdByFileName.get(currentFileName);
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).barrelFileNames.has(canonicalBarrelFileName)) {
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
- checkDeclarationSelfImport(node);
248822
+ checkDeclarationModuleSpecifier(node);
248798
248823
  },
248799
248824
  ExportNamedDeclaration(node) {
248800
248825
  checkImportCycle(node);
248801
- checkDeclarationSelfImport(node);
248826
+ checkDeclarationModuleSpecifier(node);
248802
248827
  checkNamedAsDefaultExport(node);
248803
248828
  },
248804
248829
  ImportDeclaration(node) {
248805
248830
  checkImportCycle(node);
248806
- checkDeclarationSelfImport(node);
248831
+ checkDeclarationModuleSpecifier(node);
248807
248832
  collectDuplicateImport(node);
248808
248833
  checkDefaultImport(node);
248809
248834
  collectNamespaceImport(node);
248810
248835
  },
248811
- ImportExpression: checkDynamicSelfImport,
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, and import/no-self-import checks',
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@taiga-ui/eslint-plugin-experience-next",
3
- "version": "0.503.0",
3
+ "version": "0.505.0",
4
4
  "description": "An ESLint plugin to enforce a consistent code styles across taiga-ui projects",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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
  ];