@taiga-ui/eslint-plugin-experience-next 0.499.0 → 0.500.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.esm.js +146 -11
- package/package.json +2 -2
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 default import, namespace export, named-as-default, and import cycle 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.esm.js
CHANGED
|
@@ -247671,6 +247671,15 @@ const moduleExportNamesCache = new WeakMap();
|
|
|
247671
247671
|
const resolutionStateByProgram = new WeakMap();
|
|
247672
247672
|
const sourceFileCacheByProgram = new WeakMap();
|
|
247673
247673
|
const codeFileExtensionRegExp = /\.[cm]?[jt]sx?$/;
|
|
247674
|
+
// Angular DI functions resolve tokens at instantiation time, not at module load time,
|
|
247675
|
+
// so cycles where all usages are DI-only are safe and should not be reported.
|
|
247676
|
+
const ANGULAR_DI_FIRST_ARG_FUNCTIONS = new Set([
|
|
247677
|
+
'contentChild',
|
|
247678
|
+
'contentChildren',
|
|
247679
|
+
'inject',
|
|
247680
|
+
'viewChild',
|
|
247681
|
+
'viewChildren',
|
|
247682
|
+
]);
|
|
247674
247683
|
function createCanonicalFileName() {
|
|
247675
247684
|
const useCaseSensitiveFileNames = ts.sys.useCaseSensitiveFileNames;
|
|
247676
247685
|
return (fileName) => {
|
|
@@ -248094,6 +248103,43 @@ function getExportSpecifierName(name) {
|
|
|
248094
248103
|
function hasNamedValueExport(exportNames, exportedName) {
|
|
248095
248104
|
return exportedName !== 'default' && (exportNames?.has(exportedName) ?? false);
|
|
248096
248105
|
}
|
|
248106
|
+
function isImportUsedOnlyAsAngularDiFirstArg(node, sourceCode) {
|
|
248107
|
+
const valueSpecifiers = node.specifiers.filter((specifier) => {
|
|
248108
|
+
if (specifier.type === dist$3.AST_NODE_TYPES.ImportDefaultSpecifier ||
|
|
248109
|
+
specifier.type === dist$3.AST_NODE_TYPES.ImportNamespaceSpecifier) {
|
|
248110
|
+
return true;
|
|
248111
|
+
}
|
|
248112
|
+
return specifier.importKind !== 'type';
|
|
248113
|
+
});
|
|
248114
|
+
if (valueSpecifiers.length === 0) {
|
|
248115
|
+
return false;
|
|
248116
|
+
}
|
|
248117
|
+
for (const specifier of valueSpecifiers) {
|
|
248118
|
+
const [variable] = sourceCode.getDeclaredVariables(specifier);
|
|
248119
|
+
if (!variable || variable.references.length === 0) {
|
|
248120
|
+
return false;
|
|
248121
|
+
}
|
|
248122
|
+
for (const ref of variable.references) {
|
|
248123
|
+
const { identifier } = ref;
|
|
248124
|
+
const parent = identifier.parent;
|
|
248125
|
+
const callee = parent.type === dist$3.AST_NODE_TYPES.CallExpression ? parent.callee : null;
|
|
248126
|
+
const isDirectCall = callee?.type === dist$3.AST_NODE_TYPES.Identifier &&
|
|
248127
|
+
ANGULAR_DI_FIRST_ARG_FUNCTIONS.has(callee.name);
|
|
248128
|
+
const isRequiredCall = callee?.type === dist$3.AST_NODE_TYPES.MemberExpression &&
|
|
248129
|
+
!callee.computed &&
|
|
248130
|
+
callee.object.type === dist$3.AST_NODE_TYPES.Identifier &&
|
|
248131
|
+
ANGULAR_DI_FIRST_ARG_FUNCTIONS.has(callee.object.name) &&
|
|
248132
|
+
callee.property.type === dist$3.AST_NODE_TYPES.Identifier &&
|
|
248133
|
+
callee.property.name === 'required';
|
|
248134
|
+
if (parent.type !== dist$3.AST_NODE_TYPES.CallExpression ||
|
|
248135
|
+
(!isDirectCall && !isRequiredCall) ||
|
|
248136
|
+
parent.arguments[0] !== identifier) {
|
|
248137
|
+
return false;
|
|
248138
|
+
}
|
|
248139
|
+
}
|
|
248140
|
+
}
|
|
248141
|
+
return true;
|
|
248142
|
+
}
|
|
248097
248143
|
const rule$K = createRule({
|
|
248098
248144
|
create(context) {
|
|
248099
248145
|
const { checker, esTreeNodeToTSNodeMap, sourceCode, tsProgram } = getTypeAwareRuleContext(context);
|
|
@@ -248119,7 +248165,9 @@ const rule$K = createRule({
|
|
|
248119
248165
|
}
|
|
248120
248166
|
const graph = getImportGraph(tsProgram);
|
|
248121
248167
|
const targetFileName = findCyclicTargetFileName(graph, currentFileName, moduleSpecifier);
|
|
248122
|
-
if (!targetFileName
|
|
248168
|
+
if (!targetFileName ||
|
|
248169
|
+
(node.type === dist$3.AST_NODE_TYPES.ImportDeclaration &&
|
|
248170
|
+
isImportUsedOnlyAsAngularDiFirstArg(node, sourceCode))) {
|
|
248123
248171
|
return;
|
|
248124
248172
|
}
|
|
248125
248173
|
context.report({
|
|
@@ -248222,6 +248270,42 @@ const rule$K = createRule({
|
|
|
248222
248270
|
variable,
|
|
248223
248271
|
});
|
|
248224
248272
|
}
|
|
248273
|
+
function buildNamespaceToNamedFix(fixer, usage) {
|
|
248274
|
+
const importDecl = usage.node.parent;
|
|
248275
|
+
if (importDecl.specifiers.length !== 1) {
|
|
248276
|
+
return null;
|
|
248277
|
+
}
|
|
248278
|
+
const memberNames = new Set();
|
|
248279
|
+
const memberNodes = [];
|
|
248280
|
+
for (const ref of usage.variable.references) {
|
|
248281
|
+
const parent = ref.identifier.parent;
|
|
248282
|
+
if (parent.type !== dist$3.AST_NODE_TYPES.MemberExpression ||
|
|
248283
|
+
parent.object !== ref.identifier ||
|
|
248284
|
+
parent.computed) {
|
|
248285
|
+
return null;
|
|
248286
|
+
}
|
|
248287
|
+
const memberName = getMemberExpressionPropertyName(parent);
|
|
248288
|
+
if (!memberName || memberName === 'default') {
|
|
248289
|
+
return null;
|
|
248290
|
+
}
|
|
248291
|
+
memberNames.add(memberName);
|
|
248292
|
+
memberNodes.push(parent);
|
|
248293
|
+
}
|
|
248294
|
+
if (memberNames.size === 0) {
|
|
248295
|
+
return null;
|
|
248296
|
+
}
|
|
248297
|
+
const sourceText = sourceCode.getText(importDecl.source);
|
|
248298
|
+
const hasSemi = sourceCode.getText(importDecl).endsWith(';');
|
|
248299
|
+
const sortedMembers = [...memberNames].sort();
|
|
248300
|
+
const newImportText = `import {${sortedMembers.join(', ')}} from ${sourceText}${hasSemi ? ';' : ''}`;
|
|
248301
|
+
return [
|
|
248302
|
+
fixer.replaceText(importDecl, newImportText),
|
|
248303
|
+
...memberNodes.flatMap((memberNode) => {
|
|
248304
|
+
const name = getMemberExpressionPropertyName(memberNode);
|
|
248305
|
+
return name ? [fixer.replaceText(memberNode, name)] : [];
|
|
248306
|
+
}),
|
|
248307
|
+
];
|
|
248308
|
+
}
|
|
248225
248309
|
function checkNamespaceMember(node) {
|
|
248226
248310
|
if (!checkNamespaceMembers ||
|
|
248227
248311
|
node.object.type !== dist$3.AST_NODE_TYPES.Identifier) {
|
|
@@ -248241,6 +248325,7 @@ const rule$K = createRule({
|
|
|
248241
248325
|
moduleSpecifier: usage.moduleSpecifier,
|
|
248242
248326
|
namespaceName: usage.node.local.name,
|
|
248243
248327
|
},
|
|
248328
|
+
fix: (fixer) => buildNamespaceToNamedFix(fixer, usage),
|
|
248244
248329
|
messageId: 'unknownNamespaceMember',
|
|
248245
248330
|
node: node.property,
|
|
248246
248331
|
});
|
|
@@ -248263,6 +248348,7 @@ const rule$K = createRule({
|
|
|
248263
248348
|
docs: {
|
|
248264
248349
|
description: 'Fast replacement for import/default, import/namespace, import/no-cycle, and import/no-named-as-default checks',
|
|
248265
248350
|
},
|
|
248351
|
+
fixable: 'code',
|
|
248266
248352
|
messages: {
|
|
248267
248353
|
importCycle: 'Import cycle detected: {{cyclePath}}.',
|
|
248268
248354
|
missingDefaultExport: 'No default export found in "{{moduleSpecifier}}".',
|
|
@@ -250629,6 +250715,9 @@ function getCalleeName(node) {
|
|
|
250629
250715
|
}
|
|
250630
250716
|
function findParentStatement(node) {
|
|
250631
250717
|
for (let current = node; current.parent; current = current.parent) {
|
|
250718
|
+
if (isFunctionLike(current)) {
|
|
250719
|
+
return null;
|
|
250720
|
+
}
|
|
250632
250721
|
if (current.parent.type === dist$3.AST_NODE_TYPES.BlockStatement ||
|
|
250633
250722
|
current.parent.type === dist$3.AST_NODE_TYPES.Program) {
|
|
250634
250723
|
return current;
|
|
@@ -250636,6 +250725,26 @@ function findParentStatement(node) {
|
|
|
250636
250725
|
}
|
|
250637
250726
|
return null;
|
|
250638
250727
|
}
|
|
250728
|
+
function findConciseArrowAncestor(node) {
|
|
250729
|
+
for (let current = node; current.parent; current = current.parent) {
|
|
250730
|
+
const { parent } = current;
|
|
250731
|
+
if (parent.type === dist$3.AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
250732
|
+
parent.body.type !== dist$3.AST_NODE_TYPES.BlockStatement) {
|
|
250733
|
+
return parent;
|
|
250734
|
+
}
|
|
250735
|
+
if (isFunctionLike(parent)) {
|
|
250736
|
+
return null;
|
|
250737
|
+
}
|
|
250738
|
+
}
|
|
250739
|
+
return null;
|
|
250740
|
+
}
|
|
250741
|
+
function getArrowBodyIndent(arrowFn, sourceText) {
|
|
250742
|
+
const arrowStart = arrowFn.range[0];
|
|
250743
|
+
const lineStart = sourceText.lastIndexOf('\n', arrowStart - 1) + 1;
|
|
250744
|
+
const textBeforeArrow = sourceText.slice(lineStart, arrowStart);
|
|
250745
|
+
const outerIndent = /^(\s*)/.exec(textBeforeArrow)?.[1] ?? '';
|
|
250746
|
+
return { innerIndent: `${outerIndent} `, outerIndent };
|
|
250747
|
+
}
|
|
250639
250748
|
function getStatementIndent(statement, sourceText) {
|
|
250640
250749
|
const start = statement.range[0];
|
|
250641
250750
|
const lineStart = sourceText.lastIndexOf('\n', start - 1) + 1;
|
|
@@ -250678,23 +250787,49 @@ const rule$r = createRule({
|
|
|
250678
250787
|
context.report({
|
|
250679
250788
|
data: { call: callText },
|
|
250680
250789
|
fix(fixer) {
|
|
250790
|
+
const varName = getCalleeName(firstCall);
|
|
250681
250791
|
const parentStatement = findParentStatement(node);
|
|
250682
|
-
if (
|
|
250792
|
+
if (parentStatement) {
|
|
250793
|
+
const indent = getStatementIndent(parentStatement, sourceCode.text);
|
|
250794
|
+
const fixes = [
|
|
250795
|
+
fixer.insertTextBefore(parentStatement, `const ${varName} = ${callText};\n\n${indent}`),
|
|
250796
|
+
];
|
|
250797
|
+
for (const call of calls) {
|
|
250798
|
+
const { parent } = call;
|
|
250799
|
+
const target = parent.type === dist$3.AST_NODE_TYPES.TSAsExpression
|
|
250800
|
+
? parent
|
|
250801
|
+
: call;
|
|
250802
|
+
fixes.push(fixer.replaceText(target, varName));
|
|
250803
|
+
}
|
|
250804
|
+
return fixes;
|
|
250805
|
+
}
|
|
250806
|
+
const arrowFn = findConciseArrowAncestor(node);
|
|
250807
|
+
if (!arrowFn) {
|
|
250683
250808
|
return null;
|
|
250684
250809
|
}
|
|
250685
|
-
const
|
|
250686
|
-
const
|
|
250687
|
-
const
|
|
250688
|
-
|
|
250689
|
-
|
|
250690
|
-
|
|
250810
|
+
const arrowBody = arrowFn.body;
|
|
250811
|
+
const { innerIndent, outerIndent } = getArrowBodyIndent(arrowFn, sourceCode.text);
|
|
250812
|
+
const bodyText = sourceCode.getText(arrowBody);
|
|
250813
|
+
const bodyStart = arrowBody.range[0];
|
|
250814
|
+
const targets = calls
|
|
250815
|
+
.map((call) => {
|
|
250691
250816
|
const { parent } = call;
|
|
250692
|
-
|
|
250817
|
+
return parent.type === dist$3.AST_NODE_TYPES.TSAsExpression
|
|
250693
250818
|
? parent
|
|
250694
250819
|
: call;
|
|
250695
|
-
|
|
250820
|
+
})
|
|
250821
|
+
.sort((a, b) => a.range[0] - b.range[0]);
|
|
250822
|
+
let replacedBody = '';
|
|
250823
|
+
let lastIndex = 0;
|
|
250824
|
+
for (const target of targets) {
|
|
250825
|
+
const start = target.range[0] - bodyStart;
|
|
250826
|
+
const end = target.range[1] - bodyStart;
|
|
250827
|
+
replacedBody += `${bodyText.slice(lastIndex, start)}${varName}`;
|
|
250828
|
+
lastIndex = end;
|
|
250696
250829
|
}
|
|
250697
|
-
|
|
250830
|
+
replacedBody += bodyText.slice(lastIndex);
|
|
250831
|
+
const newBody = `{\n${innerIndent}const ${varName} = ${callText};\n\n${innerIndent}return ${replacedBody};\n${outerIndent}}`;
|
|
250832
|
+
return [fixer.replaceText(arrowBody, newBody)];
|
|
250698
250833
|
},
|
|
250699
250834
|
messageId: 'noRepeatedSignalInConditional',
|
|
250700
250835
|
node: firstCall,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taiga-ui/eslint-plugin-experience-next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.500.0",
|
|
4
4
|
"description": "An ESLint plugin to enforce a consistent code styles across taiga-ui projects",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"eslint-plugin-jest": "^29.15.2",
|
|
47
47
|
"eslint-plugin-package-json": "^0.91.1",
|
|
48
48
|
"eslint-plugin-perfectionist": "^5.9.0",
|
|
49
|
-
"eslint-plugin-playwright": "^2.10.
|
|
49
|
+
"eslint-plugin-playwright": "^2.10.2",
|
|
50
50
|
"eslint-plugin-prettier": "^5.5.5",
|
|
51
51
|
"eslint-plugin-promise": "^7.2.1",
|
|
52
52
|
"eslint-plugin-regexp": "^3.1.0",
|