@taiga-ui/eslint-plugin-experience-next 0.509.0 → 0.511.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 -0
- package/index.d.ts +3 -0
- package/index.esm.js +169 -61
- package/package.json +1 -1
- package/rules/recommended/no-signal-outside-class.d.ts +4 -0
- package/rules/utils/ast/ast-expressions.d.ts +4 -3
package/README.md
CHANGED
|
@@ -103,6 +103,7 @@ from third-party plugins. The exact severities and file globs live in
|
|
|
103
103
|
| [no-redundant-type-annotation](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-redundant-type-annotation.md) | Disallow redundant type annotations when the type is already inferred from the initializer | ✅ | 🔧 | |
|
|
104
104
|
| [no-repeated-signal-in-conditional](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-repeated-signal-in-conditional.md) | Disallow reading the same nullable Angular signal more than once in a conditional expression | ✅ | 🔧 | |
|
|
105
105
|
| [no-side-effects-in-computed](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-side-effects-in-computed.md) | Disallow side effects and effectful helper calls inside Angular `computed()` callbacks | ✅ | | |
|
|
106
|
+
| [no-signal-outside-class](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-signal-outside-class.md) | Disallow class properties that reference a module-scope Angular signal | ✅ | | |
|
|
106
107
|
| [no-signal-reads-after-await-in-reactive-context](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-signal-reads-after-await-in-reactive-context.md) | Disallow bare signal reads after `await` inside reactive callbacks | ✅ | | |
|
|
107
108
|
| [no-string-literal-concat](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-string-literal-concat.md) | Disallow string literal concatenation; merge adjacent literals into one | ✅ | 🔧 | |
|
|
108
109
|
| [no-untracked-outside-reactive-context](https://github.com/taiga-family/toolkit/tree/main/projects/eslint-plugin-experience-next/docs/no-untracked-outside-reactive-context.md) | Disallow `untracked()` outside reactive callbacks, except explicit post-`await` snapshots | ✅ | 🔧 | |
|
package/index.d.ts
CHANGED
|
@@ -125,6 +125,9 @@ declare const plugin: {
|
|
|
125
125
|
'no-side-effects-in-computed': import("@typescript-eslint/utils/ts-eslint").RuleModule<"sideEffectInComputed", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
126
126
|
name: string;
|
|
127
127
|
};
|
|
128
|
+
'no-signal-outside-class': import("@typescript-eslint/utils/ts-eslint").RuleModule<"noSignalOutsideClass", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
129
|
+
name: string;
|
|
130
|
+
};
|
|
128
131
|
'no-signal-reads-after-await-in-reactive-context': import("@typescript-eslint/utils/ts-eslint").RuleModule<"readAfterAwait", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
129
132
|
name: string;
|
|
130
133
|
};
|
package/index.esm.js
CHANGED
|
@@ -1210,6 +1210,7 @@ var recommended = defineConfig([
|
|
|
1210
1210
|
'@taiga-ui/experience-next/no-redundant-type-annotation': 'error',
|
|
1211
1211
|
'@taiga-ui/experience-next/no-repeated-signal-in-conditional': 'error',
|
|
1212
1212
|
'@taiga-ui/experience-next/no-side-effects-in-computed': 'error',
|
|
1213
|
+
'@taiga-ui/experience-next/no-signal-outside-class': 'error',
|
|
1213
1214
|
'@taiga-ui/experience-next/no-signal-reads-after-await-in-reactive-context': 'error',
|
|
1214
1215
|
'@taiga-ui/experience-next/no-untracked-outside-reactive-context': 'error',
|
|
1215
1216
|
'@taiga-ui/experience-next/no-useless-untracked': 'error',
|
|
@@ -46224,7 +46225,7 @@ function buildMultilineStartTag(node, sourceText) {
|
|
|
46224
46225
|
closing,
|
|
46225
46226
|
].join('\n');
|
|
46226
46227
|
}
|
|
46227
|
-
const rule$
|
|
46228
|
+
const rule$T = createRule({
|
|
46228
46229
|
name: 'attrs-newline',
|
|
46229
46230
|
rule: {
|
|
46230
46231
|
create(context) {
|
|
@@ -46404,7 +46405,7 @@ function sameOrder(a, b) {
|
|
|
46404
46405
|
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
46405
46406
|
}
|
|
46406
46407
|
|
|
46407
|
-
const rule$
|
|
46408
|
+
const rule$S = createRule({
|
|
46408
46409
|
create(context, [order]) {
|
|
46409
46410
|
const decorators = new Set(Object.keys(order));
|
|
46410
46411
|
return {
|
|
@@ -46540,7 +46541,7 @@ function getNodeLabel(node) {
|
|
|
46540
46541
|
}
|
|
46541
46542
|
return node instanceof dist$4.TmplAstBoundText ? 'binding' : 'text';
|
|
46542
46543
|
}
|
|
46543
|
-
const rule$
|
|
46544
|
+
const rule$R = createRule({
|
|
46544
46545
|
name: 'element-newline',
|
|
46545
46546
|
rule: {
|
|
46546
46547
|
create(context) {
|
|
@@ -46679,7 +46680,7 @@ const PRESETS = {
|
|
|
46679
46680
|
$VUE: ['$CLASS', '$ID', '$VUE_ATTRIBUTE'],
|
|
46680
46681
|
$VUE_ATTRIBUTE: /^v-/,
|
|
46681
46682
|
};
|
|
46682
|
-
const rule$
|
|
46683
|
+
const rule$Q = createRule({
|
|
46683
46684
|
create(context, [options]) {
|
|
46684
46685
|
const sourceCode = context.sourceCode;
|
|
46685
46686
|
const settings = {
|
|
@@ -47010,7 +47011,7 @@ const config$4 = {
|
|
|
47010
47011
|
type: 'suggestion',
|
|
47011
47012
|
},
|
|
47012
47013
|
};
|
|
47013
|
-
const rule$
|
|
47014
|
+
const rule$P = createRule({
|
|
47014
47015
|
name: 'html-logical-properties',
|
|
47015
47016
|
rule: config$4,
|
|
47016
47017
|
});
|
|
@@ -248252,7 +248253,7 @@ function isImportUsedOnlyAsAngularDiFirstArg(node, sourceCode) {
|
|
|
248252
248253
|
}
|
|
248253
248254
|
return hasSafeRuntimeUsage;
|
|
248254
248255
|
}
|
|
248255
|
-
const rule$
|
|
248256
|
+
const rule$O = createRule({
|
|
248256
248257
|
create(context) {
|
|
248257
248258
|
const { checker, esTreeNodeToTSNodeMap, sourceCode, tsProgram } = getTypeAwareRuleContext(context);
|
|
248258
248259
|
const checkCycles = context.options[0]?.checkCycles ?? true;
|
|
@@ -248977,7 +248978,7 @@ function getNgDevModeDeclarationFix(program, fixer) {
|
|
|
248977
248978
|
? fixer.insertTextBefore(firstStatement, 'declare const ngDevMode: boolean;\n\n')
|
|
248978
248979
|
: fixer.insertTextBeforeRange([0, 0], 'declare const ngDevMode: boolean;\n');
|
|
248979
248980
|
}
|
|
248980
|
-
const rule$
|
|
248981
|
+
const rule$N = createRule({
|
|
248981
248982
|
create(context) {
|
|
248982
248983
|
const { sourceCode } = context;
|
|
248983
248984
|
const program = sourceCode.ast;
|
|
@@ -249026,7 +249027,7 @@ const rule$M = createRule({
|
|
|
249026
249027
|
name: 'injection-token-description',
|
|
249027
249028
|
});
|
|
249028
249029
|
|
|
249029
|
-
const rule$
|
|
249030
|
+
const rule$M = createRule({
|
|
249030
249031
|
create(context) {
|
|
249031
249032
|
const { sourceCode } = context;
|
|
249032
249033
|
const namespaceImports = new Map();
|
|
@@ -249121,7 +249122,7 @@ const DEFAULT_OPTIONS = {
|
|
|
249121
249122
|
importDeclaration: '^@taiga-ui*',
|
|
249122
249123
|
projectName: String.raw `(?<=^@taiga-ui/)([-\w]+)`,
|
|
249123
249124
|
};
|
|
249124
|
-
const rule$
|
|
249125
|
+
const rule$L = createRule({
|
|
249125
249126
|
create(context) {
|
|
249126
249127
|
const { currentProject, deepImport, ignoreImports, importDeclaration, projectName, } = { ...DEFAULT_OPTIONS, ...context.options[0] };
|
|
249127
249128
|
const hasNonCodeExtension = (source) => {
|
|
@@ -249213,7 +249214,7 @@ const nearestFileUpCache = new Map();
|
|
|
249213
249214
|
const markerCache = new Map();
|
|
249214
249215
|
const indexFileCache = new Map();
|
|
249215
249216
|
const indexExportsCache = new Map();
|
|
249216
|
-
const rule$
|
|
249217
|
+
const rule$K = createRule({
|
|
249217
249218
|
create(context) {
|
|
249218
249219
|
const parserServices = dist$3.ESLintUtils.getParserServices(context);
|
|
249219
249220
|
const program = parserServices.program;
|
|
@@ -249407,13 +249408,13 @@ const noDuplicateAttributesRule = angular.templatePlugin.rules?.['no-duplicate-a
|
|
|
249407
249408
|
if (!noDuplicateAttributesRule) {
|
|
249408
249409
|
throw new Error('angular-eslint template rule "no-duplicate-attributes" is not available');
|
|
249409
249410
|
}
|
|
249410
|
-
const rule$
|
|
249411
|
+
const rule$J = createRule({
|
|
249411
249412
|
name: 'no-duplicate-attrs',
|
|
249412
249413
|
rule: noDuplicateAttributesRule,
|
|
249413
249414
|
});
|
|
249414
249415
|
|
|
249415
249416
|
const MESSAGE_ID$c = 'duplicateId';
|
|
249416
|
-
const rule$
|
|
249417
|
+
const rule$I = createRule({
|
|
249417
249418
|
name: 'no-duplicate-id',
|
|
249418
249419
|
rule: {
|
|
249419
249420
|
create(context) {
|
|
@@ -249474,7 +249475,7 @@ function getTrackingKey(node) {
|
|
|
249474
249475
|
? 'link[rel=canonical]'
|
|
249475
249476
|
: null;
|
|
249476
249477
|
}
|
|
249477
|
-
const rule$
|
|
249478
|
+
const rule$H = createRule({
|
|
249478
249479
|
name: 'no-duplicate-in-head',
|
|
249479
249480
|
rule: {
|
|
249480
249481
|
create(context) {
|
|
@@ -249530,7 +249531,7 @@ const rule$G = createRule({
|
|
|
249530
249531
|
});
|
|
249531
249532
|
|
|
249532
249533
|
const COMPONENT_DECORATORS = new Set(['Component']);
|
|
249533
|
-
const rule$
|
|
249534
|
+
const rule$G = createRule({
|
|
249534
249535
|
create(context) {
|
|
249535
249536
|
const { sourceCode } = context;
|
|
249536
249537
|
return {
|
|
@@ -249823,7 +249824,7 @@ function walkAst(root, visitor) {
|
|
|
249823
249824
|
}
|
|
249824
249825
|
}
|
|
249825
249826
|
|
|
249826
|
-
const ANGULAR_CORE$
|
|
249827
|
+
const ANGULAR_CORE$2 = '@angular/core';
|
|
249827
249828
|
/**
|
|
249828
249829
|
* Returns the local name bound to a named import from a given source.
|
|
249829
249830
|
* Handles aliased imports: `import { untracked as ngUntracked } from '@angular/core'`
|
|
@@ -249851,7 +249852,7 @@ function getLocalNameForImport(program, source, exportedName) {
|
|
|
249851
249852
|
}
|
|
249852
249853
|
function findAngularCoreImports(program) {
|
|
249853
249854
|
return program.body.filter((node) => node.type === dist$3.AST_NODE_TYPES.ImportDeclaration &&
|
|
249854
|
-
node.source.value === ANGULAR_CORE$
|
|
249855
|
+
node.source.value === ANGULAR_CORE$2);
|
|
249855
249856
|
}
|
|
249856
249857
|
function findRuntimeAngularCoreImport(program) {
|
|
249857
249858
|
return (findAngularCoreImports(program).find((node) => node.importKind !== 'type') ?? null);
|
|
@@ -249873,7 +249874,7 @@ function findAngularCoreImportSpecifier(program, exportedName) {
|
|
|
249873
249874
|
return null;
|
|
249874
249875
|
}
|
|
249875
249876
|
|
|
249876
|
-
const ANGULAR_CORE = '@angular/core';
|
|
249877
|
+
const ANGULAR_CORE$1 = '@angular/core';
|
|
249877
249878
|
const SIGNAL_WRITE_METHODS = new Set(['mutate', 'set', 'update']);
|
|
249878
249879
|
const AFTER_RENDER_EFFECT_PHASES = new Map([
|
|
249879
249880
|
['earlyRead', 'afterRenderEffect().earlyRead'],
|
|
@@ -249899,7 +249900,7 @@ function getPropertyName(property) {
|
|
|
249899
249900
|
return typeof property.key.value === 'string' ? property.key.value : null;
|
|
249900
249901
|
}
|
|
249901
249902
|
function isAngularCoreCall(node, program, exportedName) {
|
|
249902
|
-
const localName = getLocalNameForImport(program, ANGULAR_CORE, exportedName);
|
|
249903
|
+
const localName = getLocalNameForImport(program, ANGULAR_CORE$1, exportedName);
|
|
249903
249904
|
return localName
|
|
249904
249905
|
? node.callee.type === dist$3.AST_NODE_TYPES.Identifier &&
|
|
249905
249906
|
node.callee.name === localName &&
|
|
@@ -250178,7 +250179,7 @@ const ANGULAR_SIGNALS_UNTRACKED_GUIDE_URL = 'https://angular.dev/guide/signals#r
|
|
|
250178
250179
|
const ANGULAR_SIGNALS_ASYNC_GUIDE_URL = 'https://angular.dev/guide/signals#reactive-context-and-async-operations';
|
|
250179
250180
|
const createUntrackedRule = createRule;
|
|
250180
250181
|
|
|
250181
|
-
const rule$
|
|
250182
|
+
const rule$F = createUntrackedRule({
|
|
250182
250183
|
create(context) {
|
|
250183
250184
|
const { checker, esTreeNodeToTSNodeMap, program } = getTypeAwareRuleContext(context);
|
|
250184
250185
|
const signalNodeMap = esTreeNodeToTSNodeMap;
|
|
@@ -250251,7 +250252,7 @@ const config$3 = {
|
|
|
250251
250252
|
type: 'problem',
|
|
250252
250253
|
},
|
|
250253
250254
|
};
|
|
250254
|
-
const rule$
|
|
250255
|
+
const rule$E = createRule({
|
|
250255
250256
|
name: 'no-href-with-router-link',
|
|
250256
250257
|
rule: config$3,
|
|
250257
250258
|
});
|
|
@@ -250312,7 +250313,7 @@ function getScopeRoot(node) {
|
|
|
250312
250313
|
return (findAncestor(node, (ancestor) => ancestor.type === dist$3.AST_NODE_TYPES.Program || isFunctionLike(ancestor)) ?? node);
|
|
250313
250314
|
}
|
|
250314
250315
|
|
|
250315
|
-
const rule$
|
|
250316
|
+
const rule$D = createRule({
|
|
250316
250317
|
create(context) {
|
|
250317
250318
|
const checkImplicitPublic = (node) => {
|
|
250318
250319
|
const classRef = getEnclosingClass(node);
|
|
@@ -250374,7 +250375,7 @@ const rule$C = createRule({
|
|
|
250374
250375
|
name: 'no-implicit-public',
|
|
250375
250376
|
});
|
|
250376
250377
|
|
|
250377
|
-
const rule$
|
|
250378
|
+
const rule$C = createRule({
|
|
250378
250379
|
create(context) {
|
|
250379
250380
|
const { sourceCode } = context;
|
|
250380
250381
|
return {
|
|
@@ -250430,7 +250431,7 @@ function isInfiniteLoopLiteral(node) {
|
|
|
250430
250431
|
function isInfiniteLoopTest(test) {
|
|
250431
250432
|
return test == null || isInfiniteLoopLiteral(test);
|
|
250432
250433
|
}
|
|
250433
|
-
const rule$
|
|
250434
|
+
const rule$B = createRule({
|
|
250434
250435
|
create(context) {
|
|
250435
250436
|
return {
|
|
250436
250437
|
DoWhileStatement(node) {
|
|
@@ -250475,7 +250476,7 @@ const rule$A = createRule({
|
|
|
250475
250476
|
});
|
|
250476
250477
|
|
|
250477
250478
|
const LEGACY_PEER_DEPS_PATTERN = /^legacy-peer-deps\s*=\s*true$/i;
|
|
250478
|
-
const rule$
|
|
250479
|
+
const rule$A = createRule({
|
|
250479
250480
|
create(context) {
|
|
250480
250481
|
return {
|
|
250481
250482
|
Program(node) {
|
|
@@ -250935,7 +250936,7 @@ const OBSOLETE_HTML_ATTRS = {
|
|
|
250935
250936
|
};
|
|
250936
250937
|
|
|
250937
250938
|
const MESSAGE_ID$9 = 'obsolete';
|
|
250938
|
-
const rule$
|
|
250939
|
+
const rule$z = createRule({
|
|
250939
250940
|
name: 'no-obsolete-attrs',
|
|
250940
250941
|
rule: {
|
|
250941
250942
|
create(context) {
|
|
@@ -251013,7 +251014,7 @@ const OBSOLETE_HTML_TAGS = new Set([
|
|
|
251013
251014
|
]);
|
|
251014
251015
|
|
|
251015
251016
|
const MESSAGE_ID$8 = 'unexpected';
|
|
251016
|
-
const rule$
|
|
251017
|
+
const rule$y = createRule({
|
|
251017
251018
|
name: 'no-obsolete-tags',
|
|
251018
251019
|
rule: {
|
|
251019
251020
|
create(context) {
|
|
@@ -251040,7 +251041,7 @@ const rule$x = createRule({
|
|
|
251040
251041
|
},
|
|
251041
251042
|
});
|
|
251042
251043
|
|
|
251043
|
-
const rule$
|
|
251044
|
+
const rule$x = createRule({
|
|
251044
251045
|
create(context) {
|
|
251045
251046
|
const { checker, esTreeNodeToTSNodeMap, sourceCode } = getTypeAwareRuleContext(context);
|
|
251046
251047
|
return {
|
|
@@ -251184,7 +251185,7 @@ const config$2 = {
|
|
|
251184
251185
|
type: 'problem',
|
|
251185
251186
|
},
|
|
251186
251187
|
};
|
|
251187
|
-
const rule$
|
|
251188
|
+
const rule$w = createRule({
|
|
251188
251189
|
name: 'no-project-as-in-ng-template',
|
|
251189
251190
|
rule: config$2,
|
|
251190
251191
|
});
|
|
@@ -251221,7 +251222,7 @@ function collectArrayExpressions(node) {
|
|
|
251221
251222
|
}
|
|
251222
251223
|
return result;
|
|
251223
251224
|
}
|
|
251224
|
-
const rule$
|
|
251225
|
+
const rule$v = createRule({
|
|
251225
251226
|
create(context) {
|
|
251226
251227
|
const { checker: typeChecker, esTreeNodeToTSNodeMap } = getTypeAwareRuleContext(context);
|
|
251227
251228
|
const ignoreTupleContextualTyping = context.options[0]?.ignoreTupleContextualTyping ?? true;
|
|
@@ -251419,7 +251420,7 @@ function isOptionalMemberReceiver(call) {
|
|
|
251419
251420
|
parent.object === current &&
|
|
251420
251421
|
parent.optional);
|
|
251421
251422
|
}
|
|
251422
|
-
const rule$
|
|
251423
|
+
const rule$u = createRule({
|
|
251423
251424
|
create(context) {
|
|
251424
251425
|
const { checker, esTreeNodeToTSNodeMap, sourceCode } = getTypeAwareRuleContext(context);
|
|
251425
251426
|
const signalNodeMap = esTreeNodeToTSNodeMap;
|
|
@@ -251522,15 +251523,22 @@ const rule$t = createRule({
|
|
|
251522
251523
|
});
|
|
251523
251524
|
|
|
251524
251525
|
/**
|
|
251525
|
-
* Strips
|
|
251526
|
-
* `as` casts, non-null assertions (`!`), type
|
|
251527
|
-
* optional-chain wrappers. Iterates until no more
|
|
251526
|
+
* Strips expression wrapper nodes that do not affect the underlying expression:
|
|
251527
|
+
* parentheses, `as` casts, `satisfies`, non-null assertions (`!`), type
|
|
251528
|
+
* assertions (`<T>expr`), and optional-chain wrappers. Iterates until no more
|
|
251529
|
+
* wrappers are found.
|
|
251528
251530
|
*/
|
|
251529
251531
|
function unwrapExpression(expression) {
|
|
251530
251532
|
let current = expression;
|
|
251531
251533
|
let didUnwrap = true;
|
|
251532
251534
|
while (didUnwrap) {
|
|
251533
251535
|
didUnwrap = false;
|
|
251536
|
+
const parenthesized = getParenthesizedExpression(current);
|
|
251537
|
+
if (parenthesized) {
|
|
251538
|
+
current = parenthesized;
|
|
251539
|
+
didUnwrap = true;
|
|
251540
|
+
continue;
|
|
251541
|
+
}
|
|
251534
251542
|
switch (current.type) {
|
|
251535
251543
|
case dist$3.AST_NODE_TYPES.ChainExpression:
|
|
251536
251544
|
current = current.expression;
|
|
@@ -251544,6 +251552,10 @@ function unwrapExpression(expression) {
|
|
|
251544
251552
|
current = current.expression;
|
|
251545
251553
|
didUnwrap = true;
|
|
251546
251554
|
break;
|
|
251555
|
+
case dist$3.AST_NODE_TYPES.TSSatisfiesExpression:
|
|
251556
|
+
current = current.expression;
|
|
251557
|
+
didUnwrap = true;
|
|
251558
|
+
break;
|
|
251547
251559
|
case dist$3.AST_NODE_TYPES.TSTypeAssertion:
|
|
251548
251560
|
current = current.expression;
|
|
251549
251561
|
didUnwrap = true;
|
|
@@ -251552,6 +251564,19 @@ function unwrapExpression(expression) {
|
|
|
251552
251564
|
}
|
|
251553
251565
|
return current;
|
|
251554
251566
|
}
|
|
251567
|
+
function isExpressionLike(value) {
|
|
251568
|
+
return (typeof value === 'object' &&
|
|
251569
|
+
value !== null &&
|
|
251570
|
+
'type' in value &&
|
|
251571
|
+
typeof value.type === 'string');
|
|
251572
|
+
}
|
|
251573
|
+
function getParenthesizedExpression(expression) {
|
|
251574
|
+
const maybeExpression = expression;
|
|
251575
|
+
return maybeExpression.type === 'ParenthesizedExpression' &&
|
|
251576
|
+
isExpressionLike(maybeExpression.expression)
|
|
251577
|
+
? maybeExpression.expression
|
|
251578
|
+
: null;
|
|
251579
|
+
}
|
|
251555
251580
|
|
|
251556
251581
|
function unwrapMutationTarget(node) {
|
|
251557
251582
|
let current = node;
|
|
@@ -251840,7 +251865,7 @@ function inspectComputedBody(root, context, localScopes, visitedFunctions, repor
|
|
|
251840
251865
|
return;
|
|
251841
251866
|
});
|
|
251842
251867
|
}
|
|
251843
|
-
const rule$
|
|
251868
|
+
const rule$t = createRule({
|
|
251844
251869
|
create(context) {
|
|
251845
251870
|
const { checker, esTreeNodeToTSNodeMap, program, sourceCode, tsNodeToESTreeNodeMap, } = getTypeAwareRuleContext(context);
|
|
251846
251871
|
const signalNodeMap = esTreeNodeToTSNodeMap;
|
|
@@ -251883,6 +251908,88 @@ const rule$s = createRule({
|
|
|
251883
251908
|
name: 'no-side-effects-in-computed',
|
|
251884
251909
|
});
|
|
251885
251910
|
|
|
251911
|
+
const ANGULAR_CORE = '@angular/core';
|
|
251912
|
+
const SIGNAL_FACTORIES = ['computed', 'effect', 'linkedSignal', 'signal'];
|
|
251913
|
+
function isSignalFactoryCall(node, factoryNames) {
|
|
251914
|
+
if (!node) {
|
|
251915
|
+
return false;
|
|
251916
|
+
}
|
|
251917
|
+
const expression = unwrapExpression(node);
|
|
251918
|
+
return (expression.type === dist$3.AST_NODE_TYPES.CallExpression &&
|
|
251919
|
+
expression.callee.type === dist$3.AST_NODE_TYPES.Identifier &&
|
|
251920
|
+
factoryNames.has(expression.callee.name));
|
|
251921
|
+
}
|
|
251922
|
+
function getModuleScopeVariableDeclaration(statement) {
|
|
251923
|
+
if (statement.type === dist$3.AST_NODE_TYPES.VariableDeclaration) {
|
|
251924
|
+
return statement;
|
|
251925
|
+
}
|
|
251926
|
+
if (statement.type !== dist$3.AST_NODE_TYPES.ExportNamedDeclaration) {
|
|
251927
|
+
return null;
|
|
251928
|
+
}
|
|
251929
|
+
return statement.declaration?.type === dist$3.AST_NODE_TYPES.VariableDeclaration
|
|
251930
|
+
? statement.declaration
|
|
251931
|
+
: null;
|
|
251932
|
+
}
|
|
251933
|
+
function collectModuleScopeSignalNames(program, factoryNames) {
|
|
251934
|
+
const names = new Set();
|
|
251935
|
+
for (const statement of program.body) {
|
|
251936
|
+
const declaration = getModuleScopeVariableDeclaration(statement);
|
|
251937
|
+
if (!declaration) {
|
|
251938
|
+
continue;
|
|
251939
|
+
}
|
|
251940
|
+
for (const declarator of declaration.declarations) {
|
|
251941
|
+
if (declarator.id.type === dist$3.AST_NODE_TYPES.Identifier &&
|
|
251942
|
+
isSignalFactoryCall(declarator.init, factoryNames)) {
|
|
251943
|
+
names.add(declarator.id.name);
|
|
251944
|
+
}
|
|
251945
|
+
}
|
|
251946
|
+
}
|
|
251947
|
+
return names;
|
|
251948
|
+
}
|
|
251949
|
+
const rule$s = createRule({
|
|
251950
|
+
create(context) {
|
|
251951
|
+
const program = context.sourceCode.ast;
|
|
251952
|
+
const factoryNames = new Set();
|
|
251953
|
+
for (const name of SIGNAL_FACTORIES) {
|
|
251954
|
+
const localName = getLocalNameForImport(program, ANGULAR_CORE, name);
|
|
251955
|
+
if (localName) {
|
|
251956
|
+
factoryNames.add(localName);
|
|
251957
|
+
}
|
|
251958
|
+
}
|
|
251959
|
+
if (factoryNames.size === 0) {
|
|
251960
|
+
return {};
|
|
251961
|
+
}
|
|
251962
|
+
const moduleScopeSignals = collectModuleScopeSignalNames(program, factoryNames);
|
|
251963
|
+
return moduleScopeSignals.size === 0
|
|
251964
|
+
? {}
|
|
251965
|
+
: {
|
|
251966
|
+
PropertyDefinition(node) {
|
|
251967
|
+
const value = node.value ? unwrapExpression(node.value) : null;
|
|
251968
|
+
if (value?.type !== dist$3.AST_NODE_TYPES.Identifier ||
|
|
251969
|
+
!moduleScopeSignals.has(value.name)) {
|
|
251970
|
+
return;
|
|
251971
|
+
}
|
|
251972
|
+
context.report({
|
|
251973
|
+
data: { name: value.name },
|
|
251974
|
+
messageId: 'noSignalOutsideClass',
|
|
251975
|
+
node: value,
|
|
251976
|
+
});
|
|
251977
|
+
},
|
|
251978
|
+
};
|
|
251979
|
+
},
|
|
251980
|
+
meta: {
|
|
251981
|
+
docs: {
|
|
251982
|
+
description: 'Disallow class properties that reference a module-scope Angular signal; move the signal creation into the class body',
|
|
251983
|
+
},
|
|
251984
|
+
messages: {
|
|
251985
|
+
noSignalOutsideClass: '`{{name}}` is a module-scope signal. Move it into the class body: `{{name}} = signal(...)`.',
|
|
251986
|
+
},
|
|
251987
|
+
schema: [],
|
|
251988
|
+
type: 'problem',
|
|
251989
|
+
},
|
|
251990
|
+
name: 'no-signal-outside-class',
|
|
251991
|
+
});
|
|
251992
|
+
|
|
251886
251993
|
const rule$r = createUntrackedRule({
|
|
251887
251994
|
create(context) {
|
|
251888
251995
|
const { checker, esTreeNodeToTSNodeMap, program, sourceCode } = getTypeAwareRuleContext(context);
|
|
@@ -255592,36 +255699,37 @@ const plugin = {
|
|
|
255592
255699
|
},
|
|
255593
255700
|
rules: {
|
|
255594
255701
|
'array-as-const': rule$5,
|
|
255595
|
-
'attrs-newline': rule$
|
|
255702
|
+
'attrs-newline': rule$T,
|
|
255596
255703
|
'class-property-naming': rule$4,
|
|
255597
|
-
'decorator-key-sort': rule$
|
|
255598
|
-
'element-newline': rule$
|
|
255704
|
+
'decorator-key-sort': rule$S,
|
|
255705
|
+
'element-newline': rule$R,
|
|
255599
255706
|
'flat-exports': rule$3,
|
|
255600
|
-
'host-attributes-sort': rule$
|
|
255601
|
-
'html-logical-properties': rule$
|
|
255602
|
-
'import-integrity': rule$
|
|
255603
|
-
'injection-token-description': rule$
|
|
255604
|
-
'no-commonjs-import-patterns': rule$
|
|
255605
|
-
'no-deep-imports': rule$
|
|
255606
|
-
'no-deep-imports-to-indexed-packages': rule$
|
|
255607
|
-
'no-duplicate-attrs': rule$
|
|
255608
|
-
'no-duplicate-id': rule$
|
|
255609
|
-
'no-duplicate-in-head': rule$
|
|
255610
|
-
'no-empty-style-metadata': rule$
|
|
255611
|
-
'no-fully-untracked-effect': rule$
|
|
255612
|
-
'no-href-with-router-link': rule$
|
|
255613
|
-
'no-implicit-public': rule$
|
|
255614
|
-
'no-import-assertions': rule$
|
|
255615
|
-
'no-infinite-loop': rule$
|
|
255616
|
-
'no-legacy-peer-deps': rule$
|
|
255617
|
-
'no-obsolete-attrs': rule$
|
|
255618
|
-
'no-obsolete-tags': rule$
|
|
255619
|
-
'no-playwright-empty-fill': rule$
|
|
255620
|
-
'no-project-as-in-ng-template': rule$
|
|
255621
|
-
'no-redundant-type-annotation': rule$
|
|
255622
|
-
'no-repeated-signal-in-conditional': rule$
|
|
255707
|
+
'host-attributes-sort': rule$Q,
|
|
255708
|
+
'html-logical-properties': rule$P,
|
|
255709
|
+
'import-integrity': rule$O,
|
|
255710
|
+
'injection-token-description': rule$N,
|
|
255711
|
+
'no-commonjs-import-patterns': rule$M,
|
|
255712
|
+
'no-deep-imports': rule$L,
|
|
255713
|
+
'no-deep-imports-to-indexed-packages': rule$K,
|
|
255714
|
+
'no-duplicate-attrs': rule$J,
|
|
255715
|
+
'no-duplicate-id': rule$I,
|
|
255716
|
+
'no-duplicate-in-head': rule$H,
|
|
255717
|
+
'no-empty-style-metadata': rule$G,
|
|
255718
|
+
'no-fully-untracked-effect': rule$F,
|
|
255719
|
+
'no-href-with-router-link': rule$E,
|
|
255720
|
+
'no-implicit-public': rule$D,
|
|
255721
|
+
'no-import-assertions': rule$C,
|
|
255722
|
+
'no-infinite-loop': rule$B,
|
|
255723
|
+
'no-legacy-peer-deps': rule$A,
|
|
255724
|
+
'no-obsolete-attrs': rule$z,
|
|
255725
|
+
'no-obsolete-tags': rule$y,
|
|
255726
|
+
'no-playwright-empty-fill': rule$x,
|
|
255727
|
+
'no-project-as-in-ng-template': rule$w,
|
|
255728
|
+
'no-redundant-type-annotation': rule$v,
|
|
255729
|
+
'no-repeated-signal-in-conditional': rule$u,
|
|
255623
255730
|
'no-restricted-attr-values': rule$2,
|
|
255624
|
-
'no-side-effects-in-computed': rule$
|
|
255731
|
+
'no-side-effects-in-computed': rule$t,
|
|
255732
|
+
'no-signal-outside-class': rule$s,
|
|
255625
255733
|
'no-signal-reads-after-await-in-reactive-context': rule$r,
|
|
255626
255734
|
'no-string-literal-concat': rule$q,
|
|
255627
255735
|
'no-untracked-outside-reactive-context': rule$p,
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type TSESTree } from '@typescript-eslint/utils';
|
|
2
2
|
/**
|
|
3
|
-
* Strips
|
|
4
|
-
* `as` casts, non-null assertions (`!`), type
|
|
5
|
-
* optional-chain wrappers. Iterates until no more
|
|
3
|
+
* Strips expression wrapper nodes that do not affect the underlying expression:
|
|
4
|
+
* parentheses, `as` casts, `satisfies`, non-null assertions (`!`), type
|
|
5
|
+
* assertions (`<T>expr`), and optional-chain wrappers. Iterates until no more
|
|
6
|
+
* wrappers are found.
|
|
6
7
|
*/
|
|
7
8
|
export declare function unwrapExpression(expression: TSESTree.Expression): TSESTree.Expression;
|