@taiga-ui/eslint-plugin-experience-next 0.502.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 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 default import, namespace export, named-as-default, and import cycle 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,11 +38,15 @@ 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<"importCycle" | "missingDefaultExport" | "namedAsDefault" | "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
+ checkDuplicateImports?: boolean;
44
45
  checkNamedAsDefault?: boolean;
46
+ checkNamedAsDefaultMembers?: boolean;
45
47
  checkNamespaceMembers?: boolean;
48
+ checkSelfImports?: boolean;
49
+ checkUselessPathSegments?: boolean;
46
50
  ignoreExternalDefaultImports?: boolean;
47
51
  } | undefined)?], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
48
52
  name: string;
package/index.esm.js CHANGED
@@ -867,7 +867,7 @@ var recommended = defineConfig([
867
867
  'error',
868
868
  {
869
869
  methods: 'above',
870
- printWidth: 120,
870
+ printWidth: 90,
871
871
  properties: 'above',
872
872
  },
873
873
  ],
@@ -875,21 +875,22 @@ var recommended = defineConfig([
875
875
  'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
876
876
  'guard-for-in': 'error',
877
877
  'import/consistent-type-specifier-style': ['error', 'prefer-inline'],
878
- 'import/default': 'off',
878
+ 'import/default': 'off', // Covered by @taiga-ui/experience-next/import-integrity.
879
879
  'import/enforce-node-protocol-usage': ['error', 'always'],
880
880
  'import/export': 'off',
881
881
  'import/first': 'error',
882
- 'import/namespace': 'off',
882
+ 'import/namespace': 'off', // Covered by @taiga-ui/experience-next/import-integrity.
883
883
  'import/newline-after-import': ['error', { count: 1 }],
884
884
  'import/no-absolute-path': 'error',
885
- 'import/no-cycle': 'off',
886
- 'import/no-duplicates': ['error', { 'prefer-inline': true }],
885
+ 'import/no-cycle': 'off', // Covered by @taiga-ui/experience-next/import-integrity.
886
+ 'import/no-duplicates': 'off', // Covered by @taiga-ui/experience-next/import-integrity.
887
887
  'import/no-extraneous-dependencies': 'off',
888
888
  'import/no-mutable-exports': 'error',
889
- 'import/no-named-as-default': 'off',
890
- 'import/no-self-import': 'error',
889
+ 'import/no-named-as-default': 'off', // Covered by @taiga-ui/experience-next/import-integrity.
890
+ 'import/no-named-as-default-member': 'off', // Covered by @taiga-ui/experience-next/import-integrity.
891
+ 'import/no-self-import': 'off', // Covered by @taiga-ui/experience-next/import-integrity.
891
892
  'import/no-unresolved': 'off',
892
- 'import/no-useless-path-segments': ['error', { noUselessIndex: true }],
893
+ 'import/no-useless-path-segments': 'off', // Covered by @taiga-ui/experience-next/import-integrity.
893
894
  'import/no-webpack-loader-syntax': 'error',
894
895
  'lines-around-comment': [
895
896
  'error',
@@ -247753,6 +247754,84 @@ function resolveModuleFileName(program, containingFile, moduleSpecifier) {
247753
247754
  }
247754
247755
  return resolved.resolvedFileName;
247755
247756
  }
247757
+ function splitModuleSpecifierQuery(moduleSpecifier) {
247758
+ const queryIndex = moduleSpecifier.indexOf('?');
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}`;
247788
+ }
247789
+ function resolveModuleKey(program, containingFile, moduleSpecifier, canonicalFileName) {
247790
+ const moduleSpecifierPath = getModuleSpecifierPath(moduleSpecifier);
247791
+ const resolved = resolveModule(program, containingFile, moduleSpecifierPath);
247792
+ if (!resolved) {
247793
+ return moduleSpecifier;
247794
+ }
247795
+ const queryIndex = moduleSpecifier.indexOf('?');
247796
+ const query = queryIndex === -1 ? '' : moduleSpecifier.slice(queryIndex);
247797
+ return `${canonicalFileName(resolved.resolvedFileName)}${query}`;
247798
+ }
247799
+ function getDefaultImportName(node) {
247800
+ const defaultImport = node.specifiers.find((specifier) => specifier.type === dist$3.AST_NODE_TYPES.ImportDefaultSpecifier);
247801
+ return defaultImport?.local.name ?? null;
247802
+ }
247803
+ function hasNamespaceImport(node) {
247804
+ return node.specifiers.some((specifier) => specifier.type === dist$3.AST_NODE_TYPES.ImportNamespaceSpecifier);
247805
+ }
247806
+ function hasTypeOnlyDefaultImport(node) {
247807
+ return node.importKind === 'type' && getDefaultImportName(node) !== null;
247808
+ }
247809
+ function hasImportAttributes(node) {
247810
+ const record = node;
247811
+ const attributes = record['attributes'];
247812
+ const assertions = record['assertions'];
247813
+ return ((Array.isArray(attributes) && attributes.length > 0) ||
247814
+ (Array.isArray(assertions) && assertions.length > 0));
247815
+ }
247816
+ function hasProblematicImportComments(node, sourceCode) {
247817
+ const text = sourceCode.getText(node);
247818
+ return (text.includes('/*') ||
247819
+ text.includes('//') ||
247820
+ sourceCode
247821
+ .getCommentsBefore(node)
247822
+ .some((comment) => comment.loc.end.line >= node.loc.start.line - 1) ||
247823
+ sourceCode
247824
+ .getCommentsAfter(node)
247825
+ .some((comment) => comment.loc.start.line === node.loc.end.line));
247826
+ }
247827
+ function getNamedSpecifierText(node, specifier, sourceCode) {
247828
+ const text = sourceCode.getText(specifier);
247829
+ const isTypeOnly = node.importKind === 'type' || specifier.importKind === 'type';
247830
+ return isTypeOnly && !text.trimStart().startsWith('type ') ? `type ${text}` : text;
247831
+ }
247832
+ function getNamedSpecifierKey(text) {
247833
+ return text.trim().replace(/^type\s+/, '');
247834
+ }
247756
247835
  function importDeclarationHasRuntimeEdge(node) {
247757
247836
  const importClause = node.importClause;
247758
247837
  if (!importClause) {
@@ -247809,149 +247888,14 @@ function getRuntimeModuleSpecifier(statement) {
247809
247888
  }
247810
247889
  return null;
247811
247890
  }
247812
- function buildDependenciesByFileName(program, canonicalFileName) {
247813
- const sourceFiles = program.getSourceFiles().filter(isProjectCodeFile);
247814
- const projectFileNames = new Set(sourceFiles.map((sourceFile) => canonicalFileName(sourceFile.fileName)));
247815
- const dependenciesByFileName = new Map();
247816
- const displayFileNameByFileName = new Map();
247817
- for (const sourceFile of sourceFiles) {
247818
- const fileName = canonicalFileName(sourceFile.fileName);
247819
- const edges = [];
247820
- displayFileNameByFileName.set(fileName, sourceFile.fileName);
247821
- for (const statement of sourceFile.statements) {
247822
- const moduleSpecifier = getRuntimeModuleSpecifier(statement);
247823
- if (!moduleSpecifier) {
247824
- continue;
247825
- }
247826
- const resolvedFileName = resolveModuleFileName(program, sourceFile.fileName, moduleSpecifier);
247827
- if (!resolvedFileName) {
247828
- continue;
247829
- }
247830
- const targetFileName = canonicalFileName(resolvedFileName);
247831
- if (!projectFileNames.has(targetFileName)) {
247832
- continue;
247833
- }
247834
- edges.push({
247835
- isImport: ts.isImportDeclaration(statement),
247836
- moduleSpecifier,
247837
- targetFileName,
247838
- });
247839
- }
247840
- dependenciesByFileName.set(fileName, edges);
247841
- }
247842
- return { dependenciesByFileName, displayFileNameByFileName };
247843
- }
247844
- function findStronglyConnectedComponents(dependenciesByFileName) {
247845
- let nextComponentId = 0;
247846
- let nextIndex = 0;
247847
- const componentIdByFileName = new Map();
247848
- const componentSizeById = new Map();
247849
- const nodeStateByFileName = new Map();
247850
- const stack = [];
247851
- function visit(fileName) {
247852
- const state = {
247853
- index: nextIndex,
247854
- lowLink: nextIndex,
247855
- onStack: true,
247856
- };
247857
- nextIndex += 1;
247858
- nodeStateByFileName.set(fileName, state);
247859
- stack.push(fileName);
247860
- for (const edge of dependenciesByFileName.get(fileName) ?? []) {
247861
- const targetState = nodeStateByFileName.get(edge.targetFileName);
247862
- if (!targetState) {
247863
- visit(edge.targetFileName);
247864
- const visitedTargetState = nodeStateByFileName.get(edge.targetFileName);
247865
- if (visitedTargetState) {
247866
- state.lowLink = Math.min(state.lowLink, visitedTargetState.lowLink);
247867
- }
247868
- continue;
247869
- }
247870
- if (targetState.onStack) {
247871
- state.lowLink = Math.min(state.lowLink, targetState.index);
247872
- }
247873
- }
247874
- if (state.lowLink !== state.index) {
247875
- return;
247876
- }
247877
- const componentId = nextComponentId;
247878
- let componentSize = 0;
247879
- let shouldPop = stack.length > 0;
247880
- nextComponentId += 1;
247881
- while (shouldPop) {
247882
- const memberFileName = stack.pop();
247883
- if (!memberFileName) {
247884
- shouldPop = false;
247885
- continue;
247886
- }
247887
- const memberState = nodeStateByFileName.get(memberFileName);
247888
- if (memberState) {
247889
- memberState.onStack = false;
247890
- }
247891
- componentSize += 1;
247892
- componentIdByFileName.set(memberFileName, componentId);
247893
- shouldPop = stack.length > 0 && memberFileName !== fileName;
247894
- }
247895
- componentSizeById.set(componentId, componentSize);
247896
- }
247897
- for (const fileName of dependenciesByFileName.keys()) {
247898
- if (!nodeStateByFileName.has(fileName)) {
247899
- visit(fileName);
247900
- }
247901
- }
247902
- return { componentIdByFileName, componentSizeById };
247903
- }
247904
- function collectSelfCycles(dependenciesByFileName) {
247905
- const selfCycleFileNames = new Set();
247906
- for (const [fileName, edges] of dependenciesByFileName) {
247907
- if (edges.some((edge) => edge.targetFileName === fileName)) {
247908
- selfCycleFileNames.add(fileName);
247909
- }
247910
- }
247911
- return selfCycleFileNames;
247912
- }
247913
- function buildSccHasImportEdge(dependenciesByFileName, componentIdByFileName) {
247914
- const result = new Map();
247915
- for (const [fileName, edges] of dependenciesByFileName) {
247916
- const sourceComponentId = componentIdByFileName.get(fileName);
247917
- if (sourceComponentId === undefined || result.get(sourceComponentId) === true) {
247918
- continue;
247919
- }
247920
- for (const edge of edges) {
247921
- if (edge.isImport &&
247922
- componentIdByFileName.get(edge.targetFileName) === sourceComponentId) {
247923
- result.set(sourceComponentId, true);
247924
- break;
247925
- }
247926
- }
247927
- }
247928
- return result;
247929
- }
247930
- function buildBarrelFileNames(dependenciesByFileName) {
247931
- const result = new Set();
247932
- for (const [fileName, edges] of dependenciesByFileName) {
247933
- if (edges.some((edge) => !edge.isImport)) {
247934
- result.add(fileName);
247935
- }
247936
- }
247937
- return result;
247938
- }
247939
247891
  function getImportGraph(program) {
247940
247892
  const cached = importGraphCacheByProgram.get(program);
247941
247893
  if (cached) {
247942
247894
  return cached;
247943
247895
  }
247944
- const canonicalFileName = createCanonicalFileName();
247945
- const { dependenciesByFileName, displayFileNameByFileName } = buildDependenciesByFileName(program, canonicalFileName);
247946
- const { componentIdByFileName, componentSizeById } = findStronglyConnectedComponents(dependenciesByFileName);
247947
247896
  const cache = {
247948
- barrelFileNames: buildBarrelFileNames(dependenciesByFileName),
247949
- componentIdByFileName,
247950
- componentSizeById,
247951
- dependenciesByFileName,
247952
- displayFileNameByFileName,
247953
- sccHasImportEdgeById: buildSccHasImportEdge(dependenciesByFileName, componentIdByFileName),
247954
- selfCycleFileNames: collectSelfCycles(dependenciesByFileName),
247897
+ dependenciesByFileName: new Map(),
247898
+ displayFileNameByFileName: new Map(),
247955
247899
  };
247956
247900
  importGraphCacheByProgram.set(program, cache);
247957
247901
  return cache;
@@ -248041,76 +247985,136 @@ function getImportOrReExportModuleSpecifier(node) {
248041
247985
  }
248042
247986
  return typeof node.source.value === 'string' ? node.source.value : null;
248043
247987
  }
248044
- function findCyclicTargetFileName(graph, currentFileName, moduleSpecifier) {
248045
- const currentComponentId = graph.componentIdByFileName.get(currentFileName);
248046
- if (currentComponentId === undefined) {
248047
- return null;
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;
248048
247996
  }
248049
- const currentComponentSize = graph.componentSizeById.get(currentComponentId) ?? 0;
248050
- for (const edge of graph.dependenciesByFileName.get(currentFileName) ?? []) {
248051
- if (edge.moduleSpecifier !== moduleSpecifier) {
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) {
248007
+ continue;
248008
+ }
248009
+ const resolvedFileName = resolveModuleFileName(program, sourceFile.fileName, moduleSpecifier);
248010
+ if (!resolvedFileName) {
248052
248011
  continue;
248053
248012
  }
248054
- const isSelfCycle = edge.targetFileName === currentFileName &&
248055
- graph.selfCycleFileNames.has(currentFileName);
248056
- const isSameComponentCycle = currentComponentSize > 1 &&
248057
- graph.componentIdByFileName.get(edge.targetFileName) === currentComponentId;
248058
- if (isSelfCycle || isSameComponentCycle) {
248059
- return edge.targetFileName;
248013
+ const targetSourceFile = getProjectSourceFile(program, resolvedFileName);
248014
+ if (!targetSourceFile) {
248015
+ continue;
248060
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
+ });
248061
248024
  }
248062
- return null;
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();
248063
248049
  }
248064
- function findPathWithinComponent(graph, startFileName, endFileName, componentId) {
248065
- const queue = [startFileName];
248066
- const previousFileName = new Map([[startFileName, null]]);
248067
- for (let index = 0; index < queue.length && !previousFileName.has(endFileName); index += 1) {
248068
- const currentFileName = queue[index];
248069
- if (!currentFileName) {
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;
248070
248076
  continue;
248071
248077
  }
248072
- for (const edge of graph.dependenciesByFileName.get(currentFileName) ?? []) {
248073
- if (graph.componentIdByFileName.get(edge.targetFileName) !== componentId ||
248074
- previousFileName.has(edge.targetFileName)) {
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)) {
248075
248085
  continue;
248076
248086
  }
248077
- previousFileName.set(edge.targetFileName, currentFileName);
248078
- queue.push(edge.targetFileName);
248087
+ previousStateKeyByStateKey.set(nextStateKey, stateKey);
248088
+ fileNameByStateKey.set(nextStateKey, nextState.fileName);
248089
+ queue.push(nextState);
248079
248090
  }
248080
248091
  }
248081
- if (!previousFileName.has(endFileName)) {
248082
- return [startFileName, endFileName];
248083
- }
248084
- const pathSegments = [];
248085
- let currentFileName = endFileName;
248086
- while (currentFileName !== null) {
248087
- pathSegments.push(currentFileName);
248088
- const nextFileName = previousFileName.get(currentFileName);
248089
- if (nextFileName === undefined) {
248090
- return [startFileName, endFileName];
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;
248091
248106
  }
248092
- currentFileName = nextFileName;
248107
+ firstResult ??= result;
248093
248108
  }
248094
- return pathSegments.reverse();
248109
+ return firstResult;
248095
248110
  }
248096
248111
  function formatFileName(graph, fileName, cwd) {
248097
248112
  const displayFileName = graph.displayFileNameByFileName.get(fileName) ?? fileName;
248098
248113
  const relativeFileName = normalizeSlashes(path.relative(cwd, displayFileName));
248099
248114
  return relativeFileName || normalizeSlashes(path.basename(displayFileName));
248100
248115
  }
248101
- function formatCyclePath(graph, currentFileName, targetFileName, cwd) {
248102
- const componentId = graph.componentIdByFileName.get(currentFileName);
248103
- if (componentId === undefined || currentFileName === targetFileName) {
248104
- return [currentFileName, targetFileName]
248105
- .map((fileName) => formatFileName(graph, fileName, cwd))
248106
- .join(' -> ');
248107
- }
248108
- return [
248109
- currentFileName,
248110
- ...findPathWithinComponent(graph, targetFileName, currentFileName, componentId),
248111
- ]
248112
- .map((fileName) => formatFileName(graph, fileName, cwd))
248113
- .join(' -> ');
248116
+ function formatCyclePath(graph, cyclePath, cwd) {
248117
+ return cyclePath.map((fileName) => formatFileName(graph, fileName, cwd)).join(' -> ');
248114
248118
  }
248115
248119
  function getAliasedSymbolIfNeeded(checker, symbol) {
248116
248120
  return (symbol.flags & ts.SymbolFlags.Alias) === 0
@@ -248250,12 +248254,240 @@ const rule$K = createRule({
248250
248254
  const { checker, esTreeNodeToTSNodeMap, sourceCode, tsProgram } = getTypeAwareRuleContext(context);
248251
248255
  const checkCycles = context.options[0]?.checkCycles ?? true;
248252
248256
  const checkDefaultImports = context.options[0]?.checkDefaultImports ?? true;
248257
+ const checkDuplicateImports = context.options[0]?.checkDuplicateImports ?? true;
248253
248258
  const checkNamedAsDefault = context.options[0]?.checkNamedAsDefault ?? true;
248259
+ const checkNamedAsDefaultMembers = context.options[0]?.checkNamedAsDefaultMembers ?? true;
248254
248260
  const checkNamespaceMembers = context.options[0]?.checkNamespaceMembers ?? true;
248261
+ const checkSelfImports = context.options[0]?.checkSelfImports ?? true;
248262
+ const shouldCheckUselessPathSegments = context.options[0]?.checkUselessPathSegments ?? true;
248255
248263
  const ignoreExternalDefaultImports = context.options[0]?.ignoreExternalDefaultImports ?? true;
248256
248264
  const canonicalFileName = createCanonicalFileName();
248257
248265
  const currentFileName = canonicalFileName(context.filename);
248266
+ const defaultImports = new Map();
248267
+ const duplicateImportMaps = {
248268
+ importsByModule: new Map(),
248269
+ namespaceImportsByModule: new Map(),
248270
+ };
248258
248271
  const namespaceImports = new Map();
248272
+ function getDuplicateImportMap(node) {
248273
+ return hasNamespaceImport(node)
248274
+ ? duplicateImportMaps.namespaceImportsByModule
248275
+ : duplicateImportMaps.importsByModule;
248276
+ }
248277
+ function collectDuplicateImport(node) {
248278
+ if (!checkDuplicateImports) {
248279
+ return;
248280
+ }
248281
+ const moduleKey = resolveModuleKey(tsProgram, context.filename, node.source.value, canonicalFileName);
248282
+ const importsByModule = getDuplicateImportMap(node);
248283
+ const imports = importsByModule.get(moduleKey) ?? [];
248284
+ imports.push(node);
248285
+ importsByModule.set(moduleKey, imports);
248286
+ }
248287
+ function buildImportRemovalFix(fixer, node) {
248288
+ const [start, end] = node.range;
248289
+ const lineStart = sourceCode.text.lastIndexOf('\n', start - 1) + 1;
248290
+ const removeStart = /^\s*$/.test(sourceCode.text.slice(lineStart, start))
248291
+ ? lineStart
248292
+ : start;
248293
+ const removeEnd = sourceCode.text[end] === '\n' ? end + 1 : end;
248294
+ return [fixer.removeRange([removeStart, removeEnd])];
248295
+ }
248296
+ function buildDuplicateImportFix(fixer, first, rest) {
248297
+ const nodes = [first, ...rest];
248298
+ if (nodes.some((node) => hasNamespaceImport(node) ||
248299
+ hasTypeOnlyDefaultImport(node) ||
248300
+ hasImportAttributes(node) ||
248301
+ hasProblematicImportComments(node, sourceCode))) {
248302
+ return null;
248303
+ }
248304
+ const defaultNames = new Set(nodes.flatMap((node) => {
248305
+ const name = getDefaultImportName(node);
248306
+ return name ? [name] : [];
248307
+ }));
248308
+ if (defaultNames.size > 1) {
248309
+ return null;
248310
+ }
248311
+ const namedSpecifiersByKey = new Map();
248312
+ for (const node of nodes) {
248313
+ for (const specifier of node.specifiers) {
248314
+ if (specifier.type !== dist$3.AST_NODE_TYPES.ImportSpecifier) {
248315
+ continue;
248316
+ }
248317
+ const text = getNamedSpecifierText(node, specifier, sourceCode).trim();
248318
+ const key = getNamedSpecifierKey(text);
248319
+ const existing = namedSpecifiersByKey.get(key);
248320
+ const isTypeOnly = text.startsWith('type ');
248321
+ if (!existing || (existing.startsWith('type ') && !isTypeOnly)) {
248322
+ namedSpecifiersByKey.set(key, text);
248323
+ }
248324
+ }
248325
+ }
248326
+ const namedSpecifiers = [...namedSpecifiersByKey.values()];
248327
+ const [defaultName = null] = defaultNames;
248328
+ const bindings = [
248329
+ ...(defaultName ? [defaultName] : []),
248330
+ ...(namedSpecifiers.length > 0
248331
+ ? [`{${namedSpecifiers.join(', ')}}`]
248332
+ : []),
248333
+ ];
248334
+ const sourceText = sourceCode.getText(first.source);
248335
+ const semi = sourceCode.getText(first).endsWith(';') ? ';' : '';
248336
+ const replacement = bindings.length === 0
248337
+ ? `import ${sourceText}${semi}`
248338
+ : `import ${bindings.join(', ')} from ${sourceText}${semi}`;
248339
+ return [
248340
+ fixer.replaceText(first, replacement),
248341
+ ...rest.flatMap((node) => buildImportRemovalFix(fixer, node)),
248342
+ ];
248343
+ }
248344
+ function reportDuplicateImports(importsByModule) {
248345
+ for (const nodes of importsByModule.values()) {
248346
+ if (nodes.length < 2) {
248347
+ continue;
248348
+ }
248349
+ const [first, ...rest] = nodes;
248350
+ if (!first) {
248351
+ continue;
248352
+ }
248353
+ const moduleSpecifier = first.source.value;
248354
+ context.report({
248355
+ data: { moduleSpecifier },
248356
+ fix: (fixer) => buildDuplicateImportFix(fixer, first, rest),
248357
+ messageId: 'duplicateImport',
248358
+ node: first.source,
248359
+ });
248360
+ for (const node of rest) {
248361
+ context.report({
248362
+ data: { moduleSpecifier },
248363
+ messageId: 'duplicateImport',
248364
+ node: node.source,
248365
+ });
248366
+ }
248367
+ }
248368
+ }
248369
+ function reportAllDuplicateImports() {
248370
+ if (!checkDuplicateImports) {
248371
+ return;
248372
+ }
248373
+ reportDuplicateImports(duplicateImportMaps.importsByModule);
248374
+ reportDuplicateImports(duplicateImportMaps.namespaceImportsByModule);
248375
+ }
248376
+ function checkSelfImport(moduleSpecifier, node) {
248377
+ if (!checkSelfImports ||
248378
+ context.filename === '<text>' ||
248379
+ moduleSpecifier.includes('?')) {
248380
+ return;
248381
+ }
248382
+ const resolved = resolveModule(tsProgram, context.filename, getModuleSpecifierPath(moduleSpecifier));
248383
+ if (!resolved ||
248384
+ canonicalFileName(resolved.resolvedFileName) !== currentFileName) {
248385
+ return;
248386
+ }
248387
+ context.report({
248388
+ messageId: 'selfImport',
248389
+ node,
248390
+ });
248391
+ }
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)) {
248465
+ return;
248466
+ }
248467
+ checkUselessPathSegments(node.source);
248468
+ checkSelfImport(node.source.value, node.source);
248469
+ }
248470
+ function checkRequireSelfImport(node) {
248471
+ if (node.callee.type !== dist$3.AST_NODE_TYPES.Identifier ||
248472
+ node.callee.name !== 'require' ||
248473
+ node.arguments.length !== 1) {
248474
+ return;
248475
+ }
248476
+ const [moduleSpecifier] = node.arguments;
248477
+ if (moduleSpecifier?.type !== dist$3.AST_NODE_TYPES.Literal ||
248478
+ typeof moduleSpecifier.value !== 'string') {
248479
+ return;
248480
+ }
248481
+ checkSelfImport(moduleSpecifier.value, moduleSpecifier);
248482
+ }
248483
+ function checkDynamicImport(node) {
248484
+ const { source } = node;
248485
+ if (!isStringLiteral(source)) {
248486
+ return;
248487
+ }
248488
+ checkUselessPathSegments(source);
248489
+ checkSelfImport(source.value, source);
248490
+ }
248259
248491
  function checkImportCycle(node) {
248260
248492
  if (!checkCycles) {
248261
248493
  return;
@@ -248269,11 +248501,11 @@ const rule$K = createRule({
248269
248501
  return;
248270
248502
  }
248271
248503
  const graph = getImportGraph(tsProgram);
248272
- const targetFileName = findCyclicTargetFileName(graph, currentFileName, moduleSpecifier);
248273
- if (!targetFileName) {
248504
+ const cycle = findCyclePathForSpecifier(tsProgram, graph, currentFileName, moduleSpecifier, canonicalFileName);
248505
+ if (!cycle) {
248274
248506
  return;
248275
248507
  }
248276
- const cyclePath = formatCyclePath(graph, currentFileName, targetFileName, context.cwd);
248508
+ const cyclePath = formatCyclePath(graph, cycle.cyclePath, context.cwd);
248277
248509
  if (node.type === dist$3.AST_NODE_TYPES.ImportDeclaration) {
248278
248510
  // Compute the redirect replacement eagerly so we can decide whether
248279
248511
  // to suppress. If a redirect to the direct source file is possible,
@@ -248295,12 +248527,10 @@ const rule$K = createRule({
248295
248527
  }
248296
248528
  else {
248297
248529
  // For re-export nodes (export * from / export {x} from), suppress the
248298
- // error when the same SCC already contains an ImportDeclaration edge.
248530
+ // error when the reachable cycle contains an ImportDeclaration edge.
248299
248531
  // That ImportDeclaration will be reported (and fixed) directly, so
248300
248532
  // reporting the barrel re-export would create duplicate, unfixable noise.
248301
- const componentId = graph.componentIdByFileName.get(currentFileName);
248302
- if (componentId !== undefined &&
248303
- graph.sccHasImportEdgeById.get(componentId) === true) {
248533
+ if (cycle.hasImportEdge) {
248304
248534
  return;
248305
248535
  }
248306
248536
  context.report({
@@ -248322,7 +248552,7 @@ const rule$K = createRule({
248322
248552
  // Not a barrel (has no re-export edges) — symbols are defined locally,
248323
248553
  // so there is no shorter direct-source path to redirect to.
248324
248554
  if (!canonicalBarrelFileName ||
248325
- !getImportGraph(tsProgram).barrelFileNames.has(canonicalBarrelFileName)) {
248555
+ !fileHasReExportEdges(tsProgram, getImportGraph(tsProgram), canonicalBarrelFileName, canonicalFileName)) {
248326
248556
  return null;
248327
248557
  }
248328
248558
  const specifiersBySourceFile = new Map();
@@ -248377,7 +248607,9 @@ const rule$K = createRule({
248377
248607
  return newImports.join('\n');
248378
248608
  }
248379
248609
  function checkDefaultImport(node) {
248380
- if ((!checkDefaultImports && !checkNamedAsDefault) ||
248610
+ if ((!checkDefaultImports &&
248611
+ !checkNamedAsDefault &&
248612
+ !checkNamedAsDefaultMembers) ||
248381
248613
  node.importKind === 'type') {
248382
248614
  return;
248383
248615
  }
@@ -248404,6 +248636,17 @@ const rule$K = createRule({
248404
248636
  return;
248405
248637
  }
248406
248638
  const exportNames = getNamespaceImportExportNames(checker, esTreeNodeToTSNodeMap, node);
248639
+ if (checkNamedAsDefaultMembers && exportNames) {
248640
+ const [variable] = sourceCode.getDeclaredVariables(defaultImport);
248641
+ if (variable) {
248642
+ defaultImports.set(defaultImport.local.name, {
248643
+ exportNames,
248644
+ moduleSpecifier,
248645
+ node: defaultImport,
248646
+ variable,
248647
+ });
248648
+ }
248649
+ }
248407
248650
  if (!checkNamedAsDefault ||
248408
248651
  !hasNamedValueExport(exportNames, defaultImport.local.name)) {
248409
248652
  return;
@@ -248414,6 +248657,49 @@ const rule$K = createRule({
248414
248657
  node: defaultImport,
248415
248658
  });
248416
248659
  }
248660
+ function reportNamedAsDefaultMember(defaultImportIdentifier, memberName, reportNode) {
248661
+ if (!checkNamedAsDefaultMembers || !memberName || memberName === 'default') {
248662
+ return;
248663
+ }
248664
+ const usage = defaultImports.get(defaultImportIdentifier.name);
248665
+ if (!usage ||
248666
+ !hasNamedValueExport(usage.exportNames, memberName) ||
248667
+ getResolvedVariable(sourceCode, defaultImportIdentifier) !==
248668
+ usage.variable) {
248669
+ return;
248670
+ }
248671
+ context.report({
248672
+ data: {
248673
+ defaultName: usage.node.local.name,
248674
+ memberName,
248675
+ moduleSpecifier: usage.moduleSpecifier,
248676
+ },
248677
+ messageId: 'namedAsDefaultMember',
248678
+ node: reportNode,
248679
+ });
248680
+ }
248681
+ function checkNamedAsDefaultMemberExpression(node) {
248682
+ if (!checkNamedAsDefaultMembers ||
248683
+ defaultImports.size === 0 ||
248684
+ node.object.type !== dist$3.AST_NODE_TYPES.Identifier) {
248685
+ return;
248686
+ }
248687
+ reportNamedAsDefaultMember(node.object, getMemberExpressionPropertyName(node), node.property);
248688
+ }
248689
+ function checkNamedAsDefaultMemberDestructuring(node) {
248690
+ if (!checkNamedAsDefaultMembers ||
248691
+ defaultImports.size === 0 ||
248692
+ node.id.type !== dist$3.AST_NODE_TYPES.ObjectPattern ||
248693
+ node.init?.type !== dist$3.AST_NODE_TYPES.Identifier) {
248694
+ return;
248695
+ }
248696
+ for (const property of node.id.properties) {
248697
+ if (property.type !== dist$3.AST_NODE_TYPES.Property) {
248698
+ continue;
248699
+ }
248700
+ reportNamedAsDefaultMember(node.init, getObjectPropertyName(property), property.key);
248701
+ }
248702
+ }
248417
248703
  function checkNamedAsDefaultExport(node) {
248418
248704
  if (!checkNamedAsDefault ||
248419
248705
  node.exportKind === 'type' ||
@@ -248529,29 +248815,46 @@ const rule$K = createRule({
248529
248815
  });
248530
248816
  }
248531
248817
  return {
248532
- ExportAllDeclaration: checkImportCycle,
248818
+ CallExpression: checkRequireSelfImport,
248819
+ ExportAllDeclaration(node) {
248820
+ checkImportCycle(node);
248821
+ checkDeclarationModuleSpecifier(node);
248822
+ },
248533
248823
  ExportNamedDeclaration(node) {
248534
248824
  checkImportCycle(node);
248825
+ checkDeclarationModuleSpecifier(node);
248535
248826
  checkNamedAsDefaultExport(node);
248536
248827
  },
248537
248828
  ImportDeclaration(node) {
248538
248829
  checkImportCycle(node);
248830
+ checkDeclarationModuleSpecifier(node);
248831
+ collectDuplicateImport(node);
248539
248832
  checkDefaultImport(node);
248540
248833
  collectNamespaceImport(node);
248541
248834
  },
248542
- MemberExpression: checkNamespaceMember,
248835
+ ImportExpression: checkDynamicImport,
248836
+ MemberExpression(node) {
248837
+ checkNamedAsDefaultMemberExpression(node);
248838
+ checkNamespaceMember(node);
248839
+ },
248840
+ 'Program:exit': reportAllDuplicateImports,
248841
+ VariableDeclarator: checkNamedAsDefaultMemberDestructuring,
248543
248842
  };
248544
248843
  },
248545
248844
  meta: {
248546
248845
  docs: {
248547
- description: 'Fast replacement for import/default, import/namespace, import/no-cycle, and import/no-named-as-default checks',
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',
248548
248847
  },
248549
248848
  fixable: 'code',
248550
248849
  messages: {
248850
+ duplicateImport: '"{{moduleSpecifier}}" imported multiple times.',
248551
248851
  importCycle: 'Import cycle detected: {{cyclePath}}.',
248552
248852
  missingDefaultExport: 'No default export found in "{{moduleSpecifier}}".',
248553
248853
  namedAsDefault: 'Using exported name "{{name}}" as identifier for default export.',
248854
+ namedAsDefaultMember: 'Default import "{{defaultName}}" from "{{moduleSpecifier}}" also has a named export "{{memberName}}". Use a named import instead.',
248855
+ selfImport: 'Module imports itself.',
248554
248856
  unknownNamespaceMember: 'Namespace import "{{namespaceName}}" from "{{moduleSpecifier}}" has no exported member "{{memberName}}".',
248857
+ uselessPathSegments: 'Useless path segments for "{{moduleSpecifier}}", should be "{{proposedPath}}".',
248555
248858
  },
248556
248859
  schema: [
248557
248860
  {
@@ -248565,14 +248868,30 @@ const rule$K = createRule({
248565
248868
  description: 'Report default imports from modules without a default export. Defaults to true.',
248566
248869
  type: 'boolean',
248567
248870
  },
248871
+ checkDuplicateImports: {
248872
+ description: 'Report repeated import declarations for the same resolved module. Defaults to true.',
248873
+ type: 'boolean',
248874
+ },
248568
248875
  checkNamedAsDefault: {
248569
248876
  description: 'Report default imports and default re-exports named after a named export from the same module. Defaults to true.',
248570
248877
  type: 'boolean',
248571
248878
  },
248879
+ checkNamedAsDefaultMembers: {
248880
+ description: 'Report property access or destructuring of default imports when the property name is a named export from the same module. Defaults to true.',
248881
+ type: 'boolean',
248882
+ },
248572
248883
  checkNamespaceMembers: {
248573
248884
  description: 'Report static namespace import member accesses that are not exported by the imported module. Defaults to true.',
248574
248885
  type: 'boolean',
248575
248886
  },
248887
+ checkSelfImports: {
248888
+ description: 'Report imports, re-exports, dynamic imports, and require() calls that resolve to the current file. Defaults to true.',
248889
+ type: 'boolean',
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
+ },
248576
248895
  ignoreExternalDefaultImports: {
248577
248896
  description: 'Skip default import checks for modules resolved from external libraries. Defaults to true.',
248578
248897
  type: 'boolean',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taiga-ui/eslint-plugin-experience-next",
3
- "version": "0.502.0",
3
+ "version": "0.504.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,11 +1,15 @@
1
1
  import { type TSESLint } from '@typescript-eslint/utils';
2
- type MessageId = 'importCycle' | 'missingDefaultExport' | 'namedAsDefault' | 'unknownNamespaceMember';
2
+ type MessageId = 'duplicateImport' | 'importCycle' | 'missingDefaultExport' | 'namedAsDefault' | 'namedAsDefaultMember' | 'selfImport' | 'unknownNamespaceMember' | 'uselessPathSegments';
3
3
  type Options = [
4
4
  {
5
5
  checkCycles?: boolean;
6
6
  checkDefaultImports?: boolean;
7
+ checkDuplicateImports?: boolean;
7
8
  checkNamedAsDefault?: boolean;
9
+ checkNamedAsDefaultMembers?: boolean;
8
10
  checkNamespaceMembers?: boolean;
11
+ checkSelfImports?: boolean;
12
+ checkUselessPathSegments?: boolean;
9
13
  ignoreExternalDefaultImports?: boolean;
10
14
  }?
11
15
  ];