@taiga-ui/eslint-plugin-experience-next 0.473.0 → 0.475.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 +52 -4
- package/index.d.ts +3 -0
- package/index.esm.js +724 -563
- package/package.json +1 -1
- package/rules/no-fully-untracked-effect.d.ts +1 -2
- package/rules/no-infinite-loop.d.ts +6 -0
- package/rules/no-signal-reads-after-await-in-reactive-context.d.ts +1 -2
- package/rules/no-untracked-outside-reactive-context.d.ts +1 -2
- package/rules/no-useless-untracked.d.ts +1 -2
- package/rules/prefer-untracked-incidental-signal-reads.d.ts +1 -27
- package/rules/prefer-untracked-signal-getter.d.ts +1 -2
- package/rules/utils/{angular-signals.d.ts → angular/angular-signals.d.ts} +10 -5
- package/rules/utils/angular/pipes.d.ts +2 -0
- package/rules/utils/angular/providers.d.ts +3 -0
- package/rules/utils/ast/ancestors.d.ts +12 -0
- package/rules/utils/{ast-walk.d.ts → ast/ast-walk.d.ts} +1 -0
- package/rules/utils/ast/call-expressions.d.ts +2 -0
- package/rules/utils/ast/mutation-targets.d.ts +4 -0
- package/rules/utils/ast/parenthesized.d.ts +3 -0
- package/rules/utils/ast/property-names.d.ts +5 -0
- package/rules/utils/ast/returned-expression.d.ts +2 -0
- package/rules/utils/ast/string-literals.d.ts +10 -0
- package/rules/utils/text/dedent.d.ts +5 -0
- package/rules/utils/typescript/decorators.d.ts +2 -0
- package/rules/utils/typescript/function-usage.d.ts +5 -0
- package/rules/utils/typescript/node-map.d.ts +6 -0
- package/rules/utils/typescript/symbols.d.ts +4 -0
- package/rules/utils/typescript/type-aware-context.d.ts +14 -0
- /package/rules/utils/{angular-imports.d.ts → angular/angular-imports.d.ts} +0 -0
- /package/rules/utils/{get-decorator-metadata.d.ts → angular/get-decorator-metadata.d.ts} +0 -0
- /package/rules/utils/{get-imports-array.d.ts → angular/get-imports-array.d.ts} +0 -0
- /package/rules/utils/{import-fix-helpers.d.ts → angular/import-fix-helpers.d.ts} +0 -0
- /package/rules/utils/{is-imports-array-property.d.ts → angular/is-imports-array-property.d.ts} +0 -0
- /package/rules/utils/{untracked-docs.d.ts → angular/untracked-docs.d.ts} +0 -0
- /package/rules/utils/{ast-expressions.d.ts → ast/ast-expressions.d.ts} +0 -0
- /package/rules/utils/{get-const-array.d.ts → ast/get-const-array.d.ts} +0 -0
- /package/rules/utils/{is-array.d.ts → ast/is-array.d.ts} +0 -0
- /package/rules/utils/{is-object.d.ts → ast/is-object.d.ts} +0 -0
- /package/rules/utils/{is-spread.d.ts → ast/is-spread.d.ts} +0 -0
- /package/rules/utils/{name-of.d.ts → ast/name-of.d.ts} +0 -0
- /package/rules/utils/{intersect.d.ts → collections/intersect.d.ts} +0 -0
- /package/rules/utils/{same-order.d.ts → collections/same-order.d.ts} +0 -0
- /package/rules/utils/{get-imported-name.d.ts → imports/get-imported-name.d.ts} +0 -0
- /package/rules/utils/{npmrc-parser.d.ts → parsers/npmrc-parser.d.ts} +0 -0
- /package/rules/utils/{get-sorted-names.d.ts → sorting/get-sorted-names.d.ts} +0 -0
- /package/rules/utils/{get-field-types.d.ts → typescript/get-field-types.d.ts} +0 -0
- /package/rules/utils/{get-type-name.d.ts → typescript/get-type-name.d.ts} +0 -0
- /package/rules/utils/{is-class-type.d.ts → typescript/is-class-type.d.ts} +0 -0
- /package/rules/utils/{is-external-tuple.d.ts → typescript/is-external-tuple.d.ts} +0 -0
package/index.esm.js
CHANGED
|
@@ -908,6 +908,7 @@ var recommended = defineConfig([
|
|
|
908
908
|
'@taiga-ui/experience-next/no-deep-imports-to-indexed-packages': 'error',
|
|
909
909
|
'@taiga-ui/experience-next/no-fully-untracked-effect': 'error',
|
|
910
910
|
'@taiga-ui/experience-next/no-implicit-public': 'error',
|
|
911
|
+
'@taiga-ui/experience-next/no-infinite-loop': 'error',
|
|
911
912
|
'@taiga-ui/experience-next/no-redundant-type-annotation': 'error',
|
|
912
913
|
'@taiga-ui/experience-next/no-side-effects-in-computed': 'error',
|
|
913
914
|
'@taiga-ui/experience-next/no-signal-reads-after-await-in-reactive-context': 'error',
|
|
@@ -1299,6 +1300,11 @@ const config$4 = {
|
|
|
1299
1300
|
},
|
|
1300
1301
|
};
|
|
1301
1302
|
|
|
1303
|
+
function intersect(a, b) {
|
|
1304
|
+
const origin = new Set(b);
|
|
1305
|
+
return a.some((type) => origin.has(type));
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1302
1308
|
function getFieldTypes(type, checker) {
|
|
1303
1309
|
const typeNames = [];
|
|
1304
1310
|
if (type.isUnionOrIntersection()) {
|
|
@@ -1331,16 +1337,24 @@ function getFieldTypes(type, checker) {
|
|
|
1331
1337
|
return typeNames;
|
|
1332
1338
|
}
|
|
1333
1339
|
|
|
1334
|
-
function
|
|
1335
|
-
const
|
|
1336
|
-
|
|
1340
|
+
function getTypeAwareRuleContext(context) {
|
|
1341
|
+
const parserServices = ESLintUtils.getParserServices(context);
|
|
1342
|
+
const { sourceCode } = context;
|
|
1343
|
+
return {
|
|
1344
|
+
checker: parserServices.program.getTypeChecker(),
|
|
1345
|
+
esTreeNodeToTSNodeMap: parserServices.esTreeNodeToTSNodeMap,
|
|
1346
|
+
parserServices,
|
|
1347
|
+
program: sourceCode.ast,
|
|
1348
|
+
sourceCode,
|
|
1349
|
+
tsNodeToESTreeNodeMap: parserServices.tsNodeToESTreeNodeMap,
|
|
1350
|
+
tsProgram: parserServices.program,
|
|
1351
|
+
};
|
|
1337
1352
|
}
|
|
1338
1353
|
|
|
1339
|
-
const createRule$
|
|
1340
|
-
var classPropertyNaming = createRule$
|
|
1354
|
+
const createRule$j = ESLintUtils.RuleCreator((name) => name);
|
|
1355
|
+
var classPropertyNaming = createRule$j({
|
|
1341
1356
|
create(context, [configs]) {
|
|
1342
|
-
const
|
|
1343
|
-
const typeChecker = parserServices.program.getTypeChecker();
|
|
1357
|
+
const { checker: typeChecker, esTreeNodeToTSNodeMap } = getTypeAwareRuleContext(context);
|
|
1344
1358
|
const flatConfig = configs.flat();
|
|
1345
1359
|
return {
|
|
1346
1360
|
PropertyDefinition(node) {
|
|
@@ -1348,7 +1362,7 @@ var classPropertyNaming = createRule$i({
|
|
|
1348
1362
|
if (!fieldName) {
|
|
1349
1363
|
return;
|
|
1350
1364
|
}
|
|
1351
|
-
const tsNode =
|
|
1365
|
+
const tsNode = esTreeNodeToTSNodeMap.get(node);
|
|
1352
1366
|
const nodeType = typeChecker.getTypeAtLocation(tsNode);
|
|
1353
1367
|
const fieldTypes = getFieldTypes(nodeType, typeChecker);
|
|
1354
1368
|
const rule = flatConfig.find((rule) => intersect(fieldTypes, rule.withTypesSpecifier) &&
|
|
@@ -1404,6 +1418,10 @@ var classPropertyNaming = createRule$i({
|
|
|
1404
1418
|
name: 'class-property-naming',
|
|
1405
1419
|
});
|
|
1406
1420
|
|
|
1421
|
+
function sameOrder(a, b) {
|
|
1422
|
+
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1407
1425
|
const config$3 = {
|
|
1408
1426
|
create(context) {
|
|
1409
1427
|
const order = context.options[0] || {};
|
|
@@ -1422,7 +1440,7 @@ const config$3 = {
|
|
|
1422
1440
|
.map((prop) => prop.key?.name)
|
|
1423
1441
|
.filter(Boolean);
|
|
1424
1442
|
const correct = getCorrectOrderRelative(orderList, current);
|
|
1425
|
-
if (!
|
|
1443
|
+
if (!sameOrder(correct, current.filter((item) => correct.includes(item)))) {
|
|
1426
1444
|
context.report({
|
|
1427
1445
|
fix: (fixer) => {
|
|
1428
1446
|
const fileContent = context.sourceCode.text;
|
|
@@ -1458,10 +1476,6 @@ const config$3 = {
|
|
|
1458
1476
|
type: 'problem',
|
|
1459
1477
|
},
|
|
1460
1478
|
};
|
|
1461
|
-
function isCorrectSortedAccording(correct, current) {
|
|
1462
|
-
return (JSON.stringify(correct) ===
|
|
1463
|
-
JSON.stringify(current.filter((item) => correct.includes(item))));
|
|
1464
|
-
}
|
|
1465
1479
|
function getCorrectOrderRelative(correct, current) {
|
|
1466
1480
|
return correct.filter((item) => current.includes(item));
|
|
1467
1481
|
}
|
|
@@ -1506,12 +1520,11 @@ function isExternalPureTuple(typeChecker, type) {
|
|
|
1506
1520
|
return typeArgs.every((item) => isClassType(item));
|
|
1507
1521
|
}
|
|
1508
1522
|
|
|
1509
|
-
const createRule$
|
|
1523
|
+
const createRule$i = ESLintUtils.RuleCreator((name) => name);
|
|
1510
1524
|
const MESSAGE_ID$7 = 'spreadArrays';
|
|
1511
|
-
var flatExports = createRule$
|
|
1525
|
+
var flatExports = createRule$i({
|
|
1512
1526
|
create(context) {
|
|
1513
|
-
const
|
|
1514
|
-
const typeChecker = parserServices.program.getTypeChecker();
|
|
1527
|
+
const { checker: typeChecker, esTreeNodeToTSNodeMap } = getTypeAwareRuleContext(context);
|
|
1515
1528
|
const arrays = new Map();
|
|
1516
1529
|
const purityCache = new WeakMap();
|
|
1517
1530
|
const isPureArray = (arr) => {
|
|
@@ -1595,7 +1608,7 @@ var flatExports = createRule$h({
|
|
|
1595
1608
|
isDirty = true;
|
|
1596
1609
|
continue;
|
|
1597
1610
|
}
|
|
1598
|
-
const tsNode =
|
|
1611
|
+
const tsNode = esTreeNodeToTSNodeMap.get(el);
|
|
1599
1612
|
const elType = typeChecker.getTypeAtLocation(tsNode);
|
|
1600
1613
|
const isClass = isClassType(elType);
|
|
1601
1614
|
const isArrayLike = typeChecker.isArrayLikeType(elType) ||
|
|
@@ -1675,8 +1688,53 @@ function getDecoratorMetadata(decorator, allowedNames) {
|
|
|
1675
1688
|
return isObject(arg) ? arg : null;
|
|
1676
1689
|
}
|
|
1677
1690
|
|
|
1678
|
-
function
|
|
1679
|
-
return
|
|
1691
|
+
function isStringLiteral(node) {
|
|
1692
|
+
return node?.type === AST_NODE_TYPES.Literal && typeof node.value === 'string';
|
|
1693
|
+
}
|
|
1694
|
+
function isStaticTemplateLiteral(node) {
|
|
1695
|
+
return (node?.type === AST_NODE_TYPES.TemplateLiteral &&
|
|
1696
|
+
node.expressions.length === 0 &&
|
|
1697
|
+
node.quasis.length === 1);
|
|
1698
|
+
}
|
|
1699
|
+
function getStaticStringValue(node) {
|
|
1700
|
+
if (isStringLiteral(node)) {
|
|
1701
|
+
return node.value;
|
|
1702
|
+
}
|
|
1703
|
+
if (!isStaticTemplateLiteral(node)) {
|
|
1704
|
+
return null;
|
|
1705
|
+
}
|
|
1706
|
+
return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw ?? '';
|
|
1707
|
+
}
|
|
1708
|
+
function isEmptyStaticString(node) {
|
|
1709
|
+
return getStaticStringValue(node) === '';
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
function getStaticPropertyName(key) {
|
|
1713
|
+
if (key.type === AST_NODE_TYPES.Identifier) {
|
|
1714
|
+
return key.name;
|
|
1715
|
+
}
|
|
1716
|
+
if (key.type === AST_NODE_TYPES.Literal &&
|
|
1717
|
+
(typeof key.value === 'string' || typeof key.value === 'number')) {
|
|
1718
|
+
return String(key.value);
|
|
1719
|
+
}
|
|
1720
|
+
return getStaticStringValue(key);
|
|
1721
|
+
}
|
|
1722
|
+
function getObjectPropertyName(node) {
|
|
1723
|
+
if (node.computed) {
|
|
1724
|
+
return null;
|
|
1725
|
+
}
|
|
1726
|
+
return getStaticPropertyName(node.key);
|
|
1727
|
+
}
|
|
1728
|
+
function getMemberExpressionPropertyName(node) {
|
|
1729
|
+
if (!node.computed && node.property.type === AST_NODE_TYPES.Identifier) {
|
|
1730
|
+
return node.property.name;
|
|
1731
|
+
}
|
|
1732
|
+
return node.computed ? getStaticStringValue(node.property) : null;
|
|
1733
|
+
}
|
|
1734
|
+
function getClassMemberName(member) {
|
|
1735
|
+
return member.key.type === AST_NODE_TYPES.PrivateIdentifier
|
|
1736
|
+
? null
|
|
1737
|
+
: getStaticPropertyName(member.key);
|
|
1680
1738
|
}
|
|
1681
1739
|
|
|
1682
1740
|
const DEFAULT_GROUP = '$DEFAULT';
|
|
@@ -1744,8 +1802,8 @@ const PRESETS = {
|
|
|
1744
1802
|
$VUE: ['$CLASS', '$ID', '$VUE_ATTRIBUTE'],
|
|
1745
1803
|
$VUE_ATTRIBUTE: /^v-/,
|
|
1746
1804
|
};
|
|
1747
|
-
const createRule$
|
|
1748
|
-
const rule$
|
|
1805
|
+
const createRule$h = ESLintUtils.RuleCreator((name) => name);
|
|
1806
|
+
const rule$k = createRule$h({
|
|
1749
1807
|
create(context, [options]) {
|
|
1750
1808
|
const sourceCode = context.sourceCode;
|
|
1751
1809
|
const settings = {
|
|
@@ -1867,21 +1925,6 @@ function getHostAttributeProperties(hostObject) {
|
|
|
1867
1925
|
}
|
|
1868
1926
|
return properties;
|
|
1869
1927
|
}
|
|
1870
|
-
function getStaticPropertyName(key) {
|
|
1871
|
-
if (key.type === AST_NODE_TYPES$1.Identifier) {
|
|
1872
|
-
return key.name;
|
|
1873
|
-
}
|
|
1874
|
-
if (key.type === AST_NODE_TYPES$1.Literal &&
|
|
1875
|
-
(typeof key.value === 'string' || typeof key.value === 'number')) {
|
|
1876
|
-
return String(key.value);
|
|
1877
|
-
}
|
|
1878
|
-
if (key.type === AST_NODE_TYPES$1.TemplateLiteral &&
|
|
1879
|
-
key.expressions.length === 0 &&
|
|
1880
|
-
key.quasis.length === 1) {
|
|
1881
|
-
return key.quasis[0]?.value.cooked ?? null;
|
|
1882
|
-
}
|
|
1883
|
-
return null;
|
|
1884
|
-
}
|
|
1885
1928
|
function organizeProperties(properties, options) {
|
|
1886
1929
|
const groups = getGroups(options.attributeGroups.length > 0 ? options.attributeGroups : ['$ANGULAR'], options.attributeIgnoreCase);
|
|
1887
1930
|
const defaultGroup = ensureDefaultGroup(groups);
|
|
@@ -2021,7 +2064,7 @@ const config$2 = {
|
|
|
2021
2064
|
const MESSAGE_ID$5 = 'invalid-injection-token-description';
|
|
2022
2065
|
const ERROR_MESSAGE$3 = "InjectionToken's description should contain token's name";
|
|
2023
2066
|
const NG_DEV_MODE = 'ngDevMode';
|
|
2024
|
-
const createRule$
|
|
2067
|
+
const createRule$g = ESLintUtils.RuleCreator((name) => name);
|
|
2025
2068
|
function getVariableName(node) {
|
|
2026
2069
|
if (node.parent.type !== AST_NODE_TYPES$1.VariableDeclarator) {
|
|
2027
2070
|
return undefined;
|
|
@@ -2029,21 +2072,18 @@ function getVariableName(node) {
|
|
|
2029
2072
|
const { id } = node.parent;
|
|
2030
2073
|
return id.type === AST_NODE_TYPES$1.Identifier ? id.name : undefined;
|
|
2031
2074
|
}
|
|
2032
|
-
function isStringLiteral$1(node) {
|
|
2033
|
-
return node.type === AST_NODE_TYPES$1.Literal && typeof node.value === 'string';
|
|
2034
|
-
}
|
|
2035
2075
|
function isStringLike(node) {
|
|
2036
|
-
return isStringLiteral
|
|
2076
|
+
return isStringLiteral(node) || node.type === AST_NODE_TYPES$1.TemplateLiteral;
|
|
2037
2077
|
}
|
|
2038
2078
|
function getStringValue(node) {
|
|
2039
|
-
if (isStringLiteral
|
|
2079
|
+
if (isStringLiteral(node)) {
|
|
2040
2080
|
return node.value;
|
|
2041
2081
|
}
|
|
2042
2082
|
return node.quasis[0]?.value.raw || '';
|
|
2043
2083
|
}
|
|
2044
|
-
function isEmptyString
|
|
2045
|
-
return (
|
|
2046
|
-
(!('expressions' in node) ||
|
|
2084
|
+
function isEmptyString(node) {
|
|
2085
|
+
return (isEmptyStaticString(node) &&
|
|
2086
|
+
(!('expressions' in node) || node.expressions.length === 0));
|
|
2047
2087
|
}
|
|
2048
2088
|
function isNgDevModeConditional(node) {
|
|
2049
2089
|
return (node.type === AST_NODE_TYPES$1.ConditionalExpression &&
|
|
@@ -2051,7 +2091,7 @@ function isNgDevModeConditional(node) {
|
|
|
2051
2091
|
node.test.name === NG_DEV_MODE &&
|
|
2052
2092
|
isStringLike(node.consequent) &&
|
|
2053
2093
|
isStringLike(node.alternate) &&
|
|
2054
|
-
isEmptyString
|
|
2094
|
+
isEmptyString(node.alternate));
|
|
2055
2095
|
}
|
|
2056
2096
|
function getDescriptionValue(node) {
|
|
2057
2097
|
if (isStringLike(node)) {
|
|
@@ -2092,7 +2132,7 @@ function getNgDevModeDeclarationFix(program, fixer) {
|
|
|
2092
2132
|
}
|
|
2093
2133
|
return fixer.insertTextBeforeRange([0, 0], 'declare const ngDevMode: boolean;\n');
|
|
2094
2134
|
}
|
|
2095
|
-
const rule$
|
|
2135
|
+
const rule$j = createRule$g({
|
|
2096
2136
|
create(context) {
|
|
2097
2137
|
const { sourceCode } = context;
|
|
2098
2138
|
const program = sourceCode.ast;
|
|
@@ -2160,8 +2200,8 @@ const DEFAULT_OPTIONS = {
|
|
|
2160
2200
|
importDeclaration: '^@taiga-ui*',
|
|
2161
2201
|
projectName: String.raw `(?<=^@taiga-ui/)([-\w]+)`,
|
|
2162
2202
|
};
|
|
2163
|
-
const createRule$
|
|
2164
|
-
const rule$
|
|
2203
|
+
const createRule$f = ESLintUtils.RuleCreator((name) => name);
|
|
2204
|
+
const rule$i = createRule$f({
|
|
2165
2205
|
create(context) {
|
|
2166
2206
|
const { currentProject, deepImport, ignoreImports, importDeclaration, projectName, } = { ...DEFAULT_OPTIONS, ...context.options[0] };
|
|
2167
2207
|
const hasNonCodeExtension = (source) => {
|
|
@@ -2248,13 +2288,13 @@ const rule$h = createRule$e({
|
|
|
2248
2288
|
name: 'no-deep-imports',
|
|
2249
2289
|
});
|
|
2250
2290
|
|
|
2251
|
-
const createRule$
|
|
2291
|
+
const createRule$e = ESLintUtils.RuleCreator((name) => name);
|
|
2252
2292
|
const resolveCacheByOptions = new WeakMap();
|
|
2253
2293
|
const nearestFileUpCache = new Map();
|
|
2254
2294
|
const markerCache = new Map();
|
|
2255
2295
|
const indexFileCache = new Map();
|
|
2256
2296
|
const indexExportsCache = new Map();
|
|
2257
|
-
var noDeepImportsToIndexedPackages = createRule$
|
|
2297
|
+
var noDeepImportsToIndexedPackages = createRule$e({
|
|
2258
2298
|
create(context) {
|
|
2259
2299
|
const parserServices = ESLintUtils.getParserServices(context);
|
|
2260
2300
|
const program = parserServices.program;
|
|
@@ -2443,63 +2483,13 @@ function stripKnownExtensions(filePathOrSpecifier) {
|
|
|
2443
2483
|
return filePathOrSpecifier.replace(/\.(?:d\.ts|ts|tsx|js|jsx|mjs|cjs)$/, '');
|
|
2444
2484
|
}
|
|
2445
2485
|
|
|
2446
|
-
|
|
2447
|
-
/**
|
|
2448
|
-
* Returns the local name bound to a named import from a given source.
|
|
2449
|
-
* Handles aliased imports: `import { untracked as ngUntracked } from '@angular/core'`
|
|
2450
|
-
* returns `'ngUntracked'` for `exportedName = 'untracked'`.
|
|
2451
|
-
*/
|
|
2452
|
-
function getLocalNameForImport(program, source, exportedName) {
|
|
2453
|
-
for (const node of program.body) {
|
|
2454
|
-
if (node.type !== AST_NODE_TYPES.ImportDeclaration ||
|
|
2455
|
-
node.source.value !== source) {
|
|
2456
|
-
continue;
|
|
2457
|
-
}
|
|
2458
|
-
for (const spec of node.specifiers) {
|
|
2459
|
-
if (spec.type !== AST_NODE_TYPES.ImportSpecifier) {
|
|
2460
|
-
continue;
|
|
2461
|
-
}
|
|
2462
|
-
const imported = spec.imported.type === AST_NODE_TYPES.Identifier
|
|
2463
|
-
? spec.imported.name
|
|
2464
|
-
: spec.imported.value;
|
|
2465
|
-
if (imported === exportedName) {
|
|
2466
|
-
return spec.local.name;
|
|
2467
|
-
}
|
|
2468
|
-
}
|
|
2469
|
-
}
|
|
2470
|
-
return null;
|
|
2471
|
-
}
|
|
2472
|
-
function findAngularCoreImports(program) {
|
|
2473
|
-
return program.body.filter((node) => node.type === AST_NODE_TYPES.ImportDeclaration &&
|
|
2474
|
-
node.source.value === ANGULAR_CORE$1);
|
|
2475
|
-
}
|
|
2476
|
-
function findRuntimeAngularCoreImport(program) {
|
|
2477
|
-
return (findAngularCoreImports(program).find((node) => node.importKind !== 'type') ?? null);
|
|
2478
|
-
}
|
|
2479
|
-
function findAngularCoreImportSpecifier(program, exportedName) {
|
|
2480
|
-
for (const importDecl of findAngularCoreImports(program)) {
|
|
2481
|
-
for (const specifier of importDecl.specifiers) {
|
|
2482
|
-
if (specifier.type !== AST_NODE_TYPES.ImportSpecifier) {
|
|
2483
|
-
continue;
|
|
2484
|
-
}
|
|
2485
|
-
const imported = specifier.imported.type === AST_NODE_TYPES.Identifier
|
|
2486
|
-
? specifier.imported.name
|
|
2487
|
-
: specifier.imported.value;
|
|
2488
|
-
if (imported === exportedName) {
|
|
2489
|
-
return { importDecl, specifier };
|
|
2490
|
-
}
|
|
2491
|
-
}
|
|
2492
|
-
}
|
|
2493
|
-
return null;
|
|
2494
|
-
}
|
|
2495
|
-
|
|
2496
|
-
function isFunctionLike$1(node) {
|
|
2486
|
+
function isFunctionLike(node) {
|
|
2497
2487
|
return (node.type === AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
2498
2488
|
node.type === AST_NODE_TYPES.FunctionDeclaration ||
|
|
2499
2489
|
node.type === AST_NODE_TYPES.FunctionExpression);
|
|
2500
2490
|
}
|
|
2501
2491
|
function getOrderedChildren(node) {
|
|
2502
|
-
if (isFunctionLike
|
|
2492
|
+
if (isFunctionLike(node)) {
|
|
2503
2493
|
const children = [
|
|
2504
2494
|
...node.params,
|
|
2505
2495
|
node.body,
|
|
@@ -2568,7 +2558,7 @@ function getOrderedChildren(node) {
|
|
|
2568
2558
|
function walkSynchronousAst(root, visitor) {
|
|
2569
2559
|
traverse(root, true);
|
|
2570
2560
|
function traverse(node, isRoot = false) {
|
|
2571
|
-
if (visitor(node) === false || (!isRoot && isFunctionLike
|
|
2561
|
+
if (visitor(node) === false || (!isRoot && isFunctionLike(node))) {
|
|
2572
2562
|
return false;
|
|
2573
2563
|
}
|
|
2574
2564
|
if (node.type === AST_NODE_TYPES.AwaitExpression) {
|
|
@@ -2597,7 +2587,7 @@ function walkAfterAsyncBoundaryAst(root, visitor) {
|
|
|
2597
2587
|
if (afterBoundary) {
|
|
2598
2588
|
visitor(node);
|
|
2599
2589
|
}
|
|
2600
|
-
if (!isRoot && isFunctionLike
|
|
2590
|
+
if (!isRoot && isFunctionLike(node)) {
|
|
2601
2591
|
return false;
|
|
2602
2592
|
}
|
|
2603
2593
|
if (node.type === AST_NODE_TYPES.AwaitExpression) {
|
|
@@ -2660,6 +2650,56 @@ function walkAst(root, visitor) {
|
|
|
2660
2650
|
}
|
|
2661
2651
|
}
|
|
2662
2652
|
|
|
2653
|
+
const ANGULAR_CORE$1 = '@angular/core';
|
|
2654
|
+
/**
|
|
2655
|
+
* Returns the local name bound to a named import from a given source.
|
|
2656
|
+
* Handles aliased imports: `import { untracked as ngUntracked } from '@angular/core'`
|
|
2657
|
+
* returns `'ngUntracked'` for `exportedName = 'untracked'`.
|
|
2658
|
+
*/
|
|
2659
|
+
function getLocalNameForImport(program, source, exportedName) {
|
|
2660
|
+
for (const node of program.body) {
|
|
2661
|
+
if (node.type !== AST_NODE_TYPES.ImportDeclaration ||
|
|
2662
|
+
node.source.value !== source) {
|
|
2663
|
+
continue;
|
|
2664
|
+
}
|
|
2665
|
+
for (const spec of node.specifiers) {
|
|
2666
|
+
if (spec.type !== AST_NODE_TYPES.ImportSpecifier) {
|
|
2667
|
+
continue;
|
|
2668
|
+
}
|
|
2669
|
+
const imported = spec.imported.type === AST_NODE_TYPES.Identifier
|
|
2670
|
+
? spec.imported.name
|
|
2671
|
+
: spec.imported.value;
|
|
2672
|
+
if (imported === exportedName) {
|
|
2673
|
+
return spec.local.name;
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
return null;
|
|
2678
|
+
}
|
|
2679
|
+
function findAngularCoreImports(program) {
|
|
2680
|
+
return program.body.filter((node) => node.type === AST_NODE_TYPES.ImportDeclaration &&
|
|
2681
|
+
node.source.value === ANGULAR_CORE$1);
|
|
2682
|
+
}
|
|
2683
|
+
function findRuntimeAngularCoreImport(program) {
|
|
2684
|
+
return (findAngularCoreImports(program).find((node) => node.importKind !== 'type') ?? null);
|
|
2685
|
+
}
|
|
2686
|
+
function findAngularCoreImportSpecifier(program, exportedName) {
|
|
2687
|
+
for (const importDecl of findAngularCoreImports(program)) {
|
|
2688
|
+
for (const specifier of importDecl.specifiers) {
|
|
2689
|
+
if (specifier.type !== AST_NODE_TYPES.ImportSpecifier) {
|
|
2690
|
+
continue;
|
|
2691
|
+
}
|
|
2692
|
+
const imported = specifier.imported.type === AST_NODE_TYPES.Identifier
|
|
2693
|
+
? specifier.imported.name
|
|
2694
|
+
: specifier.imported.value;
|
|
2695
|
+
if (imported === exportedName) {
|
|
2696
|
+
return { importDecl, specifier };
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
return null;
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2663
2703
|
const ANGULAR_CORE = '@angular/core';
|
|
2664
2704
|
const SIGNAL_WRITE_METHODS = new Set(['mutate', 'set', 'update']);
|
|
2665
2705
|
const AFTER_RENDER_EFFECT_PHASES = new Map([
|
|
@@ -2668,10 +2708,14 @@ const AFTER_RENDER_EFFECT_PHASES = new Map([
|
|
|
2668
2708
|
['read', 'afterRenderEffect().read'],
|
|
2669
2709
|
['write', 'afterRenderEffect().write'],
|
|
2670
2710
|
]);
|
|
2671
|
-
function isReactiveCallback
|
|
2711
|
+
function isReactiveCallback(node) {
|
|
2672
2712
|
return (node?.type === AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
2673
2713
|
node?.type === AST_NODE_TYPES.FunctionExpression);
|
|
2674
2714
|
}
|
|
2715
|
+
function getReactiveCallbackArgument(node) {
|
|
2716
|
+
const [arg] = node.arguments;
|
|
2717
|
+
return isReactiveCallback(arg) ? arg : null;
|
|
2718
|
+
}
|
|
2675
2719
|
function getPropertyName(property) {
|
|
2676
2720
|
if (property.computed) {
|
|
2677
2721
|
return null;
|
|
@@ -2691,11 +2735,11 @@ function isAngularCoreCall(node, program, exportedName) {
|
|
|
2691
2735
|
node.arguments.length >= 1);
|
|
2692
2736
|
}
|
|
2693
2737
|
function appendFirstArgReactiveScope(scopes, call, kind) {
|
|
2694
|
-
const
|
|
2695
|
-
if (!
|
|
2738
|
+
const callback = getReactiveCallbackArgument(call);
|
|
2739
|
+
if (!callback) {
|
|
2696
2740
|
return;
|
|
2697
2741
|
}
|
|
2698
|
-
scopes.push({ callback
|
|
2742
|
+
scopes.push({ callback, kind, owner: call, reportNode: call });
|
|
2699
2743
|
}
|
|
2700
2744
|
function appendObjectPropertyReactiveScopes(scopes, call, object, labels) {
|
|
2701
2745
|
for (const property of object.properties) {
|
|
@@ -2704,7 +2748,7 @@ function appendObjectPropertyReactiveScopes(scopes, call, object, labels) {
|
|
|
2704
2748
|
}
|
|
2705
2749
|
const name = getPropertyName(property);
|
|
2706
2750
|
const label = name ? labels.get(name) : null;
|
|
2707
|
-
if (!label || !isReactiveCallback
|
|
2751
|
+
if (!label || !isReactiveCallback(property.value)) {
|
|
2708
2752
|
continue;
|
|
2709
2753
|
}
|
|
2710
2754
|
scopes.push({
|
|
@@ -2718,6 +2762,9 @@ function appendObjectPropertyReactiveScopes(scopes, call, object, labels) {
|
|
|
2718
2762
|
function isAngularEffectCall(node, program) {
|
|
2719
2763
|
return isAngularCoreCall(node, program, 'effect');
|
|
2720
2764
|
}
|
|
2765
|
+
function isAngularInjectCall(node, program) {
|
|
2766
|
+
return isAngularCoreCall(node, program, 'inject');
|
|
2767
|
+
}
|
|
2721
2768
|
function isAngularUntrackedCall(node, program) {
|
|
2722
2769
|
return isAngularCoreCall(node, program, 'untracked');
|
|
2723
2770
|
}
|
|
@@ -2911,54 +2958,74 @@ function collectSignalUsages(scopeNode, checker, esTreeNodeToTSNodeMap, program)
|
|
|
2911
2958
|
});
|
|
2912
2959
|
return { reads, writes };
|
|
2913
2960
|
}
|
|
2914
|
-
|
|
2915
|
-
const ANGULAR_SIGNALS_UNTRACKED_GUIDE_URL = 'https://angular.dev/guide/signals#reading-without-tracking-dependencies';
|
|
2916
|
-
const ANGULAR_SIGNALS_ASYNC_GUIDE_URL = 'https://angular.dev/guide/signals#reactive-context-and-async-operations';
|
|
2917
|
-
const UNTRACKED_RULES_README_URL = 'https://github.com/taiga-family/taiga-ui/blob/main/projects/eslint-plugin-experience-next/README.md';
|
|
2918
|
-
const createUntrackedRule = ESLintUtils.RuleCreator((name) => `${UNTRACKED_RULES_README_URL}#${name}`);
|
|
2919
|
-
|
|
2920
|
-
/**
|
|
2921
|
-
* Collects signal reads that appear inside `untracked(...)` callbacks within
|
|
2922
|
-
* `root`, without descending into nested `untracked(...)` scopes.
|
|
2923
|
-
*/
|
|
2924
|
-
function collectReadsInsideUntracked(root, checker, esTreeNodeToTSNodeMap, program) {
|
|
2961
|
+
function collectSignalReadsInsideUntracked(root, checker, esTreeNodeToTSNodeMap, program) {
|
|
2925
2962
|
const reads = [];
|
|
2926
2963
|
walkSynchronousAst(root, (node) => {
|
|
2927
2964
|
if (node.type !== AST_NODE_TYPES.CallExpression ||
|
|
2928
2965
|
!isAngularUntrackedCall(node, program)) {
|
|
2929
2966
|
return;
|
|
2930
2967
|
}
|
|
2931
|
-
const
|
|
2932
|
-
if (!
|
|
2933
|
-
(arg.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
2934
|
-
arg.type !== AST_NODE_TYPES.FunctionExpression)) {
|
|
2968
|
+
const callback = getReactiveCallbackArgument(node);
|
|
2969
|
+
if (!callback) {
|
|
2935
2970
|
return false;
|
|
2936
2971
|
}
|
|
2937
|
-
walkSynchronousAst(
|
|
2972
|
+
walkSynchronousAst(callback, (inner) => {
|
|
2938
2973
|
if (inner.type === AST_NODE_TYPES.CallExpression &&
|
|
2939
2974
|
isSignalReadCall(inner, checker, esTreeNodeToTSNodeMap)) {
|
|
2940
2975
|
reads.push(inner);
|
|
2941
2976
|
}
|
|
2942
2977
|
});
|
|
2943
|
-
return false;
|
|
2978
|
+
return false;
|
|
2944
2979
|
});
|
|
2945
2980
|
return reads;
|
|
2946
2981
|
}
|
|
2947
|
-
|
|
2982
|
+
function isReactiveOwnerCall(node, program) {
|
|
2983
|
+
return (node.type === AST_NODE_TYPES.CallExpression &&
|
|
2984
|
+
getReactiveScopes(node, program).length > 0);
|
|
2985
|
+
}
|
|
2986
|
+
function getReturnedReactiveOwnerCall(node, program) {
|
|
2987
|
+
const callback = getReactiveCallbackArgument(node);
|
|
2988
|
+
if (!callback) {
|
|
2989
|
+
return null;
|
|
2990
|
+
}
|
|
2991
|
+
if (isReactiveOwnerCall(callback.body, program)) {
|
|
2992
|
+
return callback.body;
|
|
2993
|
+
}
|
|
2994
|
+
if (callback.body.type !== AST_NODE_TYPES.BlockStatement ||
|
|
2995
|
+
callback.body.body.length !== 1) {
|
|
2996
|
+
return null;
|
|
2997
|
+
}
|
|
2998
|
+
const [statement] = callback.body.body;
|
|
2999
|
+
if (statement?.type === AST_NODE_TYPES.ReturnStatement &&
|
|
3000
|
+
statement.argument &&
|
|
3001
|
+
isReactiveOwnerCall(statement.argument, program)) {
|
|
3002
|
+
return statement.argument;
|
|
3003
|
+
}
|
|
3004
|
+
if (node.parent.type === AST_NODE_TYPES.ExpressionStatement &&
|
|
3005
|
+
statement?.type === AST_NODE_TYPES.ExpressionStatement &&
|
|
3006
|
+
isReactiveOwnerCall(statement.expression, program)) {
|
|
3007
|
+
return statement.expression;
|
|
3008
|
+
}
|
|
3009
|
+
return null;
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
const ANGULAR_SIGNALS_UNTRACKED_GUIDE_URL = 'https://angular.dev/guide/signals#reading-without-tracking-dependencies';
|
|
3013
|
+
const ANGULAR_SIGNALS_ASYNC_GUIDE_URL = 'https://angular.dev/guide/signals#reactive-context-and-async-operations';
|
|
3014
|
+
const UNTRACKED_RULES_README_URL = 'https://github.com/taiga-family/taiga-ui/blob/main/projects/eslint-plugin-experience-next/README.md';
|
|
3015
|
+
const createUntrackedRule = ESLintUtils.RuleCreator((name) => `${UNTRACKED_RULES_README_URL}#${name}`);
|
|
3016
|
+
|
|
3017
|
+
const rule$h = createUntrackedRule({
|
|
2948
3018
|
create(context) {
|
|
2949
|
-
const
|
|
2950
|
-
const
|
|
2951
|
-
const esTreeNodeToTSNodeMap = parserServices.esTreeNodeToTSNodeMap;
|
|
2952
|
-
const { sourceCode } = context;
|
|
2953
|
-
const program = sourceCode.ast;
|
|
3019
|
+
const { checker, esTreeNodeToTSNodeMap, program } = getTypeAwareRuleContext(context);
|
|
3020
|
+
const signalNodeMap = esTreeNodeToTSNodeMap;
|
|
2954
3021
|
return {
|
|
2955
3022
|
CallExpression(node) {
|
|
2956
3023
|
for (const scope of getReactiveScopes(node, program)) {
|
|
2957
|
-
const { reads: trackedReads } = collectSignalUsages(scope.callback, checker,
|
|
3024
|
+
const { reads: trackedReads } = collectSignalUsages(scope.callback, checker, signalNodeMap, program);
|
|
2958
3025
|
if (trackedReads.length > 0) {
|
|
2959
3026
|
continue;
|
|
2960
3027
|
}
|
|
2961
|
-
const untrackedReads =
|
|
3028
|
+
const untrackedReads = collectSignalReadsInsideUntracked(scope.callback, checker, signalNodeMap, program);
|
|
2962
3029
|
if (untrackedReads.length === 0) {
|
|
2963
3030
|
continue;
|
|
2964
3031
|
}
|
|
@@ -3030,46 +3097,102 @@ const config$1 = {
|
|
|
3030
3097
|
},
|
|
3031
3098
|
};
|
|
3032
3099
|
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3100
|
+
function findAncestor(node, predicate) {
|
|
3101
|
+
for (let current = node?.parent; current; current = current.parent) {
|
|
3102
|
+
if (predicate(current)) {
|
|
3103
|
+
return current;
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
return null;
|
|
3107
|
+
}
|
|
3108
|
+
function findSelfOrAncestor(node, predicate) {
|
|
3109
|
+
for (let current = node; current; current = current.parent) {
|
|
3110
|
+
if (predicate(current)) {
|
|
3111
|
+
return current;
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
return null;
|
|
3115
|
+
}
|
|
3116
|
+
function hasAncestor(node, predicate) {
|
|
3117
|
+
for (let current = node?.parent; current; current = current.parent) {
|
|
3118
|
+
if (predicate(current)) {
|
|
3119
|
+
return true;
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
return false;
|
|
3123
|
+
}
|
|
3124
|
+
function isNodeInside(node, ancestor) {
|
|
3125
|
+
for (let current = node; current; current = current.parent) {
|
|
3126
|
+
if (current === ancestor) {
|
|
3127
|
+
return true;
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
return false;
|
|
3131
|
+
}
|
|
3132
|
+
function isNodeInsideAny(node, ancestors) {
|
|
3133
|
+
return ancestors.some((ancestor) => isNodeInside(node, ancestor));
|
|
3134
|
+
}
|
|
3135
|
+
function isClassLike(node) {
|
|
3136
|
+
return (node.type === AST_NODE_TYPES.ClassDeclaration ||
|
|
3137
|
+
node.type === AST_NODE_TYPES.ClassExpression);
|
|
3138
|
+
}
|
|
3139
|
+
function isClassMember(node) {
|
|
3140
|
+
return (node.type === AST_NODE_TYPES.MethodDefinition ||
|
|
3141
|
+
node.type === AST_NODE_TYPES.PropertyDefinition);
|
|
3142
|
+
}
|
|
3143
|
+
function getEnclosingFunction(node) {
|
|
3144
|
+
return findAncestor(node, isFunctionLike);
|
|
3145
|
+
}
|
|
3146
|
+
function getEnclosingClass(node) {
|
|
3147
|
+
return findSelfOrAncestor(node, isClassLike);
|
|
3148
|
+
}
|
|
3149
|
+
function getEnclosingClassMember(node) {
|
|
3150
|
+
return findAncestor(node, isClassMember);
|
|
3151
|
+
}
|
|
3152
|
+
function getScopeRoot(node) {
|
|
3153
|
+
return (findAncestor(node, (ancestor) => ancestor.type === AST_NODE_TYPES.Program || isFunctionLike(ancestor)) ?? node);
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
const createRule$d = ESLintUtils.RuleCreator((name) => name);
|
|
3157
|
+
const rule$g = createRule$d({
|
|
3158
|
+
create(context) {
|
|
3159
|
+
const checkImplicitPublic = (node) => {
|
|
3160
|
+
const classRef = getEnclosingClass(node);
|
|
3161
|
+
if (!classRef ||
|
|
3162
|
+
node.kind === 'constructor' ||
|
|
3163
|
+
!!node?.accessibility ||
|
|
3164
|
+
node.key?.type === AST_NODE_TYPES.PrivateIdentifier) {
|
|
3165
|
+
return;
|
|
3166
|
+
}
|
|
3167
|
+
const name = node?.key?.name ||
|
|
3168
|
+
node?.parameter?.name ||
|
|
3169
|
+
(node?.key?.type === 'Identifier' ? node.key.name : 'member');
|
|
3170
|
+
let range = node?.parameter?.range ??
|
|
3171
|
+
node?.key?.range ??
|
|
3172
|
+
node?.range ?? [0, 0];
|
|
3173
|
+
if (node.kind === 'set' || node.kind === 'get') {
|
|
3174
|
+
const [start, end] = node.key.range;
|
|
3175
|
+
range = [start - node.kind.length - 1, end - node.kind.length - 1];
|
|
3176
|
+
}
|
|
3177
|
+
else if (node.kind === 'method' && node.key?.object?.name === 'Symbol') {
|
|
3178
|
+
const [start, end] = range;
|
|
3179
|
+
range = [start - 1, end - 1];
|
|
3180
|
+
}
|
|
3181
|
+
if (node.type === 'PropertyDefinition' && node.decorators?.length > 0) {
|
|
3182
|
+
const [, end] = node.decorators[node.decorators.length - 1].range ?? [];
|
|
3183
|
+
range = [end + 1, end + 2];
|
|
3184
|
+
}
|
|
3185
|
+
context.report({
|
|
3186
|
+
data: {
|
|
3187
|
+
kind: node.kind || 'property',
|
|
3188
|
+
name,
|
|
3189
|
+
},
|
|
3190
|
+
fix: (fixer) => fixer.insertTextBeforeRange(range, ' public '),
|
|
3191
|
+
messageId: 'implicitPublic',
|
|
3192
|
+
node,
|
|
3193
|
+
});
|
|
3194
|
+
};
|
|
3195
|
+
return {
|
|
3073
3196
|
MethodDefinition(node) {
|
|
3074
3197
|
checkImplicitPublic(node);
|
|
3075
3198
|
},
|
|
@@ -3092,16 +3215,64 @@ const rule$f = createRule$c({
|
|
|
3092
3215
|
},
|
|
3093
3216
|
name: 'explicit-public-member',
|
|
3094
3217
|
});
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3218
|
+
|
|
3219
|
+
function getParenthesizedInner(node) {
|
|
3220
|
+
const maybeNode = node;
|
|
3221
|
+
if (maybeNode.type === 'ParenthesizedExpression') {
|
|
3222
|
+
return maybeNode.expression ?? null;
|
|
3098
3223
|
}
|
|
3099
|
-
|
|
3100
|
-
|
|
3224
|
+
return null;
|
|
3225
|
+
}
|
|
3226
|
+
function unwrapParenthesized(node) {
|
|
3227
|
+
let current = node;
|
|
3228
|
+
let inner = getParenthesizedInner(current);
|
|
3229
|
+
while (inner) {
|
|
3230
|
+
current = inner;
|
|
3231
|
+
inner = getParenthesizedInner(current);
|
|
3101
3232
|
}
|
|
3102
|
-
return
|
|
3233
|
+
return current;
|
|
3103
3234
|
}
|
|
3104
3235
|
|
|
3236
|
+
const createRule$c = ESLintUtils.RuleCreator((name) => name);
|
|
3237
|
+
function isBooleanTrue(node) {
|
|
3238
|
+
const unwrapped = unwrapParenthesized(node);
|
|
3239
|
+
return unwrapped.type === AST_NODE_TYPES.Literal && unwrapped.value === true;
|
|
3240
|
+
}
|
|
3241
|
+
const rule$f = createRule$c({
|
|
3242
|
+
create(context) {
|
|
3243
|
+
return {
|
|
3244
|
+
ForStatement(node) {
|
|
3245
|
+
if (!node.test) {
|
|
3246
|
+
context.report({
|
|
3247
|
+
messageId: 'forLoop',
|
|
3248
|
+
node,
|
|
3249
|
+
});
|
|
3250
|
+
}
|
|
3251
|
+
},
|
|
3252
|
+
WhileStatement(node) {
|
|
3253
|
+
if (isBooleanTrue(node.test)) {
|
|
3254
|
+
context.report({
|
|
3255
|
+
messageId: 'whileLoop',
|
|
3256
|
+
node: node.test,
|
|
3257
|
+
});
|
|
3258
|
+
}
|
|
3259
|
+
},
|
|
3260
|
+
};
|
|
3261
|
+
},
|
|
3262
|
+
meta: {
|
|
3263
|
+
docs: {
|
|
3264
|
+
description: 'Disallow `while (true)` and `for` loops without an explicit condition. Prefer loops with meaningful exit conditions.',
|
|
3265
|
+
},
|
|
3266
|
+
messages: {
|
|
3267
|
+
forLoop: 'Use an explicit exit condition instead of a `for` loop without a condition.',
|
|
3268
|
+
whileLoop: 'Use an explicit exit condition instead of `while (true)`.',
|
|
3269
|
+
},
|
|
3270
|
+
schema: [],
|
|
3271
|
+
type: 'suggestion',
|
|
3272
|
+
},
|
|
3273
|
+
name: 'no-infinite-loop',
|
|
3274
|
+
});
|
|
3275
|
+
|
|
3105
3276
|
const createRule$b = ESLintUtils.RuleCreator((name) => name);
|
|
3106
3277
|
const LEGACY_PEER_DEPS_PATTERN = /^legacy-peer-deps\s*=\s*true$/i;
|
|
3107
3278
|
const rule$e = createRule$b({
|
|
@@ -3145,8 +3316,7 @@ const rule$e = createRule$b({
|
|
|
3145
3316
|
const createRule$a = ESLintUtils.RuleCreator((name) => name);
|
|
3146
3317
|
const rule$d = createRule$a({
|
|
3147
3318
|
create(context) {
|
|
3148
|
-
const
|
|
3149
|
-
const checker = services.program.getTypeChecker();
|
|
3319
|
+
const { checker, esTreeNodeToTSNodeMap, sourceCode } = getTypeAwareRuleContext(context);
|
|
3150
3320
|
return {
|
|
3151
3321
|
CallExpression(node) {
|
|
3152
3322
|
const callee = node.callee;
|
|
@@ -3160,18 +3330,18 @@ const rule$d = createRule$a({
|
|
|
3160
3330
|
return;
|
|
3161
3331
|
}
|
|
3162
3332
|
const [argument] = node.arguments;
|
|
3163
|
-
if (!argument || !
|
|
3333
|
+
if (!argument || !isEmptyStaticString(argument)) {
|
|
3164
3334
|
return;
|
|
3165
3335
|
}
|
|
3166
3336
|
const objectExpression = callee.object;
|
|
3167
|
-
const tsNode =
|
|
3337
|
+
const tsNode = esTreeNodeToTSNodeMap.get(objectExpression);
|
|
3168
3338
|
const type = checker.getTypeAtLocation(tsNode);
|
|
3169
3339
|
if (!isPlaywrightLocator(type, checker)) {
|
|
3170
3340
|
return;
|
|
3171
3341
|
}
|
|
3172
3342
|
context.report({
|
|
3173
3343
|
fix(fixer) {
|
|
3174
|
-
const objectText =
|
|
3344
|
+
const objectText = sourceCode.getText(objectExpression);
|
|
3175
3345
|
return fixer.replaceText(node, `${objectText}.clear()`);
|
|
3176
3346
|
},
|
|
3177
3347
|
messageId: 'useClear',
|
|
@@ -3191,13 +3361,6 @@ const rule$d = createRule$a({
|
|
|
3191
3361
|
},
|
|
3192
3362
|
name: 'no-playwright-empty-fill',
|
|
3193
3363
|
});
|
|
3194
|
-
function isEmptyString(node) {
|
|
3195
|
-
return ((node.type === AST_NODE_TYPES$1.Literal && node.value === '') ||
|
|
3196
|
-
(node.type === AST_NODE_TYPES$1.TemplateLiteral &&
|
|
3197
|
-
node.expressions.length === 0 &&
|
|
3198
|
-
node.quasis.length === 1 &&
|
|
3199
|
-
node.quasis[0]?.value.cooked === ''));
|
|
3200
|
-
}
|
|
3201
3364
|
function isPlaywrightLocator(type, checker) {
|
|
3202
3365
|
if (isPlaywrightLocatorType(type)) {
|
|
3203
3366
|
return true;
|
|
@@ -3336,15 +3499,14 @@ function collectArrayExpressions(node) {
|
|
|
3336
3499
|
}
|
|
3337
3500
|
const rule$c = createRule$9({
|
|
3338
3501
|
create(context) {
|
|
3339
|
-
const
|
|
3340
|
-
const typeChecker = parserServices.program.getTypeChecker();
|
|
3502
|
+
const { checker: typeChecker, esTreeNodeToTSNodeMap } = getTypeAwareRuleContext(context);
|
|
3341
3503
|
const ignoreTupleContextualTyping = context.options[0]?.ignoreTupleContextualTyping ?? true;
|
|
3342
3504
|
function check(node, typeAnnotation, value) {
|
|
3343
3505
|
if (!typeAnnotation || !value) {
|
|
3344
3506
|
return;
|
|
3345
3507
|
}
|
|
3346
|
-
const tsNode =
|
|
3347
|
-
const tsValueNode =
|
|
3508
|
+
const tsNode = esTreeNodeToTSNodeMap.get(node);
|
|
3509
|
+
const tsValueNode = esTreeNodeToTSNodeMap.get(value);
|
|
3348
3510
|
const declaredType = typeChecker.getTypeAtLocation(tsNode);
|
|
3349
3511
|
const inferredType = typeChecker.getTypeAtLocation(tsValueNode);
|
|
3350
3512
|
if (typeChecker.typeToString(declaredType) !==
|
|
@@ -3368,7 +3530,7 @@ const rule$c = createRule$9({
|
|
|
3368
3530
|
if (ignoreTupleContextualTyping) {
|
|
3369
3531
|
const arrayExpressions = collectArrayExpressions(value);
|
|
3370
3532
|
for (const arrayExpression of arrayExpressions) {
|
|
3371
|
-
const tsArrayNode =
|
|
3533
|
+
const tsArrayNode = esTreeNodeToTSNodeMap.get(arrayExpression);
|
|
3372
3534
|
if (typeChecker.isTupleType(typeChecker.getTypeAtLocation(tsArrayNode))) {
|
|
3373
3535
|
return;
|
|
3374
3536
|
}
|
|
@@ -3462,11 +3624,6 @@ function unwrapExpression(expression) {
|
|
|
3462
3624
|
return current;
|
|
3463
3625
|
}
|
|
3464
3626
|
|
|
3465
|
-
const createRule$8 = ESLintUtils.RuleCreator((name) => name);
|
|
3466
|
-
function isReactiveCallback(node) {
|
|
3467
|
-
return (node?.type === AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
3468
|
-
node?.type === AST_NODE_TYPES.FunctionExpression);
|
|
3469
|
-
}
|
|
3470
3627
|
function unwrapMutationTarget(node) {
|
|
3471
3628
|
let current = node;
|
|
3472
3629
|
while (current.type === AST_NODE_TYPES.TSAsExpression ||
|
|
@@ -3500,6 +3657,7 @@ function collectMutationTargets(node) {
|
|
|
3500
3657
|
return [];
|
|
3501
3658
|
}
|
|
3502
3659
|
}
|
|
3660
|
+
|
|
3503
3661
|
function getSymbolAtNode(node, checker, esTreeNodeToTSNodeMap) {
|
|
3504
3662
|
const tsNode = esTreeNodeToTSNodeMap.get(node);
|
|
3505
3663
|
if (!tsNode) {
|
|
@@ -3507,7 +3665,12 @@ function getSymbolAtNode(node, checker, esTreeNodeToTSNodeMap) {
|
|
|
3507
3665
|
}
|
|
3508
3666
|
return checker.getSymbolAtLocation(tsNode) ?? null;
|
|
3509
3667
|
}
|
|
3510
|
-
|
|
3668
|
+
|
|
3669
|
+
const createRule$8 = ESLintUtils.RuleCreator((name) => name);
|
|
3670
|
+
function isFunctionLikeScope(node) {
|
|
3671
|
+
return !!node && isFunctionLike(node);
|
|
3672
|
+
}
|
|
3673
|
+
function isLocalIdentifier(node, context, localScopes) {
|
|
3511
3674
|
const symbol = getSymbolAtNode(node, context.checker, context.esTreeNodeToTSNodeMap);
|
|
3512
3675
|
if (!symbol) {
|
|
3513
3676
|
return false;
|
|
@@ -3515,10 +3678,24 @@ function isLocalIdentifier(node, context) {
|
|
|
3515
3678
|
return (symbol.declarations ?? []).some((declaration) => {
|
|
3516
3679
|
const estreeDeclaration = context.tsNodeToESTreeNodeMap.get(declaration);
|
|
3517
3680
|
return (!!estreeDeclaration &&
|
|
3518
|
-
|
|
3681
|
+
isDeclaredInsideLocalScope(estreeDeclaration, localScopes));
|
|
3682
|
+
});
|
|
3683
|
+
}
|
|
3684
|
+
function isDeclaredInsideLocalScope(node, localScopes) {
|
|
3685
|
+
return localScopes.some((scope) => isNodeInsideFunctionScope(node, scope));
|
|
3686
|
+
}
|
|
3687
|
+
function isNodeInsideFunctionScope(node, scope) {
|
|
3688
|
+
let found = false;
|
|
3689
|
+
walkSynchronousAst(scope, (inner) => {
|
|
3690
|
+
if (inner !== node) {
|
|
3691
|
+
return;
|
|
3692
|
+
}
|
|
3693
|
+
found = true;
|
|
3694
|
+
return false;
|
|
3519
3695
|
});
|
|
3696
|
+
return found;
|
|
3520
3697
|
}
|
|
3521
|
-
function isLocallyCreatedExpression(node, context) {
|
|
3698
|
+
function isLocallyCreatedExpression(node, context, localScopes) {
|
|
3522
3699
|
const expression = unwrapExpression(node);
|
|
3523
3700
|
switch (expression.type) {
|
|
3524
3701
|
case AST_NODE_TYPES.ArrayExpression:
|
|
@@ -3526,87 +3703,245 @@ function isLocallyCreatedExpression(node, context) {
|
|
|
3526
3703
|
case AST_NODE_TYPES.ObjectExpression:
|
|
3527
3704
|
return true;
|
|
3528
3705
|
case AST_NODE_TYPES.Identifier:
|
|
3529
|
-
return isLocalIdentifier(expression, context);
|
|
3706
|
+
return isLocalIdentifier(expression, context, localScopes);
|
|
3530
3707
|
case AST_NODE_TYPES.MemberExpression:
|
|
3531
|
-
return isLocallyCreatedExpression(expression.object, context);
|
|
3708
|
+
return isLocallyCreatedExpression(expression.object, context, localScopes);
|
|
3532
3709
|
default:
|
|
3533
3710
|
return false;
|
|
3534
3711
|
}
|
|
3535
3712
|
}
|
|
3536
|
-
function hasObservableMutationTarget(node, context) {
|
|
3713
|
+
function hasObservableMutationTarget(node, context, localScopes) {
|
|
3537
3714
|
return collectMutationTargets(node).some((target) => {
|
|
3538
3715
|
if (target.type === AST_NODE_TYPES.Identifier) {
|
|
3539
|
-
return !isLocalIdentifier(target, context);
|
|
3716
|
+
return !isLocalIdentifier(target, context, localScopes);
|
|
3540
3717
|
}
|
|
3541
|
-
return !isLocallyCreatedExpression(target.object, context);
|
|
3718
|
+
return !isLocallyCreatedExpression(target.object, context, localScopes);
|
|
3542
3719
|
});
|
|
3543
3720
|
}
|
|
3544
|
-
function reportSideEffect(node,
|
|
3721
|
+
function reportSideEffect(node, context, report) {
|
|
3545
3722
|
const key = String(node.range);
|
|
3546
|
-
if (
|
|
3723
|
+
if (context.reported.has(key)) {
|
|
3547
3724
|
return;
|
|
3548
3725
|
}
|
|
3549
|
-
|
|
3726
|
+
context.reported.add(key);
|
|
3550
3727
|
report(node);
|
|
3551
3728
|
}
|
|
3552
|
-
function
|
|
3729
|
+
function isDirectAngularSideEffectCall(node, context) {
|
|
3730
|
+
return (isWritableSignalWrite(node, context.checker, context.esTreeNodeToTSNodeMap) ||
|
|
3731
|
+
isAngularEffectCall(node, context.program) ||
|
|
3732
|
+
isAngularInjectCall(node, context.program));
|
|
3733
|
+
}
|
|
3734
|
+
function isInspectableFunctionContainer(node) {
|
|
3735
|
+
return (!!node &&
|
|
3736
|
+
(isFunctionLikeScope(node) ||
|
|
3737
|
+
node.type === AST_NODE_TYPES.MethodDefinition ||
|
|
3738
|
+
node.type === AST_NODE_TYPES.Property ||
|
|
3739
|
+
node.type === AST_NODE_TYPES.PropertyDefinition ||
|
|
3740
|
+
node.type === AST_NODE_TYPES.VariableDeclarator));
|
|
3741
|
+
}
|
|
3742
|
+
function resolveFunctionLikeFromContainer(node, context, seenSymbols = new Set()) {
|
|
3743
|
+
if (isFunctionLikeScope(node)) {
|
|
3744
|
+
return [node];
|
|
3745
|
+
}
|
|
3746
|
+
if ((node.type === AST_NODE_TYPES.MethodDefinition &&
|
|
3747
|
+
isFunctionLikeScope(node.value)) ||
|
|
3748
|
+
(node.type === AST_NODE_TYPES.Property && isFunctionLikeScope(node.value))) {
|
|
3749
|
+
return [node.value];
|
|
3750
|
+
}
|
|
3751
|
+
if (node.type === AST_NODE_TYPES.Property &&
|
|
3752
|
+
node.value.type === AST_NODE_TYPES.Identifier) {
|
|
3753
|
+
return resolveFunctionLikeFromIdentifier(node.value, context, seenSymbols);
|
|
3754
|
+
}
|
|
3755
|
+
if (node.type === AST_NODE_TYPES.PropertyDefinition &&
|
|
3756
|
+
node.value &&
|
|
3757
|
+
isFunctionLikeScope(node.value)) {
|
|
3758
|
+
return [node.value];
|
|
3759
|
+
}
|
|
3760
|
+
if (node.type === AST_NODE_TYPES.PropertyDefinition &&
|
|
3761
|
+
node.value?.type === AST_NODE_TYPES.Identifier) {
|
|
3762
|
+
return resolveFunctionLikeFromIdentifier(node.value, context, seenSymbols);
|
|
3763
|
+
}
|
|
3764
|
+
if (node.type === AST_NODE_TYPES.VariableDeclarator) {
|
|
3765
|
+
const { init } = node;
|
|
3766
|
+
if (init && isFunctionLikeScope(init)) {
|
|
3767
|
+
return [init];
|
|
3768
|
+
}
|
|
3769
|
+
if (init?.type === AST_NODE_TYPES.Identifier) {
|
|
3770
|
+
return resolveFunctionLikeFromIdentifier(init, context, seenSymbols);
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
return [];
|
|
3774
|
+
}
|
|
3775
|
+
function resolveFunctionLikeFromIdentifier(node, context, seenSymbols = new Set()) {
|
|
3776
|
+
const symbol = getSymbolAtNode(node, context.checker, context.esTreeNodeToTSNodeMap);
|
|
3777
|
+
if (!symbol) {
|
|
3778
|
+
return [];
|
|
3779
|
+
}
|
|
3780
|
+
const symbolId = `${symbol.name}:${symbol.declarations?.[0]?.pos ?? -1}`;
|
|
3781
|
+
if (seenSymbols.has(symbolId)) {
|
|
3782
|
+
return [];
|
|
3783
|
+
}
|
|
3784
|
+
seenSymbols.add(symbolId);
|
|
3785
|
+
return (symbol.declarations ?? []).flatMap((declaration) => {
|
|
3786
|
+
const estreeDeclaration = context.tsNodeToESTreeNodeMap.get(declaration);
|
|
3787
|
+
if (!estreeDeclaration || !isInspectableFunctionContainer(estreeDeclaration)) {
|
|
3788
|
+
return [];
|
|
3789
|
+
}
|
|
3790
|
+
return resolveFunctionLikeFromContainer(estreeDeclaration, context, seenSymbols);
|
|
3791
|
+
});
|
|
3792
|
+
}
|
|
3793
|
+
function resolveCalledFunctions(node, context) {
|
|
3794
|
+
const resolved = new Map();
|
|
3795
|
+
const tsNode = context.esTreeNodeToTSNodeMap.get(node);
|
|
3796
|
+
const signature = tsNode ? context.checker.getResolvedSignature(tsNode) : undefined;
|
|
3797
|
+
const declarations = new Set();
|
|
3798
|
+
if (signature?.declaration) {
|
|
3799
|
+
declarations.add(signature.declaration);
|
|
3800
|
+
}
|
|
3801
|
+
const callee = unwrapExpression(node.callee);
|
|
3802
|
+
if (callee.type === AST_NODE_TYPES.Identifier ||
|
|
3803
|
+
callee.type === AST_NODE_TYPES.MemberExpression) {
|
|
3804
|
+
const symbol = getSymbolAtNode(callee, context.checker, context.esTreeNodeToTSNodeMap);
|
|
3805
|
+
for (const declaration of symbol?.declarations ?? []) {
|
|
3806
|
+
declarations.add(declaration);
|
|
3807
|
+
}
|
|
3808
|
+
}
|
|
3809
|
+
for (const declaration of declarations) {
|
|
3810
|
+
const estreeDeclaration = context.tsNodeToESTreeNodeMap.get(declaration);
|
|
3811
|
+
if (!estreeDeclaration || !isInspectableFunctionContainer(estreeDeclaration)) {
|
|
3812
|
+
continue;
|
|
3813
|
+
}
|
|
3814
|
+
for (const fn of resolveFunctionLikeFromContainer(estreeDeclaration, context)) {
|
|
3815
|
+
resolved.set(String(fn.range), fn);
|
|
3816
|
+
}
|
|
3817
|
+
}
|
|
3818
|
+
return [...resolved.values()];
|
|
3819
|
+
}
|
|
3820
|
+
function functionHasObservableSideEffects(root, context, localScopes, visitedFunctions) {
|
|
3821
|
+
let hasSideEffect = false;
|
|
3553
3822
|
walkSynchronousAst(root, (node) => {
|
|
3554
3823
|
if (node.type === AST_NODE_TYPES.CallExpression) {
|
|
3555
|
-
if (
|
|
3556
|
-
|
|
3824
|
+
if (isDirectAngularSideEffectCall(node, context)) {
|
|
3825
|
+
hasSideEffect = true;
|
|
3826
|
+
return false;
|
|
3827
|
+
}
|
|
3828
|
+
if (isAngularUntrackedCall(node, context.program)) {
|
|
3829
|
+
const callback = getReactiveCallbackArgument(node);
|
|
3830
|
+
if (callback &&
|
|
3831
|
+
functionHasObservableSideEffects(callback, context, [...localScopes, callback], visitedFunctions)) {
|
|
3832
|
+
hasSideEffect = true;
|
|
3833
|
+
}
|
|
3834
|
+
return false;
|
|
3557
3835
|
}
|
|
3558
|
-
if (
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
inspectComputedBody(arg, inspectionContext, report);
|
|
3836
|
+
if (isReactiveCallback(node.callee)) {
|
|
3837
|
+
if (functionHasObservableSideEffects(node.callee, context, [...localScopes, node.callee], visitedFunctions)) {
|
|
3838
|
+
hasSideEffect = true;
|
|
3562
3839
|
}
|
|
3563
3840
|
return false;
|
|
3564
3841
|
}
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3842
|
+
for (const calledFunction of resolveCalledFunctions(node, context)) {
|
|
3843
|
+
const key = String(calledFunction.range);
|
|
3844
|
+
if (visitedFunctions.has(key)) {
|
|
3845
|
+
continue;
|
|
3846
|
+
}
|
|
3847
|
+
visitedFunctions.add(key);
|
|
3848
|
+
const calledFunctionHasSideEffects = functionHasObservableSideEffects(calledFunction, context, [...localScopes, calledFunction], visitedFunctions);
|
|
3849
|
+
visitedFunctions.delete(key);
|
|
3850
|
+
if (!calledFunctionHasSideEffects) {
|
|
3851
|
+
continue;
|
|
3852
|
+
}
|
|
3853
|
+
hasSideEffect = true;
|
|
3568
3854
|
return false;
|
|
3569
3855
|
}
|
|
3570
3856
|
}
|
|
3571
3857
|
if (node.type === AST_NODE_TYPES.AssignmentExpression &&
|
|
3572
|
-
hasObservableMutationTarget(node.left,
|
|
3573
|
-
|
|
3858
|
+
hasObservableMutationTarget(node.left, context, localScopes)) {
|
|
3859
|
+
hasSideEffect = true;
|
|
3860
|
+
return false;
|
|
3574
3861
|
}
|
|
3575
3862
|
if (node.type === AST_NODE_TYPES.UpdateExpression &&
|
|
3576
|
-
hasObservableMutationTarget(node.argument,
|
|
3577
|
-
|
|
3863
|
+
hasObservableMutationTarget(node.argument, context, localScopes)) {
|
|
3864
|
+
hasSideEffect = true;
|
|
3865
|
+
return false;
|
|
3578
3866
|
}
|
|
3579
3867
|
if (node.type === AST_NODE_TYPES.UnaryExpression &&
|
|
3580
3868
|
node.operator === 'delete' &&
|
|
3581
|
-
hasObservableMutationTarget(node.argument,
|
|
3582
|
-
|
|
3869
|
+
hasObservableMutationTarget(node.argument, context, localScopes)) {
|
|
3870
|
+
hasSideEffect = true;
|
|
3871
|
+
return false;
|
|
3872
|
+
}
|
|
3873
|
+
return;
|
|
3874
|
+
});
|
|
3875
|
+
return hasSideEffect;
|
|
3876
|
+
}
|
|
3877
|
+
function inspectComputedBody(root, context, localScopes, visitedFunctions, report) {
|
|
3878
|
+
walkSynchronousAst(root, (node) => {
|
|
3879
|
+
if (node.type === AST_NODE_TYPES.CallExpression) {
|
|
3880
|
+
if (isDirectAngularSideEffectCall(node, context)) {
|
|
3881
|
+
reportSideEffect(node, context, report);
|
|
3882
|
+
return false;
|
|
3883
|
+
}
|
|
3884
|
+
if (isAngularUntrackedCall(node, context.program)) {
|
|
3885
|
+
const callback = getReactiveCallbackArgument(node);
|
|
3886
|
+
if (callback) {
|
|
3887
|
+
inspectComputedBody(callback, context, [...localScopes, callback], visitedFunctions, report);
|
|
3888
|
+
}
|
|
3889
|
+
return false;
|
|
3890
|
+
}
|
|
3891
|
+
if (isReactiveCallback(node.callee)) {
|
|
3892
|
+
inspectComputedBody(node.callee, context, [...localScopes, node.callee], visitedFunctions, report);
|
|
3893
|
+
return false;
|
|
3894
|
+
}
|
|
3895
|
+
for (const calledFunction of resolveCalledFunctions(node, context)) {
|
|
3896
|
+
const key = String(calledFunction.range);
|
|
3897
|
+
if (visitedFunctions.has(key)) {
|
|
3898
|
+
continue;
|
|
3899
|
+
}
|
|
3900
|
+
visitedFunctions.add(key);
|
|
3901
|
+
const calledFunctionHasSideEffects = functionHasObservableSideEffects(calledFunction, context, [...localScopes, calledFunction], visitedFunctions);
|
|
3902
|
+
visitedFunctions.delete(key);
|
|
3903
|
+
if (!calledFunctionHasSideEffects) {
|
|
3904
|
+
continue;
|
|
3905
|
+
}
|
|
3906
|
+
reportSideEffect(node, context, report);
|
|
3907
|
+
return false;
|
|
3908
|
+
}
|
|
3909
|
+
}
|
|
3910
|
+
if (node.type === AST_NODE_TYPES.AssignmentExpression &&
|
|
3911
|
+
hasObservableMutationTarget(node.left, context, localScopes)) {
|
|
3912
|
+
reportSideEffect(node.left, context, report);
|
|
3913
|
+
}
|
|
3914
|
+
if (node.type === AST_NODE_TYPES.UpdateExpression &&
|
|
3915
|
+
hasObservableMutationTarget(node.argument, context, localScopes)) {
|
|
3916
|
+
reportSideEffect(node.argument, context, report);
|
|
3917
|
+
}
|
|
3918
|
+
if (node.type === AST_NODE_TYPES.UnaryExpression &&
|
|
3919
|
+
node.operator === 'delete' &&
|
|
3920
|
+
hasObservableMutationTarget(node.argument, context, localScopes)) {
|
|
3921
|
+
reportSideEffect(node.argument, context, report);
|
|
3583
3922
|
}
|
|
3584
3923
|
return;
|
|
3585
3924
|
});
|
|
3586
3925
|
}
|
|
3587
3926
|
const rule$b = createRule$8({
|
|
3588
3927
|
create(context) {
|
|
3589
|
-
const
|
|
3590
|
-
const
|
|
3591
|
-
const
|
|
3592
|
-
const tsNodeToESTreeNodeMap = parserServices.tsNodeToESTreeNodeMap;
|
|
3593
|
-
const { sourceCode } = context;
|
|
3594
|
-
const program = sourceCode.ast;
|
|
3928
|
+
const { checker, esTreeNodeToTSNodeMap, program, sourceCode, tsNodeToESTreeNodeMap, } = getTypeAwareRuleContext(context);
|
|
3929
|
+
const signalNodeMap = esTreeNodeToTSNodeMap;
|
|
3930
|
+
const estreeNodeMap = tsNodeToESTreeNodeMap;
|
|
3595
3931
|
return {
|
|
3596
3932
|
CallExpression(node) {
|
|
3597
3933
|
for (const scope of getReactiveScopes(node, program)) {
|
|
3598
3934
|
if (scope.kind !== 'computed()') {
|
|
3599
3935
|
continue;
|
|
3600
3936
|
}
|
|
3601
|
-
const
|
|
3602
|
-
callback: scope.callback,
|
|
3937
|
+
const analysisContext = {
|
|
3603
3938
|
checker,
|
|
3604
|
-
esTreeNodeToTSNodeMap,
|
|
3939
|
+
esTreeNodeToTSNodeMap: signalNodeMap,
|
|
3605
3940
|
program,
|
|
3606
3941
|
reported: new Set(),
|
|
3607
|
-
tsNodeToESTreeNodeMap,
|
|
3942
|
+
tsNodeToESTreeNodeMap: estreeNodeMap,
|
|
3608
3943
|
};
|
|
3609
|
-
inspectComputedBody(scope.callback,
|
|
3944
|
+
inspectComputedBody(scope.callback, analysisContext, [scope.callback], new Set([String(scope.callback.range)]), (reportNode) => {
|
|
3610
3945
|
context.report({
|
|
3611
3946
|
data: { expression: sourceCode.getText(reportNode) },
|
|
3612
3947
|
messageId: 'sideEffectInComputed',
|
|
@@ -3633,11 +3968,8 @@ const rule$b = createRule$8({
|
|
|
3633
3968
|
|
|
3634
3969
|
const rule$a = createUntrackedRule({
|
|
3635
3970
|
create(context) {
|
|
3636
|
-
const
|
|
3637
|
-
const
|
|
3638
|
-
const esTreeNodeToTSNodeMap = parserServices.esTreeNodeToTSNodeMap;
|
|
3639
|
-
const { sourceCode } = context;
|
|
3640
|
-
const program = sourceCode.ast;
|
|
3971
|
+
const { checker, esTreeNodeToTSNodeMap, program, sourceCode } = getTypeAwareRuleContext(context);
|
|
3972
|
+
const signalNodeMap = esTreeNodeToTSNodeMap;
|
|
3641
3973
|
return {
|
|
3642
3974
|
CallExpression(node) {
|
|
3643
3975
|
for (const scope of getReactiveScopes(node, program)) {
|
|
@@ -3645,7 +3977,7 @@ const rule$a = createUntrackedRule({
|
|
|
3645
3977
|
walkAfterAsyncBoundaryAst(scope.callback, (inner) => {
|
|
3646
3978
|
if (inner.type !== AST_NODE_TYPES.CallExpression ||
|
|
3647
3979
|
isAngularUntrackedCall(inner, program) ||
|
|
3648
|
-
!isSignalReadCall(inner, checker,
|
|
3980
|
+
!isSignalReadCall(inner, checker, signalNodeMap)) {
|
|
3649
3981
|
return;
|
|
3650
3982
|
}
|
|
3651
3983
|
const key = String(inner.range);
|
|
@@ -3681,10 +4013,6 @@ const rule$a = createUntrackedRule({
|
|
|
3681
4013
|
});
|
|
3682
4014
|
|
|
3683
4015
|
const createRule$7 = ESLintUtils.RuleCreator((name) => name);
|
|
3684
|
-
function isStringLiteral(node) {
|
|
3685
|
-
return (node.type === AST_NODE_TYPES.Literal &&
|
|
3686
|
-
typeof node.value === 'string');
|
|
3687
|
-
}
|
|
3688
4016
|
function collectParts(node) {
|
|
3689
4017
|
if (node.type === AST_NODE_TYPES.BinaryExpression && node.operator === '+') {
|
|
3690
4018
|
return [...collectParts(node.left), ...collectParts(node.right)];
|
|
@@ -3734,16 +4062,6 @@ function templateContent(template, renderExpr) {
|
|
|
3734
4062
|
: ''}`)
|
|
3735
4063
|
.join('');
|
|
3736
4064
|
}
|
|
3737
|
-
function hasTemplateLiteralAncestor(node) {
|
|
3738
|
-
let current = node.parent;
|
|
3739
|
-
while (current != null) {
|
|
3740
|
-
if (current.type === AST_NODE_TYPES.TemplateLiteral) {
|
|
3741
|
-
return true;
|
|
3742
|
-
}
|
|
3743
|
-
current = current.parent;
|
|
3744
|
-
}
|
|
3745
|
-
return false;
|
|
3746
|
-
}
|
|
3747
4065
|
const rule$9 = createRule$7({
|
|
3748
4066
|
create(context) {
|
|
3749
4067
|
const { sourceCode } = context;
|
|
@@ -3799,7 +4117,7 @@ const rule$9 = createRule$7({
|
|
|
3799
4117
|
}
|
|
3800
4118
|
// Nested inside a template but not direct child — would produce
|
|
3801
4119
|
// `${`${a}${b}`.method()}`, so skip
|
|
3802
|
-
if (
|
|
4120
|
+
if (hasAncestor(node, (ancestor) => ancestor.type === AST_NODE_TYPES.TemplateLiteral)) {
|
|
3803
4121
|
return;
|
|
3804
4122
|
}
|
|
3805
4123
|
context.report({
|
|
@@ -3913,67 +4231,6 @@ function buildImportRemovalFixes(program, fixer, sourceCode) {
|
|
|
3913
4231
|
return [fixer.remove(untrackedSpec)];
|
|
3914
4232
|
}
|
|
3915
4233
|
|
|
3916
|
-
const IMPERATIVE_UNTRACKED_METHODS = new Set(['registerOnChange', 'writeValue']);
|
|
3917
|
-
function dedent$1(text, extraSpaces) {
|
|
3918
|
-
if (extraSpaces <= 0) {
|
|
3919
|
-
return text;
|
|
3920
|
-
}
|
|
3921
|
-
const prefix = ' '.repeat(extraSpaces);
|
|
3922
|
-
return text
|
|
3923
|
-
.split('\n')
|
|
3924
|
-
.map((line) => (line.startsWith(prefix) ? line.slice(extraSpaces) : line))
|
|
3925
|
-
.join('\n');
|
|
3926
|
-
}
|
|
3927
|
-
function isFunctionLike(node) {
|
|
3928
|
-
return (node.type === AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
3929
|
-
node.type === AST_NODE_TYPES.FunctionDeclaration ||
|
|
3930
|
-
node.type === AST_NODE_TYPES.FunctionExpression);
|
|
3931
|
-
}
|
|
3932
|
-
function getObjectPropertyName(node) {
|
|
3933
|
-
if (node.computed) {
|
|
3934
|
-
return null;
|
|
3935
|
-
}
|
|
3936
|
-
if (node.key.type === AST_NODE_TYPES.Identifier) {
|
|
3937
|
-
return node.key.name;
|
|
3938
|
-
}
|
|
3939
|
-
return typeof node.key.value === 'string' ? node.key.value : null;
|
|
3940
|
-
}
|
|
3941
|
-
function getMemberExpressionPropertyName(node) {
|
|
3942
|
-
if (!node.computed && node.property.type === AST_NODE_TYPES.Identifier) {
|
|
3943
|
-
return node.property.name;
|
|
3944
|
-
}
|
|
3945
|
-
return node.property.type === AST_NODE_TYPES.Literal &&
|
|
3946
|
-
typeof node.property.value === 'string'
|
|
3947
|
-
? node.property.value
|
|
3948
|
-
: null;
|
|
3949
|
-
}
|
|
3950
|
-
function getEnclosingClassMember(node) {
|
|
3951
|
-
for (let current = node.parent; current; current = current.parent) {
|
|
3952
|
-
if (current.type === AST_NODE_TYPES.MethodDefinition ||
|
|
3953
|
-
current.type === AST_NODE_TYPES.PropertyDefinition) {
|
|
3954
|
-
return current;
|
|
3955
|
-
}
|
|
3956
|
-
}
|
|
3957
|
-
return null;
|
|
3958
|
-
}
|
|
3959
|
-
function getEnclosingFunction(node) {
|
|
3960
|
-
for (let current = node.parent; current; current = current.parent) {
|
|
3961
|
-
if (isFunctionLike(current)) {
|
|
3962
|
-
return current;
|
|
3963
|
-
}
|
|
3964
|
-
}
|
|
3965
|
-
return null;
|
|
3966
|
-
}
|
|
3967
|
-
function getClassMemberName(member) {
|
|
3968
|
-
if (member.key.type === AST_NODE_TYPES.Identifier) {
|
|
3969
|
-
return member.key.name;
|
|
3970
|
-
}
|
|
3971
|
-
if (member.key.type === AST_NODE_TYPES.Literal &&
|
|
3972
|
-
typeof member.key.value === 'string') {
|
|
3973
|
-
return member.key.value;
|
|
3974
|
-
}
|
|
3975
|
-
return null;
|
|
3976
|
-
}
|
|
3977
4234
|
function hasNamedDecorator(node, name) {
|
|
3978
4235
|
return node.decorators.some((decorator) => {
|
|
3979
4236
|
const expression = decorator.expression;
|
|
@@ -3985,68 +4242,75 @@ function hasNamedDecorator(node, name) {
|
|
|
3985
4242
|
expression.callee.name === name);
|
|
3986
4243
|
});
|
|
3987
4244
|
}
|
|
4245
|
+
|
|
3988
4246
|
function isPipeTransformMember(member) {
|
|
3989
4247
|
if (getClassMemberName(member) !== 'transform') {
|
|
3990
4248
|
return false;
|
|
3991
4249
|
}
|
|
3992
|
-
|
|
4250
|
+
const ownerClass = getEnclosingClass(member);
|
|
4251
|
+
return !!ownerClass && hasNamedDecorator(ownerClass, 'Pipe');
|
|
3993
4252
|
}
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
parent.left.type === AST_NODE_TYPES.MemberExpression) {
|
|
4003
|
-
const memberName = getMemberExpressionPropertyName(parent.left);
|
|
4004
|
-
if (memberName && IMPERATIVE_UNTRACKED_METHODS.has(memberName)) {
|
|
4005
|
-
return true;
|
|
4006
|
-
}
|
|
4007
|
-
}
|
|
4253
|
+
|
|
4254
|
+
function isAngularInjectionTokenFactoryFunction(fn, program) {
|
|
4255
|
+
const parent = fn.parent;
|
|
4256
|
+
const injectionTokenName = getLocalNameForImport(program, '@angular/core', 'InjectionToken');
|
|
4257
|
+
if (!injectionTokenName ||
|
|
4258
|
+
parent.type !== AST_NODE_TYPES.Property ||
|
|
4259
|
+
getObjectPropertyName(parent) !== 'factory') {
|
|
4260
|
+
return false;
|
|
4008
4261
|
}
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
!!memberName &&
|
|
4016
|
-
(IMPERATIVE_UNTRACKED_METHODS.has(memberName) ||
|
|
4017
|
-
isPipeTransformMember(member))) ||
|
|
4018
|
-
hasAllowedImperativeAssignment(node));
|
|
4262
|
+
const objectExpression = parent.parent;
|
|
4263
|
+
return (objectExpression.type === AST_NODE_TYPES.ObjectExpression &&
|
|
4264
|
+
objectExpression.parent.type === AST_NODE_TYPES.NewExpression &&
|
|
4265
|
+
objectExpression.parent.arguments.includes(objectExpression) &&
|
|
4266
|
+
objectExpression.parent.callee.type === AST_NODE_TYPES.Identifier &&
|
|
4267
|
+
objectExpression.parent.callee.name === injectionTokenName);
|
|
4019
4268
|
}
|
|
4020
|
-
function
|
|
4269
|
+
function isAngularUseFactoryFunction(fn) {
|
|
4021
4270
|
const parent = fn.parent;
|
|
4022
|
-
if (
|
|
4023
|
-
|
|
4271
|
+
if (parent.type !== AST_NODE_TYPES.Property ||
|
|
4272
|
+
getObjectPropertyName(parent) !== 'useFactory') {
|
|
4024
4273
|
return false;
|
|
4025
4274
|
}
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4275
|
+
const objectExpression = parent.parent;
|
|
4276
|
+
return (objectExpression.type === AST_NODE_TYPES.ObjectExpression &&
|
|
4277
|
+
objectExpression.properties.some((property) => property.type === AST_NODE_TYPES.Property &&
|
|
4278
|
+
getObjectPropertyName(property) === 'provide'));
|
|
4029
4279
|
}
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4280
|
+
|
|
4281
|
+
/**
|
|
4282
|
+
* Removes `extraSpaces` leading spaces from every line of `text` that starts
|
|
4283
|
+
* with at least that many spaces.
|
|
4284
|
+
*/
|
|
4285
|
+
function dedent(text, extraSpaces) {
|
|
4286
|
+
if (extraSpaces <= 0) {
|
|
4287
|
+
return text;
|
|
4288
|
+
}
|
|
4289
|
+
const prefix = ' '.repeat(extraSpaces);
|
|
4290
|
+
return text
|
|
4291
|
+
.split('\n')
|
|
4292
|
+
.map((line) => (line.startsWith(prefix) ? line.slice(extraSpaces) : line))
|
|
4293
|
+
.join('\n');
|
|
4294
|
+
}
|
|
4295
|
+
|
|
4296
|
+
function isDirectCallOrNewArgument(node) {
|
|
4297
|
+
const parent = node.parent;
|
|
4298
|
+
if (node.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
4299
|
+
node.type !== AST_NODE_TYPES.FunctionExpression) {
|
|
4300
|
+
return false;
|
|
4035
4301
|
}
|
|
4036
|
-
return
|
|
4302
|
+
return ((parent.type === AST_NODE_TYPES.CallExpression ||
|
|
4303
|
+
parent.type === AST_NODE_TYPES.NewExpression) &&
|
|
4304
|
+
parent.arguments.includes(node));
|
|
4037
4305
|
}
|
|
4038
|
-
function
|
|
4306
|
+
function isStoredFunctionUsedAsCallOrNewArgument(fn, checker, esTreeNodeToTSNodeMap) {
|
|
4039
4307
|
const parent = fn.parent;
|
|
4040
4308
|
if (parent.type !== AST_NODE_TYPES.VariableDeclarator ||
|
|
4041
4309
|
parent.id.type !== AST_NODE_TYPES.Identifier) {
|
|
4042
4310
|
return false;
|
|
4043
4311
|
}
|
|
4044
4312
|
const id = parent.id;
|
|
4045
|
-
const
|
|
4046
|
-
if (!tsNode) {
|
|
4047
|
-
return false;
|
|
4048
|
-
}
|
|
4049
|
-
const symbol = checker.getSymbolAtLocation(tsNode);
|
|
4313
|
+
const symbol = getSymbolAtNode(id, checker, esTreeNodeToTSNodeMap);
|
|
4050
4314
|
if (!symbol) {
|
|
4051
4315
|
return false;
|
|
4052
4316
|
}
|
|
@@ -4058,10 +4322,7 @@ function isStoredCallbackUsedAsArgument(fn, checker, esTreeNodeToTSNodeMap) {
|
|
|
4058
4322
|
node.name !== id.name) {
|
|
4059
4323
|
return;
|
|
4060
4324
|
}
|
|
4061
|
-
const
|
|
4062
|
-
const referenceSymbol = referenceTsNode
|
|
4063
|
-
? checker.getSymbolAtLocation(referenceTsNode)
|
|
4064
|
-
: null;
|
|
4325
|
+
const referenceSymbol = getSymbolAtNode(node, checker, esTreeNodeToTSNodeMap);
|
|
4065
4326
|
if (referenceSymbol !== symbol) {
|
|
4066
4327
|
return;
|
|
4067
4328
|
}
|
|
@@ -4076,81 +4337,50 @@ function isStoredCallbackUsedAsArgument(fn, checker, esTreeNodeToTSNodeMap) {
|
|
|
4076
4337
|
});
|
|
4077
4338
|
return found;
|
|
4078
4339
|
}
|
|
4340
|
+
|
|
4341
|
+
const IMPERATIVE_UNTRACKED_METHODS = new Set(['registerOnChange', 'writeValue']);
|
|
4342
|
+
function hasAllowedImperativeAssignment(node) {
|
|
4343
|
+
for (let current = node.parent; current; current = current.parent) {
|
|
4344
|
+
if (current.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
4345
|
+
current.type !== AST_NODE_TYPES.FunctionDeclaration &&
|
|
4346
|
+
current.type !== AST_NODE_TYPES.FunctionExpression) {
|
|
4347
|
+
continue;
|
|
4348
|
+
}
|
|
4349
|
+
const parent = current.parent;
|
|
4350
|
+
if (parent.type === AST_NODE_TYPES.AssignmentExpression &&
|
|
4351
|
+
parent.right === current &&
|
|
4352
|
+
parent.left.type === AST_NODE_TYPES.MemberExpression) {
|
|
4353
|
+
const memberName = getMemberExpressionPropertyName(parent.left);
|
|
4354
|
+
if (memberName && IMPERATIVE_UNTRACKED_METHODS.has(memberName)) {
|
|
4355
|
+
return true;
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4358
|
+
}
|
|
4359
|
+
return false;
|
|
4360
|
+
}
|
|
4361
|
+
function isAllowedImperativeAngularContext(node) {
|
|
4362
|
+
const member = getEnclosingClassMember(node);
|
|
4363
|
+
const memberName = member ? getClassMemberName(member) : null;
|
|
4364
|
+
return ((!!member &&
|
|
4365
|
+
!!memberName &&
|
|
4366
|
+
(IMPERATIVE_UNTRACKED_METHODS.has(memberName) ||
|
|
4367
|
+
isPipeTransformMember(member))) ||
|
|
4368
|
+
hasAllowedImperativeAssignment(node));
|
|
4369
|
+
}
|
|
4079
4370
|
function isAllowedDeferredCallbackContext(node, checker, esTreeNodeToTSNodeMap) {
|
|
4080
|
-
|
|
4081
|
-
if (!arg ||
|
|
4082
|
-
arg.type === AST_NODE_TYPES.SpreadElement ||
|
|
4083
|
-
(arg.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
4084
|
-
arg.type !== AST_NODE_TYPES.FunctionExpression)) {
|
|
4371
|
+
if (!getReactiveCallbackArgument(node)) {
|
|
4085
4372
|
return false;
|
|
4086
4373
|
}
|
|
4087
4374
|
const fn = getEnclosingFunction(node);
|
|
4088
4375
|
if (!fn) {
|
|
4089
4376
|
return false;
|
|
4090
4377
|
}
|
|
4091
|
-
return (
|
|
4092
|
-
|
|
4093
|
-
}
|
|
4094
|
-
function isAngularInjectionTokenFactoryFunction(fn, program) {
|
|
4095
|
-
const parent = fn.parent;
|
|
4096
|
-
const injectionTokenName = getLocalNameForImport(program, '@angular/core', 'InjectionToken');
|
|
4097
|
-
if (!injectionTokenName ||
|
|
4098
|
-
parent.type !== AST_NODE_TYPES.Property ||
|
|
4099
|
-
getObjectPropertyName(parent) !== 'factory') {
|
|
4100
|
-
return false;
|
|
4101
|
-
}
|
|
4102
|
-
const objectExpression = parent.parent;
|
|
4103
|
-
return (objectExpression.type === AST_NODE_TYPES.ObjectExpression &&
|
|
4104
|
-
objectExpression.parent.type === AST_NODE_TYPES.NewExpression &&
|
|
4105
|
-
objectExpression.parent.arguments.includes(objectExpression) &&
|
|
4106
|
-
objectExpression.parent.callee.type === AST_NODE_TYPES.Identifier &&
|
|
4107
|
-
objectExpression.parent.callee.name === injectionTokenName);
|
|
4108
|
-
}
|
|
4109
|
-
function isAngularUseFactoryFunction(fn) {
|
|
4110
|
-
const parent = fn.parent;
|
|
4111
|
-
if (parent.type !== AST_NODE_TYPES.Property ||
|
|
4112
|
-
getObjectPropertyName(parent) !== 'useFactory') {
|
|
4113
|
-
return false;
|
|
4114
|
-
}
|
|
4115
|
-
const objectExpression = parent.parent;
|
|
4116
|
-
return (objectExpression.type === AST_NODE_TYPES.ObjectExpression &&
|
|
4117
|
-
objectExpression.properties.some((property) => property.type === AST_NODE_TYPES.Property &&
|
|
4118
|
-
getObjectPropertyName(property) === 'provide'));
|
|
4119
|
-
}
|
|
4120
|
-
function isReactiveOwnerCall(node, program) {
|
|
4121
|
-
return (node.type === AST_NODE_TYPES.CallExpression &&
|
|
4122
|
-
getReactiveScopes(node, program).length > 0);
|
|
4123
|
-
}
|
|
4124
|
-
function getFixableReactiveCall(node, program) {
|
|
4125
|
-
const [arg] = node.arguments;
|
|
4126
|
-
if (!arg ||
|
|
4127
|
-
arg.type === AST_NODE_TYPES.SpreadElement ||
|
|
4128
|
-
(arg.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
4129
|
-
arg.type !== AST_NODE_TYPES.FunctionExpression)) {
|
|
4130
|
-
return null;
|
|
4131
|
-
}
|
|
4132
|
-
if (isReactiveOwnerCall(arg.body, program)) {
|
|
4133
|
-
return arg.body;
|
|
4134
|
-
}
|
|
4135
|
-
if (arg.body.type !== AST_NODE_TYPES.BlockStatement || arg.body.body.length !== 1) {
|
|
4136
|
-
return null;
|
|
4137
|
-
}
|
|
4138
|
-
const [statement] = arg.body.body;
|
|
4139
|
-
if (statement?.type === AST_NODE_TYPES.ReturnStatement &&
|
|
4140
|
-
statement.argument &&
|
|
4141
|
-
isReactiveOwnerCall(statement.argument, program)) {
|
|
4142
|
-
return statement.argument;
|
|
4143
|
-
}
|
|
4144
|
-
if (node.parent.type === AST_NODE_TYPES.ExpressionStatement &&
|
|
4145
|
-
statement?.type === AST_NODE_TYPES.ExpressionStatement &&
|
|
4146
|
-
isReactiveOwnerCall(statement.expression, program)) {
|
|
4147
|
-
return statement.expression;
|
|
4148
|
-
}
|
|
4149
|
-
return null;
|
|
4378
|
+
return (isDirectCallOrNewArgument(fn) ||
|
|
4379
|
+
isStoredFunctionUsedAsCallOrNewArgument(fn, checker, esTreeNodeToTSNodeMap));
|
|
4150
4380
|
}
|
|
4151
4381
|
function isAllowedLazyAngularFactoryContext(node, program) {
|
|
4152
4382
|
const fn = getEnclosingFunction(node);
|
|
4153
|
-
if (!fn || !
|
|
4383
|
+
if (!fn || !getReturnedReactiveOwnerCall(node, program)) {
|
|
4154
4384
|
return false;
|
|
4155
4385
|
}
|
|
4156
4386
|
return (isAngularInjectionTokenFactoryFunction(fn, program) ||
|
|
@@ -4162,16 +4392,12 @@ function buildReactiveCallReplacement(outerUntrackedCall, reactiveCall, sourceCo
|
|
|
4162
4392
|
outerUntrackedCall.parent.type !== AST_NODE_TYPES.ExpressionStatement) {
|
|
4163
4393
|
return text;
|
|
4164
4394
|
}
|
|
4165
|
-
return dedent
|
|
4395
|
+
return dedent(text, reactiveCall.loc.start.column - outerUntrackedCall.parent.loc.start.column);
|
|
4166
4396
|
}
|
|
4167
4397
|
const rule$8 = createUntrackedRule({
|
|
4168
4398
|
create(context) {
|
|
4169
|
-
const
|
|
4170
|
-
const
|
|
4171
|
-
const esTreeNodeToTSNodeMap = parserServices.esTreeNodeToTSNodeMap;
|
|
4172
|
-
const { sourceCode } = context;
|
|
4173
|
-
const program = sourceCode.ast;
|
|
4174
|
-
const getUntrackedLocalName = () => findUntrackedAlias(program);
|
|
4399
|
+
const { checker, esTreeNodeToTSNodeMap, program, sourceCode } = getTypeAwareRuleContext(context);
|
|
4400
|
+
const signalNodeMap = esTreeNodeToTSNodeMap;
|
|
4175
4401
|
function isUntrackedUsedElsewhere(localName, excludeNode) {
|
|
4176
4402
|
let found = false;
|
|
4177
4403
|
walkAst(program, (node) => {
|
|
@@ -4192,18 +4418,18 @@ const rule$8 = createUntrackedRule({
|
|
|
4192
4418
|
findEnclosingReactiveScope(node, program) ||
|
|
4193
4419
|
findEnclosingReactiveScopeAfterAsyncBoundary(node, program) ||
|
|
4194
4420
|
isAllowedImperativeAngularContext(node) ||
|
|
4195
|
-
isAllowedDeferredCallbackContext(node, checker,
|
|
4421
|
+
isAllowedDeferredCallbackContext(node, checker, signalNodeMap) ||
|
|
4196
4422
|
isAllowedLazyAngularFactoryContext(node, program)) {
|
|
4197
4423
|
return;
|
|
4198
4424
|
}
|
|
4199
|
-
const reactiveCall =
|
|
4425
|
+
const reactiveCall = getReturnedReactiveOwnerCall(node, program);
|
|
4200
4426
|
context.report({
|
|
4201
4427
|
fix: reactiveCall
|
|
4202
4428
|
? (fixer) => {
|
|
4203
4429
|
const fixes = [
|
|
4204
4430
|
fixer.replaceText(node, buildReactiveCallReplacement(node, reactiveCall, sourceCode)),
|
|
4205
4431
|
];
|
|
4206
|
-
const untrackedLocalName =
|
|
4432
|
+
const untrackedLocalName = findUntrackedAlias(program);
|
|
4207
4433
|
const stillUsed = untrackedLocalName !== null &&
|
|
4208
4434
|
isUntrackedUsedElsewhere(untrackedLocalName, node);
|
|
4209
4435
|
if (!stillUsed) {
|
|
@@ -4235,20 +4461,16 @@ const rule$8 = createUntrackedRule({
|
|
|
4235
4461
|
name: 'no-untracked-outside-reactive-context',
|
|
4236
4462
|
});
|
|
4237
4463
|
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
const prefix = ' '.repeat(extraSpaces);
|
|
4247
|
-
return text
|
|
4248
|
-
.split('\n')
|
|
4249
|
-
.map((line) => (line.startsWith(prefix) ? line.slice(extraSpaces) : line))
|
|
4250
|
-
.join('\n');
|
|
4464
|
+
function collectCallExpressions(root) {
|
|
4465
|
+
const result = [];
|
|
4466
|
+
walkAst(root, (node) => {
|
|
4467
|
+
if (node.type === AST_NODE_TYPES.CallExpression) {
|
|
4468
|
+
result.push(node);
|
|
4469
|
+
}
|
|
4470
|
+
});
|
|
4471
|
+
return result;
|
|
4251
4472
|
}
|
|
4473
|
+
|
|
4252
4474
|
/**
|
|
4253
4475
|
* Builds the replacement text for the parent ExpressionStatement of an
|
|
4254
4476
|
* `untracked(...)` call.
|
|
@@ -4260,13 +4482,11 @@ function dedent(text, extraSpaces) {
|
|
|
4260
4482
|
* Returns null if the untracked argument is not a function expression.
|
|
4261
4483
|
*/
|
|
4262
4484
|
function buildReplacement(untrackedCall, parentStatement, sourceCode) {
|
|
4263
|
-
const
|
|
4264
|
-
if (!
|
|
4265
|
-
(arg.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
4266
|
-
arg.type !== AST_NODE_TYPES.FunctionExpression)) {
|
|
4485
|
+
const callback = getReactiveCallbackArgument(untrackedCall);
|
|
4486
|
+
if (!callback) {
|
|
4267
4487
|
return null;
|
|
4268
4488
|
}
|
|
4269
|
-
const { body } =
|
|
4489
|
+
const { body } = callback;
|
|
4270
4490
|
if (body.type === AST_NODE_TYPES.BlockStatement) {
|
|
4271
4491
|
const { body: stmts } = body;
|
|
4272
4492
|
if (stmts.length === 0) {
|
|
@@ -4281,15 +4501,6 @@ function buildReplacement(untrackedCall, parentStatement, sourceCode) {
|
|
|
4281
4501
|
// Expression body: arrow `() => expr` — just emit `expr;`
|
|
4282
4502
|
return `${sourceCode.getText(body)};`;
|
|
4283
4503
|
}
|
|
4284
|
-
function getAllCallExpressions(root) {
|
|
4285
|
-
const result = [];
|
|
4286
|
-
walkAst(root, (node) => {
|
|
4287
|
-
if (node.type === AST_NODE_TYPES.CallExpression) {
|
|
4288
|
-
result.push(node);
|
|
4289
|
-
}
|
|
4290
|
-
});
|
|
4291
|
-
return result;
|
|
4292
|
-
}
|
|
4293
4504
|
function hasOpaqueSynchronousCalls(root, checker, esTreeNodeToTSNodeMap, program) {
|
|
4294
4505
|
let found = false;
|
|
4295
4506
|
walkSynchronousAst(root, (node) => {
|
|
@@ -4313,27 +4524,21 @@ function hasOpaqueSynchronousCalls(root, checker, esTreeNodeToTSNodeMap, program
|
|
|
4313
4524
|
}
|
|
4314
4525
|
const rule$7 = createUntrackedRule({
|
|
4315
4526
|
create(context) {
|
|
4316
|
-
const
|
|
4317
|
-
const
|
|
4318
|
-
const esTreeNodeToTSNodeMap = parserServices.esTreeNodeToTSNodeMap;
|
|
4319
|
-
const { sourceCode } = context;
|
|
4320
|
-
const program = sourceCode.ast;
|
|
4321
|
-
const getUntrackedLocalName = () => getLocalNameForImport(program, '@angular/core', 'untracked');
|
|
4527
|
+
const { checker, esTreeNodeToTSNodeMap, program, sourceCode } = getTypeAwareRuleContext(context);
|
|
4528
|
+
const signalNodeMap = esTreeNodeToTSNodeMap;
|
|
4322
4529
|
function isUntrackedUsedElsewhere(localName, excludeNode) {
|
|
4323
|
-
return
|
|
4530
|
+
return collectCallExpressions(program).some((n) => n !== excludeNode &&
|
|
4324
4531
|
n.callee.type === AST_NODE_TYPES.Identifier &&
|
|
4325
4532
|
n.callee.name === localName);
|
|
4326
4533
|
}
|
|
4327
4534
|
function checkUntrackedCall(untrackedCall, kind) {
|
|
4328
|
-
const
|
|
4329
|
-
if (!
|
|
4330
|
-
(arg.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
4331
|
-
arg.type !== AST_NODE_TYPES.FunctionExpression)) {
|
|
4535
|
+
const callback = getReactiveCallbackArgument(untrackedCall);
|
|
4536
|
+
if (!callback) {
|
|
4332
4537
|
return;
|
|
4333
4538
|
}
|
|
4334
|
-
const { reads } = collectSignalUsages(
|
|
4539
|
+
const { reads } = collectSignalUsages(callback, checker, signalNodeMap, program);
|
|
4335
4540
|
if (reads.length > 0 ||
|
|
4336
|
-
hasOpaqueSynchronousCalls(
|
|
4541
|
+
hasOpaqueSynchronousCalls(callback, checker, signalNodeMap, program)) {
|
|
4337
4542
|
// Snapshot reads inside reactive callbacks are a valid Angular
|
|
4338
4543
|
// pattern even when the snapshot later influences branching.
|
|
4339
4544
|
return;
|
|
@@ -4351,7 +4556,7 @@ const rule$7 = createUntrackedRule({
|
|
|
4351
4556
|
if (replacement === null) {
|
|
4352
4557
|
return null;
|
|
4353
4558
|
}
|
|
4354
|
-
const untrackedLocalName =
|
|
4559
|
+
const untrackedLocalName = findUntrackedAlias(program);
|
|
4355
4560
|
const stillUsed = untrackedLocalName !== null &&
|
|
4356
4561
|
isUntrackedUsedElsewhere(untrackedLocalName, untrackedCall);
|
|
4357
4562
|
const fixes = [fixer.replaceText(parentStmt, replacement)];
|
|
@@ -4654,22 +4859,6 @@ const rule$6 = createRule$6({
|
|
|
4654
4859
|
|
|
4655
4860
|
const createRule$5 = ESLintUtils.RuleCreator((name) => name);
|
|
4656
4861
|
const EMPTY_ARGUMENT = '__EMPTY_ARGUMENT__';
|
|
4657
|
-
function getParenthesizedInner(node) {
|
|
4658
|
-
const maybeNode = node;
|
|
4659
|
-
if (maybeNode.type === 'ParenthesizedExpression') {
|
|
4660
|
-
return maybeNode.expression ?? null;
|
|
4661
|
-
}
|
|
4662
|
-
return null;
|
|
4663
|
-
}
|
|
4664
|
-
function unwrapParenthesized(node) {
|
|
4665
|
-
let current = node;
|
|
4666
|
-
let inner = getParenthesizedInner(current);
|
|
4667
|
-
while (inner) {
|
|
4668
|
-
current = inner;
|
|
4669
|
-
inner = getParenthesizedInner(current);
|
|
4670
|
-
}
|
|
4671
|
-
return current;
|
|
4672
|
-
}
|
|
4673
4862
|
function isSupportedControlFlowStatement(node) {
|
|
4674
4863
|
return (node.type === AST_NODE_TYPES.BreakStatement ||
|
|
4675
4864
|
node.type === AST_NODE_TYPES.ContinueStatement ||
|
|
@@ -5309,17 +5498,6 @@ function unwrapUsageExpression(node) {
|
|
|
5309
5498
|
}
|
|
5310
5499
|
return current;
|
|
5311
5500
|
}
|
|
5312
|
-
function isNodeInside(node, ancestor) {
|
|
5313
|
-
for (let current = node; current; current = current.parent) {
|
|
5314
|
-
if (current === ancestor) {
|
|
5315
|
-
return true;
|
|
5316
|
-
}
|
|
5317
|
-
}
|
|
5318
|
-
return false;
|
|
5319
|
-
}
|
|
5320
|
-
function isNodeInsideAny(node, ancestors) {
|
|
5321
|
-
return ancestors.some((ancestor) => isNodeInside(node, ancestor));
|
|
5322
|
-
}
|
|
5323
5501
|
function isStatementPositionCall(node) {
|
|
5324
5502
|
const usage = unwrapUsageExpression(node);
|
|
5325
5503
|
if (usage.parent?.type === AST_NODE_TYPES.ExpressionStatement) {
|
|
@@ -5372,11 +5550,7 @@ function isAliasDeclarationIdentifier(node) {
|
|
|
5372
5550
|
node.parent.id === node);
|
|
5373
5551
|
}
|
|
5374
5552
|
function aliasHasExternalUsage(alias, consumers, scope, checker, esTreeNodeToTSNodeMap) {
|
|
5375
|
-
const
|
|
5376
|
-
if (!tsNode) {
|
|
5377
|
-
return false;
|
|
5378
|
-
}
|
|
5379
|
-
const symbol = checker.getSymbolAtLocation(tsNode);
|
|
5553
|
+
const symbol = getSymbolAtNode(alias, checker, esTreeNodeToTSNodeMap);
|
|
5380
5554
|
if (!symbol) {
|
|
5381
5555
|
return false;
|
|
5382
5556
|
}
|
|
@@ -5388,10 +5562,7 @@ function aliasHasExternalUsage(alias, consumers, scope, checker, esTreeNodeToTSN
|
|
|
5388
5562
|
isNodeInsideAny(node, consumers)) {
|
|
5389
5563
|
return;
|
|
5390
5564
|
}
|
|
5391
|
-
const
|
|
5392
|
-
const referenceSymbol = referenceTsNode
|
|
5393
|
-
? checker.getSymbolAtLocation(referenceTsNode)
|
|
5394
|
-
: null;
|
|
5565
|
+
const referenceSymbol = getSymbolAtNode(node, checker, esTreeNodeToTSNodeMap);
|
|
5395
5566
|
if (referenceSymbol !== symbol) {
|
|
5396
5567
|
return;
|
|
5397
5568
|
}
|
|
@@ -5418,11 +5589,7 @@ function resolveSignalReadAlias(expression, context, seen = new Set()) {
|
|
|
5418
5589
|
if (unwrapped.type !== AST_NODE_TYPES.Identifier) {
|
|
5419
5590
|
return null;
|
|
5420
5591
|
}
|
|
5421
|
-
const
|
|
5422
|
-
if (!tsNode) {
|
|
5423
|
-
return null;
|
|
5424
|
-
}
|
|
5425
|
-
const symbol = context.checker.getSymbolAtLocation(tsNode);
|
|
5592
|
+
const symbol = getSymbolAtNode(unwrapped, context.checker, context.esTreeNodeToTSNodeMap);
|
|
5426
5593
|
if (!symbol) {
|
|
5427
5594
|
return null;
|
|
5428
5595
|
}
|
|
@@ -5510,12 +5677,9 @@ function collectSuspiciousReads(scope, checker, esTreeNodeToTSNodeMap, tsNodeToE
|
|
|
5510
5677
|
}
|
|
5511
5678
|
const rule$3 = createUntrackedRule({
|
|
5512
5679
|
create(context) {
|
|
5513
|
-
const
|
|
5514
|
-
const
|
|
5515
|
-
const
|
|
5516
|
-
const tsNodeToESTreeNodeMap = parserServices.tsNodeToESTreeNodeMap;
|
|
5517
|
-
const { sourceCode } = context;
|
|
5518
|
-
const program = sourceCode.ast;
|
|
5680
|
+
const { checker, esTreeNodeToTSNodeMap, program, sourceCode, tsNodeToESTreeNodeMap, } = getTypeAwareRuleContext(context);
|
|
5681
|
+
const signalNodeMap = esTreeNodeToTSNodeMap;
|
|
5682
|
+
const estreeNodeMap = tsNodeToESTreeNodeMap;
|
|
5519
5683
|
function buildFix(read) {
|
|
5520
5684
|
const untrackedAlias = findUntrackedAlias(program);
|
|
5521
5685
|
const alreadyHasUntracked = untrackedAlias !== null;
|
|
@@ -5531,13 +5695,13 @@ const rule$3 = createUntrackedRule({
|
|
|
5531
5695
|
return {
|
|
5532
5696
|
CallExpression(node) {
|
|
5533
5697
|
for (const scope of getReactiveScopes(node, program)) {
|
|
5534
|
-
const suspicious = collectSuspiciousReads(scope, checker,
|
|
5535
|
-
const { reads: trackedReads } = collectSignalUsages(scope.callback, checker,
|
|
5698
|
+
const suspicious = collectSuspiciousReads(scope, checker, signalNodeMap, estreeNodeMap, program);
|
|
5699
|
+
const { reads: trackedReads } = collectSignalUsages(scope.callback, checker, signalNodeMap, program);
|
|
5536
5700
|
const suspiciousReads = new Set(suspicious.map(({ read }) => read));
|
|
5537
5701
|
for (const { aliases, consumers, read } of suspicious) {
|
|
5538
5702
|
const hasTrackedDependency = consumers.some((consumer) => trackedReads.some((trackedRead) => !suspiciousReads.has(trackedRead) &&
|
|
5539
5703
|
!isNodeInside(trackedRead, consumer)));
|
|
5540
|
-
const hasExternalAliasUsage = aliases.some((alias) => aliasHasExternalUsage(alias, consumers, scope, checker,
|
|
5704
|
+
const hasExternalAliasUsage = aliases.some((alias) => aliasHasExternalUsage(alias, consumers, scope, checker, signalNodeMap));
|
|
5541
5705
|
if (!hasTrackedDependency || hasExternalAliasUsage) {
|
|
5542
5706
|
continue;
|
|
5543
5707
|
}
|
|
@@ -5569,7 +5733,7 @@ const rule$3 = createUntrackedRule({
|
|
|
5569
5733
|
|
|
5570
5734
|
function getReturnedExpression(node) {
|
|
5571
5735
|
if (node.body.type !== AST_NODE_TYPES.BlockStatement) {
|
|
5572
|
-
return
|
|
5736
|
+
return node.body;
|
|
5573
5737
|
}
|
|
5574
5738
|
if (node.body.body.length !== 1) {
|
|
5575
5739
|
return null;
|
|
@@ -5578,17 +5742,16 @@ function getReturnedExpression(node) {
|
|
|
5578
5742
|
if (statement?.type !== AST_NODE_TYPES.ReturnStatement || !statement.argument) {
|
|
5579
5743
|
return null;
|
|
5580
5744
|
}
|
|
5581
|
-
return
|
|
5745
|
+
return statement.argument;
|
|
5582
5746
|
}
|
|
5747
|
+
|
|
5583
5748
|
function getWrappedSignalGetter(node, checker, esTreeNodeToTSNodeMap) {
|
|
5584
|
-
const
|
|
5585
|
-
if (!
|
|
5586
|
-
arg.type === AST_NODE_TYPES.SpreadElement ||
|
|
5587
|
-
(arg.type !== AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
5588
|
-
arg.type !== AST_NODE_TYPES.FunctionExpression)) {
|
|
5749
|
+
const callback = getReactiveCallbackArgument(node);
|
|
5750
|
+
if (!callback) {
|
|
5589
5751
|
return null;
|
|
5590
5752
|
}
|
|
5591
|
-
const
|
|
5753
|
+
const returnedExpression = getReturnedExpression(callback);
|
|
5754
|
+
const body = returnedExpression ? unwrapExpression(returnedExpression) : null;
|
|
5592
5755
|
if (body?.type !== AST_NODE_TYPES.CallExpression ||
|
|
5593
5756
|
!isSignalReadCall(body, checker, esTreeNodeToTSNodeMap)) {
|
|
5594
5757
|
return null;
|
|
@@ -5602,17 +5765,14 @@ function getWrappedSignalGetter(node, checker, esTreeNodeToTSNodeMap) {
|
|
|
5602
5765
|
}
|
|
5603
5766
|
const rule$2 = createUntrackedRule({
|
|
5604
5767
|
create(context) {
|
|
5605
|
-
const
|
|
5606
|
-
const
|
|
5607
|
-
const esTreeNodeToTSNodeMap = parserServices.esTreeNodeToTSNodeMap;
|
|
5608
|
-
const { sourceCode } = context;
|
|
5609
|
-
const program = sourceCode.ast;
|
|
5768
|
+
const { checker, esTreeNodeToTSNodeMap, program, sourceCode } = getTypeAwareRuleContext(context);
|
|
5769
|
+
const signalNodeMap = esTreeNodeToTSNodeMap;
|
|
5610
5770
|
return {
|
|
5611
5771
|
CallExpression(node) {
|
|
5612
5772
|
if (!isAngularUntrackedCall(node, program)) {
|
|
5613
5773
|
return;
|
|
5614
5774
|
}
|
|
5615
|
-
const getter = getWrappedSignalGetter(node, checker,
|
|
5775
|
+
const getter = getWrappedSignalGetter(node, checker, signalNodeMap);
|
|
5616
5776
|
if (!getter) {
|
|
5617
5777
|
return;
|
|
5618
5778
|
}
|
|
@@ -5639,13 +5799,6 @@ const rule$2 = createUntrackedRule({
|
|
|
5639
5799
|
name: 'prefer-untracked-signal-getter',
|
|
5640
5800
|
});
|
|
5641
5801
|
|
|
5642
|
-
function getImportedName(spec) {
|
|
5643
|
-
if (spec.imported.type === AST_NODE_TYPES.Identifier) {
|
|
5644
|
-
return spec.imported.name;
|
|
5645
|
-
}
|
|
5646
|
-
return spec.imported.value;
|
|
5647
|
-
}
|
|
5648
|
-
|
|
5649
5802
|
function isImportsArrayProperty(property) {
|
|
5650
5803
|
const isProperty = property?.type === AST_NODE_TYPES.Property;
|
|
5651
5804
|
const hasIdentifierKey = property?.key.type === AST_NODE_TYPES.Identifier &&
|
|
@@ -5653,6 +5806,13 @@ function isImportsArrayProperty(property) {
|
|
|
5653
5806
|
return isProperty && hasIdentifierKey && isArray(property.value);
|
|
5654
5807
|
}
|
|
5655
5808
|
|
|
5809
|
+
function getImportedName(spec) {
|
|
5810
|
+
if (spec.imported.type === AST_NODE_TYPES.Identifier) {
|
|
5811
|
+
return spec.imported.name;
|
|
5812
|
+
}
|
|
5813
|
+
return spec.imported.value;
|
|
5814
|
+
}
|
|
5815
|
+
|
|
5656
5816
|
const MESSAGE_ID = 'replaceTuiImport';
|
|
5657
5817
|
const DEFAULT_DECORATORS = ['Component', 'Directive', 'NgModule', 'Pipe'];
|
|
5658
5818
|
const DEFAULT_EXCEPTIONS = [
|
|
@@ -6063,14 +6223,15 @@ const plugin = {
|
|
|
6063
6223
|
'class-property-naming': classPropertyNaming,
|
|
6064
6224
|
'decorator-key-sort': config$3,
|
|
6065
6225
|
'flat-exports': flatExports,
|
|
6066
|
-
'host-attributes-sort': rule$
|
|
6226
|
+
'host-attributes-sort': rule$k,
|
|
6067
6227
|
'html-logical-properties': config$2,
|
|
6068
|
-
'injection-token-description': rule$
|
|
6069
|
-
'no-deep-imports': rule$
|
|
6228
|
+
'injection-token-description': rule$j,
|
|
6229
|
+
'no-deep-imports': rule$i,
|
|
6070
6230
|
'no-deep-imports-to-indexed-packages': noDeepImportsToIndexedPackages,
|
|
6071
|
-
'no-fully-untracked-effect': rule$
|
|
6231
|
+
'no-fully-untracked-effect': rule$h,
|
|
6072
6232
|
'no-href-with-router-link': config$1,
|
|
6073
|
-
'no-implicit-public': rule$
|
|
6233
|
+
'no-implicit-public': rule$g,
|
|
6234
|
+
'no-infinite-loop': rule$f,
|
|
6074
6235
|
'no-legacy-peer-deps': rule$e,
|
|
6075
6236
|
'no-playwright-empty-fill': rule$d,
|
|
6076
6237
|
'no-project-as-in-ng-template': config,
|