@taiga-ui/eslint-plugin-experience-next 0.501.0 → 0.503.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 +4 -1
- package/index.esm.js +550 -52
- package/package.json +1 -1
- package/rules/recommended/import-integrity.d.ts +4 -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 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, and self-import 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,14 @@ 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", [({
|
|
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;
|
|
46
49
|
ignoreExternalDefaultImports?: boolean;
|
|
47
50
|
} | undefined)?], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
48
51
|
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:
|
|
870
|
+
printWidth: 90,
|
|
871
871
|
properties: 'above',
|
|
872
872
|
},
|
|
873
873
|
],
|
|
@@ -875,19 +875,20 @@ 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':
|
|
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-
|
|
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
893
|
'import/no-useless-path-segments': ['error', { noUselessIndex: true }],
|
|
893
894
|
'import/no-webpack-loader-syntax': 'error',
|
|
@@ -247667,8 +247668,8 @@ function getTypeAwareRuleContext(context) {
|
|
|
247667
247668
|
|
|
247668
247669
|
const importGraphCacheByProgram = new WeakMap();
|
|
247669
247670
|
const defaultExportCacheByProgram = new WeakMap();
|
|
247671
|
+
const fallbackResolutionByProgram = new WeakMap();
|
|
247670
247672
|
const moduleExportNamesCache = new WeakMap();
|
|
247671
|
-
const resolutionStateByProgram = new WeakMap();
|
|
247672
247673
|
const sourceFileCacheByProgram = new WeakMap();
|
|
247673
247674
|
const codeFileExtensionRegExp = /\.[cm]?[jt]sx?$/;
|
|
247674
247675
|
// Angular DI functions resolve tokens at instantiation time, not at module load time,
|
|
@@ -247689,8 +247690,38 @@ function createCanonicalFileName() {
|
|
|
247689
247690
|
: resolvedFileName.toLowerCase();
|
|
247690
247691
|
};
|
|
247691
247692
|
}
|
|
247692
|
-
function
|
|
247693
|
-
|
|
247693
|
+
function normalizeSlashes(fileName) {
|
|
247694
|
+
return fileName.replaceAll('\\', '/');
|
|
247695
|
+
}
|
|
247696
|
+
function computeRelativeImportPath(fromFile, toFile) {
|
|
247697
|
+
const relative = path.relative(path.dirname(fromFile), toFile);
|
|
247698
|
+
const normalized = normalizeSlashes(relative).replace(codeFileExtensionRegExp, '');
|
|
247699
|
+
return normalized.startsWith('.') ? normalized : `./${normalized}`;
|
|
247700
|
+
}
|
|
247701
|
+
function isProjectCodeFile(sourceFile) {
|
|
247702
|
+
const normalizedFileName = normalizeSlashes(sourceFile.fileName);
|
|
247703
|
+
return (!sourceFile.isDeclarationFile &&
|
|
247704
|
+
codeFileExtensionRegExp.test(normalizedFileName) &&
|
|
247705
|
+
!normalizedFileName.includes('/node_modules/'));
|
|
247706
|
+
}
|
|
247707
|
+
// Returns the module resolution cache that the TypeScript program itself populated during
|
|
247708
|
+
// compilation. Not part of the public ts.Program API but available at runtime on TS 4+.
|
|
247709
|
+
function getProgramResolutionCache(program) {
|
|
247710
|
+
const record = program;
|
|
247711
|
+
const getCache = record['getModuleResolutionCache'];
|
|
247712
|
+
if (typeof getCache !== 'function') {
|
|
247713
|
+
return null;
|
|
247714
|
+
}
|
|
247715
|
+
const cache = getCache.call(program);
|
|
247716
|
+
if (cache === null ||
|
|
247717
|
+
typeof cache !== 'object' ||
|
|
247718
|
+
!('getOrCreateCacheForDirectory' in cache)) {
|
|
247719
|
+
return null;
|
|
247720
|
+
}
|
|
247721
|
+
return cache;
|
|
247722
|
+
}
|
|
247723
|
+
function getFallbackResolution(program) {
|
|
247724
|
+
const cached = fallbackResolutionByProgram.get(program);
|
|
247694
247725
|
if (cached) {
|
|
247695
247726
|
return cached;
|
|
247696
247727
|
}
|
|
@@ -247699,23 +247730,22 @@ function getResolutionState(program) {
|
|
|
247699
247730
|
compilerHost: ts.createCompilerHost(compilerOptions, true),
|
|
247700
247731
|
resolutionCache: ts.createModuleResolutionCache(program.getCurrentDirectory(), (fileName) => fileName, compilerOptions),
|
|
247701
247732
|
};
|
|
247702
|
-
|
|
247733
|
+
fallbackResolutionByProgram.set(program, state);
|
|
247703
247734
|
return state;
|
|
247704
247735
|
}
|
|
247705
|
-
function normalizeSlashes(fileName) {
|
|
247706
|
-
return fileName.replaceAll('\\', '/');
|
|
247707
|
-
}
|
|
247708
|
-
function isProjectCodeFile(sourceFile) {
|
|
247709
|
-
const normalizedFileName = normalizeSlashes(sourceFile.fileName);
|
|
247710
|
-
return (!sourceFile.isDeclarationFile &&
|
|
247711
|
-
codeFileExtensionRegExp.test(normalizedFileName) &&
|
|
247712
|
-
!normalizedFileName.includes('/node_modules/'));
|
|
247713
|
-
}
|
|
247714
247736
|
function resolveModule(program, containingFile, moduleSpecifier) {
|
|
247715
|
-
|
|
247716
|
-
|
|
247717
|
-
|
|
247718
|
-
|
|
247737
|
+
// Prefer the program's own resolution cache (already populated during compilation)
|
|
247738
|
+
// over running a fresh resolution, which requires file system access per unique
|
|
247739
|
+
// (directory, module) pair and dominates lint time on cold starts.
|
|
247740
|
+
const programCache = getProgramResolutionCache(program);
|
|
247741
|
+
if (programCache) {
|
|
247742
|
+
const fromCache = ts.resolveModuleNameFromCache(moduleSpecifier, containingFile, programCache);
|
|
247743
|
+
if (fromCache !== undefined) {
|
|
247744
|
+
return fromCache.resolvedModule ?? null;
|
|
247745
|
+
}
|
|
247746
|
+
}
|
|
247747
|
+
const { compilerHost, resolutionCache } = getFallbackResolution(program);
|
|
247748
|
+
return (ts.resolveModuleName(moduleSpecifier, containingFile, program.getCompilerOptions(), compilerHost, resolutionCache).resolvedModule ?? null);
|
|
247719
247749
|
}
|
|
247720
247750
|
function resolveModuleFileName(program, containingFile, moduleSpecifier) {
|
|
247721
247751
|
const resolved = resolveModule(program, containingFile, moduleSpecifier);
|
|
@@ -247724,6 +247754,56 @@ function resolveModuleFileName(program, containingFile, moduleSpecifier) {
|
|
|
247724
247754
|
}
|
|
247725
247755
|
return resolved.resolvedFileName;
|
|
247726
247756
|
}
|
|
247757
|
+
function getModuleSpecifierPath(moduleSpecifier) {
|
|
247758
|
+
const queryIndex = moduleSpecifier.indexOf('?');
|
|
247759
|
+
return queryIndex === -1 ? moduleSpecifier : moduleSpecifier.slice(0, queryIndex);
|
|
247760
|
+
}
|
|
247761
|
+
function resolveModuleKey(program, containingFile, moduleSpecifier, canonicalFileName) {
|
|
247762
|
+
const moduleSpecifierPath = getModuleSpecifierPath(moduleSpecifier);
|
|
247763
|
+
const resolved = resolveModule(program, containingFile, moduleSpecifierPath);
|
|
247764
|
+
if (!resolved) {
|
|
247765
|
+
return moduleSpecifier;
|
|
247766
|
+
}
|
|
247767
|
+
const queryIndex = moduleSpecifier.indexOf('?');
|
|
247768
|
+
const query = queryIndex === -1 ? '' : moduleSpecifier.slice(queryIndex);
|
|
247769
|
+
return `${canonicalFileName(resolved.resolvedFileName)}${query}`;
|
|
247770
|
+
}
|
|
247771
|
+
function getDefaultImportName(node) {
|
|
247772
|
+
const defaultImport = node.specifiers.find((specifier) => specifier.type === dist$3.AST_NODE_TYPES.ImportDefaultSpecifier);
|
|
247773
|
+
return defaultImport?.local.name ?? null;
|
|
247774
|
+
}
|
|
247775
|
+
function hasNamespaceImport(node) {
|
|
247776
|
+
return node.specifiers.some((specifier) => specifier.type === dist$3.AST_NODE_TYPES.ImportNamespaceSpecifier);
|
|
247777
|
+
}
|
|
247778
|
+
function hasTypeOnlyDefaultImport(node) {
|
|
247779
|
+
return node.importKind === 'type' && getDefaultImportName(node) !== null;
|
|
247780
|
+
}
|
|
247781
|
+
function hasImportAttributes(node) {
|
|
247782
|
+
const record = node;
|
|
247783
|
+
const attributes = record['attributes'];
|
|
247784
|
+
const assertions = record['assertions'];
|
|
247785
|
+
return ((Array.isArray(attributes) && attributes.length > 0) ||
|
|
247786
|
+
(Array.isArray(assertions) && assertions.length > 0));
|
|
247787
|
+
}
|
|
247788
|
+
function hasProblematicImportComments(node, sourceCode) {
|
|
247789
|
+
const text = sourceCode.getText(node);
|
|
247790
|
+
return (text.includes('/*') ||
|
|
247791
|
+
text.includes('//') ||
|
|
247792
|
+
sourceCode
|
|
247793
|
+
.getCommentsBefore(node)
|
|
247794
|
+
.some((comment) => comment.loc.end.line >= node.loc.start.line - 1) ||
|
|
247795
|
+
sourceCode
|
|
247796
|
+
.getCommentsAfter(node)
|
|
247797
|
+
.some((comment) => comment.loc.start.line === node.loc.end.line));
|
|
247798
|
+
}
|
|
247799
|
+
function getNamedSpecifierText(node, specifier, sourceCode) {
|
|
247800
|
+
const text = sourceCode.getText(specifier);
|
|
247801
|
+
const isTypeOnly = node.importKind === 'type' || specifier.importKind === 'type';
|
|
247802
|
+
return isTypeOnly && !text.trimStart().startsWith('type ') ? `type ${text}` : text;
|
|
247803
|
+
}
|
|
247804
|
+
function getNamedSpecifierKey(text) {
|
|
247805
|
+
return text.trim().replace(/^type\s+/, '');
|
|
247806
|
+
}
|
|
247727
247807
|
function importDeclarationHasRuntimeEdge(node) {
|
|
247728
247808
|
const importClause = node.importClause;
|
|
247729
247809
|
if (!importClause) {
|
|
@@ -247802,7 +247882,11 @@ function buildDependenciesByFileName(program, canonicalFileName) {
|
|
|
247802
247882
|
if (!projectFileNames.has(targetFileName)) {
|
|
247803
247883
|
continue;
|
|
247804
247884
|
}
|
|
247805
|
-
edges.push({
|
|
247885
|
+
edges.push({
|
|
247886
|
+
isImport: ts.isImportDeclaration(statement),
|
|
247887
|
+
moduleSpecifier,
|
|
247888
|
+
targetFileName,
|
|
247889
|
+
});
|
|
247806
247890
|
}
|
|
247807
247891
|
dependenciesByFileName.set(fileName, edges);
|
|
247808
247892
|
}
|
|
@@ -247877,6 +247961,32 @@ function collectSelfCycles(dependenciesByFileName) {
|
|
|
247877
247961
|
}
|
|
247878
247962
|
return selfCycleFileNames;
|
|
247879
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
|
+
}
|
|
247880
247990
|
function getImportGraph(program) {
|
|
247881
247991
|
const cached = importGraphCacheByProgram.get(program);
|
|
247882
247992
|
if (cached) {
|
|
@@ -247886,10 +247996,12 @@ function getImportGraph(program) {
|
|
|
247886
247996
|
const { dependenciesByFileName, displayFileNameByFileName } = buildDependenciesByFileName(program, canonicalFileName);
|
|
247887
247997
|
const { componentIdByFileName, componentSizeById } = findStronglyConnectedComponents(dependenciesByFileName);
|
|
247888
247998
|
const cache = {
|
|
247999
|
+
barrelFileNames: buildBarrelFileNames(dependenciesByFileName),
|
|
247889
248000
|
componentIdByFileName,
|
|
247890
248001
|
componentSizeById,
|
|
247891
248002
|
dependenciesByFileName,
|
|
247892
248003
|
displayFileNameByFileName,
|
|
248004
|
+
sccHasImportEdgeById: buildSccHasImportEdge(dependenciesByFileName, componentIdByFileName),
|
|
247893
248005
|
selfCycleFileNames: collectSelfCycles(dependenciesByFileName),
|
|
247894
248006
|
};
|
|
247895
248007
|
importGraphCacheByProgram.set(program, cache);
|
|
@@ -248103,6 +248215,34 @@ function getExportSpecifierName(name) {
|
|
|
248103
248215
|
function hasNamedValueExport(exportNames, exportedName) {
|
|
248104
248216
|
return exportedName !== 'default' && (exportNames?.has(exportedName) ?? false);
|
|
248105
248217
|
}
|
|
248218
|
+
// Angular resolves the reference lazily (at instantiation, not at module load time)
|
|
248219
|
+
// in all of these contexts, so a cycle through them is safe at the ES module level.
|
|
248220
|
+
function isInAngularSafeContext(identifier) {
|
|
248221
|
+
// TSESTree types parent as Node (non-optional), but the root node's parent is
|
|
248222
|
+
// null at runtime. Widening to include null/undefined lets while(current) serve
|
|
248223
|
+
// as the exit condition when traversal reaches the top of the tree.
|
|
248224
|
+
let current = identifier.parent;
|
|
248225
|
+
while (current) {
|
|
248226
|
+
// Lazy evaluation: body runs only when the function/arrow is called.
|
|
248227
|
+
if (current.type === dist$3.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
248228
|
+
current.type === dist$3.AST_NODE_TYPES.FunctionExpression ||
|
|
248229
|
+
current.type === dist$3.AST_NODE_TYPES.FunctionDeclaration) {
|
|
248230
|
+
return true;
|
|
248231
|
+
}
|
|
248232
|
+
// Angular decorator metadata (@Component, @Directive, etc.) is processed
|
|
248233
|
+
// after all modules in the cycle have finished loading.
|
|
248234
|
+
if (current.type === dist$3.AST_NODE_TYPES.Decorator) {
|
|
248235
|
+
return true;
|
|
248236
|
+
}
|
|
248237
|
+
// Non-static class field initializers run at instantiation time, not at
|
|
248238
|
+
// module load time, so they do not create a load-time cycle edge.
|
|
248239
|
+
if (current.type === dist$3.AST_NODE_TYPES.PropertyDefinition && !current.static) {
|
|
248240
|
+
return true;
|
|
248241
|
+
}
|
|
248242
|
+
current = current.parent;
|
|
248243
|
+
}
|
|
248244
|
+
return false;
|
|
248245
|
+
}
|
|
248106
248246
|
function isImportUsedOnlyAsAngularDiFirstArg(node, sourceCode) {
|
|
248107
248247
|
const valueSpecifiers = node.specifiers.filter((specifier) => {
|
|
248108
248248
|
if (specifier.type === dist$3.AST_NODE_TYPES.ImportDefaultSpecifier ||
|
|
@@ -248114,6 +248254,10 @@ function isImportUsedOnlyAsAngularDiFirstArg(node, sourceCode) {
|
|
|
248114
248254
|
if (valueSpecifiers.length === 0) {
|
|
248115
248255
|
return false;
|
|
248116
248256
|
}
|
|
248257
|
+
// True if at least one reference is a genuine DI call or lives in a lazy/decorator
|
|
248258
|
+
// context. Pure type-only references don't count: an import used only as a type
|
|
248259
|
+
// should use `import type` and is not considered safe from a cycle perspective.
|
|
248260
|
+
let hasSafeRuntimeUsage = false;
|
|
248117
248261
|
for (const specifier of valueSpecifiers) {
|
|
248118
248262
|
const [variable] = sourceCode.getDeclaredVariables(specifier);
|
|
248119
248263
|
if (!variable || variable.references.length === 0) {
|
|
@@ -248122,6 +248266,17 @@ function isImportUsedOnlyAsAngularDiFirstArg(node, sourceCode) {
|
|
|
248122
248266
|
for (const ref of variable.references) {
|
|
248123
248267
|
const { identifier } = ref;
|
|
248124
248268
|
const parent = identifier.parent;
|
|
248269
|
+
// Type-only references (e.g., param: Type<T>) don't create runtime
|
|
248270
|
+
// dependencies. Skip without marking hasSafeRuntimeUsage — an import
|
|
248271
|
+
// that is exclusively used as a type should use `import type` instead.
|
|
248272
|
+
if (parent.type === dist$3.AST_NODE_TYPES.TSTypeReference) {
|
|
248273
|
+
continue;
|
|
248274
|
+
}
|
|
248275
|
+
// References inside lazy/decorator contexts don't create load-time edges.
|
|
248276
|
+
if (isInAngularSafeContext(identifier)) {
|
|
248277
|
+
hasSafeRuntimeUsage = true;
|
|
248278
|
+
continue;
|
|
248279
|
+
}
|
|
248125
248280
|
const callee = parent.type === dist$3.AST_NODE_TYPES.CallExpression ? parent.callee : null;
|
|
248126
248281
|
const isDirectCall = callee?.type === dist$3.AST_NODE_TYPES.Identifier &&
|
|
248127
248282
|
ANGULAR_DI_FIRST_ARG_FUNCTIONS.has(callee.name);
|
|
@@ -248136,21 +248291,177 @@ function isImportUsedOnlyAsAngularDiFirstArg(node, sourceCode) {
|
|
|
248136
248291
|
parent.arguments[0] !== identifier) {
|
|
248137
248292
|
return false;
|
|
248138
248293
|
}
|
|
248294
|
+
hasSafeRuntimeUsage = true;
|
|
248139
248295
|
}
|
|
248140
248296
|
}
|
|
248141
|
-
return
|
|
248297
|
+
return hasSafeRuntimeUsage;
|
|
248142
248298
|
}
|
|
248143
248299
|
const rule$K = createRule({
|
|
248144
248300
|
create(context) {
|
|
248145
248301
|
const { checker, esTreeNodeToTSNodeMap, sourceCode, tsProgram } = getTypeAwareRuleContext(context);
|
|
248146
248302
|
const checkCycles = context.options[0]?.checkCycles ?? true;
|
|
248147
248303
|
const checkDefaultImports = context.options[0]?.checkDefaultImports ?? true;
|
|
248304
|
+
const checkDuplicateImports = context.options[0]?.checkDuplicateImports ?? true;
|
|
248148
248305
|
const checkNamedAsDefault = context.options[0]?.checkNamedAsDefault ?? true;
|
|
248306
|
+
const checkNamedAsDefaultMembers = context.options[0]?.checkNamedAsDefaultMembers ?? true;
|
|
248149
248307
|
const checkNamespaceMembers = context.options[0]?.checkNamespaceMembers ?? true;
|
|
248308
|
+
const checkSelfImports = context.options[0]?.checkSelfImports ?? true;
|
|
248150
248309
|
const ignoreExternalDefaultImports = context.options[0]?.ignoreExternalDefaultImports ?? true;
|
|
248151
248310
|
const canonicalFileName = createCanonicalFileName();
|
|
248152
248311
|
const currentFileName = canonicalFileName(context.filename);
|
|
248312
|
+
const defaultImports = new Map();
|
|
248313
|
+
const duplicateImportMaps = {
|
|
248314
|
+
importsByModule: new Map(),
|
|
248315
|
+
namespaceImportsByModule: new Map(),
|
|
248316
|
+
};
|
|
248153
248317
|
const namespaceImports = new Map();
|
|
248318
|
+
function getDuplicateImportMap(node) {
|
|
248319
|
+
return hasNamespaceImport(node)
|
|
248320
|
+
? duplicateImportMaps.namespaceImportsByModule
|
|
248321
|
+
: duplicateImportMaps.importsByModule;
|
|
248322
|
+
}
|
|
248323
|
+
function collectDuplicateImport(node) {
|
|
248324
|
+
if (!checkDuplicateImports) {
|
|
248325
|
+
return;
|
|
248326
|
+
}
|
|
248327
|
+
const moduleKey = resolveModuleKey(tsProgram, context.filename, node.source.value, canonicalFileName);
|
|
248328
|
+
const importsByModule = getDuplicateImportMap(node);
|
|
248329
|
+
const imports = importsByModule.get(moduleKey) ?? [];
|
|
248330
|
+
imports.push(node);
|
|
248331
|
+
importsByModule.set(moduleKey, imports);
|
|
248332
|
+
}
|
|
248333
|
+
function buildImportRemovalFix(fixer, node) {
|
|
248334
|
+
const [start, end] = node.range;
|
|
248335
|
+
const lineStart = sourceCode.text.lastIndexOf('\n', start - 1) + 1;
|
|
248336
|
+
const removeStart = /^\s*$/.test(sourceCode.text.slice(lineStart, start))
|
|
248337
|
+
? lineStart
|
|
248338
|
+
: start;
|
|
248339
|
+
const removeEnd = sourceCode.text[end] === '\n' ? end + 1 : end;
|
|
248340
|
+
return [fixer.removeRange([removeStart, removeEnd])];
|
|
248341
|
+
}
|
|
248342
|
+
function buildDuplicateImportFix(fixer, first, rest) {
|
|
248343
|
+
const nodes = [first, ...rest];
|
|
248344
|
+
if (nodes.some((node) => hasNamespaceImport(node) ||
|
|
248345
|
+
hasTypeOnlyDefaultImport(node) ||
|
|
248346
|
+
hasImportAttributes(node) ||
|
|
248347
|
+
hasProblematicImportComments(node, sourceCode))) {
|
|
248348
|
+
return null;
|
|
248349
|
+
}
|
|
248350
|
+
const defaultNames = new Set(nodes.flatMap((node) => {
|
|
248351
|
+
const name = getDefaultImportName(node);
|
|
248352
|
+
return name ? [name] : [];
|
|
248353
|
+
}));
|
|
248354
|
+
if (defaultNames.size > 1) {
|
|
248355
|
+
return null;
|
|
248356
|
+
}
|
|
248357
|
+
const namedSpecifiersByKey = new Map();
|
|
248358
|
+
for (const node of nodes) {
|
|
248359
|
+
for (const specifier of node.specifiers) {
|
|
248360
|
+
if (specifier.type !== dist$3.AST_NODE_TYPES.ImportSpecifier) {
|
|
248361
|
+
continue;
|
|
248362
|
+
}
|
|
248363
|
+
const text = getNamedSpecifierText(node, specifier, sourceCode).trim();
|
|
248364
|
+
const key = getNamedSpecifierKey(text);
|
|
248365
|
+
const existing = namedSpecifiersByKey.get(key);
|
|
248366
|
+
const isTypeOnly = text.startsWith('type ');
|
|
248367
|
+
if (!existing || (existing.startsWith('type ') && !isTypeOnly)) {
|
|
248368
|
+
namedSpecifiersByKey.set(key, text);
|
|
248369
|
+
}
|
|
248370
|
+
}
|
|
248371
|
+
}
|
|
248372
|
+
const namedSpecifiers = [...namedSpecifiersByKey.values()];
|
|
248373
|
+
const [defaultName = null] = defaultNames;
|
|
248374
|
+
const bindings = [
|
|
248375
|
+
...(defaultName ? [defaultName] : []),
|
|
248376
|
+
...(namedSpecifiers.length > 0
|
|
248377
|
+
? [`{${namedSpecifiers.join(', ')}}`]
|
|
248378
|
+
: []),
|
|
248379
|
+
];
|
|
248380
|
+
const sourceText = sourceCode.getText(first.source);
|
|
248381
|
+
const semi = sourceCode.getText(first).endsWith(';') ? ';' : '';
|
|
248382
|
+
const replacement = bindings.length === 0
|
|
248383
|
+
? `import ${sourceText}${semi}`
|
|
248384
|
+
: `import ${bindings.join(', ')} from ${sourceText}${semi}`;
|
|
248385
|
+
return [
|
|
248386
|
+
fixer.replaceText(first, replacement),
|
|
248387
|
+
...rest.flatMap((node) => buildImportRemovalFix(fixer, node)),
|
|
248388
|
+
];
|
|
248389
|
+
}
|
|
248390
|
+
function reportDuplicateImports(importsByModule) {
|
|
248391
|
+
for (const nodes of importsByModule.values()) {
|
|
248392
|
+
if (nodes.length < 2) {
|
|
248393
|
+
continue;
|
|
248394
|
+
}
|
|
248395
|
+
const [first, ...rest] = nodes;
|
|
248396
|
+
if (!first) {
|
|
248397
|
+
continue;
|
|
248398
|
+
}
|
|
248399
|
+
const moduleSpecifier = first.source.value;
|
|
248400
|
+
context.report({
|
|
248401
|
+
data: { moduleSpecifier },
|
|
248402
|
+
fix: (fixer) => buildDuplicateImportFix(fixer, first, rest),
|
|
248403
|
+
messageId: 'duplicateImport',
|
|
248404
|
+
node: first.source,
|
|
248405
|
+
});
|
|
248406
|
+
for (const node of rest) {
|
|
248407
|
+
context.report({
|
|
248408
|
+
data: { moduleSpecifier },
|
|
248409
|
+
messageId: 'duplicateImport',
|
|
248410
|
+
node: node.source,
|
|
248411
|
+
});
|
|
248412
|
+
}
|
|
248413
|
+
}
|
|
248414
|
+
}
|
|
248415
|
+
function reportAllDuplicateImports() {
|
|
248416
|
+
if (!checkDuplicateImports) {
|
|
248417
|
+
return;
|
|
248418
|
+
}
|
|
248419
|
+
reportDuplicateImports(duplicateImportMaps.importsByModule);
|
|
248420
|
+
reportDuplicateImports(duplicateImportMaps.namespaceImportsByModule);
|
|
248421
|
+
}
|
|
248422
|
+
function checkSelfImport(moduleSpecifier, node) {
|
|
248423
|
+
if (!checkSelfImports ||
|
|
248424
|
+
context.filename === '<text>' ||
|
|
248425
|
+
moduleSpecifier.includes('?')) {
|
|
248426
|
+
return;
|
|
248427
|
+
}
|
|
248428
|
+
const resolved = resolveModule(tsProgram, context.filename, getModuleSpecifierPath(moduleSpecifier));
|
|
248429
|
+
if (!resolved ||
|
|
248430
|
+
canonicalFileName(resolved.resolvedFileName) !== currentFileName) {
|
|
248431
|
+
return;
|
|
248432
|
+
}
|
|
248433
|
+
context.report({
|
|
248434
|
+
messageId: 'selfImport',
|
|
248435
|
+
node,
|
|
248436
|
+
});
|
|
248437
|
+
}
|
|
248438
|
+
function checkDeclarationSelfImport(node) {
|
|
248439
|
+
if (!node.source || typeof node.source.value !== 'string') {
|
|
248440
|
+
return;
|
|
248441
|
+
}
|
|
248442
|
+
checkSelfImport(node.source.value, node.source);
|
|
248443
|
+
}
|
|
248444
|
+
function checkRequireSelfImport(node) {
|
|
248445
|
+
if (node.callee.type !== dist$3.AST_NODE_TYPES.Identifier ||
|
|
248446
|
+
node.callee.name !== 'require' ||
|
|
248447
|
+
node.arguments.length !== 1) {
|
|
248448
|
+
return;
|
|
248449
|
+
}
|
|
248450
|
+
const [moduleSpecifier] = node.arguments;
|
|
248451
|
+
if (moduleSpecifier?.type !== dist$3.AST_NODE_TYPES.Literal ||
|
|
248452
|
+
typeof moduleSpecifier.value !== 'string') {
|
|
248453
|
+
return;
|
|
248454
|
+
}
|
|
248455
|
+
checkSelfImport(moduleSpecifier.value, moduleSpecifier);
|
|
248456
|
+
}
|
|
248457
|
+
function checkDynamicSelfImport(node) {
|
|
248458
|
+
const { source } = node;
|
|
248459
|
+
if (source.type !== dist$3.AST_NODE_TYPES.Literal ||
|
|
248460
|
+
typeof source.value !== 'string') {
|
|
248461
|
+
return;
|
|
248462
|
+
}
|
|
248463
|
+
checkSelfImport(source.value, source);
|
|
248464
|
+
}
|
|
248154
248465
|
function checkImportCycle(node) {
|
|
248155
248466
|
if (!checkCycles) {
|
|
248156
248467
|
return;
|
|
@@ -248165,21 +248476,116 @@ const rule$K = createRule({
|
|
|
248165
248476
|
}
|
|
248166
248477
|
const graph = getImportGraph(tsProgram);
|
|
248167
248478
|
const targetFileName = findCyclicTargetFileName(graph, currentFileName, moduleSpecifier);
|
|
248168
|
-
if (!targetFileName
|
|
248169
|
-
(node.type === dist$3.AST_NODE_TYPES.ImportDeclaration &&
|
|
248170
|
-
isImportUsedOnlyAsAngularDiFirstArg(node, sourceCode))) {
|
|
248479
|
+
if (!targetFileName) {
|
|
248171
248480
|
return;
|
|
248172
248481
|
}
|
|
248173
|
-
context.
|
|
248174
|
-
|
|
248175
|
-
|
|
248176
|
-
|
|
248177
|
-
|
|
248178
|
-
|
|
248179
|
-
|
|
248482
|
+
const cyclePath = formatCyclePath(graph, currentFileName, targetFileName, context.cwd);
|
|
248483
|
+
if (node.type === dist$3.AST_NODE_TYPES.ImportDeclaration) {
|
|
248484
|
+
// Compute the redirect replacement eagerly so we can decide whether
|
|
248485
|
+
// to suppress. If a redirect to the direct source file is possible,
|
|
248486
|
+
// always report (the fix breaks the cycle). Otherwise, suppress when
|
|
248487
|
+
// all usages are Angular-DI-safe (inject, class fields, etc.).
|
|
248488
|
+
const replacement = computeCycleBreakingReplacement(node);
|
|
248489
|
+
if (!replacement &&
|
|
248490
|
+
isImportUsedOnlyAsAngularDiFirstArg(node, sourceCode)) {
|
|
248491
|
+
return;
|
|
248492
|
+
}
|
|
248493
|
+
context.report({
|
|
248494
|
+
data: { cyclePath },
|
|
248495
|
+
fix: replacement
|
|
248496
|
+
? (fixer) => [fixer.replaceText(node, replacement)]
|
|
248497
|
+
: undefined,
|
|
248498
|
+
messageId: 'importCycle',
|
|
248499
|
+
node: sourceNode,
|
|
248500
|
+
});
|
|
248501
|
+
}
|
|
248502
|
+
else {
|
|
248503
|
+
// For re-export nodes (export * from / export {x} from), suppress the
|
|
248504
|
+
// error when the same SCC already contains an ImportDeclaration edge.
|
|
248505
|
+
// That ImportDeclaration will be reported (and fixed) directly, so
|
|
248506
|
+
// 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) {
|
|
248510
|
+
return;
|
|
248511
|
+
}
|
|
248512
|
+
context.report({
|
|
248513
|
+
data: { cyclePath },
|
|
248514
|
+
messageId: 'importCycle',
|
|
248515
|
+
node: sourceNode,
|
|
248516
|
+
});
|
|
248517
|
+
}
|
|
248518
|
+
}
|
|
248519
|
+
function computeCycleBreakingReplacement(node) {
|
|
248520
|
+
// Only named imports can be safely redirected to their source file
|
|
248521
|
+
if (node.specifiers.some((s) => s.type !== dist$3.AST_NODE_TYPES.ImportSpecifier)) {
|
|
248522
|
+
return null;
|
|
248523
|
+
}
|
|
248524
|
+
const barrelFileName = resolveModuleFileName(tsProgram, context.filename, node.source.value);
|
|
248525
|
+
const canonicalBarrelFileName = barrelFileName
|
|
248526
|
+
? canonicalFileName(barrelFileName)
|
|
248527
|
+
: null;
|
|
248528
|
+
// Not a barrel (has no re-export edges) — symbols are defined locally,
|
|
248529
|
+
// so there is no shorter direct-source path to redirect to.
|
|
248530
|
+
if (!canonicalBarrelFileName ||
|
|
248531
|
+
!getImportGraph(tsProgram).barrelFileNames.has(canonicalBarrelFileName)) {
|
|
248532
|
+
return null;
|
|
248533
|
+
}
|
|
248534
|
+
const specifiersBySourceFile = new Map();
|
|
248535
|
+
for (const specifier of node.specifiers) {
|
|
248536
|
+
if (specifier.type !== dist$3.AST_NODE_TYPES.ImportSpecifier) {
|
|
248537
|
+
return null;
|
|
248538
|
+
}
|
|
248539
|
+
const tsSpecifier = esTreeNodeToTSNodeMap.get(specifier);
|
|
248540
|
+
if (!ts.isImportSpecifier(tsSpecifier)) {
|
|
248541
|
+
return null;
|
|
248542
|
+
}
|
|
248543
|
+
const localSymbol = checker.getSymbolAtLocation(tsSpecifier.name);
|
|
248544
|
+
if (!localSymbol) {
|
|
248545
|
+
return null;
|
|
248546
|
+
}
|
|
248547
|
+
const originalSymbol = getAliasedSymbolIfNeeded(checker, localSymbol);
|
|
248548
|
+
const { declarations } = originalSymbol;
|
|
248549
|
+
const firstDeclaration = declarations?.[0];
|
|
248550
|
+
if (!firstDeclaration) {
|
|
248551
|
+
return null;
|
|
248552
|
+
}
|
|
248553
|
+
const sourceFile = firstDeclaration.getSourceFile();
|
|
248554
|
+
if (!isProjectCodeFile(sourceFile)) {
|
|
248555
|
+
return null;
|
|
248556
|
+
}
|
|
248557
|
+
const sourceFilePath = sourceFile.fileName;
|
|
248558
|
+
// Symbol is defined directly in the barrel — no shorter path exists
|
|
248559
|
+
if (canonicalBarrelFileName === canonicalFileName(sourceFilePath)) {
|
|
248560
|
+
return null;
|
|
248561
|
+
}
|
|
248562
|
+
const importedName = tsSpecifier.propertyName?.text ?? tsSpecifier.name.text;
|
|
248563
|
+
const localName = tsSpecifier.name.text;
|
|
248564
|
+
const typePrefix = specifier.importKind === 'type' ? 'type ' : '';
|
|
248565
|
+
const specText = importedName === localName
|
|
248566
|
+
? `${typePrefix}${importedName}`
|
|
248567
|
+
: `${typePrefix}${importedName} as ${localName}`;
|
|
248568
|
+
const group = specifiersBySourceFile.get(sourceFilePath) ?? [];
|
|
248569
|
+
group.push(specText);
|
|
248570
|
+
specifiersBySourceFile.set(sourceFilePath, group);
|
|
248571
|
+
}
|
|
248572
|
+
if (specifiersBySourceFile.size === 0) {
|
|
248573
|
+
return null;
|
|
248574
|
+
}
|
|
248575
|
+
const semi = sourceCode.getText(node).endsWith(';') ? ';' : '';
|
|
248576
|
+
const quote = node.source.raw[0] ?? "'";
|
|
248577
|
+
const importPrefix = node.importKind === 'type' ? 'import type' : 'import';
|
|
248578
|
+
const newImports = [];
|
|
248579
|
+
for (const [sourceFilePath, names] of specifiersBySourceFile) {
|
|
248580
|
+
const relPath = computeRelativeImportPath(context.filename, sourceFilePath);
|
|
248581
|
+
newImports.push(`${importPrefix} {${names.join(', ')}} from ${quote}${relPath}${quote}${semi}`);
|
|
248582
|
+
}
|
|
248583
|
+
return newImports.join('\n');
|
|
248180
248584
|
}
|
|
248181
248585
|
function checkDefaultImport(node) {
|
|
248182
|
-
if ((!checkDefaultImports &&
|
|
248586
|
+
if ((!checkDefaultImports &&
|
|
248587
|
+
!checkNamedAsDefault &&
|
|
248588
|
+
!checkNamedAsDefaultMembers) ||
|
|
248183
248589
|
node.importKind === 'type') {
|
|
248184
248590
|
return;
|
|
248185
248591
|
}
|
|
@@ -248206,6 +248612,17 @@ const rule$K = createRule({
|
|
|
248206
248612
|
return;
|
|
248207
248613
|
}
|
|
248208
248614
|
const exportNames = getNamespaceImportExportNames(checker, esTreeNodeToTSNodeMap, node);
|
|
248615
|
+
if (checkNamedAsDefaultMembers && exportNames) {
|
|
248616
|
+
const [variable] = sourceCode.getDeclaredVariables(defaultImport);
|
|
248617
|
+
if (variable) {
|
|
248618
|
+
defaultImports.set(defaultImport.local.name, {
|
|
248619
|
+
exportNames,
|
|
248620
|
+
moduleSpecifier,
|
|
248621
|
+
node: defaultImport,
|
|
248622
|
+
variable,
|
|
248623
|
+
});
|
|
248624
|
+
}
|
|
248625
|
+
}
|
|
248209
248626
|
if (!checkNamedAsDefault ||
|
|
248210
248627
|
!hasNamedValueExport(exportNames, defaultImport.local.name)) {
|
|
248211
248628
|
return;
|
|
@@ -248216,6 +248633,49 @@ const rule$K = createRule({
|
|
|
248216
248633
|
node: defaultImport,
|
|
248217
248634
|
});
|
|
248218
248635
|
}
|
|
248636
|
+
function reportNamedAsDefaultMember(defaultImportIdentifier, memberName, reportNode) {
|
|
248637
|
+
if (!checkNamedAsDefaultMembers || !memberName || memberName === 'default') {
|
|
248638
|
+
return;
|
|
248639
|
+
}
|
|
248640
|
+
const usage = defaultImports.get(defaultImportIdentifier.name);
|
|
248641
|
+
if (!usage ||
|
|
248642
|
+
!hasNamedValueExport(usage.exportNames, memberName) ||
|
|
248643
|
+
getResolvedVariable(sourceCode, defaultImportIdentifier) !==
|
|
248644
|
+
usage.variable) {
|
|
248645
|
+
return;
|
|
248646
|
+
}
|
|
248647
|
+
context.report({
|
|
248648
|
+
data: {
|
|
248649
|
+
defaultName: usage.node.local.name,
|
|
248650
|
+
memberName,
|
|
248651
|
+
moduleSpecifier: usage.moduleSpecifier,
|
|
248652
|
+
},
|
|
248653
|
+
messageId: 'namedAsDefaultMember',
|
|
248654
|
+
node: reportNode,
|
|
248655
|
+
});
|
|
248656
|
+
}
|
|
248657
|
+
function checkNamedAsDefaultMemberExpression(node) {
|
|
248658
|
+
if (!checkNamedAsDefaultMembers ||
|
|
248659
|
+
defaultImports.size === 0 ||
|
|
248660
|
+
node.object.type !== dist$3.AST_NODE_TYPES.Identifier) {
|
|
248661
|
+
return;
|
|
248662
|
+
}
|
|
248663
|
+
reportNamedAsDefaultMember(node.object, getMemberExpressionPropertyName(node), node.property);
|
|
248664
|
+
}
|
|
248665
|
+
function checkNamedAsDefaultMemberDestructuring(node) {
|
|
248666
|
+
if (!checkNamedAsDefaultMembers ||
|
|
248667
|
+
defaultImports.size === 0 ||
|
|
248668
|
+
node.id.type !== dist$3.AST_NODE_TYPES.ObjectPattern ||
|
|
248669
|
+
node.init?.type !== dist$3.AST_NODE_TYPES.Identifier) {
|
|
248670
|
+
return;
|
|
248671
|
+
}
|
|
248672
|
+
for (const property of node.id.properties) {
|
|
248673
|
+
if (property.type !== dist$3.AST_NODE_TYPES.Property) {
|
|
248674
|
+
continue;
|
|
248675
|
+
}
|
|
248676
|
+
reportNamedAsDefaultMember(node.init, getObjectPropertyName(property), property.key);
|
|
248677
|
+
}
|
|
248678
|
+
}
|
|
248219
248679
|
function checkNamedAsDefaultExport(node) {
|
|
248220
248680
|
if (!checkNamedAsDefault ||
|
|
248221
248681
|
node.exportKind === 'type' ||
|
|
@@ -248331,28 +248791,44 @@ const rule$K = createRule({
|
|
|
248331
248791
|
});
|
|
248332
248792
|
}
|
|
248333
248793
|
return {
|
|
248334
|
-
|
|
248794
|
+
CallExpression: checkRequireSelfImport,
|
|
248795
|
+
ExportAllDeclaration(node) {
|
|
248796
|
+
checkImportCycle(node);
|
|
248797
|
+
checkDeclarationSelfImport(node);
|
|
248798
|
+
},
|
|
248335
248799
|
ExportNamedDeclaration(node) {
|
|
248336
248800
|
checkImportCycle(node);
|
|
248801
|
+
checkDeclarationSelfImport(node);
|
|
248337
248802
|
checkNamedAsDefaultExport(node);
|
|
248338
248803
|
},
|
|
248339
248804
|
ImportDeclaration(node) {
|
|
248340
248805
|
checkImportCycle(node);
|
|
248806
|
+
checkDeclarationSelfImport(node);
|
|
248807
|
+
collectDuplicateImport(node);
|
|
248341
248808
|
checkDefaultImport(node);
|
|
248342
248809
|
collectNamespaceImport(node);
|
|
248343
248810
|
},
|
|
248344
|
-
|
|
248811
|
+
ImportExpression: checkDynamicSelfImport,
|
|
248812
|
+
MemberExpression(node) {
|
|
248813
|
+
checkNamedAsDefaultMemberExpression(node);
|
|
248814
|
+
checkNamespaceMember(node);
|
|
248815
|
+
},
|
|
248816
|
+
'Program:exit': reportAllDuplicateImports,
|
|
248817
|
+
VariableDeclarator: checkNamedAsDefaultMemberDestructuring,
|
|
248345
248818
|
};
|
|
248346
248819
|
},
|
|
248347
248820
|
meta: {
|
|
248348
248821
|
docs: {
|
|
248349
|
-
description: 'Fast replacement for import/default, import/namespace, import/no-cycle,
|
|
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',
|
|
248350
248823
|
},
|
|
248351
248824
|
fixable: 'code',
|
|
248352
248825
|
messages: {
|
|
248826
|
+
duplicateImport: '"{{moduleSpecifier}}" imported multiple times.',
|
|
248353
248827
|
importCycle: 'Import cycle detected: {{cyclePath}}.',
|
|
248354
248828
|
missingDefaultExport: 'No default export found in "{{moduleSpecifier}}".',
|
|
248355
248829
|
namedAsDefault: 'Using exported name "{{name}}" as identifier for default export.',
|
|
248830
|
+
namedAsDefaultMember: 'Default import "{{defaultName}}" from "{{moduleSpecifier}}" also has a named export "{{memberName}}". Use a named import instead.',
|
|
248831
|
+
selfImport: 'Module imports itself.',
|
|
248356
248832
|
unknownNamespaceMember: 'Namespace import "{{namespaceName}}" from "{{moduleSpecifier}}" has no exported member "{{memberName}}".',
|
|
248357
248833
|
},
|
|
248358
248834
|
schema: [
|
|
@@ -248367,14 +248843,26 @@ const rule$K = createRule({
|
|
|
248367
248843
|
description: 'Report default imports from modules without a default export. Defaults to true.',
|
|
248368
248844
|
type: 'boolean',
|
|
248369
248845
|
},
|
|
248846
|
+
checkDuplicateImports: {
|
|
248847
|
+
description: 'Report repeated import declarations for the same resolved module. Defaults to true.',
|
|
248848
|
+
type: 'boolean',
|
|
248849
|
+
},
|
|
248370
248850
|
checkNamedAsDefault: {
|
|
248371
248851
|
description: 'Report default imports and default re-exports named after a named export from the same module. Defaults to true.',
|
|
248372
248852
|
type: 'boolean',
|
|
248373
248853
|
},
|
|
248854
|
+
checkNamedAsDefaultMembers: {
|
|
248855
|
+
description: 'Report property access or destructuring of default imports when the property name is a named export from the same module. Defaults to true.',
|
|
248856
|
+
type: 'boolean',
|
|
248857
|
+
},
|
|
248374
248858
|
checkNamespaceMembers: {
|
|
248375
248859
|
description: 'Report static namespace import member accesses that are not exported by the imported module. Defaults to true.',
|
|
248376
248860
|
type: 'boolean',
|
|
248377
248861
|
},
|
|
248862
|
+
checkSelfImports: {
|
|
248863
|
+
description: 'Report imports, re-exports, dynamic imports, and require() calls that resolve to the current file. Defaults to true.',
|
|
248864
|
+
type: 'boolean',
|
|
248865
|
+
},
|
|
248378
248866
|
ignoreExternalDefaultImports: {
|
|
248379
248867
|
description: 'Skip default import checks for modules resolved from external libraries. Defaults to true.',
|
|
248380
248868
|
type: 'boolean',
|
|
@@ -250700,6 +251188,19 @@ function isNullableCallType(call, checker, nodeMap) {
|
|
|
250700
251188
|
return false;
|
|
250701
251189
|
}
|
|
250702
251190
|
}
|
|
251191
|
+
function getTargetNode(call) {
|
|
251192
|
+
const { parent } = call;
|
|
251193
|
+
if (parent.type === dist$3.AST_NODE_TYPES.TSAsExpression) {
|
|
251194
|
+
return parent;
|
|
251195
|
+
}
|
|
251196
|
+
if (parent.type === dist$3.AST_NODE_TYPES.UnaryExpression &&
|
|
251197
|
+
parent.operator === '!' &&
|
|
251198
|
+
parent.parent.type === dist$3.AST_NODE_TYPES.UnaryExpression &&
|
|
251199
|
+
parent.parent.operator === '!') {
|
|
251200
|
+
return parent.parent;
|
|
251201
|
+
}
|
|
251202
|
+
return call;
|
|
251203
|
+
}
|
|
250703
251204
|
function getCalleeName(node) {
|
|
250704
251205
|
const { callee } = node;
|
|
250705
251206
|
if (callee.type === dist$3.AST_NODE_TYPES.MemberExpression &&
|
|
@@ -250795,11 +251296,7 @@ const rule$r = createRule({
|
|
|
250795
251296
|
fixer.insertTextBefore(parentStatement, `const ${varName} = ${callText};\n\n${indent}`),
|
|
250796
251297
|
];
|
|
250797
251298
|
for (const call of calls) {
|
|
250798
|
-
|
|
250799
|
-
const target = parent.type === dist$3.AST_NODE_TYPES.TSAsExpression
|
|
250800
|
-
? parent
|
|
250801
|
-
: call;
|
|
250802
|
-
fixes.push(fixer.replaceText(target, varName));
|
|
251299
|
+
fixes.push(fixer.replaceText(getTargetNode(call), varName));
|
|
250803
251300
|
}
|
|
250804
251301
|
return fixes;
|
|
250805
251302
|
}
|
|
@@ -250812,12 +251309,7 @@ const rule$r = createRule({
|
|
|
250812
251309
|
const bodyText = sourceCode.getText(arrowBody);
|
|
250813
251310
|
const bodyStart = arrowBody.range[0];
|
|
250814
251311
|
const targets = calls
|
|
250815
|
-
.map(
|
|
250816
|
-
const { parent } = call;
|
|
250817
|
-
return parent.type === dist$3.AST_NODE_TYPES.TSAsExpression
|
|
250818
|
-
? parent
|
|
250819
|
-
: call;
|
|
250820
|
-
})
|
|
251312
|
+
.map(getTargetNode)
|
|
250821
251313
|
.sort((a, b) => a.range[0] - b.range[0]);
|
|
250822
251314
|
let replacedBody = '';
|
|
250823
251315
|
let lastIndex = 0;
|
|
@@ -250829,7 +251321,13 @@ const rule$r = createRule({
|
|
|
250829
251321
|
}
|
|
250830
251322
|
replacedBody += bodyText.slice(lastIndex);
|
|
250831
251323
|
const newBody = `{\n${innerIndent}const ${varName} = ${callText};\n\n${innerIndent}return ${replacedBody};\n${outerIndent}}`;
|
|
250832
|
-
|
|
251324
|
+
const bodyRangeStart = arrowBody.range[0];
|
|
251325
|
+
const textBeforeBody = sourceCode.text.slice(0, bodyRangeStart);
|
|
251326
|
+
const whitespaceBeforeBody = /\s*$/.exec(textBeforeBody)?.[0] ?? '';
|
|
251327
|
+
const replaceFrom = bodyRangeStart - whitespaceBeforeBody.length;
|
|
251328
|
+
return [
|
|
251329
|
+
fixer.replaceTextRange([replaceFrom, arrowBody.range[1]], ` ${newBody}`),
|
|
251330
|
+
];
|
|
250833
251331
|
},
|
|
250834
251332
|
messageId: 'noRepeatedSignalInConditional',
|
|
250835
251333
|
node: firstCall,
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
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';
|
|
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;
|
|
9
12
|
ignoreExternalDefaultImports?: boolean;
|
|
10
13
|
}?
|
|
11
14
|
];
|