@taiga-ui/eslint-plugin-experience-next 0.510.0 → 0.512.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 +177 -61
- package/package.json +1 -1
- package/rules/convention.d.ts +14 -0
- 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
|
@@ -373,6 +373,14 @@ const TUI_RECOMMENDED_NAMING_CONVENTION = [
|
|
|
373
373
|
format: ['camelCase'],
|
|
374
374
|
selector: ['classMethod', 'classProperty'],
|
|
375
375
|
},
|
|
376
|
+
{
|
|
377
|
+
filter: {
|
|
378
|
+
match: true,
|
|
379
|
+
regex: String.raw `^(Infinity|NaN|Number|Math)$`,
|
|
380
|
+
},
|
|
381
|
+
format: null,
|
|
382
|
+
selector: 'classProperty',
|
|
383
|
+
},
|
|
376
384
|
{
|
|
377
385
|
format: ['UPPER_CASE', 'camelCase', 'PascalCase'],
|
|
378
386
|
selector: ['variable'],
|
|
@@ -1210,6 +1218,7 @@ var recommended = defineConfig([
|
|
|
1210
1218
|
'@taiga-ui/experience-next/no-redundant-type-annotation': 'error',
|
|
1211
1219
|
'@taiga-ui/experience-next/no-repeated-signal-in-conditional': 'error',
|
|
1212
1220
|
'@taiga-ui/experience-next/no-side-effects-in-computed': 'error',
|
|
1221
|
+
'@taiga-ui/experience-next/no-signal-outside-class': 'error',
|
|
1213
1222
|
'@taiga-ui/experience-next/no-signal-reads-after-await-in-reactive-context': 'error',
|
|
1214
1223
|
'@taiga-ui/experience-next/no-untracked-outside-reactive-context': 'error',
|
|
1215
1224
|
'@taiga-ui/experience-next/no-useless-untracked': 'error',
|
|
@@ -46224,7 +46233,7 @@ function buildMultilineStartTag(node, sourceText) {
|
|
|
46224
46233
|
closing,
|
|
46225
46234
|
].join('\n');
|
|
46226
46235
|
}
|
|
46227
|
-
const rule$
|
|
46236
|
+
const rule$T = createRule({
|
|
46228
46237
|
name: 'attrs-newline',
|
|
46229
46238
|
rule: {
|
|
46230
46239
|
create(context) {
|
|
@@ -46404,7 +46413,7 @@ function sameOrder(a, b) {
|
|
|
46404
46413
|
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
46405
46414
|
}
|
|
46406
46415
|
|
|
46407
|
-
const rule$
|
|
46416
|
+
const rule$S = createRule({
|
|
46408
46417
|
create(context, [order]) {
|
|
46409
46418
|
const decorators = new Set(Object.keys(order));
|
|
46410
46419
|
return {
|
|
@@ -46540,7 +46549,7 @@ function getNodeLabel(node) {
|
|
|
46540
46549
|
}
|
|
46541
46550
|
return node instanceof dist$4.TmplAstBoundText ? 'binding' : 'text';
|
|
46542
46551
|
}
|
|
46543
|
-
const rule$
|
|
46552
|
+
const rule$R = createRule({
|
|
46544
46553
|
name: 'element-newline',
|
|
46545
46554
|
rule: {
|
|
46546
46555
|
create(context) {
|
|
@@ -46679,7 +46688,7 @@ const PRESETS = {
|
|
|
46679
46688
|
$VUE: ['$CLASS', '$ID', '$VUE_ATTRIBUTE'],
|
|
46680
46689
|
$VUE_ATTRIBUTE: /^v-/,
|
|
46681
46690
|
};
|
|
46682
|
-
const rule$
|
|
46691
|
+
const rule$Q = createRule({
|
|
46683
46692
|
create(context, [options]) {
|
|
46684
46693
|
const sourceCode = context.sourceCode;
|
|
46685
46694
|
const settings = {
|
|
@@ -47010,7 +47019,7 @@ const config$4 = {
|
|
|
47010
47019
|
type: 'suggestion',
|
|
47011
47020
|
},
|
|
47012
47021
|
};
|
|
47013
|
-
const rule$
|
|
47022
|
+
const rule$P = createRule({
|
|
47014
47023
|
name: 'html-logical-properties',
|
|
47015
47024
|
rule: config$4,
|
|
47016
47025
|
});
|
|
@@ -248252,7 +248261,7 @@ function isImportUsedOnlyAsAngularDiFirstArg(node, sourceCode) {
|
|
|
248252
248261
|
}
|
|
248253
248262
|
return hasSafeRuntimeUsage;
|
|
248254
248263
|
}
|
|
248255
|
-
const rule$
|
|
248264
|
+
const rule$O = createRule({
|
|
248256
248265
|
create(context) {
|
|
248257
248266
|
const { checker, esTreeNodeToTSNodeMap, sourceCode, tsProgram } = getTypeAwareRuleContext(context);
|
|
248258
248267
|
const checkCycles = context.options[0]?.checkCycles ?? true;
|
|
@@ -248977,7 +248986,7 @@ function getNgDevModeDeclarationFix(program, fixer) {
|
|
|
248977
248986
|
? fixer.insertTextBefore(firstStatement, 'declare const ngDevMode: boolean;\n\n')
|
|
248978
248987
|
: fixer.insertTextBeforeRange([0, 0], 'declare const ngDevMode: boolean;\n');
|
|
248979
248988
|
}
|
|
248980
|
-
const rule$
|
|
248989
|
+
const rule$N = createRule({
|
|
248981
248990
|
create(context) {
|
|
248982
248991
|
const { sourceCode } = context;
|
|
248983
248992
|
const program = sourceCode.ast;
|
|
@@ -249026,7 +249035,7 @@ const rule$M = createRule({
|
|
|
249026
249035
|
name: 'injection-token-description',
|
|
249027
249036
|
});
|
|
249028
249037
|
|
|
249029
|
-
const rule$
|
|
249038
|
+
const rule$M = createRule({
|
|
249030
249039
|
create(context) {
|
|
249031
249040
|
const { sourceCode } = context;
|
|
249032
249041
|
const namespaceImports = new Map();
|
|
@@ -249121,7 +249130,7 @@ const DEFAULT_OPTIONS = {
|
|
|
249121
249130
|
importDeclaration: '^@taiga-ui*',
|
|
249122
249131
|
projectName: String.raw `(?<=^@taiga-ui/)([-\w]+)`,
|
|
249123
249132
|
};
|
|
249124
|
-
const rule$
|
|
249133
|
+
const rule$L = createRule({
|
|
249125
249134
|
create(context) {
|
|
249126
249135
|
const { currentProject, deepImport, ignoreImports, importDeclaration, projectName, } = { ...DEFAULT_OPTIONS, ...context.options[0] };
|
|
249127
249136
|
const hasNonCodeExtension = (source) => {
|
|
@@ -249213,7 +249222,7 @@ const nearestFileUpCache = new Map();
|
|
|
249213
249222
|
const markerCache = new Map();
|
|
249214
249223
|
const indexFileCache = new Map();
|
|
249215
249224
|
const indexExportsCache = new Map();
|
|
249216
|
-
const rule$
|
|
249225
|
+
const rule$K = createRule({
|
|
249217
249226
|
create(context) {
|
|
249218
249227
|
const parserServices = dist$3.ESLintUtils.getParserServices(context);
|
|
249219
249228
|
const program = parserServices.program;
|
|
@@ -249407,13 +249416,13 @@ const noDuplicateAttributesRule = angular.templatePlugin.rules?.['no-duplicate-a
|
|
|
249407
249416
|
if (!noDuplicateAttributesRule) {
|
|
249408
249417
|
throw new Error('angular-eslint template rule "no-duplicate-attributes" is not available');
|
|
249409
249418
|
}
|
|
249410
|
-
const rule$
|
|
249419
|
+
const rule$J = createRule({
|
|
249411
249420
|
name: 'no-duplicate-attrs',
|
|
249412
249421
|
rule: noDuplicateAttributesRule,
|
|
249413
249422
|
});
|
|
249414
249423
|
|
|
249415
249424
|
const MESSAGE_ID$c = 'duplicateId';
|
|
249416
|
-
const rule$
|
|
249425
|
+
const rule$I = createRule({
|
|
249417
249426
|
name: 'no-duplicate-id',
|
|
249418
249427
|
rule: {
|
|
249419
249428
|
create(context) {
|
|
@@ -249474,7 +249483,7 @@ function getTrackingKey(node) {
|
|
|
249474
249483
|
? 'link[rel=canonical]'
|
|
249475
249484
|
: null;
|
|
249476
249485
|
}
|
|
249477
|
-
const rule$
|
|
249486
|
+
const rule$H = createRule({
|
|
249478
249487
|
name: 'no-duplicate-in-head',
|
|
249479
249488
|
rule: {
|
|
249480
249489
|
create(context) {
|
|
@@ -249530,7 +249539,7 @@ const rule$G = createRule({
|
|
|
249530
249539
|
});
|
|
249531
249540
|
|
|
249532
249541
|
const COMPONENT_DECORATORS = new Set(['Component']);
|
|
249533
|
-
const rule$
|
|
249542
|
+
const rule$G = createRule({
|
|
249534
249543
|
create(context) {
|
|
249535
249544
|
const { sourceCode } = context;
|
|
249536
249545
|
return {
|
|
@@ -249823,7 +249832,7 @@ function walkAst(root, visitor) {
|
|
|
249823
249832
|
}
|
|
249824
249833
|
}
|
|
249825
249834
|
|
|
249826
|
-
const ANGULAR_CORE$
|
|
249835
|
+
const ANGULAR_CORE$2 = '@angular/core';
|
|
249827
249836
|
/**
|
|
249828
249837
|
* Returns the local name bound to a named import from a given source.
|
|
249829
249838
|
* Handles aliased imports: `import { untracked as ngUntracked } from '@angular/core'`
|
|
@@ -249851,7 +249860,7 @@ function getLocalNameForImport(program, source, exportedName) {
|
|
|
249851
249860
|
}
|
|
249852
249861
|
function findAngularCoreImports(program) {
|
|
249853
249862
|
return program.body.filter((node) => node.type === dist$3.AST_NODE_TYPES.ImportDeclaration &&
|
|
249854
|
-
node.source.value === ANGULAR_CORE$
|
|
249863
|
+
node.source.value === ANGULAR_CORE$2);
|
|
249855
249864
|
}
|
|
249856
249865
|
function findRuntimeAngularCoreImport(program) {
|
|
249857
249866
|
return (findAngularCoreImports(program).find((node) => node.importKind !== 'type') ?? null);
|
|
@@ -249873,7 +249882,7 @@ function findAngularCoreImportSpecifier(program, exportedName) {
|
|
|
249873
249882
|
return null;
|
|
249874
249883
|
}
|
|
249875
249884
|
|
|
249876
|
-
const ANGULAR_CORE = '@angular/core';
|
|
249885
|
+
const ANGULAR_CORE$1 = '@angular/core';
|
|
249877
249886
|
const SIGNAL_WRITE_METHODS = new Set(['mutate', 'set', 'update']);
|
|
249878
249887
|
const AFTER_RENDER_EFFECT_PHASES = new Map([
|
|
249879
249888
|
['earlyRead', 'afterRenderEffect().earlyRead'],
|
|
@@ -249899,7 +249908,7 @@ function getPropertyName(property) {
|
|
|
249899
249908
|
return typeof property.key.value === 'string' ? property.key.value : null;
|
|
249900
249909
|
}
|
|
249901
249910
|
function isAngularCoreCall(node, program, exportedName) {
|
|
249902
|
-
const localName = getLocalNameForImport(program, ANGULAR_CORE, exportedName);
|
|
249911
|
+
const localName = getLocalNameForImport(program, ANGULAR_CORE$1, exportedName);
|
|
249903
249912
|
return localName
|
|
249904
249913
|
? node.callee.type === dist$3.AST_NODE_TYPES.Identifier &&
|
|
249905
249914
|
node.callee.name === localName &&
|
|
@@ -250178,7 +250187,7 @@ const ANGULAR_SIGNALS_UNTRACKED_GUIDE_URL = 'https://angular.dev/guide/signals#r
|
|
|
250178
250187
|
const ANGULAR_SIGNALS_ASYNC_GUIDE_URL = 'https://angular.dev/guide/signals#reactive-context-and-async-operations';
|
|
250179
250188
|
const createUntrackedRule = createRule;
|
|
250180
250189
|
|
|
250181
|
-
const rule$
|
|
250190
|
+
const rule$F = createUntrackedRule({
|
|
250182
250191
|
create(context) {
|
|
250183
250192
|
const { checker, esTreeNodeToTSNodeMap, program } = getTypeAwareRuleContext(context);
|
|
250184
250193
|
const signalNodeMap = esTreeNodeToTSNodeMap;
|
|
@@ -250251,7 +250260,7 @@ const config$3 = {
|
|
|
250251
250260
|
type: 'problem',
|
|
250252
250261
|
},
|
|
250253
250262
|
};
|
|
250254
|
-
const rule$
|
|
250263
|
+
const rule$E = createRule({
|
|
250255
250264
|
name: 'no-href-with-router-link',
|
|
250256
250265
|
rule: config$3,
|
|
250257
250266
|
});
|
|
@@ -250312,7 +250321,7 @@ function getScopeRoot(node) {
|
|
|
250312
250321
|
return (findAncestor(node, (ancestor) => ancestor.type === dist$3.AST_NODE_TYPES.Program || isFunctionLike(ancestor)) ?? node);
|
|
250313
250322
|
}
|
|
250314
250323
|
|
|
250315
|
-
const rule$
|
|
250324
|
+
const rule$D = createRule({
|
|
250316
250325
|
create(context) {
|
|
250317
250326
|
const checkImplicitPublic = (node) => {
|
|
250318
250327
|
const classRef = getEnclosingClass(node);
|
|
@@ -250374,7 +250383,7 @@ const rule$C = createRule({
|
|
|
250374
250383
|
name: 'no-implicit-public',
|
|
250375
250384
|
});
|
|
250376
250385
|
|
|
250377
|
-
const rule$
|
|
250386
|
+
const rule$C = createRule({
|
|
250378
250387
|
create(context) {
|
|
250379
250388
|
const { sourceCode } = context;
|
|
250380
250389
|
return {
|
|
@@ -250430,7 +250439,7 @@ function isInfiniteLoopLiteral(node) {
|
|
|
250430
250439
|
function isInfiniteLoopTest(test) {
|
|
250431
250440
|
return test == null || isInfiniteLoopLiteral(test);
|
|
250432
250441
|
}
|
|
250433
|
-
const rule$
|
|
250442
|
+
const rule$B = createRule({
|
|
250434
250443
|
create(context) {
|
|
250435
250444
|
return {
|
|
250436
250445
|
DoWhileStatement(node) {
|
|
@@ -250475,7 +250484,7 @@ const rule$A = createRule({
|
|
|
250475
250484
|
});
|
|
250476
250485
|
|
|
250477
250486
|
const LEGACY_PEER_DEPS_PATTERN = /^legacy-peer-deps\s*=\s*true$/i;
|
|
250478
|
-
const rule$
|
|
250487
|
+
const rule$A = createRule({
|
|
250479
250488
|
create(context) {
|
|
250480
250489
|
return {
|
|
250481
250490
|
Program(node) {
|
|
@@ -250935,7 +250944,7 @@ const OBSOLETE_HTML_ATTRS = {
|
|
|
250935
250944
|
};
|
|
250936
250945
|
|
|
250937
250946
|
const MESSAGE_ID$9 = 'obsolete';
|
|
250938
|
-
const rule$
|
|
250947
|
+
const rule$z = createRule({
|
|
250939
250948
|
name: 'no-obsolete-attrs',
|
|
250940
250949
|
rule: {
|
|
250941
250950
|
create(context) {
|
|
@@ -251013,7 +251022,7 @@ const OBSOLETE_HTML_TAGS = new Set([
|
|
|
251013
251022
|
]);
|
|
251014
251023
|
|
|
251015
251024
|
const MESSAGE_ID$8 = 'unexpected';
|
|
251016
|
-
const rule$
|
|
251025
|
+
const rule$y = createRule({
|
|
251017
251026
|
name: 'no-obsolete-tags',
|
|
251018
251027
|
rule: {
|
|
251019
251028
|
create(context) {
|
|
@@ -251040,7 +251049,7 @@ const rule$x = createRule({
|
|
|
251040
251049
|
},
|
|
251041
251050
|
});
|
|
251042
251051
|
|
|
251043
|
-
const rule$
|
|
251052
|
+
const rule$x = createRule({
|
|
251044
251053
|
create(context) {
|
|
251045
251054
|
const { checker, esTreeNodeToTSNodeMap, sourceCode } = getTypeAwareRuleContext(context);
|
|
251046
251055
|
return {
|
|
@@ -251184,7 +251193,7 @@ const config$2 = {
|
|
|
251184
251193
|
type: 'problem',
|
|
251185
251194
|
},
|
|
251186
251195
|
};
|
|
251187
|
-
const rule$
|
|
251196
|
+
const rule$w = createRule({
|
|
251188
251197
|
name: 'no-project-as-in-ng-template',
|
|
251189
251198
|
rule: config$2,
|
|
251190
251199
|
});
|
|
@@ -251221,7 +251230,7 @@ function collectArrayExpressions(node) {
|
|
|
251221
251230
|
}
|
|
251222
251231
|
return result;
|
|
251223
251232
|
}
|
|
251224
|
-
const rule$
|
|
251233
|
+
const rule$v = createRule({
|
|
251225
251234
|
create(context) {
|
|
251226
251235
|
const { checker: typeChecker, esTreeNodeToTSNodeMap } = getTypeAwareRuleContext(context);
|
|
251227
251236
|
const ignoreTupleContextualTyping = context.options[0]?.ignoreTupleContextualTyping ?? true;
|
|
@@ -251419,7 +251428,7 @@ function isOptionalMemberReceiver(call) {
|
|
|
251419
251428
|
parent.object === current &&
|
|
251420
251429
|
parent.optional);
|
|
251421
251430
|
}
|
|
251422
|
-
const rule$
|
|
251431
|
+
const rule$u = createRule({
|
|
251423
251432
|
create(context) {
|
|
251424
251433
|
const { checker, esTreeNodeToTSNodeMap, sourceCode } = getTypeAwareRuleContext(context);
|
|
251425
251434
|
const signalNodeMap = esTreeNodeToTSNodeMap;
|
|
@@ -251522,15 +251531,22 @@ const rule$t = createRule({
|
|
|
251522
251531
|
});
|
|
251523
251532
|
|
|
251524
251533
|
/**
|
|
251525
|
-
* Strips
|
|
251526
|
-
* `as` casts, non-null assertions (`!`), type
|
|
251527
|
-
* optional-chain wrappers. Iterates until no more
|
|
251534
|
+
* Strips expression wrapper nodes that do not affect the underlying expression:
|
|
251535
|
+
* parentheses, `as` casts, `satisfies`, non-null assertions (`!`), type
|
|
251536
|
+
* assertions (`<T>expr`), and optional-chain wrappers. Iterates until no more
|
|
251537
|
+
* wrappers are found.
|
|
251528
251538
|
*/
|
|
251529
251539
|
function unwrapExpression(expression) {
|
|
251530
251540
|
let current = expression;
|
|
251531
251541
|
let didUnwrap = true;
|
|
251532
251542
|
while (didUnwrap) {
|
|
251533
251543
|
didUnwrap = false;
|
|
251544
|
+
const parenthesized = getParenthesizedExpression(current);
|
|
251545
|
+
if (parenthesized) {
|
|
251546
|
+
current = parenthesized;
|
|
251547
|
+
didUnwrap = true;
|
|
251548
|
+
continue;
|
|
251549
|
+
}
|
|
251534
251550
|
switch (current.type) {
|
|
251535
251551
|
case dist$3.AST_NODE_TYPES.ChainExpression:
|
|
251536
251552
|
current = current.expression;
|
|
@@ -251544,6 +251560,10 @@ function unwrapExpression(expression) {
|
|
|
251544
251560
|
current = current.expression;
|
|
251545
251561
|
didUnwrap = true;
|
|
251546
251562
|
break;
|
|
251563
|
+
case dist$3.AST_NODE_TYPES.TSSatisfiesExpression:
|
|
251564
|
+
current = current.expression;
|
|
251565
|
+
didUnwrap = true;
|
|
251566
|
+
break;
|
|
251547
251567
|
case dist$3.AST_NODE_TYPES.TSTypeAssertion:
|
|
251548
251568
|
current = current.expression;
|
|
251549
251569
|
didUnwrap = true;
|
|
@@ -251552,6 +251572,19 @@ function unwrapExpression(expression) {
|
|
|
251552
251572
|
}
|
|
251553
251573
|
return current;
|
|
251554
251574
|
}
|
|
251575
|
+
function isExpressionLike(value) {
|
|
251576
|
+
return (typeof value === 'object' &&
|
|
251577
|
+
value !== null &&
|
|
251578
|
+
'type' in value &&
|
|
251579
|
+
typeof value.type === 'string');
|
|
251580
|
+
}
|
|
251581
|
+
function getParenthesizedExpression(expression) {
|
|
251582
|
+
const maybeExpression = expression;
|
|
251583
|
+
return maybeExpression.type === 'ParenthesizedExpression' &&
|
|
251584
|
+
isExpressionLike(maybeExpression.expression)
|
|
251585
|
+
? maybeExpression.expression
|
|
251586
|
+
: null;
|
|
251587
|
+
}
|
|
251555
251588
|
|
|
251556
251589
|
function unwrapMutationTarget(node) {
|
|
251557
251590
|
let current = node;
|
|
@@ -251840,7 +251873,7 @@ function inspectComputedBody(root, context, localScopes, visitedFunctions, repor
|
|
|
251840
251873
|
return;
|
|
251841
251874
|
});
|
|
251842
251875
|
}
|
|
251843
|
-
const rule$
|
|
251876
|
+
const rule$t = createRule({
|
|
251844
251877
|
create(context) {
|
|
251845
251878
|
const { checker, esTreeNodeToTSNodeMap, program, sourceCode, tsNodeToESTreeNodeMap, } = getTypeAwareRuleContext(context);
|
|
251846
251879
|
const signalNodeMap = esTreeNodeToTSNodeMap;
|
|
@@ -251883,6 +251916,88 @@ const rule$s = createRule({
|
|
|
251883
251916
|
name: 'no-side-effects-in-computed',
|
|
251884
251917
|
});
|
|
251885
251918
|
|
|
251919
|
+
const ANGULAR_CORE = '@angular/core';
|
|
251920
|
+
const SIGNAL_FACTORIES = ['computed', 'effect', 'linkedSignal', 'signal'];
|
|
251921
|
+
function isSignalFactoryCall(node, factoryNames) {
|
|
251922
|
+
if (!node) {
|
|
251923
|
+
return false;
|
|
251924
|
+
}
|
|
251925
|
+
const expression = unwrapExpression(node);
|
|
251926
|
+
return (expression.type === dist$3.AST_NODE_TYPES.CallExpression &&
|
|
251927
|
+
expression.callee.type === dist$3.AST_NODE_TYPES.Identifier &&
|
|
251928
|
+
factoryNames.has(expression.callee.name));
|
|
251929
|
+
}
|
|
251930
|
+
function getModuleScopeVariableDeclaration(statement) {
|
|
251931
|
+
if (statement.type === dist$3.AST_NODE_TYPES.VariableDeclaration) {
|
|
251932
|
+
return statement;
|
|
251933
|
+
}
|
|
251934
|
+
if (statement.type !== dist$3.AST_NODE_TYPES.ExportNamedDeclaration) {
|
|
251935
|
+
return null;
|
|
251936
|
+
}
|
|
251937
|
+
return statement.declaration?.type === dist$3.AST_NODE_TYPES.VariableDeclaration
|
|
251938
|
+
? statement.declaration
|
|
251939
|
+
: null;
|
|
251940
|
+
}
|
|
251941
|
+
function collectModuleScopeSignalNames(program, factoryNames) {
|
|
251942
|
+
const names = new Set();
|
|
251943
|
+
for (const statement of program.body) {
|
|
251944
|
+
const declaration = getModuleScopeVariableDeclaration(statement);
|
|
251945
|
+
if (!declaration) {
|
|
251946
|
+
continue;
|
|
251947
|
+
}
|
|
251948
|
+
for (const declarator of declaration.declarations) {
|
|
251949
|
+
if (declarator.id.type === dist$3.AST_NODE_TYPES.Identifier &&
|
|
251950
|
+
isSignalFactoryCall(declarator.init, factoryNames)) {
|
|
251951
|
+
names.add(declarator.id.name);
|
|
251952
|
+
}
|
|
251953
|
+
}
|
|
251954
|
+
}
|
|
251955
|
+
return names;
|
|
251956
|
+
}
|
|
251957
|
+
const rule$s = createRule({
|
|
251958
|
+
create(context) {
|
|
251959
|
+
const program = context.sourceCode.ast;
|
|
251960
|
+
const factoryNames = new Set();
|
|
251961
|
+
for (const name of SIGNAL_FACTORIES) {
|
|
251962
|
+
const localName = getLocalNameForImport(program, ANGULAR_CORE, name);
|
|
251963
|
+
if (localName) {
|
|
251964
|
+
factoryNames.add(localName);
|
|
251965
|
+
}
|
|
251966
|
+
}
|
|
251967
|
+
if (factoryNames.size === 0) {
|
|
251968
|
+
return {};
|
|
251969
|
+
}
|
|
251970
|
+
const moduleScopeSignals = collectModuleScopeSignalNames(program, factoryNames);
|
|
251971
|
+
return moduleScopeSignals.size === 0
|
|
251972
|
+
? {}
|
|
251973
|
+
: {
|
|
251974
|
+
PropertyDefinition(node) {
|
|
251975
|
+
const value = node.value ? unwrapExpression(node.value) : null;
|
|
251976
|
+
if (value?.type !== dist$3.AST_NODE_TYPES.Identifier ||
|
|
251977
|
+
!moduleScopeSignals.has(value.name)) {
|
|
251978
|
+
return;
|
|
251979
|
+
}
|
|
251980
|
+
context.report({
|
|
251981
|
+
data: { name: value.name },
|
|
251982
|
+
messageId: 'noSignalOutsideClass',
|
|
251983
|
+
node: value,
|
|
251984
|
+
});
|
|
251985
|
+
},
|
|
251986
|
+
};
|
|
251987
|
+
},
|
|
251988
|
+
meta: {
|
|
251989
|
+
docs: {
|
|
251990
|
+
description: 'Disallow class properties that reference a module-scope Angular signal; move the signal creation into the class body',
|
|
251991
|
+
},
|
|
251992
|
+
messages: {
|
|
251993
|
+
noSignalOutsideClass: '`{{name}}` is a module-scope signal. Move it into the class body: `{{name}} = signal(...)`.',
|
|
251994
|
+
},
|
|
251995
|
+
schema: [],
|
|
251996
|
+
type: 'problem',
|
|
251997
|
+
},
|
|
251998
|
+
name: 'no-signal-outside-class',
|
|
251999
|
+
});
|
|
252000
|
+
|
|
251886
252001
|
const rule$r = createUntrackedRule({
|
|
251887
252002
|
create(context) {
|
|
251888
252003
|
const { checker, esTreeNodeToTSNodeMap, program, sourceCode } = getTypeAwareRuleContext(context);
|
|
@@ -255592,36 +255707,37 @@ const plugin = {
|
|
|
255592
255707
|
},
|
|
255593
255708
|
rules: {
|
|
255594
255709
|
'array-as-const': rule$5,
|
|
255595
|
-
'attrs-newline': rule$
|
|
255710
|
+
'attrs-newline': rule$T,
|
|
255596
255711
|
'class-property-naming': rule$4,
|
|
255597
|
-
'decorator-key-sort': rule$
|
|
255598
|
-
'element-newline': rule$
|
|
255712
|
+
'decorator-key-sort': rule$S,
|
|
255713
|
+
'element-newline': rule$R,
|
|
255599
255714
|
'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$
|
|
255715
|
+
'host-attributes-sort': rule$Q,
|
|
255716
|
+
'html-logical-properties': rule$P,
|
|
255717
|
+
'import-integrity': rule$O,
|
|
255718
|
+
'injection-token-description': rule$N,
|
|
255719
|
+
'no-commonjs-import-patterns': rule$M,
|
|
255720
|
+
'no-deep-imports': rule$L,
|
|
255721
|
+
'no-deep-imports-to-indexed-packages': rule$K,
|
|
255722
|
+
'no-duplicate-attrs': rule$J,
|
|
255723
|
+
'no-duplicate-id': rule$I,
|
|
255724
|
+
'no-duplicate-in-head': rule$H,
|
|
255725
|
+
'no-empty-style-metadata': rule$G,
|
|
255726
|
+
'no-fully-untracked-effect': rule$F,
|
|
255727
|
+
'no-href-with-router-link': rule$E,
|
|
255728
|
+
'no-implicit-public': rule$D,
|
|
255729
|
+
'no-import-assertions': rule$C,
|
|
255730
|
+
'no-infinite-loop': rule$B,
|
|
255731
|
+
'no-legacy-peer-deps': rule$A,
|
|
255732
|
+
'no-obsolete-attrs': rule$z,
|
|
255733
|
+
'no-obsolete-tags': rule$y,
|
|
255734
|
+
'no-playwright-empty-fill': rule$x,
|
|
255735
|
+
'no-project-as-in-ng-template': rule$w,
|
|
255736
|
+
'no-redundant-type-annotation': rule$v,
|
|
255737
|
+
'no-repeated-signal-in-conditional': rule$u,
|
|
255623
255738
|
'no-restricted-attr-values': rule$2,
|
|
255624
|
-
'no-side-effects-in-computed': rule$
|
|
255739
|
+
'no-side-effects-in-computed': rule$t,
|
|
255740
|
+
'no-signal-outside-class': rule$s,
|
|
255625
255741
|
'no-signal-reads-after-await-in-reactive-context': rule$r,
|
|
255626
255742
|
'no-string-literal-concat': rule$q,
|
|
255627
255743
|
'no-untracked-outside-reactive-context': rule$p,
|
package/package.json
CHANGED
package/rules/convention.d.ts
CHANGED
|
@@ -4,6 +4,13 @@ export declare const TUI_RECOMMENDED_NAMING_CONVENTION: readonly [{
|
|
|
4
4
|
}, {
|
|
5
5
|
readonly format: readonly ["camelCase"];
|
|
6
6
|
readonly selector: readonly ["classMethod", "classProperty"];
|
|
7
|
+
}, {
|
|
8
|
+
readonly filter: {
|
|
9
|
+
readonly match: true;
|
|
10
|
+
readonly regex: string;
|
|
11
|
+
};
|
|
12
|
+
readonly format: null;
|
|
13
|
+
readonly selector: "classProperty";
|
|
7
14
|
}, {
|
|
8
15
|
readonly format: readonly ["UPPER_CASE", "camelCase", "PascalCase"];
|
|
9
16
|
readonly selector: readonly ["variable"];
|
|
@@ -28,6 +35,13 @@ export declare const TUI_CUSTOM_TAIGA_NAMING_CONVENTION: readonly [{
|
|
|
28
35
|
}, {
|
|
29
36
|
readonly format: readonly ["camelCase"];
|
|
30
37
|
readonly selector: readonly ["classMethod", "classProperty"];
|
|
38
|
+
}, {
|
|
39
|
+
readonly filter: {
|
|
40
|
+
readonly match: true;
|
|
41
|
+
readonly regex: string;
|
|
42
|
+
};
|
|
43
|
+
readonly format: null;
|
|
44
|
+
readonly selector: "classProperty";
|
|
31
45
|
}, {
|
|
32
46
|
readonly format: readonly ["UPPER_CASE", "camelCase", "PascalCase"];
|
|
33
47
|
readonly selector: readonly ["variable"];
|
|
@@ -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;
|