@dereekb/dbx-web 13.38.0 → 13.40.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/eslint/package.json +9 -9
- package/lib/button/_button.scss +0 -8
- package/lib/error/_error.scss +3 -3
- package/lib/layout/list/_list.scss +0 -11
- package/lib/router/layout/anchorlist/_anchorlist.scss +12 -7
- package/package.json +9 -10
- package/eslint/index.cjs.default.js +0 -1
- package/eslint/index.cjs.js +0 -2956
- package/eslint/index.cjs.mjs +0 -2
package/eslint/index.cjs.js
DELETED
|
@@ -1,2956 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Module that holds Angular component decorators.
|
|
5
|
-
*/ var ANGULAR_CORE_MODULE = '@angular/core';
|
|
6
|
-
/**
|
|
7
|
-
* Decorators that mark a class with a component-scoped DestroyRef lifecycle.
|
|
8
|
-
*
|
|
9
|
-
* `@Injectable` is intentionally excluded — services often expose long-lived
|
|
10
|
-
* Subjects as part of their public API and cleaning them on destroy is wrong.
|
|
11
|
-
*/ var ANGULAR_COMPONENT_DECORATORS = new Set([
|
|
12
|
-
'Component',
|
|
13
|
-
'Directive',
|
|
14
|
-
'Pipe'
|
|
15
|
-
]);
|
|
16
|
-
/**
|
|
17
|
-
* Module that holds the dbx-components RxJS extras (SubscriptionObject).
|
|
18
|
-
*/ var DBX_COMPONENTS_RXJS_MODULE = '@dereekb/rxjs';
|
|
19
|
-
/**
|
|
20
|
-
* Module that holds Subject/BehaviorSubject/etc.
|
|
21
|
-
*/ var RXJS_MODULE = 'rxjs';
|
|
22
|
-
/**
|
|
23
|
-
* Module that holds the cleanup helpers (cleanSubscription, completeOnDestroy, clean).
|
|
24
|
-
*/ var DBX_COMPONENTS_DBX_CORE_MODULE = '@dereekb/dbx-core';
|
|
25
|
-
/**
|
|
26
|
-
* Identifier name for the `SubscriptionObject` class.
|
|
27
|
-
*/ var SUBSCRIPTION_OBJECT_NAME = 'SubscriptionObject';
|
|
28
|
-
/**
|
|
29
|
-
* Identifier names for RxJS Subject classes that should be wrapped with `completeOnDestroy`.
|
|
30
|
-
*/ var SUBJECT_NAMES = new Set([
|
|
31
|
-
'Subject',
|
|
32
|
-
'BehaviorSubject',
|
|
33
|
-
'ReplaySubject',
|
|
34
|
-
'AsyncSubject'
|
|
35
|
-
]);
|
|
36
|
-
/**
|
|
37
|
-
* Helper imported from `@dereekb/dbx-core` that replaces a manual SubscriptionObject creation.
|
|
38
|
-
*/ var CLEAN_SUBSCRIPTION_HELPER = 'cleanSubscription';
|
|
39
|
-
/**
|
|
40
|
-
* Helper imported from `@dereekb/dbx-core` that wraps a Subject so it completes on destroy.
|
|
41
|
-
*/ var COMPLETE_ON_DESTROY_HELPER = 'completeOnDestroy';
|
|
42
|
-
/**
|
|
43
|
-
* Underlying Destroyable/DestroyFunction primitive helper from `@dereekb/dbx-core`.
|
|
44
|
-
*
|
|
45
|
-
* Accepted as a wrapper for `new SubscriptionObject(...)` since `SubscriptionObject`
|
|
46
|
-
* is `Destroyable`. Not accepted for raw Subjects since those are neither
|
|
47
|
-
* `Destroyable` nor `DestroyFunction` and would not actually call `.complete()`.
|
|
48
|
-
*/ var CLEAN_HELPER = 'clean';
|
|
49
|
-
/**
|
|
50
|
-
* Creates an empty {@link ImportRegistry}.
|
|
51
|
-
*
|
|
52
|
-
* @returns A fresh empty registry.
|
|
53
|
-
*/ function createImportRegistry() {
|
|
54
|
-
return {
|
|
55
|
-
bySource: new Map(),
|
|
56
|
-
localToSource: new Map(),
|
|
57
|
-
sourceToDeclaration: new Map(),
|
|
58
|
-
lastImportDeclaration: null
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Records an `ImportDeclaration` node in the registry. Call from the rule's
|
|
63
|
-
* `ImportDeclaration` visitor for every import in the file.
|
|
64
|
-
*
|
|
65
|
-
* @param registry - The registry to mutate.
|
|
66
|
-
* @param node - The ImportDeclaration AST node.
|
|
67
|
-
*/ function trackImportDeclaration(registry, node) {
|
|
68
|
-
var _registry_bySource_get, _node_specifiers;
|
|
69
|
-
var _node_source;
|
|
70
|
-
var source = (_node_source = node.source) === null || _node_source === void 0 ? void 0 : _node_source.value;
|
|
71
|
-
if (!source) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
registry.lastImportDeclaration = node;
|
|
75
|
-
registry.sourceToDeclaration.set(source, node);
|
|
76
|
-
var localNames = (_registry_bySource_get = registry.bySource.get(source)) !== null && _registry_bySource_get !== void 0 ? _registry_bySource_get : new Set();
|
|
77
|
-
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
78
|
-
try {
|
|
79
|
-
for(var _iterator = ((_node_specifiers = node.specifiers) !== null && _node_specifiers !== void 0 ? _node_specifiers : [])[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
80
|
-
var specifier = _step.value;
|
|
81
|
-
if (specifier.type === 'ImportSpecifier' || specifier.type === 'ImportDefaultSpecifier' || specifier.type === 'ImportNamespaceSpecifier') {
|
|
82
|
-
var _specifier_local;
|
|
83
|
-
var localName = (_specifier_local = specifier.local) === null || _specifier_local === void 0 ? void 0 : _specifier_local.name;
|
|
84
|
-
if (localName) {
|
|
85
|
-
localNames.add(localName);
|
|
86
|
-
registry.localToSource.set(localName, source);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
} catch (err) {
|
|
91
|
-
_didIteratorError = true;
|
|
92
|
-
_iteratorError = err;
|
|
93
|
-
} finally{
|
|
94
|
-
try {
|
|
95
|
-
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
96
|
-
_iterator.return();
|
|
97
|
-
}
|
|
98
|
-
} finally{
|
|
99
|
-
if (_didIteratorError) {
|
|
100
|
-
throw _iteratorError;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
registry.bySource.set(source, localNames);
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Returns true when the given local identifier name was imported from the given module.
|
|
108
|
-
*
|
|
109
|
-
* @param registry - The import registry built from the file's import declarations.
|
|
110
|
-
* @param localName - The local identifier (as it appears in code).
|
|
111
|
-
* @param fromSource - The expected source-module string.
|
|
112
|
-
* @returns True when the local name maps to the given source.
|
|
113
|
-
*/ function isImportedFrom(registry, localName, fromSource) {
|
|
114
|
-
return registry.localToSource.get(localName) === fromSource;
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Extracts the decorator name from a decorator AST node.
|
|
118
|
-
*
|
|
119
|
-
* Handles `@Foo()` (CallExpression) and `@Foo` (Identifier). Returns the empty
|
|
120
|
-
* string for anything else.
|
|
121
|
-
*
|
|
122
|
-
* @param decorator - The decorator AST node.
|
|
123
|
-
* @returns The decorator name, or empty string when unrecognized.
|
|
124
|
-
*/ function getDecoratorName(decorator) {
|
|
125
|
-
var expression = decorator === null || decorator === void 0 ? void 0 : decorator.expression;
|
|
126
|
-
var result = '';
|
|
127
|
-
if (expression) {
|
|
128
|
-
if (expression.type === 'CallExpression') {
|
|
129
|
-
var _expression_callee, _expression_callee1, _expression_callee_property;
|
|
130
|
-
if (((_expression_callee = expression.callee) === null || _expression_callee === void 0 ? void 0 : _expression_callee.type) === 'Identifier') {
|
|
131
|
-
result = expression.callee.name;
|
|
132
|
-
} else if (((_expression_callee1 = expression.callee) === null || _expression_callee1 === void 0 ? void 0 : _expression_callee1.type) === 'MemberExpression' && ((_expression_callee_property = expression.callee.property) === null || _expression_callee_property === void 0 ? void 0 : _expression_callee_property.type) === 'Identifier') {
|
|
133
|
-
result = expression.callee.property.name;
|
|
134
|
-
}
|
|
135
|
-
} else if (expression.type === 'Identifier') {
|
|
136
|
-
result = expression.name;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return result;
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Returns the first decorator on the class that names a component-tier
|
|
143
|
-
* Angular decorator (`@Component`, `@Directive`, `@Pipe`) imported from
|
|
144
|
-
* `@angular/core`, or null when none match.
|
|
145
|
-
*
|
|
146
|
-
* @param classNode - The ClassDeclaration / ClassExpression AST node.
|
|
147
|
-
* @param registry - The file's import registry, used to verify the decorator
|
|
148
|
-
* identifier really came from `@angular/core` (and not a local alias).
|
|
149
|
-
* @returns The matching decorator and its name, or null.
|
|
150
|
-
*/ function findAngularComponentDecorator(classNode, registry) {
|
|
151
|
-
var decorators = classNode === null || classNode === void 0 ? void 0 : classNode.decorators;
|
|
152
|
-
var result = null;
|
|
153
|
-
if (decorators && decorators.length > 0) {
|
|
154
|
-
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
155
|
-
try {
|
|
156
|
-
for(var _iterator = decorators[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
157
|
-
var decorator = _step.value;
|
|
158
|
-
var name = getDecoratorName(decorator);
|
|
159
|
-
if (ANGULAR_COMPONENT_DECORATORS.has(name) && isImportedFrom(registry, name, ANGULAR_CORE_MODULE)) {
|
|
160
|
-
result = {
|
|
161
|
-
decorator: decorator,
|
|
162
|
-
name: name
|
|
163
|
-
};
|
|
164
|
-
break;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
} catch (err) {
|
|
168
|
-
_didIteratorError = true;
|
|
169
|
-
_iteratorError = err;
|
|
170
|
-
} finally{
|
|
171
|
-
try {
|
|
172
|
-
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
173
|
-
_iterator.return();
|
|
174
|
-
}
|
|
175
|
-
} finally{
|
|
176
|
-
if (_didIteratorError) {
|
|
177
|
-
throw _iteratorError;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return result;
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Returns the property name of a class member when its key is a simple Identifier or string Literal.
|
|
186
|
-
*
|
|
187
|
-
* Returns null for computed/symbol/private keys.
|
|
188
|
-
*
|
|
189
|
-
* @param member - A ClassBody member AST node.
|
|
190
|
-
* @returns The property name, or null when not a simple key.
|
|
191
|
-
*/ function getClassMemberName(member) {
|
|
192
|
-
var key = member === null || member === void 0 ? void 0 : member.key;
|
|
193
|
-
var result = null;
|
|
194
|
-
if (key && !member.computed) {
|
|
195
|
-
if (key.type === 'Identifier') {
|
|
196
|
-
result = key.name;
|
|
197
|
-
} else if (key.type === 'Literal' && typeof key.value === 'string') {
|
|
198
|
-
result = key.value;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
return result;
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Finds the `ngOnDestroy` method declaration on the given class, if any.
|
|
205
|
-
*
|
|
206
|
-
* @param classNode - The ClassDeclaration / ClassExpression AST node.
|
|
207
|
-
* @returns The MethodDefinition AST node for `ngOnDestroy`, or null.
|
|
208
|
-
*/ function findNgOnDestroyMethod(classNode) {
|
|
209
|
-
var _classNode_body;
|
|
210
|
-
var members = classNode === null || classNode === void 0 ? void 0 : (_classNode_body = classNode.body) === null || _classNode_body === void 0 ? void 0 : _classNode_body.body;
|
|
211
|
-
var result = null;
|
|
212
|
-
if (members) {
|
|
213
|
-
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
214
|
-
try {
|
|
215
|
-
for(var _iterator = members[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
216
|
-
var member = _step.value;
|
|
217
|
-
if (member.type === 'MethodDefinition' && member.kind === 'method' && getClassMemberName(member) === 'ngOnDestroy') {
|
|
218
|
-
result = member;
|
|
219
|
-
break;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
} catch (err) {
|
|
223
|
-
_didIteratorError = true;
|
|
224
|
-
_iteratorError = err;
|
|
225
|
-
} finally{
|
|
226
|
-
try {
|
|
227
|
-
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
228
|
-
_iterator.return();
|
|
229
|
-
}
|
|
230
|
-
} finally{
|
|
231
|
-
if (_didIteratorError) {
|
|
232
|
-
throw _iteratorError;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
return result;
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* If the given expression is a `CallExpression` whose callee is one of the
|
|
241
|
-
* accepted identifier names, returns the matching name. Otherwise null.
|
|
242
|
-
*
|
|
243
|
-
* @param node - The expression AST node.
|
|
244
|
-
* @param names - The accepted identifier names.
|
|
245
|
-
* @returns The matched name, or null.
|
|
246
|
-
*
|
|
247
|
-
* @example
|
|
248
|
-
* ```
|
|
249
|
-
* isCalledIdentifier(node, ['cleanSubscription', 'clean']) // returns 'cleanSubscription'
|
|
250
|
-
* ```
|
|
251
|
-
*/ function isCalledIdentifier(node, names) {
|
|
252
|
-
var result = null;
|
|
253
|
-
if ((node === null || node === void 0 ? void 0 : node.type) === 'CallExpression') {
|
|
254
|
-
var callee = node.callee;
|
|
255
|
-
if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'Identifier' && names.has(callee.name)) {
|
|
256
|
-
result = callee.name;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
return result;
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Returns true when the given AST node is a `this.<propName>` MemberExpression.
|
|
263
|
-
*
|
|
264
|
-
* @param node - The AST node to check.
|
|
265
|
-
* @param propName - The expected property name.
|
|
266
|
-
* @returns True when the node is `this.<propName>`.
|
|
267
|
-
*/ function isThisMemberAccess(node, propName) {
|
|
268
|
-
var _node_object, _node_property;
|
|
269
|
-
return (node === null || node === void 0 ? void 0 : node.type) === 'MemberExpression' && ((_node_object = node.object) === null || _node_object === void 0 ? void 0 : _node_object.type) === 'ThisExpression' && !node.computed && ((_node_property = node.property) === null || _node_property === void 0 ? void 0 : _node_property.type) === 'Identifier' && node.property.name === propName;
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Builds a fix operation that ensures `importName` is imported from
|
|
273
|
-
* `fromSource` in the file. Returns null when the import is already present.
|
|
274
|
-
*
|
|
275
|
-
* Side effect: mutates the registry to mark the import as present, so two
|
|
276
|
-
* separate report fixes in the same lint pass don't both insert the same import.
|
|
277
|
-
*
|
|
278
|
-
* @param input - The fixer, registry, and import names.
|
|
279
|
-
* @returns A fix operation, or null when the import is already present.
|
|
280
|
-
*/ function ensureNamedImportFix(input) {
|
|
281
|
-
var fixer = input.fixer, registry = input.registry, importName = input.importName, fromSource = input.fromSource;
|
|
282
|
-
var existing = registry.bySource.get(fromSource);
|
|
283
|
-
var result = null;
|
|
284
|
-
if (!(existing === null || existing === void 0 ? void 0 : existing.has(importName))) {
|
|
285
|
-
var declaration = registry.sourceToDeclaration.get(fromSource);
|
|
286
|
-
if (declaration) {
|
|
287
|
-
var _declaration_specifiers;
|
|
288
|
-
var lastSpecifier = (_declaration_specifiers = declaration.specifiers) === null || _declaration_specifiers === void 0 ? void 0 : _declaration_specifiers[declaration.specifiers.length - 1];
|
|
289
|
-
if (lastSpecifier) {
|
|
290
|
-
var updatedSet = existing !== null && existing !== void 0 ? existing : new Set();
|
|
291
|
-
updatedSet.add(importName);
|
|
292
|
-
registry.bySource.set(fromSource, updatedSet);
|
|
293
|
-
registry.localToSource.set(importName, fromSource);
|
|
294
|
-
result = fixer.insertTextAfter(lastSpecifier, ", ".concat(importName));
|
|
295
|
-
}
|
|
296
|
-
} else if (registry.lastImportDeclaration) {
|
|
297
|
-
var updatedSet1 = existing !== null && existing !== void 0 ? existing : new Set();
|
|
298
|
-
updatedSet1.add(importName);
|
|
299
|
-
registry.bySource.set(fromSource, updatedSet1);
|
|
300
|
-
registry.localToSource.set(importName, fromSource);
|
|
301
|
-
result = fixer.insertTextAfter(registry.lastImportDeclaration, "\nimport { ".concat(importName, " } from '").concat(fromSource, "';"));
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
return result;
|
|
305
|
-
}
|
|
306
|
-
/**
|
|
307
|
-
* Returns true when the given PropertyDefinition declares a `static` member.
|
|
308
|
-
*
|
|
309
|
-
* @param node - The PropertyDefinition AST node.
|
|
310
|
-
* @returns True when the node has `static` modifier.
|
|
311
|
-
*/ function isStaticProperty(node) {
|
|
312
|
-
return (node === null || node === void 0 ? void 0 : node.static) === true;
|
|
313
|
-
}
|
|
314
|
-
/**
|
|
315
|
-
* Returns true when the given PropertyDefinition uses `declare`
|
|
316
|
-
* (`declare readonly foo: T`) — i.e. has no runtime initializer.
|
|
317
|
-
*
|
|
318
|
-
* @param node - The PropertyDefinition AST node.
|
|
319
|
-
* @returns True when the property is declared abstractly.
|
|
320
|
-
*/ function isDeclareProperty(node) {
|
|
321
|
-
return (node === null || node === void 0 ? void 0 : node.declare) === true;
|
|
322
|
-
}
|
|
323
|
-
/**
|
|
324
|
-
* The Angular `OnDestroy` lifecycle interface name.
|
|
325
|
-
*/ var ON_DESTROY_INTERFACE_NAME = 'OnDestroy';
|
|
326
|
-
/**
|
|
327
|
-
* Locates an `implements OnDestroy` clause on the class whose `OnDestroy`
|
|
328
|
-
* identifier resolves to the import from `@angular/core`. Returns null when
|
|
329
|
-
* no matching clause exists.
|
|
330
|
-
*
|
|
331
|
-
* @param classNode - The ClassDeclaration / ClassExpression AST node.
|
|
332
|
-
* @param registry - The file's import registry.
|
|
333
|
-
* @returns The match details, or null.
|
|
334
|
-
*/ function findOnDestroyImplementsClause(classNode, registry) {
|
|
335
|
-
var _ref;
|
|
336
|
-
var allImplements = (_ref = classNode === null || classNode === void 0 ? void 0 : classNode.implements) !== null && _ref !== void 0 ? _ref : [];
|
|
337
|
-
var result = null;
|
|
338
|
-
for(var index = 0; index < allImplements.length; index += 1){
|
|
339
|
-
var clauseSpecifier = allImplements[index];
|
|
340
|
-
var expression = clauseSpecifier === null || clauseSpecifier === void 0 ? void 0 : clauseSpecifier.expression;
|
|
341
|
-
if ((expression === null || expression === void 0 ? void 0 : expression.type) === 'Identifier' && expression.name === ON_DESTROY_INTERFACE_NAME && isImportedFrom(registry, ON_DESTROY_INTERFACE_NAME, ANGULAR_CORE_MODULE)) {
|
|
342
|
-
result = {
|
|
343
|
-
allImplements: allImplements,
|
|
344
|
-
clauseSpecifier: clauseSpecifier,
|
|
345
|
-
index: index
|
|
346
|
-
};
|
|
347
|
-
break;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
return result;
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* Computes the source range to remove for the given `implements` specifier so
|
|
354
|
-
* that the surrounding `implements` clause stays well-formed.
|
|
355
|
-
*
|
|
356
|
-
* Behavior:
|
|
357
|
-
* - When the specifier is the only entry, the entire `implements <X>` clause
|
|
358
|
-
* is removed, including the leading whitespace before the `implements`
|
|
359
|
-
* keyword (so `class Foo implements OnDestroy {` becomes `class Foo {`).
|
|
360
|
-
* - When the specifier is the first of several, the specifier and the
|
|
361
|
-
* following comma+whitespace are removed.
|
|
362
|
-
* - Otherwise, the preceding comma+whitespace and the specifier are removed.
|
|
363
|
-
*
|
|
364
|
-
* @param match - The `implements OnDestroy` match details.
|
|
365
|
-
* @param sourceCode - The ESLint sourceCode service.
|
|
366
|
-
* @returns A `[start, end]` range tuple suitable for `fixer.removeRange`.
|
|
367
|
-
*/ function getImplementsSpecifierRemovalRange(match, sourceCode) {
|
|
368
|
-
var allImplements = match.allImplements, clauseSpecifier = match.clauseSpecifier, index = match.index;
|
|
369
|
-
var result;
|
|
370
|
-
if (allImplements.length === 1) {
|
|
371
|
-
var implementsKeyword = sourceCode.getTokenBefore(clauseSpecifier, {
|
|
372
|
-
filter: function filter(token) {
|
|
373
|
-
return token.type === 'Keyword' && token.value === 'implements';
|
|
374
|
-
}
|
|
375
|
-
});
|
|
376
|
-
var tokenBeforeImplements = implementsKeyword ? sourceCode.getTokenBefore(implementsKeyword) : null;
|
|
377
|
-
var startPos;
|
|
378
|
-
if (tokenBeforeImplements) {
|
|
379
|
-
startPos = tokenBeforeImplements.range[1];
|
|
380
|
-
} else if (implementsKeyword) {
|
|
381
|
-
startPos = implementsKeyword.range[0];
|
|
382
|
-
} else {
|
|
383
|
-
startPos = clauseSpecifier.range[0];
|
|
384
|
-
}
|
|
385
|
-
result = [
|
|
386
|
-
startPos,
|
|
387
|
-
clauseSpecifier.range[1]
|
|
388
|
-
];
|
|
389
|
-
} else if (index === 0) {
|
|
390
|
-
result = [
|
|
391
|
-
clauseSpecifier.range[0],
|
|
392
|
-
allImplements[1].range[0]
|
|
393
|
-
];
|
|
394
|
-
} else {
|
|
395
|
-
result = [
|
|
396
|
-
allImplements[index - 1].range[1],
|
|
397
|
-
clauseSpecifier.range[1]
|
|
398
|
-
];
|
|
399
|
-
}
|
|
400
|
-
return result;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Identifier names accepted as the wrapper around a manual `new SubscriptionObject(...)`.
|
|
405
|
-
*/ var ACCEPTED_WRAPPERS$1 = new Set([
|
|
406
|
-
CLEAN_SUBSCRIPTION_HELPER,
|
|
407
|
-
CLEAN_HELPER
|
|
408
|
-
]);
|
|
409
|
-
/**
|
|
410
|
-
* ESLint rule that requires class-field initializers of `new SubscriptionObject(...)`
|
|
411
|
-
* to be replaced with `cleanSubscription(...)` (which auto-registers cleanup with
|
|
412
|
-
* Angular's DestroyRef) on `@Component` / `@Directive` / `@Pipe` classes.
|
|
413
|
-
*
|
|
414
|
-
* Fires only when `SubscriptionObject` is imported from `@dereekb/rxjs`.
|
|
415
|
-
*
|
|
416
|
-
* Auto-fix:
|
|
417
|
-
* - Rewrites the initializer to `cleanSubscription(...)` (preserving any constructor argument).
|
|
418
|
-
* - Inserts the `cleanSubscription` named import from `@dereekb/dbx-core` if missing.
|
|
419
|
-
* - Removes any matching `this.<field>.destroy();` line from the same class's `ngOnDestroy`.
|
|
420
|
-
*/ var DBX_WEB_REQUIRE_CLEAN_SUBSCRIPTION_RULE = {
|
|
421
|
-
meta: {
|
|
422
|
-
type: 'problem',
|
|
423
|
-
fixable: 'code',
|
|
424
|
-
docs: {
|
|
425
|
-
description: 'Require cleanSubscription() instead of new SubscriptionObject() in Angular component, directive, or pipe classes',
|
|
426
|
-
recommended: true
|
|
427
|
-
},
|
|
428
|
-
messages: {
|
|
429
|
-
missingCleanSubscription: 'Replace `new SubscriptionObject(...)` with `cleanSubscription(...)` from @dereekb/dbx-core. cleanSubscription registers cleanup with Angular DestroyRef automatically, removing the need for manual destroy() in ngOnDestroy.'
|
|
430
|
-
},
|
|
431
|
-
schema: []
|
|
432
|
-
},
|
|
433
|
-
create: function create(context) {
|
|
434
|
-
var registry = createImportRegistry();
|
|
435
|
-
var sourceCode = context.sourceCode;
|
|
436
|
-
var visitClass = function visitClass(classNode) {
|
|
437
|
-
var _ref;
|
|
438
|
-
var _classNode_body;
|
|
439
|
-
var matchedDecorator = findAngularComponentDecorator(classNode, registry);
|
|
440
|
-
if (!matchedDecorator) {
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
var members = (_ref = (_classNode_body = classNode.body) === null || _classNode_body === void 0 ? void 0 : _classNode_body.body) !== null && _ref !== void 0 ? _ref : [];
|
|
444
|
-
var ngOnDestroy = findNgOnDestroyMethod(classNode);
|
|
445
|
-
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
446
|
-
try {
|
|
447
|
-
var _loop = function() {
|
|
448
|
-
var member = _step.value;
|
|
449
|
-
if (member.type !== 'PropertyDefinition' || isStaticProperty(member) || isDeclareProperty(member)) {
|
|
450
|
-
return "continue";
|
|
451
|
-
}
|
|
452
|
-
var propName = getClassMemberName(member);
|
|
453
|
-
var initializer = member.value;
|
|
454
|
-
if (!propName || !initializer) {
|
|
455
|
-
return "continue";
|
|
456
|
-
}
|
|
457
|
-
if (!isUnwrappedSubscriptionObjectNew(initializer, registry)) {
|
|
458
|
-
return "continue";
|
|
459
|
-
}
|
|
460
|
-
context.report({
|
|
461
|
-
node: initializer,
|
|
462
|
-
messageId: 'missingCleanSubscription',
|
|
463
|
-
fix: function fix(fixer) {
|
|
464
|
-
return buildSubscriptionObjectFix({
|
|
465
|
-
fixer: fixer,
|
|
466
|
-
newExpr: initializer,
|
|
467
|
-
propName: propName,
|
|
468
|
-
ngOnDestroy: ngOnDestroy,
|
|
469
|
-
registry: registry,
|
|
470
|
-
sourceCode: sourceCode
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
});
|
|
474
|
-
};
|
|
475
|
-
for(var _iterator = members[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true)_loop();
|
|
476
|
-
} catch (err) {
|
|
477
|
-
_didIteratorError = true;
|
|
478
|
-
_iteratorError = err;
|
|
479
|
-
} finally{
|
|
480
|
-
try {
|
|
481
|
-
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
482
|
-
_iterator.return();
|
|
483
|
-
}
|
|
484
|
-
} finally{
|
|
485
|
-
if (_didIteratorError) {
|
|
486
|
-
throw _iteratorError;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
};
|
|
491
|
-
return {
|
|
492
|
-
ImportDeclaration: function ImportDeclaration(node) {
|
|
493
|
-
trackImportDeclaration(registry, node);
|
|
494
|
-
},
|
|
495
|
-
ClassDeclaration: function ClassDeclaration(classNode) {
|
|
496
|
-
visitClass(classNode);
|
|
497
|
-
},
|
|
498
|
-
ClassExpression: function ClassExpression(classNode) {
|
|
499
|
-
visitClass(classNode);
|
|
500
|
-
}
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
};
|
|
504
|
-
/**
|
|
505
|
-
* Returns true when the given initializer is a bare `new SubscriptionObject(...)`
|
|
506
|
-
* expression where `SubscriptionObject` resolves to the import from `@dereekb/rxjs`.
|
|
507
|
-
*
|
|
508
|
-
* Returns false when the expression is wrapped (e.g. `cleanSubscription(...)` or
|
|
509
|
-
* `clean(new SubscriptionObject(...))`).
|
|
510
|
-
*
|
|
511
|
-
* @param expression - The initializer expression AST node.
|
|
512
|
-
* @param registry - The file's import registry.
|
|
513
|
-
* @returns True when the expression is a flagged unwrapped `new SubscriptionObject(...)`.
|
|
514
|
-
*/ function isUnwrappedSubscriptionObjectNew(expression, registry) {
|
|
515
|
-
var result = false;
|
|
516
|
-
if (!isCalledIdentifier(expression, ACCEPTED_WRAPPERS$1) && expression.type === 'NewExpression') {
|
|
517
|
-
var callee = expression.callee;
|
|
518
|
-
if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'Identifier' && callee.name === SUBSCRIPTION_OBJECT_NAME && isImportedFrom(registry, SUBSCRIPTION_OBJECT_NAME, DBX_COMPONENTS_RXJS_MODULE)) {
|
|
519
|
-
result = true;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
return result;
|
|
523
|
-
}
|
|
524
|
-
/**
|
|
525
|
-
* Builds the composite fix for one violating property.
|
|
526
|
-
*
|
|
527
|
-
* @param input - The flagged expression, its property name, the class's ngOnDestroy node, the import registry, and source-code services.
|
|
528
|
-
* @returns A list of fix operations, or null when no fix is producible.
|
|
529
|
-
*/ function buildSubscriptionObjectFix(input) {
|
|
530
|
-
var _newExpr_callee;
|
|
531
|
-
var fixer = input.fixer, newExpr = input.newExpr, propName = input.propName, ngOnDestroy = input.ngOnDestroy, registry = input.registry, sourceCode = input.sourceCode;
|
|
532
|
-
var calleeRange = (_newExpr_callee = newExpr.callee) === null || _newExpr_callee === void 0 ? void 0 : _newExpr_callee.range;
|
|
533
|
-
var fixes = null;
|
|
534
|
-
if (calleeRange) {
|
|
535
|
-
var collected = [];
|
|
536
|
-
collected.push(fixer.replaceTextRange([
|
|
537
|
-
newExpr.range[0],
|
|
538
|
-
calleeRange[1]
|
|
539
|
-
], CLEAN_SUBSCRIPTION_HELPER));
|
|
540
|
-
var importFix = ensureNamedImportFix({
|
|
541
|
-
fixer: fixer,
|
|
542
|
-
registry: registry,
|
|
543
|
-
importName: CLEAN_SUBSCRIPTION_HELPER,
|
|
544
|
-
fromSource: DBX_COMPONENTS_DBX_CORE_MODULE
|
|
545
|
-
});
|
|
546
|
-
if (importFix) {
|
|
547
|
-
collected.push(importFix);
|
|
548
|
-
}
|
|
549
|
-
if (ANGULAR_COMPONENT_DECORATORS.size > 0 && ngOnDestroy) {
|
|
550
|
-
collectNgOnDestroyRemovalFixes({
|
|
551
|
-
fixer: fixer,
|
|
552
|
-
ngOnDestroy: ngOnDestroy,
|
|
553
|
-
propName: propName,
|
|
554
|
-
methodName: 'destroy',
|
|
555
|
-
sourceCode: sourceCode,
|
|
556
|
-
fixes: collected
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
fixes = collected;
|
|
560
|
-
}
|
|
561
|
-
return fixes;
|
|
562
|
-
}
|
|
563
|
-
/**
|
|
564
|
-
* Pushes fixes that remove `this.<propName>.<methodName>()` ExpressionStatements
|
|
565
|
-
* from `ngOnDestroy`'s body. Removes the statement node and any preceding
|
|
566
|
-
* indentation on the same line so a blank line isn't left behind.
|
|
567
|
-
*
|
|
568
|
-
* @param input - The fixer, ngOnDestroy method, target property/method names, source-code service, and fix collector.
|
|
569
|
-
*/ function collectNgOnDestroyRemovalFixes(input) {
|
|
570
|
-
var _ngOnDestroy_value_body, _ngOnDestroy_value;
|
|
571
|
-
var fixer = input.fixer, ngOnDestroy = input.ngOnDestroy, propName = input.propName, methodName = input.methodName, sourceCode = input.sourceCode, fixes = input.fixes;
|
|
572
|
-
var body = (_ngOnDestroy_value = ngOnDestroy.value) === null || _ngOnDestroy_value === void 0 ? void 0 : (_ngOnDestroy_value_body = _ngOnDestroy_value.body) === null || _ngOnDestroy_value_body === void 0 ? void 0 : _ngOnDestroy_value_body.body;
|
|
573
|
-
if (!body) {
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
577
|
-
try {
|
|
578
|
-
for(var _iterator = body[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
579
|
-
var statement = _step.value;
|
|
580
|
-
var _call_callee, _member_property;
|
|
581
|
-
if (statement.type !== 'ExpressionStatement') {
|
|
582
|
-
continue;
|
|
583
|
-
}
|
|
584
|
-
var call = statement.expression;
|
|
585
|
-
if ((call === null || call === void 0 ? void 0 : call.type) !== 'CallExpression' || ((_call_callee = call.callee) === null || _call_callee === void 0 ? void 0 : _call_callee.type) !== 'MemberExpression') {
|
|
586
|
-
continue;
|
|
587
|
-
}
|
|
588
|
-
var member = call.callee;
|
|
589
|
-
if (member.computed || ((_member_property = member.property) === null || _member_property === void 0 ? void 0 : _member_property.type) !== 'Identifier' || member.property.name !== methodName) {
|
|
590
|
-
continue;
|
|
591
|
-
}
|
|
592
|
-
if (!isThisMemberAccess(member.object, propName)) {
|
|
593
|
-
continue;
|
|
594
|
-
}
|
|
595
|
-
fixes.push(fixer.removeRange(getStatementRangeWithLeadingWhitespace(statement, sourceCode)));
|
|
596
|
-
}
|
|
597
|
-
} catch (err) {
|
|
598
|
-
_didIteratorError = true;
|
|
599
|
-
_iteratorError = err;
|
|
600
|
-
} finally{
|
|
601
|
-
try {
|
|
602
|
-
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
603
|
-
_iterator.return();
|
|
604
|
-
}
|
|
605
|
-
} finally{
|
|
606
|
-
if (_didIteratorError) {
|
|
607
|
-
throw _iteratorError;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
/**
|
|
613
|
-
* Returns the range to remove for a statement, expanded to include any
|
|
614
|
-
* leading whitespace on the same line and the trailing newline. This avoids
|
|
615
|
-
* leaving a blank line after fix application.
|
|
616
|
-
*
|
|
617
|
-
* @param statement - The ExpressionStatement AST node.
|
|
618
|
-
* @param sourceCode - The ESLint sourceCode service.
|
|
619
|
-
* @returns A range tuple `[start, end]` to pass to `fixer.removeRange`.
|
|
620
|
-
*/ function getStatementRangeWithLeadingWhitespace(statement, sourceCode) {
|
|
621
|
-
var sourceText = sourceCode.text;
|
|
622
|
-
var start = statement.range[0];
|
|
623
|
-
var end = statement.range[1];
|
|
624
|
-
var lineStart = start;
|
|
625
|
-
while(lineStart > 0 && sourceText[lineStart - 1] !== '\n'){
|
|
626
|
-
lineStart -= 1;
|
|
627
|
-
}
|
|
628
|
-
var lineEnd = end;
|
|
629
|
-
if (lineEnd < sourceText.length && sourceText[lineEnd] === '\n') {
|
|
630
|
-
lineEnd += 1;
|
|
631
|
-
}
|
|
632
|
-
return [
|
|
633
|
-
lineStart,
|
|
634
|
-
lineEnd
|
|
635
|
-
];
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
/**
|
|
639
|
-
* Identifier names accepted as the wrapper around a manual `new <Subject>(...)`.
|
|
640
|
-
*
|
|
641
|
-
* Only `completeOnDestroy` is accepted — `clean()` does not call `.complete()`
|
|
642
|
-
* on a Subject (Subjects are neither `Destroyable` nor `DestroyFunction`).
|
|
643
|
-
*/ var ACCEPTED_WRAPPERS = new Set([
|
|
644
|
-
COMPLETE_ON_DESTROY_HELPER
|
|
645
|
-
]);
|
|
646
|
-
/**
|
|
647
|
-
* ESLint rule that requires class-field initializers of `new Subject(...)`,
|
|
648
|
-
* `new BehaviorSubject(...)`, `new ReplaySubject(...)`, and `new AsyncSubject(...)`
|
|
649
|
-
* to be wrapped with `completeOnDestroy(...)` from `@dereekb/dbx-core` on
|
|
650
|
-
* `@Component` / `@Directive` / `@Pipe` classes.
|
|
651
|
-
*
|
|
652
|
-
* Fires only when the Subject identifier is imported from `rxjs`.
|
|
653
|
-
*
|
|
654
|
-
* Auto-fix:
|
|
655
|
-
* - Wraps the initializer with `completeOnDestroy(...)`.
|
|
656
|
-
* - Inserts the `completeOnDestroy` named import from `@dereekb/dbx-core` if missing.
|
|
657
|
-
* - Removes any matching `this.<field>.complete();` line from the same class's `ngOnDestroy`.
|
|
658
|
-
*/ var DBX_WEB_REQUIRE_COMPLETE_ON_DESTROY_RULE = {
|
|
659
|
-
meta: {
|
|
660
|
-
type: 'problem',
|
|
661
|
-
fixable: 'code',
|
|
662
|
-
docs: {
|
|
663
|
-
description: 'Require completeOnDestroy() wrapping new Subject/BehaviorSubject/ReplaySubject/AsyncSubject in Angular component, directive, or pipe classes',
|
|
664
|
-
recommended: true
|
|
665
|
-
},
|
|
666
|
-
messages: {
|
|
667
|
-
missingCompleteOnDestroy: 'Wrap `new {{subjectName}}(...)` with `completeOnDestroy(...)` from @dereekb/dbx-core. completeOnDestroy registers cleanup with Angular DestroyRef automatically, removing the need for manual complete() in ngOnDestroy.'
|
|
668
|
-
},
|
|
669
|
-
schema: []
|
|
670
|
-
},
|
|
671
|
-
create: function create(context) {
|
|
672
|
-
var registry = createImportRegistry();
|
|
673
|
-
var sourceCode = context.sourceCode;
|
|
674
|
-
var visitClass = function visitClass(classNode) {
|
|
675
|
-
var _ref;
|
|
676
|
-
var _classNode_body;
|
|
677
|
-
var matchedDecorator = findAngularComponentDecorator(classNode, registry);
|
|
678
|
-
if (!matchedDecorator) {
|
|
679
|
-
return;
|
|
680
|
-
}
|
|
681
|
-
var members = (_ref = (_classNode_body = classNode.body) === null || _classNode_body === void 0 ? void 0 : _classNode_body.body) !== null && _ref !== void 0 ? _ref : [];
|
|
682
|
-
var ngOnDestroy = findNgOnDestroyMethod(classNode);
|
|
683
|
-
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
684
|
-
try {
|
|
685
|
-
var _loop = function() {
|
|
686
|
-
var member = _step.value;
|
|
687
|
-
if (member.type !== 'PropertyDefinition' || isStaticProperty(member) || isDeclareProperty(member)) {
|
|
688
|
-
return "continue";
|
|
689
|
-
}
|
|
690
|
-
var propName = getClassMemberName(member);
|
|
691
|
-
var initializer = member.value;
|
|
692
|
-
if (!propName || !initializer) {
|
|
693
|
-
return "continue";
|
|
694
|
-
}
|
|
695
|
-
var subjectName = unwrappedSubjectNewName(initializer, registry);
|
|
696
|
-
if (!subjectName) {
|
|
697
|
-
return "continue";
|
|
698
|
-
}
|
|
699
|
-
context.report({
|
|
700
|
-
node: initializer,
|
|
701
|
-
messageId: 'missingCompleteOnDestroy',
|
|
702
|
-
data: {
|
|
703
|
-
subjectName: subjectName
|
|
704
|
-
},
|
|
705
|
-
fix: function fix(fixer) {
|
|
706
|
-
return buildSubjectFix({
|
|
707
|
-
fixer: fixer,
|
|
708
|
-
newExpr: initializer,
|
|
709
|
-
propName: propName,
|
|
710
|
-
ngOnDestroy: ngOnDestroy,
|
|
711
|
-
registry: registry,
|
|
712
|
-
sourceCode: sourceCode
|
|
713
|
-
});
|
|
714
|
-
}
|
|
715
|
-
});
|
|
716
|
-
};
|
|
717
|
-
for(var _iterator = members[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true)_loop();
|
|
718
|
-
} catch (err) {
|
|
719
|
-
_didIteratorError = true;
|
|
720
|
-
_iteratorError = err;
|
|
721
|
-
} finally{
|
|
722
|
-
try {
|
|
723
|
-
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
724
|
-
_iterator.return();
|
|
725
|
-
}
|
|
726
|
-
} finally{
|
|
727
|
-
if (_didIteratorError) {
|
|
728
|
-
throw _iteratorError;
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
};
|
|
733
|
-
return {
|
|
734
|
-
ImportDeclaration: function ImportDeclaration(node) {
|
|
735
|
-
trackImportDeclaration(registry, node);
|
|
736
|
-
},
|
|
737
|
-
ClassDeclaration: function ClassDeclaration(classNode) {
|
|
738
|
-
visitClass(classNode);
|
|
739
|
-
},
|
|
740
|
-
ClassExpression: function ClassExpression(classNode) {
|
|
741
|
-
visitClass(classNode);
|
|
742
|
-
}
|
|
743
|
-
};
|
|
744
|
-
}
|
|
745
|
-
};
|
|
746
|
-
/**
|
|
747
|
-
* Returns the Subject class name when the given initializer is a bare
|
|
748
|
-
* `new Subject/BehaviorSubject/ReplaySubject/AsyncSubject(...)` whose
|
|
749
|
-
* identifier resolves to the import from `rxjs`. Returns null otherwise
|
|
750
|
-
* (including when the expression is already wrapped).
|
|
751
|
-
*
|
|
752
|
-
* @param expression - The initializer expression AST node.
|
|
753
|
-
* @param registry - The file's import registry.
|
|
754
|
-
* @returns The matched Subject identifier name, or null.
|
|
755
|
-
*/ function unwrappedSubjectNewName(expression, registry) {
|
|
756
|
-
var result = null;
|
|
757
|
-
if (!isCalledIdentifier(expression, ACCEPTED_WRAPPERS) && expression.type === 'NewExpression') {
|
|
758
|
-
var callee = expression.callee;
|
|
759
|
-
if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'Identifier' && SUBJECT_NAMES.has(callee.name) && isImportedFrom(registry, callee.name, RXJS_MODULE)) {
|
|
760
|
-
result = callee.name;
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
return result;
|
|
764
|
-
}
|
|
765
|
-
/**
|
|
766
|
-
* Builds the composite fix for one violating property.
|
|
767
|
-
*
|
|
768
|
-
* @param input - The flagged expression, its property name, the class's ngOnDestroy node, the import registry, and source-code services.
|
|
769
|
-
* @returns A list of fix operations.
|
|
770
|
-
*/ function buildSubjectFix(input) {
|
|
771
|
-
var fixer = input.fixer, newExpr = input.newExpr, propName = input.propName, ngOnDestroy = input.ngOnDestroy, registry = input.registry, sourceCode = input.sourceCode;
|
|
772
|
-
var fixes = [];
|
|
773
|
-
fixes.push(fixer.insertTextBefore(newExpr, "".concat(COMPLETE_ON_DESTROY_HELPER, "(")), fixer.insertTextAfter(newExpr, ')'));
|
|
774
|
-
var importFix = ensureNamedImportFix({
|
|
775
|
-
fixer: fixer,
|
|
776
|
-
registry: registry,
|
|
777
|
-
importName: COMPLETE_ON_DESTROY_HELPER,
|
|
778
|
-
fromSource: DBX_COMPONENTS_DBX_CORE_MODULE
|
|
779
|
-
});
|
|
780
|
-
if (importFix) {
|
|
781
|
-
fixes.push(importFix);
|
|
782
|
-
}
|
|
783
|
-
if (ngOnDestroy) {
|
|
784
|
-
collectNgOnDestroyRemovalFixes({
|
|
785
|
-
fixer: fixer,
|
|
786
|
-
ngOnDestroy: ngOnDestroy,
|
|
787
|
-
propName: propName,
|
|
788
|
-
methodName: 'complete',
|
|
789
|
-
sourceCode: sourceCode,
|
|
790
|
-
fixes: fixes
|
|
791
|
-
});
|
|
792
|
-
}
|
|
793
|
-
return fixes;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
/**
|
|
797
|
-
* Identifier names that, when used to wrap a class field initializer, mean the
|
|
798
|
-
* field's cleanup is registered with Angular DestroyRef and a manual
|
|
799
|
-
* `.destroy()` / `.complete()` call in ngOnDestroy is redundant.
|
|
800
|
-
*/ var HELPER_NAMES = new Set([
|
|
801
|
-
CLEAN_SUBSCRIPTION_HELPER,
|
|
802
|
-
COMPLETE_ON_DESTROY_HELPER,
|
|
803
|
-
CLEAN_HELPER
|
|
804
|
-
]);
|
|
805
|
-
/**
|
|
806
|
-
* Method names on a wrapped field whose call inside ngOnDestroy is redundant.
|
|
807
|
-
*/ var REDUNDANT_METHODS = new Set([
|
|
808
|
-
'destroy',
|
|
809
|
-
'complete'
|
|
810
|
-
]);
|
|
811
|
-
/**
|
|
812
|
-
* ESLint rule that flags `ngOnDestroy()` bodies whose statements are entirely
|
|
813
|
-
* redundant `this.<field>.destroy()` / `this.<field>.complete()` calls on
|
|
814
|
-
* fields whose initializer is wrapped with `cleanSubscription`,
|
|
815
|
-
* `completeOnDestroy`, or `clean`.
|
|
816
|
-
*
|
|
817
|
-
* Auto-fix:
|
|
818
|
-
* - Removes each redundant statement, plus its leading whitespace and trailing newline.
|
|
819
|
-
* - Removes the `ngOnDestroy` method declaration when its body becomes empty.
|
|
820
|
-
* - When the `ngOnDestroy` method is removed entirely, also removes the
|
|
821
|
-
* `implements OnDestroy` clause from the class (verified against the
|
|
822
|
-
* `@angular/core` import). The now-unused `OnDestroy` import is left for
|
|
823
|
-
* `eslint-plugin-unused-imports` to clean up.
|
|
824
|
-
* - When a class declares `implements OnDestroy` from `@angular/core` but has
|
|
825
|
-
* no `ngOnDestroy()` method (e.g. left over from a previous run), the
|
|
826
|
-
* orphaned implements clause is removed.
|
|
827
|
-
*/ var DBX_WEB_NO_REDUNDANT_ON_DESTROY_RULE = {
|
|
828
|
-
meta: {
|
|
829
|
-
type: 'suggestion',
|
|
830
|
-
fixable: 'code',
|
|
831
|
-
docs: {
|
|
832
|
-
description: 'Disallow redundant ngOnDestroy calls when fields are already wrapped with cleanSubscription/completeOnDestroy/clean',
|
|
833
|
-
recommended: true
|
|
834
|
-
},
|
|
835
|
-
messages: {
|
|
836
|
-
redundantCleanupCall: 'Redundant `this.{{name}}.{{method}}()` — `{{name}}` is initialized via `{{wrapper}}(...)` which already registers cleanup with Angular DestroyRef.',
|
|
837
|
-
redundantNgOnDestroy: '`ngOnDestroy()` only contains redundant cleanup calls for fields wrapped with cleanSubscription/completeOnDestroy/clean. Remove the method.',
|
|
838
|
-
emptyNgOnDestroy: '`ngOnDestroy()` has an empty body. Remove the method.',
|
|
839
|
-
orphanedImplementsOnDestroy: 'Class declares `implements OnDestroy` but has no `ngOnDestroy()` method. Remove the implements clause.'
|
|
840
|
-
},
|
|
841
|
-
schema: []
|
|
842
|
-
},
|
|
843
|
-
create: function create(context) {
|
|
844
|
-
var registry = createImportRegistry();
|
|
845
|
-
var sourceCode = context.sourceCode;
|
|
846
|
-
var reportOrphanedImplements = function reportOrphanedImplements(classNode) {
|
|
847
|
-
var implementsMatch = findOnDestroyImplementsClause(classNode, registry);
|
|
848
|
-
if (implementsMatch) {
|
|
849
|
-
context.report({
|
|
850
|
-
node: implementsMatch.clauseSpecifier,
|
|
851
|
-
messageId: 'orphanedImplementsOnDestroy',
|
|
852
|
-
fix: function fix(fixer) {
|
|
853
|
-
return [
|
|
854
|
-
fixer.removeRange(getImplementsSpecifierRemovalRange(implementsMatch, sourceCode))
|
|
855
|
-
];
|
|
856
|
-
}
|
|
857
|
-
});
|
|
858
|
-
}
|
|
859
|
-
};
|
|
860
|
-
var reportRemoveNgOnDestroy = function reportRemoveNgOnDestroy(ngOnDestroy, classNode, messageId) {
|
|
861
|
-
context.report({
|
|
862
|
-
node: ngOnDestroy,
|
|
863
|
-
messageId: messageId,
|
|
864
|
-
fix: function fix(fixer) {
|
|
865
|
-
return buildRemoveNgOnDestroyFixes({
|
|
866
|
-
fixer: fixer,
|
|
867
|
-
ngOnDestroy: ngOnDestroy,
|
|
868
|
-
classNode: classNode,
|
|
869
|
-
registry: registry,
|
|
870
|
-
sourceCode: sourceCode
|
|
871
|
-
});
|
|
872
|
-
}
|
|
873
|
-
});
|
|
874
|
-
};
|
|
875
|
-
var reportRedundantStatements = function reportRedundantStatements(entries) {
|
|
876
|
-
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
877
|
-
try {
|
|
878
|
-
var _loop = function() {
|
|
879
|
-
var entry = _step.value;
|
|
880
|
-
context.report({
|
|
881
|
-
node: entry.statement,
|
|
882
|
-
messageId: 'redundantCleanupCall',
|
|
883
|
-
data: {
|
|
884
|
-
name: entry.fieldName,
|
|
885
|
-
method: entry.method,
|
|
886
|
-
wrapper: entry.wrapper
|
|
887
|
-
},
|
|
888
|
-
fix: function fix(fixer) {
|
|
889
|
-
return [
|
|
890
|
-
fixer.removeRange(getStatementRangeWithLeadingWhitespace(entry.statement, sourceCode))
|
|
891
|
-
];
|
|
892
|
-
}
|
|
893
|
-
});
|
|
894
|
-
};
|
|
895
|
-
for(var _iterator = entries[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true)_loop();
|
|
896
|
-
} catch (err) {
|
|
897
|
-
_didIteratorError = true;
|
|
898
|
-
_iteratorError = err;
|
|
899
|
-
} finally{
|
|
900
|
-
try {
|
|
901
|
-
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
902
|
-
_iterator.return();
|
|
903
|
-
}
|
|
904
|
-
} finally{
|
|
905
|
-
if (_didIteratorError) {
|
|
906
|
-
throw _iteratorError;
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
};
|
|
911
|
-
var visitNgOnDestroyBody = function visitNgOnDestroyBody(ngOnDestroy, body, classNode) {
|
|
912
|
-
var _partitionNgOnDestroyStatements = partitionNgOnDestroyStatements(body, classNode), redundantStatements = _partitionNgOnDestroyStatements.redundantStatements, hasNonRedundantStatement = _partitionNgOnDestroyStatements.hasNonRedundantStatement;
|
|
913
|
-
if (redundantStatements.length > 0) {
|
|
914
|
-
if (hasNonRedundantStatement) {
|
|
915
|
-
reportRedundantStatements(redundantStatements);
|
|
916
|
-
} else {
|
|
917
|
-
reportRemoveNgOnDestroy(ngOnDestroy, classNode, 'redundantNgOnDestroy');
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
};
|
|
921
|
-
var visitClass = function visitClass(classNode) {
|
|
922
|
-
if (findAngularComponentDecorator(classNode, registry)) {
|
|
923
|
-
var _ngOnDestroy_value_body, _ngOnDestroy_value;
|
|
924
|
-
var ngOnDestroy = findNgOnDestroyMethod(classNode);
|
|
925
|
-
var body = ngOnDestroy === null || ngOnDestroy === void 0 ? void 0 : (_ngOnDestroy_value = ngOnDestroy.value) === null || _ngOnDestroy_value === void 0 ? void 0 : (_ngOnDestroy_value_body = _ngOnDestroy_value.body) === null || _ngOnDestroy_value_body === void 0 ? void 0 : _ngOnDestroy_value_body.body;
|
|
926
|
-
if (!ngOnDestroy || !body) {
|
|
927
|
-
reportOrphanedImplements(classNode);
|
|
928
|
-
} else if (body.length === 0) {
|
|
929
|
-
reportRemoveNgOnDestroy(ngOnDestroy, classNode, 'emptyNgOnDestroy');
|
|
930
|
-
} else {
|
|
931
|
-
visitNgOnDestroyBody(ngOnDestroy, body, classNode);
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
};
|
|
935
|
-
return {
|
|
936
|
-
ImportDeclaration: function ImportDeclaration(node) {
|
|
937
|
-
trackImportDeclaration(registry, node);
|
|
938
|
-
},
|
|
939
|
-
ClassDeclaration: function ClassDeclaration(classNode) {
|
|
940
|
-
visitClass(classNode);
|
|
941
|
-
},
|
|
942
|
-
ClassExpression: function ClassExpression(classNode) {
|
|
943
|
-
visitClass(classNode);
|
|
944
|
-
}
|
|
945
|
-
};
|
|
946
|
-
}
|
|
947
|
-
};
|
|
948
|
-
/**
|
|
949
|
-
* Walks the class members and returns a map of property name to the wrapper
|
|
950
|
-
* helper used in its initializer (`cleanSubscription`, `completeOnDestroy`, or
|
|
951
|
-
* `clean`). Properties whose initializer is not a recognized wrapper are
|
|
952
|
-
* omitted.
|
|
953
|
-
*
|
|
954
|
-
* @param classNode - The ClassDeclaration / ClassExpression AST node.
|
|
955
|
-
* @returns Map of field name to wrapper helper name.
|
|
956
|
-
*/ function collectWrappedFieldNames(classNode) {
|
|
957
|
-
var _ref;
|
|
958
|
-
var _classNode_body;
|
|
959
|
-
var result = new Map();
|
|
960
|
-
var members = (_ref = (_classNode_body = classNode.body) === null || _classNode_body === void 0 ? void 0 : _classNode_body.body) !== null && _ref !== void 0 ? _ref : [];
|
|
961
|
-
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
962
|
-
try {
|
|
963
|
-
for(var _iterator = members[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
964
|
-
var member = _step.value;
|
|
965
|
-
if (member.type !== 'PropertyDefinition' || isStaticProperty(member) || isDeclareProperty(member)) {
|
|
966
|
-
continue;
|
|
967
|
-
}
|
|
968
|
-
var propName = getClassMemberName(member);
|
|
969
|
-
var wrapper = propName ? wrapperNameFromInitializer(member.value) : null;
|
|
970
|
-
if (propName && wrapper) {
|
|
971
|
-
result.set(propName, wrapper);
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
} catch (err) {
|
|
975
|
-
_didIteratorError = true;
|
|
976
|
-
_iteratorError = err;
|
|
977
|
-
} finally{
|
|
978
|
-
try {
|
|
979
|
-
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
980
|
-
_iterator.return();
|
|
981
|
-
}
|
|
982
|
-
} finally{
|
|
983
|
-
if (_didIteratorError) {
|
|
984
|
-
throw _iteratorError;
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
return result;
|
|
989
|
-
}
|
|
990
|
-
/**
|
|
991
|
-
* Returns the name of the cleanup helper wrapping the given initializer
|
|
992
|
-
* expression, or null when the expression is not wrapped.
|
|
993
|
-
*
|
|
994
|
-
* @param expression - The initializer expression, or null/undefined.
|
|
995
|
-
* @returns The wrapper helper name (`cleanSubscription` etc.) or null.
|
|
996
|
-
*/ function wrapperNameFromInitializer(expression) {
|
|
997
|
-
return expression ? isCalledIdentifier(expression, HELPER_NAMES) : null;
|
|
998
|
-
}
|
|
999
|
-
/**
|
|
1000
|
-
* Splits an `ngOnDestroy` body into redundant cleanup matches and a flag
|
|
1001
|
-
* indicating whether any other (non-redundant) statement is present.
|
|
1002
|
-
*
|
|
1003
|
-
* @param body - The statements of the `ngOnDestroy` method body.
|
|
1004
|
-
* @param classNode - The owning class node, used to gather wrapped fields.
|
|
1005
|
-
* @returns The redundant matches and the non-redundant flag.
|
|
1006
|
-
*/ function partitionNgOnDestroyStatements(body, classNode) {
|
|
1007
|
-
var wrappedFields = collectWrappedFieldNames(classNode);
|
|
1008
|
-
var redundantStatements = [];
|
|
1009
|
-
var hasNonRedundantStatement = false;
|
|
1010
|
-
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
1011
|
-
try {
|
|
1012
|
-
for(var _iterator = body[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
1013
|
-
var statement = _step.value;
|
|
1014
|
-
var match = matchRedundantCleanupStatement(statement, wrappedFields);
|
|
1015
|
-
if (match) {
|
|
1016
|
-
redundantStatements.push(match);
|
|
1017
|
-
} else {
|
|
1018
|
-
hasNonRedundantStatement = true;
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
} catch (err) {
|
|
1022
|
-
_didIteratorError = true;
|
|
1023
|
-
_iteratorError = err;
|
|
1024
|
-
} finally{
|
|
1025
|
-
try {
|
|
1026
|
-
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
1027
|
-
_iterator.return();
|
|
1028
|
-
}
|
|
1029
|
-
} finally{
|
|
1030
|
-
if (_didIteratorError) {
|
|
1031
|
-
throw _iteratorError;
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
return {
|
|
1036
|
-
redundantStatements: redundantStatements,
|
|
1037
|
-
hasNonRedundantStatement: hasNonRedundantStatement
|
|
1038
|
-
};
|
|
1039
|
-
}
|
|
1040
|
-
/**
|
|
1041
|
-
* Returns the redundant method name (`destroy` / `complete`) when the call
|
|
1042
|
-
* expression is a zero-argument member call to one of those methods.
|
|
1043
|
-
*
|
|
1044
|
-
* @param expression - The expression to inspect.
|
|
1045
|
-
* @returns The method name and its callee MemberExpression, or null.
|
|
1046
|
-
*/ function getRedundantMethodCall(expression) {
|
|
1047
|
-
var _expression_arguments, _expression_callee;
|
|
1048
|
-
var result = null;
|
|
1049
|
-
var isZeroArgMemberCall = (expression === null || expression === void 0 ? void 0 : expression.type) === 'CallExpression' && ((_expression_arguments = expression.arguments) === null || _expression_arguments === void 0 ? void 0 : _expression_arguments.length) === 0 && ((_expression_callee = expression.callee) === null || _expression_callee === void 0 ? void 0 : _expression_callee.type) === 'MemberExpression';
|
|
1050
|
-
if (isZeroArgMemberCall) {
|
|
1051
|
-
var _callee_property;
|
|
1052
|
-
var callee = expression.callee;
|
|
1053
|
-
var methodName = !callee.computed && ((_callee_property = callee.property) === null || _callee_property === void 0 ? void 0 : _callee_property.type) === 'Identifier' ? callee.property.name : null;
|
|
1054
|
-
if (methodName && REDUNDANT_METHODS.has(methodName)) {
|
|
1055
|
-
result = {
|
|
1056
|
-
methodName: methodName,
|
|
1057
|
-
callee: callee
|
|
1058
|
-
};
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
return result;
|
|
1062
|
-
}
|
|
1063
|
-
/**
|
|
1064
|
-
* Returns the `this.<fieldName>` field name from a callee object, or null
|
|
1065
|
-
* when the receiver is not a non-computed `this.<identifier>` access.
|
|
1066
|
-
*
|
|
1067
|
-
* @param calleeObject - The callee's object (the receiver of the method call).
|
|
1068
|
-
* @returns The field name or null.
|
|
1069
|
-
*/ function getThisFieldName(calleeObject) {
|
|
1070
|
-
var _calleeObject_property;
|
|
1071
|
-
var result = null;
|
|
1072
|
-
if ((calleeObject === null || calleeObject === void 0 ? void 0 : calleeObject.type) === 'MemberExpression' && !calleeObject.computed && ((_calleeObject_property = calleeObject.property) === null || _calleeObject_property === void 0 ? void 0 : _calleeObject_property.type) === 'Identifier') {
|
|
1073
|
-
var fieldName = calleeObject.property.name;
|
|
1074
|
-
if (isThisMemberAccess(calleeObject, fieldName)) {
|
|
1075
|
-
result = fieldName;
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
return result;
|
|
1079
|
-
}
|
|
1080
|
-
/**
|
|
1081
|
-
* Returns details for a redundant cleanup statement, or null when the
|
|
1082
|
-
* statement is anything other than a redundant `this.<field>.<destroy|complete>()` call.
|
|
1083
|
-
*
|
|
1084
|
-
* @param statement - The body statement AST node.
|
|
1085
|
-
* @param wrappedFields - Map of class field names to their wrapper helper names.
|
|
1086
|
-
* @returns Match details, or null.
|
|
1087
|
-
*/ function matchRedundantCleanupStatement(statement, wrappedFields) {
|
|
1088
|
-
var result = null;
|
|
1089
|
-
if (statement.type === 'ExpressionStatement') {
|
|
1090
|
-
var methodCall = getRedundantMethodCall(statement.expression);
|
|
1091
|
-
var fieldName = methodCall ? getThisFieldName(methodCall.callee.object) : null;
|
|
1092
|
-
var wrapper = fieldName ? wrappedFields.get(fieldName) : undefined;
|
|
1093
|
-
if (methodCall && fieldName && wrapper) {
|
|
1094
|
-
result = {
|
|
1095
|
-
statement: statement,
|
|
1096
|
-
fieldName: fieldName,
|
|
1097
|
-
method: methodCall.methodName,
|
|
1098
|
-
wrapper: wrapper
|
|
1099
|
-
};
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
return result;
|
|
1103
|
-
}
|
|
1104
|
-
/**
|
|
1105
|
-
* Builds the fix list for removing the entire `ngOnDestroy` method along with
|
|
1106
|
-
* any matching `implements OnDestroy` clause from the class declaration.
|
|
1107
|
-
*
|
|
1108
|
-
* @param input - The fixer, method node, class node, registry, and source-code service.
|
|
1109
|
-
* @returns The fix operations to apply.
|
|
1110
|
-
*/ function buildRemoveNgOnDestroyFixes(input) {
|
|
1111
|
-
var fixer = input.fixer, ngOnDestroy = input.ngOnDestroy, classNode = input.classNode, registry = input.registry, sourceCode = input.sourceCode;
|
|
1112
|
-
var fixes = [
|
|
1113
|
-
fixer.removeRange(getStatementRangeWithLeadingWhitespace(ngOnDestroy, sourceCode))
|
|
1114
|
-
];
|
|
1115
|
-
var implementsMatch = findOnDestroyImplementsClause(classNode, registry);
|
|
1116
|
-
if (implementsMatch) {
|
|
1117
|
-
fixes.push(fixer.removeRange(getImplementsSpecifierRemovalRange(implementsMatch, sourceCode)));
|
|
1118
|
-
}
|
|
1119
|
-
return fixes;
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
/**
|
|
1123
|
-
* Initializer call names that produce a computed Signal whose property must end with `Signal`.
|
|
1124
|
-
*/ var COMPUTED_INITIALIZERS = new Set([
|
|
1125
|
-
'computed'
|
|
1126
|
-
]);
|
|
1127
|
-
/**
|
|
1128
|
-
* Initializer call names that produce a raw signal-input whose property must NOT end with `Signal`.
|
|
1129
|
-
*
|
|
1130
|
-
* Includes `input.required(...)` (handled below via CallExpression callee inspection) and
|
|
1131
|
-
* `model.required(...)` — those are recognized when the callee's MemberExpression `object` name
|
|
1132
|
-
* is in this set.
|
|
1133
|
-
*/ var INPUT_INITIALIZERS$1 = new Set([
|
|
1134
|
-
'input',
|
|
1135
|
-
'model'
|
|
1136
|
-
]);
|
|
1137
|
-
/**
|
|
1138
|
-
* Suffix that distinguishes computed signals from raw input signals.
|
|
1139
|
-
*/ var SIGNAL_SUFFIX = 'Signal';
|
|
1140
|
-
/**
|
|
1141
|
-
* ESLint rule that enforces the dbx-components Angular signal naming convention:
|
|
1142
|
-
*
|
|
1143
|
-
* - Class properties initialized with `computed(...)` must end with `Signal`.
|
|
1144
|
-
* - Class properties initialized with `input(...)`, `input.required(...)`, `model(...)`,
|
|
1145
|
-
* or `model.required(...)` must NOT end with `Signal`.
|
|
1146
|
-
*
|
|
1147
|
-
* Fires only on classes decorated with `@Component`, `@Directive`, or `@Pipe` from
|
|
1148
|
-
* `@angular/core`, and only when the relevant initializer identifier is imported from
|
|
1149
|
-
* `@angular/core`.
|
|
1150
|
-
*
|
|
1151
|
-
* Not auto-fixable: renaming a class field also requires updating every reference in the
|
|
1152
|
-
* class body and any associated templates, which is outside the safe scope of an ESLint
|
|
1153
|
-
* autofix.
|
|
1154
|
-
*
|
|
1155
|
-
* @see `dbx__note__angular-conventions` → ANG-C2 Computed Signal Naming.
|
|
1156
|
-
*/ var DBX_WEB_REQUIRE_COMPUTED_SIGNAL_SUFFIX_RULE = {
|
|
1157
|
-
meta: {
|
|
1158
|
-
type: 'suggestion',
|
|
1159
|
-
fixable: undefined,
|
|
1160
|
-
docs: {
|
|
1161
|
-
description: 'Require the `Signal` suffix on computed() class properties and disallow it on input()/model() class properties in Angular component classes.',
|
|
1162
|
-
recommended: true
|
|
1163
|
-
},
|
|
1164
|
-
messages: {
|
|
1165
|
-
missingSignalSuffix: "Computed signal '{{property}}' must end with the 'Signal' suffix (e.g. '{{suggested}}') to distinguish it from raw input signals.",
|
|
1166
|
-
signalSuffixOnInput: "Raw input signal '{{property}}' must NOT end with the 'Signal' suffix — reserve that for computed signals."
|
|
1167
|
-
},
|
|
1168
|
-
schema: []
|
|
1169
|
-
},
|
|
1170
|
-
create: function create(context) {
|
|
1171
|
-
var registry = createImportRegistry();
|
|
1172
|
-
var visitClass = function visitClass(classNode) {
|
|
1173
|
-
var _ref;
|
|
1174
|
-
var _classNode_body;
|
|
1175
|
-
var matched = findAngularComponentDecorator(classNode, registry);
|
|
1176
|
-
if (!matched) {
|
|
1177
|
-
return;
|
|
1178
|
-
}
|
|
1179
|
-
var members = (_ref = (_classNode_body = classNode.body) === null || _classNode_body === void 0 ? void 0 : _classNode_body.body) !== null && _ref !== void 0 ? _ref : [];
|
|
1180
|
-
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
1181
|
-
try {
|
|
1182
|
-
for(var _iterator = members[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
1183
|
-
var member = _step.value;
|
|
1184
|
-
if (member.type !== 'PropertyDefinition' || isStaticProperty(member) || isDeclareProperty(member)) {
|
|
1185
|
-
continue;
|
|
1186
|
-
}
|
|
1187
|
-
var propName = getClassMemberName(member);
|
|
1188
|
-
var initializer = member.value;
|
|
1189
|
-
if (!propName || (initializer === null || initializer === void 0 ? void 0 : initializer.type) !== 'CallExpression') {
|
|
1190
|
-
continue;
|
|
1191
|
-
}
|
|
1192
|
-
var initializerKind = classifyInitializer(initializer, registry);
|
|
1193
|
-
if (initializerKind === 'computed') {
|
|
1194
|
-
if (!propName.endsWith(SIGNAL_SUFFIX)) {
|
|
1195
|
-
var _member_key;
|
|
1196
|
-
context.report({
|
|
1197
|
-
node: (_member_key = member.key) !== null && _member_key !== void 0 ? _member_key : member,
|
|
1198
|
-
messageId: 'missingSignalSuffix',
|
|
1199
|
-
data: {
|
|
1200
|
-
property: propName,
|
|
1201
|
-
suggested: "".concat(propName).concat(SIGNAL_SUFFIX)
|
|
1202
|
-
}
|
|
1203
|
-
});
|
|
1204
|
-
}
|
|
1205
|
-
} else if (initializerKind === 'input' && propName.endsWith(SIGNAL_SUFFIX) && propName !== SIGNAL_SUFFIX) {
|
|
1206
|
-
var _member_key1;
|
|
1207
|
-
context.report({
|
|
1208
|
-
node: (_member_key1 = member.key) !== null && _member_key1 !== void 0 ? _member_key1 : member,
|
|
1209
|
-
messageId: 'signalSuffixOnInput',
|
|
1210
|
-
data: {
|
|
1211
|
-
property: propName
|
|
1212
|
-
}
|
|
1213
|
-
});
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
} catch (err) {
|
|
1217
|
-
_didIteratorError = true;
|
|
1218
|
-
_iteratorError = err;
|
|
1219
|
-
} finally{
|
|
1220
|
-
try {
|
|
1221
|
-
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
1222
|
-
_iterator.return();
|
|
1223
|
-
}
|
|
1224
|
-
} finally{
|
|
1225
|
-
if (_didIteratorError) {
|
|
1226
|
-
throw _iteratorError;
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
};
|
|
1231
|
-
return {
|
|
1232
|
-
ImportDeclaration: function ImportDeclaration(node) {
|
|
1233
|
-
trackImportDeclaration(registry, node);
|
|
1234
|
-
},
|
|
1235
|
-
ClassDeclaration: function ClassDeclaration(classNode) {
|
|
1236
|
-
visitClass(classNode);
|
|
1237
|
-
},
|
|
1238
|
-
ClassExpression: function ClassExpression(classNode) {
|
|
1239
|
-
visitClass(classNode);
|
|
1240
|
-
}
|
|
1241
|
-
};
|
|
1242
|
-
}
|
|
1243
|
-
};
|
|
1244
|
-
/**
|
|
1245
|
-
* Classifies a CallExpression initializer as a computed signal, raw input signal, or neither.
|
|
1246
|
-
*
|
|
1247
|
-
* Recognizes:
|
|
1248
|
-
* - `computed(...)` → `'computed'`
|
|
1249
|
-
* - `input(...)`, `input.required(...)` → `'input'`
|
|
1250
|
-
* - `model(...)`, `model.required(...)` → `'input'`
|
|
1251
|
-
*
|
|
1252
|
-
* Each form requires the root identifier to be imported from `@angular/core`.
|
|
1253
|
-
*
|
|
1254
|
-
* @param callExpression - The CallExpression AST node serving as the property initializer.
|
|
1255
|
-
* @param registry - The file's import registry.
|
|
1256
|
-
* @returns The initializer kind, or `null` when the call is unrelated.
|
|
1257
|
-
*/ function classifyInitializer(callExpression, registry) {
|
|
1258
|
-
var _callee_object, _callee_property;
|
|
1259
|
-
var callee = callExpression.callee;
|
|
1260
|
-
var result = null;
|
|
1261
|
-
if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'Identifier') {
|
|
1262
|
-
var name = callee.name;
|
|
1263
|
-
if (COMPUTED_INITIALIZERS.has(name) && isImportedFrom(registry, name, ANGULAR_CORE_MODULE)) {
|
|
1264
|
-
result = 'computed';
|
|
1265
|
-
} else if (INPUT_INITIALIZERS$1.has(name) && isImportedFrom(registry, name, ANGULAR_CORE_MODULE)) {
|
|
1266
|
-
result = 'input';
|
|
1267
|
-
}
|
|
1268
|
-
} else if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'MemberExpression' && callee.computed === false && ((_callee_object = callee.object) === null || _callee_object === void 0 ? void 0 : _callee_object.type) === 'Identifier' && ((_callee_property = callee.property) === null || _callee_property === void 0 ? void 0 : _callee_property.type) === 'Identifier' && callee.property.name === 'required') {
|
|
1269
|
-
var rootName = callee.object.name;
|
|
1270
|
-
if (INPUT_INITIALIZERS$1.has(rootName) && isImportedFrom(registry, rootName, ANGULAR_CORE_MODULE)) {
|
|
1271
|
-
result = 'input';
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
return result;
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
/**
|
|
1278
|
-
* Initializer call identifiers that produce an Angular signal input.
|
|
1279
|
-
*
|
|
1280
|
-
* Includes the bare `input(...)` form and the `input.required(...)` member form.
|
|
1281
|
-
* `model()` / `model.required()` are intentionally excluded — two-way bindings are
|
|
1282
|
-
* rare and counting them would inflate the threshold for components that mix them
|
|
1283
|
-
* sparingly. Extending the set is a one-line change.
|
|
1284
|
-
*/ var INPUT_INITIALIZERS = new Set([
|
|
1285
|
-
'input'
|
|
1286
|
-
]);
|
|
1287
|
-
/**
|
|
1288
|
-
* Default cap on the number of `input(...)` / `input.required(...)` properties a
|
|
1289
|
-
* single Angular component-tier class may declare before the rule fires.
|
|
1290
|
-
*/ var DEFAULT_INPUT_THRESHOLD = 3;
|
|
1291
|
-
/**
|
|
1292
|
-
* ESLint rule that flags `@Component` / `@Directive` / `@Pipe` classes which
|
|
1293
|
-
* declare more than `threshold` (default 3) signal-input properties.
|
|
1294
|
-
*
|
|
1295
|
-
* When a component-tier class drifts past the threshold, the convention is to
|
|
1296
|
-
* consolidate the loose inputs into a single config-typed input, e.g.
|
|
1297
|
-
* `config = input<Maybe<DbxFooConfig>>()`. This rule does not enforce a specific
|
|
1298
|
-
* property name or shape — it is purely a count, analogous to the workspace's
|
|
1299
|
-
* `dereekb-util/prefer-config-object` rule for function parameters.
|
|
1300
|
-
*
|
|
1301
|
-
* Only `input(...)` and `input.required(...)` calls whose root identifier is
|
|
1302
|
-
* imported from `@angular/core` are counted. Static and `declare` members are
|
|
1303
|
-
* ignored, as are non-decorated classes and imports from other modules.
|
|
1304
|
-
*
|
|
1305
|
-
* Not auto-fixable: consolidating loose inputs into a config interface is a
|
|
1306
|
-
* design-level refactor that updates the template, the consuming sites, and the
|
|
1307
|
-
* type surface — outside the safe scope of an ESLint autofix.
|
|
1308
|
-
*
|
|
1309
|
-
* @see `dbx__note__angular-conventions` → ANG-C1 Component Config Input.
|
|
1310
|
-
*/ var DBX_WEB_REQUIRE_COMPONENT_CONFIG_INPUT_RULE = {
|
|
1311
|
-
meta: {
|
|
1312
|
-
type: 'suggestion',
|
|
1313
|
-
fixable: undefined,
|
|
1314
|
-
docs: {
|
|
1315
|
-
description: 'Disallow more than `threshold` (default 3) signal-input properties on a single @Component/@Directive/@Pipe class; consolidate them into a single config-typed input.',
|
|
1316
|
-
recommended: true
|
|
1317
|
-
},
|
|
1318
|
-
messages: {
|
|
1319
|
-
tooManySignalInputs: "Class '{{className}}' declares {{count}} signal inputs (more than {{threshold}}). Consolidate them into a single config-typed input (e.g. `config = input<Maybe<...Config>>()`). See dbx__note__angular-conventions → ANG-C1."
|
|
1320
|
-
},
|
|
1321
|
-
schema: [
|
|
1322
|
-
{
|
|
1323
|
-
type: 'object',
|
|
1324
|
-
properties: {
|
|
1325
|
-
threshold: {
|
|
1326
|
-
type: 'number',
|
|
1327
|
-
minimum: 0,
|
|
1328
|
-
description: 'Maximum number of signal-input properties allowed before the rule reports. Defaults to 3.'
|
|
1329
|
-
}
|
|
1330
|
-
},
|
|
1331
|
-
additionalProperties: false
|
|
1332
|
-
}
|
|
1333
|
-
]
|
|
1334
|
-
},
|
|
1335
|
-
create: function create(context) {
|
|
1336
|
-
var _ref;
|
|
1337
|
-
var _context_options;
|
|
1338
|
-
var registry = createImportRegistry();
|
|
1339
|
-
var options = (_ref = (_context_options = context.options) === null || _context_options === void 0 ? void 0 : _context_options[0]) !== null && _ref !== void 0 ? _ref : {};
|
|
1340
|
-
var threshold = typeof options.threshold === 'number' ? options.threshold : DEFAULT_INPUT_THRESHOLD;
|
|
1341
|
-
var visitClass = function visitClass(classNode) {
|
|
1342
|
-
var _ref;
|
|
1343
|
-
var _classNode_body;
|
|
1344
|
-
var matched = findAngularComponentDecorator(classNode, registry);
|
|
1345
|
-
if (!matched) {
|
|
1346
|
-
return;
|
|
1347
|
-
}
|
|
1348
|
-
var members = (_ref = (_classNode_body = classNode.body) === null || _classNode_body === void 0 ? void 0 : _classNode_body.body) !== null && _ref !== void 0 ? _ref : [];
|
|
1349
|
-
var inputCount = 0;
|
|
1350
|
-
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
1351
|
-
try {
|
|
1352
|
-
for(var _iterator = members[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
1353
|
-
var member = _step.value;
|
|
1354
|
-
if (member.type !== 'PropertyDefinition' || isStaticProperty(member) || isDeclareProperty(member)) {
|
|
1355
|
-
continue;
|
|
1356
|
-
}
|
|
1357
|
-
var initializer = member.value;
|
|
1358
|
-
if ((initializer === null || initializer === void 0 ? void 0 : initializer.type) === 'CallExpression' && isAngularInputCall(initializer, registry)) {
|
|
1359
|
-
inputCount += 1;
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
|
-
} catch (err) {
|
|
1363
|
-
_didIteratorError = true;
|
|
1364
|
-
_iteratorError = err;
|
|
1365
|
-
} finally{
|
|
1366
|
-
try {
|
|
1367
|
-
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
1368
|
-
_iterator.return();
|
|
1369
|
-
}
|
|
1370
|
-
} finally{
|
|
1371
|
-
if (_didIteratorError) {
|
|
1372
|
-
throw _iteratorError;
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
}
|
|
1376
|
-
if (inputCount > threshold) {
|
|
1377
|
-
var _ref1, _classNode_id;
|
|
1378
|
-
var _classNode_id1;
|
|
1379
|
-
var className = (_ref1 = (_classNode_id1 = classNode.id) === null || _classNode_id1 === void 0 ? void 0 : _classNode_id1.name) !== null && _ref1 !== void 0 ? _ref1 : '<anonymous>';
|
|
1380
|
-
context.report({
|
|
1381
|
-
node: (_classNode_id = classNode.id) !== null && _classNode_id !== void 0 ? _classNode_id : classNode,
|
|
1382
|
-
messageId: 'tooManySignalInputs',
|
|
1383
|
-
data: {
|
|
1384
|
-
className: className,
|
|
1385
|
-
count: String(inputCount),
|
|
1386
|
-
threshold: String(threshold)
|
|
1387
|
-
}
|
|
1388
|
-
});
|
|
1389
|
-
}
|
|
1390
|
-
};
|
|
1391
|
-
return {
|
|
1392
|
-
ImportDeclaration: function ImportDeclaration(node) {
|
|
1393
|
-
trackImportDeclaration(registry, node);
|
|
1394
|
-
},
|
|
1395
|
-
ClassDeclaration: function ClassDeclaration(classNode) {
|
|
1396
|
-
visitClass(classNode);
|
|
1397
|
-
},
|
|
1398
|
-
ClassExpression: function ClassExpression(classNode) {
|
|
1399
|
-
visitClass(classNode);
|
|
1400
|
-
}
|
|
1401
|
-
};
|
|
1402
|
-
}
|
|
1403
|
-
};
|
|
1404
|
-
/**
|
|
1405
|
-
* Returns true when `callExpression` is a call to an Angular signal-input
|
|
1406
|
-
* factory — either `input(...)` (Identifier callee) or `input.required(...)`
|
|
1407
|
-
* (MemberExpression callee whose root identifier is `input`) — and the root
|
|
1408
|
-
* identifier was imported from `@angular/core`.
|
|
1409
|
-
*
|
|
1410
|
-
* @param callExpression - The CallExpression AST node serving as a property initializer.
|
|
1411
|
-
* @param registry - The file's import registry.
|
|
1412
|
-
* @returns True when the call should be counted as a signal input.
|
|
1413
|
-
*/ function isAngularInputCall(callExpression, registry) {
|
|
1414
|
-
var _callee_object, _callee_property;
|
|
1415
|
-
var callee = callExpression.callee;
|
|
1416
|
-
var result = false;
|
|
1417
|
-
if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'Identifier') {
|
|
1418
|
-
var name = callee.name;
|
|
1419
|
-
if (INPUT_INITIALIZERS.has(name) && isImportedFrom(registry, name, ANGULAR_CORE_MODULE)) {
|
|
1420
|
-
result = true;
|
|
1421
|
-
}
|
|
1422
|
-
} else if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'MemberExpression' && callee.computed === false && ((_callee_object = callee.object) === null || _callee_object === void 0 ? void 0 : _callee_object.type) === 'Identifier' && ((_callee_property = callee.property) === null || _callee_property === void 0 ? void 0 : _callee_property.type) === 'Identifier' && callee.property.name === 'required') {
|
|
1423
|
-
var rootName = callee.object.name;
|
|
1424
|
-
if (INPUT_INITIALIZERS.has(rootName) && isImportedFrom(registry, rootName, ANGULAR_CORE_MODULE)) {
|
|
1425
|
-
result = true;
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
return result;
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
function _array_like_to_array$1(arr, len) {
|
|
1432
|
-
if (len == null || len > arr.length) len = arr.length;
|
|
1433
|
-
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
1434
|
-
return arr2;
|
|
1435
|
-
}
|
|
1436
|
-
function _array_with_holes(arr) {
|
|
1437
|
-
if (Array.isArray(arr)) return arr;
|
|
1438
|
-
}
|
|
1439
|
-
function _array_without_holes$1(arr) {
|
|
1440
|
-
if (Array.isArray(arr)) return _array_like_to_array$1(arr);
|
|
1441
|
-
}
|
|
1442
|
-
function _iterable_to_array$1(iter) {
|
|
1443
|
-
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
|
|
1444
|
-
}
|
|
1445
|
-
function _iterable_to_array_limit(arr, i) {
|
|
1446
|
-
var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
|
|
1447
|
-
if (_i == null) return;
|
|
1448
|
-
var _arr = [];
|
|
1449
|
-
var _n = true;
|
|
1450
|
-
var _d = false;
|
|
1451
|
-
var _s, _e;
|
|
1452
|
-
try {
|
|
1453
|
-
for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
|
|
1454
|
-
_arr.push(_s.value);
|
|
1455
|
-
if (i && _arr.length === i) break;
|
|
1456
|
-
}
|
|
1457
|
-
} catch (err) {
|
|
1458
|
-
_d = true;
|
|
1459
|
-
_e = err;
|
|
1460
|
-
} finally{
|
|
1461
|
-
try {
|
|
1462
|
-
if (!_n && _i["return"] != null) _i["return"]();
|
|
1463
|
-
} finally{
|
|
1464
|
-
if (_d) throw _e;
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
return _arr;
|
|
1468
|
-
}
|
|
1469
|
-
function _non_iterable_rest() {
|
|
1470
|
-
throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
1471
|
-
}
|
|
1472
|
-
function _non_iterable_spread$1() {
|
|
1473
|
-
throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
1474
|
-
}
|
|
1475
|
-
function _sliced_to_array(arr, i) {
|
|
1476
|
-
return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array$1(arr, i) || _non_iterable_rest();
|
|
1477
|
-
}
|
|
1478
|
-
function _to_consumable_array$1(arr) {
|
|
1479
|
-
return _array_without_holes$1(arr) || _iterable_to_array$1(arr) || _unsupported_iterable_to_array$1(arr) || _non_iterable_spread$1();
|
|
1480
|
-
}
|
|
1481
|
-
function _type_of$1(obj) {
|
|
1482
|
-
"@swc/helpers - typeof";
|
|
1483
|
-
return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
|
|
1484
|
-
}
|
|
1485
|
-
function _unsupported_iterable_to_array$1(o, minLen) {
|
|
1486
|
-
if (!o) return;
|
|
1487
|
-
if (typeof o === "string") return _array_like_to_array$1(o, minLen);
|
|
1488
|
-
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
1489
|
-
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
1490
|
-
if (n === "Map" || n === "Set") return Array.from(n);
|
|
1491
|
-
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$1(o, minLen);
|
|
1492
|
-
}
|
|
1493
|
-
/**
|
|
1494
|
-
* Module that exposes the `toSignal` factory.
|
|
1495
|
-
*/ var ANGULAR_CORE_RXJS_INTEROP_MODULE = '@angular/core/rxjs-interop';
|
|
1496
|
-
/**
|
|
1497
|
-
* Name of the Angular `computed` factory whose callbacks this rule inspects.
|
|
1498
|
-
*/ var COMPUTED_INITIALIZER_NAME = 'computed';
|
|
1499
|
-
/**
|
|
1500
|
-
* Bare-identifier signal factories exported from `@angular/core`.
|
|
1501
|
-
*
|
|
1502
|
-
* Each name corresponds to a function that returns a Signal/InputSignal/
|
|
1503
|
-
* WritableSignal/ModelSignal — the kind of getter the rule needs to track
|
|
1504
|
-
* across class properties and module-level `const`s.
|
|
1505
|
-
*/ var ANGULAR_CORE_SIGNAL_FACTORIES = new Set([
|
|
1506
|
-
'signal',
|
|
1507
|
-
'computed',
|
|
1508
|
-
'input',
|
|
1509
|
-
'model',
|
|
1510
|
-
'linkedSignal'
|
|
1511
|
-
]);
|
|
1512
|
-
/**
|
|
1513
|
-
* `Identifier.required(...)` member-form factories. Each entry's value is the
|
|
1514
|
-
* root identifier whose `required` property returns another signal factory.
|
|
1515
|
-
*
|
|
1516
|
-
* Example: `input.required<string>()` — `input` is the root identifier and
|
|
1517
|
-
* the result is still an InputSignal.
|
|
1518
|
-
*/ var ANGULAR_CORE_REQUIRED_FACTORIES = new Set([
|
|
1519
|
-
'input',
|
|
1520
|
-
'model'
|
|
1521
|
-
]);
|
|
1522
|
-
/**
|
|
1523
|
-
* Bare-identifier signal factories exported from `@angular/core/rxjs-interop`.
|
|
1524
|
-
*/ var ANGULAR_CORE_RXJS_INTEROP_SIGNAL_FACTORIES = new Set([
|
|
1525
|
-
'toSignal'
|
|
1526
|
-
]);
|
|
1527
|
-
/**
|
|
1528
|
-
* Signal type names exported from `@angular/core`. A property or variable
|
|
1529
|
-
* whose declared type is one of these (and whose root identifier was imported
|
|
1530
|
-
* from `@angular/core`) is treated as a signal getter, even when its
|
|
1531
|
-
* initializer is not a recognized signal factory call (e.g. produced by a
|
|
1532
|
-
* helper or returned from a base class).
|
|
1533
|
-
*/ var ANGULAR_CORE_SIGNAL_TYPE_NAMES = new Set([
|
|
1534
|
-
'Signal',
|
|
1535
|
-
'WritableSignal',
|
|
1536
|
-
'InputSignal',
|
|
1537
|
-
'InputSignalWithTransform',
|
|
1538
|
-
'ModelSignal'
|
|
1539
|
-
]);
|
|
1540
|
-
/**
|
|
1541
|
-
* Maximum number of characters from the offending call expression that should
|
|
1542
|
-
* appear in the report message. Long expressions are truncated so the message
|
|
1543
|
-
* stays readable in editor tooltips.
|
|
1544
|
-
*/ var CALL_PREVIEW_MAX_LENGTH = 40;
|
|
1545
|
-
/**
|
|
1546
|
-
* ESLint rule that requires every signal read inside a `computed(() => { ... })`
|
|
1547
|
-
* callback to appear in an unconditional, top-level position rather than
|
|
1548
|
-
* inside a branching path (if/else, ternary, short-circuit, switch case,
|
|
1549
|
-
* loop body, catch handler).
|
|
1550
|
-
*
|
|
1551
|
-
* Angular `computed` re-tracks its dependencies on every run, so a signal
|
|
1552
|
-
* read that only happens inside one branch is not registered as a dependency
|
|
1553
|
-
* when the other branch executes. When that signal subsequently changes the
|
|
1554
|
-
* computed does not recompute and the value goes stale. Reading every signal
|
|
1555
|
-
* up front — before any branching — keeps the dependency set stable.
|
|
1556
|
-
*
|
|
1557
|
-
* To avoid false positives on plain methods and utility functions, the rule
|
|
1558
|
-
* only flags calls whose name can be statically traced back to a signal
|
|
1559
|
-
* factory:
|
|
1560
|
-
*
|
|
1561
|
-
* - `this.<name>()` is flagged when `<name>` is a class property initialized
|
|
1562
|
-
* with one of `signal`, `computed`, `input`, `input.required`, `model`,
|
|
1563
|
-
* `model.required`, `linkedSignal` (from `@angular/core`), or `toSignal`
|
|
1564
|
-
* (from `@angular/core/rxjs-interop`).
|
|
1565
|
-
* - `<name>()` (bare identifier) is flagged when `<name>` is a module-level
|
|
1566
|
-
* `const` initialized with one of those factories.
|
|
1567
|
-
*
|
|
1568
|
-
* Everything else — calls with arguments, chained property accesses on
|
|
1569
|
-
* services, calls on local loop variables, calls on globals — is left
|
|
1570
|
-
* alone. Cross-class signal tracking (e.g. `this.someService.someSignal()`)
|
|
1571
|
-
* is intentionally out of scope: it would require type analysis to
|
|
1572
|
-
* distinguish signal getters from plain getter methods, and the rule
|
|
1573
|
-
* prefers a clean report set over partial coverage.
|
|
1574
|
-
*
|
|
1575
|
-
* Only `computed` identifiers imported from `@angular/core` are considered.
|
|
1576
|
-
* Nested function expressions (callbacks passed to `.map` / `.filter` etc.)
|
|
1577
|
-
* are not inspected because Angular does not synchronously track signals
|
|
1578
|
-
* read inside them.
|
|
1579
|
-
*
|
|
1580
|
-
* Auto-fix: for each flagged callback, the fix inserts one
|
|
1581
|
-
* `const <localName> = this.<signalName>();` (or `const <localName> = <signalName>();`
|
|
1582
|
-
* for module-scope captures) at the top of the callback body and replaces
|
|
1583
|
-
* every flagged call with `<localName>`. The local name is the signal name
|
|
1584
|
-
* with the trailing `Signal` suffix removed when present (`xSignal` → `x`).
|
|
1585
|
-
* If that name would shadow an existing local binding in the callback, the
|
|
1586
|
-
* fix is skipped for that signal to avoid generating a syntax error.
|
|
1587
|
-
* Expression-body callbacks (`computed(() => …)`) are converted to block
|
|
1588
|
-
* bodies as part of the fix; the previous expression becomes the `return`
|
|
1589
|
-
* value.
|
|
1590
|
-
*/ var DBX_WEB_REQUIRE_TOP_LEVEL_COMPUTED_SIGNALS_RULE = {
|
|
1591
|
-
meta: {
|
|
1592
|
-
type: 'problem',
|
|
1593
|
-
fixable: 'code',
|
|
1594
|
-
docs: {
|
|
1595
|
-
description: 'Require signal reads inside a computed() callback to occur in unconditional top-level statements, not inside if/else, ternary, short-circuit, switch, loop, or catch branches.',
|
|
1596
|
-
recommended: true
|
|
1597
|
-
},
|
|
1598
|
-
messages: {
|
|
1599
|
-
conditionalSignalRead: "Signal read '{{call}}' is inside a conditional execution path of computed(); hoist it to an unconditional top-level read before the branch so the computed tracks it on every run."
|
|
1600
|
-
},
|
|
1601
|
-
schema: []
|
|
1602
|
-
},
|
|
1603
|
-
create: function create(context) {
|
|
1604
|
-
var imports = createImportRegistry();
|
|
1605
|
-
var signals = {
|
|
1606
|
-
classSignalProps: new WeakMap(),
|
|
1607
|
-
moduleSignalNames: new Set()
|
|
1608
|
-
};
|
|
1609
|
-
var visitClass = function visitClass(classNode) {
|
|
1610
|
-
collectClassSignalProperties(classNode, imports, signals);
|
|
1611
|
-
};
|
|
1612
|
-
var inspectComputedCall = function inspectComputedCall(callNode) {
|
|
1613
|
-
var _callNode_arguments;
|
|
1614
|
-
if (!isComputedCall(callNode, imports)) {
|
|
1615
|
-
return;
|
|
1616
|
-
}
|
|
1617
|
-
var callback = (_callNode_arguments = callNode.arguments) === null || _callNode_arguments === void 0 ? void 0 : _callNode_arguments[0];
|
|
1618
|
-
if (!callback || !isFunctionNode(callback) || !callback.body) {
|
|
1619
|
-
return;
|
|
1620
|
-
}
|
|
1621
|
-
var enclosingClass = findEnclosingClass(callNode);
|
|
1622
|
-
var classSignalNames = enclosingClass ? signals.classSignalProps.get(enclosingClass) : null;
|
|
1623
|
-
var violations = [];
|
|
1624
|
-
// The function body itself is the entry point. Conditional state starts
|
|
1625
|
-
// as `false` — top-level statements in the body run unconditionally.
|
|
1626
|
-
walk(callback.body, false, {
|
|
1627
|
-
classSignalNames: classSignalNames,
|
|
1628
|
-
moduleSignalNames: signals.moduleSignalNames,
|
|
1629
|
-
violations: violations
|
|
1630
|
-
});
|
|
1631
|
-
if (violations.length === 0) {
|
|
1632
|
-
return;
|
|
1633
|
-
}
|
|
1634
|
-
reportViolations(callback, violations, context);
|
|
1635
|
-
};
|
|
1636
|
-
return {
|
|
1637
|
-
ImportDeclaration: function ImportDeclaration(node) {
|
|
1638
|
-
trackImportDeclaration(imports, node);
|
|
1639
|
-
},
|
|
1640
|
-
VariableDeclaration: function VariableDeclaration(node) {
|
|
1641
|
-
collectModuleSignalConsts(node, imports, signals);
|
|
1642
|
-
},
|
|
1643
|
-
ClassDeclaration: function ClassDeclaration(classNode) {
|
|
1644
|
-
visitClass(classNode);
|
|
1645
|
-
},
|
|
1646
|
-
ClassExpression: function ClassExpression(classNode) {
|
|
1647
|
-
visitClass(classNode);
|
|
1648
|
-
},
|
|
1649
|
-
CallExpression: function CallExpression(node) {
|
|
1650
|
-
inspectComputedCall(node);
|
|
1651
|
-
}
|
|
1652
|
-
};
|
|
1653
|
-
}
|
|
1654
|
-
};
|
|
1655
|
-
/**
|
|
1656
|
-
* Returns true when `node` is a `computed(...)` call whose `computed`
|
|
1657
|
-
* identifier was imported from `@angular/core`.
|
|
1658
|
-
*
|
|
1659
|
-
* @param node - The CallExpression AST node to test.
|
|
1660
|
-
* @param imports - The file's import registry.
|
|
1661
|
-
* @returns True when the call refers to Angular's `computed`.
|
|
1662
|
-
*/ function isComputedCall(node, imports) {
|
|
1663
|
-
var _node_callee;
|
|
1664
|
-
return (node === null || node === void 0 ? void 0 : node.type) === 'CallExpression' && ((_node_callee = node.callee) === null || _node_callee === void 0 ? void 0 : _node_callee.type) === 'Identifier' && node.callee.name === COMPUTED_INITIALIZER_NAME && isImportedFrom(imports, COMPUTED_INITIALIZER_NAME, ANGULAR_CORE_MODULE);
|
|
1665
|
-
}
|
|
1666
|
-
/**
|
|
1667
|
-
* Returns true when `callExpression` is a call to one of the Angular signal
|
|
1668
|
-
* factories whose return value is a Signal/InputSignal/WritableSignal/
|
|
1669
|
-
* ModelSignal. Handles bare-identifier calls (`signal(...)`), required
|
|
1670
|
-
* member-form calls (`input.required(...)`, `model.required(...)`), and
|
|
1671
|
-
* `toSignal(...)` from `@angular/core/rxjs-interop`.
|
|
1672
|
-
*
|
|
1673
|
-
* @param callExpression - The CallExpression AST node serving as an initializer.
|
|
1674
|
-
* @param imports - The file's import registry.
|
|
1675
|
-
* @returns True when the call returns a signal.
|
|
1676
|
-
*/ function isSignalFactoryCall(callExpression, imports) {
|
|
1677
|
-
var _callee_object, _callee_property;
|
|
1678
|
-
var callee = callExpression === null || callExpression === void 0 ? void 0 : callExpression.callee;
|
|
1679
|
-
var result = false;
|
|
1680
|
-
if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'Identifier') {
|
|
1681
|
-
var name = callee.name;
|
|
1682
|
-
result = ANGULAR_CORE_SIGNAL_FACTORIES.has(name) && isImportedFrom(imports, name, ANGULAR_CORE_MODULE) || ANGULAR_CORE_RXJS_INTEROP_SIGNAL_FACTORIES.has(name) && isImportedFrom(imports, name, ANGULAR_CORE_RXJS_INTEROP_MODULE);
|
|
1683
|
-
} else if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'MemberExpression' && callee.computed === false && ((_callee_object = callee.object) === null || _callee_object === void 0 ? void 0 : _callee_object.type) === 'Identifier' && ((_callee_property = callee.property) === null || _callee_property === void 0 ? void 0 : _callee_property.type) === 'Identifier' && callee.property.name === 'required') {
|
|
1684
|
-
var rootName = callee.object.name;
|
|
1685
|
-
result = ANGULAR_CORE_REQUIRED_FACTORIES.has(rootName) && isImportedFrom(imports, rootName, ANGULAR_CORE_MODULE);
|
|
1686
|
-
}
|
|
1687
|
-
return result;
|
|
1688
|
-
}
|
|
1689
|
-
/**
|
|
1690
|
-
* Returns true when `typeAnnotation` is a TypeScript type annotation node whose
|
|
1691
|
-
* root type name is one of the Angular signal types and resolves to an
|
|
1692
|
-
* `@angular/core` import (e.g. `Signal<number>`, `InputSignal<string>`).
|
|
1693
|
-
*
|
|
1694
|
-
* Accepts both the `TSTypeAnnotation` wrapper and the inner `TSTypeReference`
|
|
1695
|
-
* form. Returns false for any other shape (unions, intersections, generic
|
|
1696
|
-
* wrappers, etc.) — the rule prefers under-coverage to false positives.
|
|
1697
|
-
*
|
|
1698
|
-
* @param typeAnnotation - The TSTypeAnnotation / TSTypeReference AST node.
|
|
1699
|
-
* @param imports - The file's import registry.
|
|
1700
|
-
* @returns True when the type annotation names an Angular signal type.
|
|
1701
|
-
*/ function isSignalTypeAnnotation(typeAnnotation, imports) {
|
|
1702
|
-
var _typeNode_typeName;
|
|
1703
|
-
var typeNode = typeAnnotation;
|
|
1704
|
-
if ((typeNode === null || typeNode === void 0 ? void 0 : typeNode.type) === 'TSTypeAnnotation') {
|
|
1705
|
-
typeNode = typeNode.typeAnnotation;
|
|
1706
|
-
}
|
|
1707
|
-
var result = false;
|
|
1708
|
-
if ((typeNode === null || typeNode === void 0 ? void 0 : typeNode.type) === 'TSTypeReference' && ((_typeNode_typeName = typeNode.typeName) === null || _typeNode_typeName === void 0 ? void 0 : _typeNode_typeName.type) === 'Identifier') {
|
|
1709
|
-
var name = typeNode.typeName.name;
|
|
1710
|
-
if (ANGULAR_CORE_SIGNAL_TYPE_NAMES.has(name) && isImportedFrom(imports, name, ANGULAR_CORE_MODULE)) {
|
|
1711
|
-
result = true;
|
|
1712
|
-
}
|
|
1713
|
-
}
|
|
1714
|
-
return result;
|
|
1715
|
-
}
|
|
1716
|
-
/**
|
|
1717
|
-
* Scans the class body for `PropertyDefinition` members whose initializer is
|
|
1718
|
-
* a signal factory call, and stores their names in `signals.classSignalProps`.
|
|
1719
|
-
*
|
|
1720
|
-
* Static and `declare` properties are ignored. Property names that are not
|
|
1721
|
-
* simple identifiers (e.g. computed keys, symbols) are also ignored.
|
|
1722
|
-
*
|
|
1723
|
-
* @param classNode - The ClassDeclaration / ClassExpression AST node.
|
|
1724
|
-
* @param imports - The file's import registry.
|
|
1725
|
-
* @param signals - The signal registry that is mutated with the results.
|
|
1726
|
-
*/ function collectClassSignalProperties(classNode, imports, signals) {
|
|
1727
|
-
var _ref;
|
|
1728
|
-
var _classNode_body;
|
|
1729
|
-
var members = (_ref = (_classNode_body = classNode.body) === null || _classNode_body === void 0 ? void 0 : _classNode_body.body) !== null && _ref !== void 0 ? _ref : [];
|
|
1730
|
-
var names = new Set();
|
|
1731
|
-
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
1732
|
-
try {
|
|
1733
|
-
for(var _iterator = members[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
1734
|
-
var member = _step.value;
|
|
1735
|
-
if ((member === null || member === void 0 ? void 0 : member.type) !== 'PropertyDefinition' || member.static === true || member.declare === true || member.computed === true) {
|
|
1736
|
-
continue;
|
|
1737
|
-
}
|
|
1738
|
-
var key = member.key;
|
|
1739
|
-
var initializer = member.value;
|
|
1740
|
-
var propName = null;
|
|
1741
|
-
if ((key === null || key === void 0 ? void 0 : key.type) === 'Identifier') {
|
|
1742
|
-
propName = key.name;
|
|
1743
|
-
} else if ((key === null || key === void 0 ? void 0 : key.type) === 'Literal' && typeof key.value === 'string') {
|
|
1744
|
-
propName = key.value;
|
|
1745
|
-
}
|
|
1746
|
-
if (propName) {
|
|
1747
|
-
var initializerIsSignal = (initializer === null || initializer === void 0 ? void 0 : initializer.type) === 'CallExpression' && isSignalFactoryCall(initializer, imports);
|
|
1748
|
-
var typeIsSignal = member.typeAnnotation && isSignalTypeAnnotation(member.typeAnnotation, imports);
|
|
1749
|
-
if (initializerIsSignal || typeIsSignal) {
|
|
1750
|
-
names.add(propName);
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
} catch (err) {
|
|
1755
|
-
_didIteratorError = true;
|
|
1756
|
-
_iteratorError = err;
|
|
1757
|
-
} finally{
|
|
1758
|
-
try {
|
|
1759
|
-
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
1760
|
-
_iterator.return();
|
|
1761
|
-
}
|
|
1762
|
-
} finally{
|
|
1763
|
-
if (_didIteratorError) {
|
|
1764
|
-
throw _iteratorError;
|
|
1765
|
-
}
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
signals.classSignalProps.set(classNode, names);
|
|
1769
|
-
}
|
|
1770
|
-
/**
|
|
1771
|
-
* Scans a `VariableDeclaration` node that lives directly inside the program
|
|
1772
|
-
* body for `const` declarators whose initializer is a signal factory call,
|
|
1773
|
-
* adding their names to `signals.moduleSignalNames`.
|
|
1774
|
-
*
|
|
1775
|
-
* Non-`const` declarations, declarations nested inside functions or blocks,
|
|
1776
|
-
* and declarations whose initializer is not a signal factory are skipped.
|
|
1777
|
-
*
|
|
1778
|
-
* @param node - The VariableDeclaration AST node.
|
|
1779
|
-
* @param imports - The file's import registry.
|
|
1780
|
-
* @param signals - The signal registry that is mutated with the results.
|
|
1781
|
-
*/ function collectModuleSignalConsts(node, imports, signals) {
|
|
1782
|
-
var _node_declarations;
|
|
1783
|
-
var _node_parent;
|
|
1784
|
-
if ((node === null || node === void 0 ? void 0 : node.kind) !== 'const' || ((_node_parent = node.parent) === null || _node_parent === void 0 ? void 0 : _node_parent.type) !== 'Program') {
|
|
1785
|
-
return;
|
|
1786
|
-
}
|
|
1787
|
-
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
1788
|
-
try {
|
|
1789
|
-
for(var _iterator = ((_node_declarations = node.declarations) !== null && _node_declarations !== void 0 ? _node_declarations : [])[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
1790
|
-
var declarator = _step.value;
|
|
1791
|
-
var id = declarator === null || declarator === void 0 ? void 0 : declarator.id;
|
|
1792
|
-
var init = declarator === null || declarator === void 0 ? void 0 : declarator.init;
|
|
1793
|
-
if ((id === null || id === void 0 ? void 0 : id.type) === 'Identifier') {
|
|
1794
|
-
var initIsSignal = (init === null || init === void 0 ? void 0 : init.type) === 'CallExpression' && isSignalFactoryCall(init, imports);
|
|
1795
|
-
var typeIsSignal = id.typeAnnotation && isSignalTypeAnnotation(id.typeAnnotation, imports);
|
|
1796
|
-
if (initIsSignal || typeIsSignal) {
|
|
1797
|
-
signals.moduleSignalNames.add(id.name);
|
|
1798
|
-
}
|
|
1799
|
-
}
|
|
1800
|
-
}
|
|
1801
|
-
} catch (err) {
|
|
1802
|
-
_didIteratorError = true;
|
|
1803
|
-
_iteratorError = err;
|
|
1804
|
-
} finally{
|
|
1805
|
-
try {
|
|
1806
|
-
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
1807
|
-
_iterator.return();
|
|
1808
|
-
}
|
|
1809
|
-
} finally{
|
|
1810
|
-
if (_didIteratorError) {
|
|
1811
|
-
throw _iteratorError;
|
|
1812
|
-
}
|
|
1813
|
-
}
|
|
1814
|
-
}
|
|
1815
|
-
}
|
|
1816
|
-
/**
|
|
1817
|
-
* Walks up from `node` via `parent` references to find the nearest enclosing
|
|
1818
|
-
* `ClassDeclaration` or `ClassExpression`, or null if there isn't one.
|
|
1819
|
-
*
|
|
1820
|
-
* @param node - The starting AST node.
|
|
1821
|
-
* @returns The enclosing class node, or null.
|
|
1822
|
-
*/ function findEnclosingClass(node) {
|
|
1823
|
-
var current = node.parent;
|
|
1824
|
-
var result = null;
|
|
1825
|
-
while(current && !result){
|
|
1826
|
-
if (current.type === 'ClassDeclaration' || current.type === 'ClassExpression') {
|
|
1827
|
-
result = current;
|
|
1828
|
-
} else {
|
|
1829
|
-
current = current.parent;
|
|
1830
|
-
}
|
|
1831
|
-
}
|
|
1832
|
-
return result;
|
|
1833
|
-
}
|
|
1834
|
-
/**
|
|
1835
|
-
* Returns true when `node` is any function-like AST node (arrow, expression,
|
|
1836
|
-
* declaration). Used both to find the computed callback and to stop the walk
|
|
1837
|
-
* when it would otherwise descend into a nested function body.
|
|
1838
|
-
*
|
|
1839
|
-
* @param node - The AST node to test.
|
|
1840
|
-
* @returns True when `node` is a function-like node.
|
|
1841
|
-
*/ function isFunctionNode(node) {
|
|
1842
|
-
return (node === null || node === void 0 ? void 0 : node.type) === 'ArrowFunctionExpression' || (node === null || node === void 0 ? void 0 : node.type) === 'FunctionExpression' || (node === null || node === void 0 ? void 0 : node.type) === 'FunctionDeclaration';
|
|
1843
|
-
}
|
|
1844
|
-
/**
|
|
1845
|
-
* Recursively walks the body of a `computed(...)` callback. For each
|
|
1846
|
-
* zero-argument `CallExpression` that can be statically traced to a known
|
|
1847
|
-
* signal getter, records a violation when `conditional` is true. Stops at
|
|
1848
|
-
* any nested function boundary.
|
|
1849
|
-
*
|
|
1850
|
-
* @param node - The current AST node being inspected.
|
|
1851
|
-
* @param conditional - True when `node` is inside a conditional execution path.
|
|
1852
|
-
* @param state - Shared walk state (signal registries + violation accumulator).
|
|
1853
|
-
*/ function walk(node, conditional, state) {
|
|
1854
|
-
if (!node || typeof node.type !== 'string') {
|
|
1855
|
-
return;
|
|
1856
|
-
}
|
|
1857
|
-
// Stop at nested functions. The caller passes the computed callback's body
|
|
1858
|
-
// (not the function node itself) as the entry point, so any function node
|
|
1859
|
-
// we encounter while walking children is a nested function whose interior
|
|
1860
|
-
// is not synchronously tracked by Angular.
|
|
1861
|
-
if (isFunctionNode(node)) {
|
|
1862
|
-
return;
|
|
1863
|
-
}
|
|
1864
|
-
if (conditional && node.type === 'CallExpression' && Array.isArray(node.arguments) && node.arguments.length === 0) {
|
|
1865
|
-
var match = classifySignalRead(node.callee, state);
|
|
1866
|
-
if (match) {
|
|
1867
|
-
state.violations.push({
|
|
1868
|
-
node: node,
|
|
1869
|
-
signalName: match.name,
|
|
1870
|
-
source: match.source
|
|
1871
|
-
});
|
|
1872
|
-
}
|
|
1873
|
-
}
|
|
1874
|
-
walkChildren(node, conditional, state);
|
|
1875
|
-
}
|
|
1876
|
-
/**
|
|
1877
|
-
* Returns the signal-name match for `callee` when it reads a known signal:
|
|
1878
|
-
* either a `this.<name>` access where `<name>` is a class signal property,
|
|
1879
|
-
* or a bare `<name>` identifier where `<name>` is a module-level signal
|
|
1880
|
-
* `const`. Returns null otherwise.
|
|
1881
|
-
*
|
|
1882
|
-
* Cross-class accesses (`this.someService.someSignal()`), method calls on
|
|
1883
|
-
* locals (`icons.reverse()`), and untracked identifiers (`Math.random()`)
|
|
1884
|
-
* return null: the rule prefers silent under-coverage to false positives.
|
|
1885
|
-
*
|
|
1886
|
-
* @param callee - The CallExpression's callee AST node.
|
|
1887
|
-
* @param state - The shared walk state (used to read the signal registries).
|
|
1888
|
-
* @returns Match details (signal name + source), or null when not a known signal read.
|
|
1889
|
-
*/ function classifySignalRead(callee, state) {
|
|
1890
|
-
var _callee_object, _callee_property;
|
|
1891
|
-
var result = null;
|
|
1892
|
-
if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'Identifier') {
|
|
1893
|
-
if (state.moduleSignalNames.has(callee.name)) {
|
|
1894
|
-
result = {
|
|
1895
|
-
name: callee.name,
|
|
1896
|
-
source: 'module'
|
|
1897
|
-
};
|
|
1898
|
-
}
|
|
1899
|
-
} else if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'MemberExpression' && callee.computed === false && ((_callee_object = callee.object) === null || _callee_object === void 0 ? void 0 : _callee_object.type) === 'ThisExpression' && ((_callee_property = callee.property) === null || _callee_property === void 0 ? void 0 : _callee_property.type) === 'Identifier') {
|
|
1900
|
-
var _state_classSignalNames;
|
|
1901
|
-
var propName = callee.property.name;
|
|
1902
|
-
if (((_state_classSignalNames = state.classSignalNames) === null || _state_classSignalNames === void 0 ? void 0 : _state_classSignalNames.has(propName)) === true) {
|
|
1903
|
-
result = {
|
|
1904
|
-
name: propName,
|
|
1905
|
-
source: 'this'
|
|
1906
|
-
};
|
|
1907
|
-
}
|
|
1908
|
-
}
|
|
1909
|
-
return result;
|
|
1910
|
-
}
|
|
1911
|
-
/**
|
|
1912
|
-
* Recurses into the appropriate children of `node`, switching `conditional`
|
|
1913
|
-
* to true when descending into a branch that may not execute on every run.
|
|
1914
|
-
*
|
|
1915
|
-
* @param node - The current AST node whose children are walked.
|
|
1916
|
-
* @param conditional - True when `node` itself is in a conditional path.
|
|
1917
|
-
* @param state - Shared walk state.
|
|
1918
|
-
*/ function walkChildren(node, conditional, state) {
|
|
1919
|
-
switch(node.type){
|
|
1920
|
-
case 'IfStatement':
|
|
1921
|
-
case 'ConditionalExpression':
|
|
1922
|
-
walk(node.test, conditional, state);
|
|
1923
|
-
walk(node.consequent, true, state);
|
|
1924
|
-
walk(node.alternate, true, state);
|
|
1925
|
-
break;
|
|
1926
|
-
case 'LogicalExpression':
|
|
1927
|
-
// `&&`, `||`, and `??` all short-circuit: the right operand only runs
|
|
1928
|
-
// when the left operand triggers the corresponding short-circuit miss.
|
|
1929
|
-
walk(node.left, conditional, state);
|
|
1930
|
-
walk(node.right, true, state);
|
|
1931
|
-
break;
|
|
1932
|
-
case 'SwitchStatement':
|
|
1933
|
-
var _node_cases;
|
|
1934
|
-
walk(node.discriminant, conditional, state);
|
|
1935
|
-
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
1936
|
-
try {
|
|
1937
|
-
for(var _iterator = ((_node_cases = node.cases) !== null && _node_cases !== void 0 ? _node_cases : [])[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
1938
|
-
var switchCase = _step.value;
|
|
1939
|
-
var _switchCase_consequent;
|
|
1940
|
-
walk(switchCase.test, conditional, state);
|
|
1941
|
-
var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
|
|
1942
|
-
try {
|
|
1943
|
-
for(var _iterator1 = ((_switchCase_consequent = switchCase.consequent) !== null && _switchCase_consequent !== void 0 ? _switchCase_consequent : [])[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
|
|
1944
|
-
var statement = _step1.value;
|
|
1945
|
-
walk(statement, true, state);
|
|
1946
|
-
}
|
|
1947
|
-
} catch (err) {
|
|
1948
|
-
_didIteratorError1 = true;
|
|
1949
|
-
_iteratorError1 = err;
|
|
1950
|
-
} finally{
|
|
1951
|
-
try {
|
|
1952
|
-
if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
|
|
1953
|
-
_iterator1.return();
|
|
1954
|
-
}
|
|
1955
|
-
} finally{
|
|
1956
|
-
if (_didIteratorError1) {
|
|
1957
|
-
throw _iteratorError1;
|
|
1958
|
-
}
|
|
1959
|
-
}
|
|
1960
|
-
}
|
|
1961
|
-
}
|
|
1962
|
-
} catch (err) {
|
|
1963
|
-
_didIteratorError = true;
|
|
1964
|
-
_iteratorError = err;
|
|
1965
|
-
} finally{
|
|
1966
|
-
try {
|
|
1967
|
-
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
1968
|
-
_iterator.return();
|
|
1969
|
-
}
|
|
1970
|
-
} finally{
|
|
1971
|
-
if (_didIteratorError) {
|
|
1972
|
-
throw _iteratorError;
|
|
1973
|
-
}
|
|
1974
|
-
}
|
|
1975
|
-
}
|
|
1976
|
-
break;
|
|
1977
|
-
case 'ForStatement':
|
|
1978
|
-
walk(node.init, conditional, state);
|
|
1979
|
-
walk(node.test, conditional, state);
|
|
1980
|
-
walk(node.update, true, state);
|
|
1981
|
-
walk(node.body, true, state);
|
|
1982
|
-
break;
|
|
1983
|
-
case 'ForInStatement':
|
|
1984
|
-
case 'ForOfStatement':
|
|
1985
|
-
walk(node.left, conditional, state);
|
|
1986
|
-
walk(node.right, conditional, state);
|
|
1987
|
-
walk(node.body, true, state);
|
|
1988
|
-
break;
|
|
1989
|
-
case 'WhileStatement':
|
|
1990
|
-
case 'DoWhileStatement':
|
|
1991
|
-
walk(node.test, conditional, state);
|
|
1992
|
-
walk(node.body, true, state);
|
|
1993
|
-
break;
|
|
1994
|
-
case 'TryStatement':
|
|
1995
|
-
walk(node.block, conditional, state);
|
|
1996
|
-
walk(node.handler, true, state);
|
|
1997
|
-
walk(node.finalizer, conditional, state);
|
|
1998
|
-
break;
|
|
1999
|
-
default:
|
|
2000
|
-
walkGenericChildren(node, conditional, state);
|
|
2001
|
-
}
|
|
2002
|
-
}
|
|
2003
|
-
/**
|
|
2004
|
-
* Generic AST-walking fallback used for node types that do not introduce
|
|
2005
|
-
* conditional execution. Recurses into every object-valued or array-valued
|
|
2006
|
-
* property whose value looks like an AST node.
|
|
2007
|
-
*
|
|
2008
|
-
* @param node - The current AST node.
|
|
2009
|
-
* @param conditional - True when `node` itself is in a conditional path.
|
|
2010
|
-
* @param state - Shared walk state.
|
|
2011
|
-
*/ function walkGenericChildren(node, conditional, state) {
|
|
2012
|
-
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
2013
|
-
try {
|
|
2014
|
-
for(var _iterator = Object.keys(node)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
2015
|
-
var key = _step.value;
|
|
2016
|
-
if (key === 'parent' || key === 'loc' || key === 'range' || key === 'start' || key === 'end') {
|
|
2017
|
-
continue;
|
|
2018
|
-
}
|
|
2019
|
-
var child = node[key];
|
|
2020
|
-
if (Array.isArray(child)) {
|
|
2021
|
-
var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
|
|
2022
|
-
try {
|
|
2023
|
-
for(var _iterator1 = child[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
|
|
2024
|
-
var entry = _step1.value;
|
|
2025
|
-
walk(entry, conditional, state);
|
|
2026
|
-
}
|
|
2027
|
-
} catch (err) {
|
|
2028
|
-
_didIteratorError1 = true;
|
|
2029
|
-
_iteratorError1 = err;
|
|
2030
|
-
} finally{
|
|
2031
|
-
try {
|
|
2032
|
-
if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
|
|
2033
|
-
_iterator1.return();
|
|
2034
|
-
}
|
|
2035
|
-
} finally{
|
|
2036
|
-
if (_didIteratorError1) {
|
|
2037
|
-
throw _iteratorError1;
|
|
2038
|
-
}
|
|
2039
|
-
}
|
|
2040
|
-
}
|
|
2041
|
-
} else if (child && (typeof child === "undefined" ? "undefined" : _type_of$1(child)) === 'object' && typeof child.type === 'string') {
|
|
2042
|
-
walk(child, conditional, state);
|
|
2043
|
-
}
|
|
2044
|
-
}
|
|
2045
|
-
} catch (err) {
|
|
2046
|
-
_didIteratorError = true;
|
|
2047
|
-
_iteratorError = err;
|
|
2048
|
-
} finally{
|
|
2049
|
-
try {
|
|
2050
|
-
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
2051
|
-
_iterator.return();
|
|
2052
|
-
}
|
|
2053
|
-
} finally{
|
|
2054
|
-
if (_didIteratorError) {
|
|
2055
|
-
throw _iteratorError;
|
|
2056
|
-
}
|
|
2057
|
-
}
|
|
2058
|
-
}
|
|
2059
|
-
}
|
|
2060
|
-
/**
|
|
2061
|
-
* Emits one diagnostic per accumulated violation. The first emitted report
|
|
2062
|
-
* carries a combined autofix that hoists each flagged signal read to the top
|
|
2063
|
-
* of the callback body and replaces every flagged call with the hoisted
|
|
2064
|
-
* local. Subsequent reports do not carry a fix because `--fix` will already
|
|
2065
|
-
* have rewritten them via the first report's fix.
|
|
2066
|
-
*
|
|
2067
|
-
* @param callback - The Function/ArrowFunction AST node passed to `computed(...)`.
|
|
2068
|
-
* @param violations - Conditional signal reads collected during the walk.
|
|
2069
|
-
* @param context - The ESLint rule context.
|
|
2070
|
-
*/ function reportViolations(callback, violations, context) {
|
|
2071
|
-
var _context_sourceCode;
|
|
2072
|
-
var _context_getSourceCode;
|
|
2073
|
-
var sourceCode = (_context_sourceCode = context.sourceCode) !== null && _context_sourceCode !== void 0 ? _context_sourceCode : (_context_getSourceCode = context.getSourceCode) === null || _context_getSourceCode === void 0 ? void 0 : _context_getSourceCode.call(context);
|
|
2074
|
-
var plan = buildFixPlan(callback, violations);
|
|
2075
|
-
violations.forEach(function(violation, index) {
|
|
2076
|
-
context.report({
|
|
2077
|
-
node: violation.node,
|
|
2078
|
-
messageId: 'conditionalSignalRead',
|
|
2079
|
-
data: {
|
|
2080
|
-
call: getCallPreview(violation.node, context)
|
|
2081
|
-
},
|
|
2082
|
-
fix: index === 0 && plan ? function(fixer) {
|
|
2083
|
-
return applyFixPlan(fixer, plan, sourceCode);
|
|
2084
|
-
} : undefined
|
|
2085
|
-
});
|
|
2086
|
-
});
|
|
2087
|
-
}
|
|
2088
|
-
/**
|
|
2089
|
-
* Plans the hoisting transformation for a callback whose violations have
|
|
2090
|
-
* been collected. Returns null when no hoist can be planned (e.g. every
|
|
2091
|
-
* candidate name would shadow an existing binding).
|
|
2092
|
-
*
|
|
2093
|
-
* @param callback - The Function/ArrowFunction AST node passed to `computed(...)`.
|
|
2094
|
-
* @param violations - Conditional signal reads collected during the walk.
|
|
2095
|
-
* @param sourceCode - The ESLint sourceCode service.
|
|
2096
|
-
* @returns A fix plan, or null when no hoist is possible.
|
|
2097
|
-
*/ function buildFixPlan(callback, violations, sourceCode) {
|
|
2098
|
-
var body = callback === null || callback === void 0 ? void 0 : callback.body;
|
|
2099
|
-
var result = null;
|
|
2100
|
-
if (body) {
|
|
2101
|
-
var existingLocals = collectExistingLocalNames(body);
|
|
2102
|
-
var localNames = new Map();
|
|
2103
|
-
var usedNames = new Set(existingLocals);
|
|
2104
|
-
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
2105
|
-
try {
|
|
2106
|
-
for(var _iterator = violations[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
2107
|
-
var violation = _step.value;
|
|
2108
|
-
if (localNames.has(violation.signalName)) {
|
|
2109
|
-
continue;
|
|
2110
|
-
}
|
|
2111
|
-
var preferred = computeLocalName(violation.signalName);
|
|
2112
|
-
if (usedNames.has(preferred)) {
|
|
2113
|
-
continue;
|
|
2114
|
-
}
|
|
2115
|
-
usedNames.add(preferred);
|
|
2116
|
-
localNames.set(violation.signalName, {
|
|
2117
|
-
name: preferred,
|
|
2118
|
-
source: violation.source
|
|
2119
|
-
});
|
|
2120
|
-
}
|
|
2121
|
-
} catch (err) {
|
|
2122
|
-
_didIteratorError = true;
|
|
2123
|
-
_iteratorError = err;
|
|
2124
|
-
} finally{
|
|
2125
|
-
try {
|
|
2126
|
-
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
2127
|
-
_iterator.return();
|
|
2128
|
-
}
|
|
2129
|
-
} finally{
|
|
2130
|
-
if (_didIteratorError) {
|
|
2131
|
-
throw _iteratorError;
|
|
2132
|
-
}
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2135
|
-
if (localNames.size > 0) {
|
|
2136
|
-
var replacements = violations.filter(function(violation) {
|
|
2137
|
-
return localNames.has(violation.signalName);
|
|
2138
|
-
});
|
|
2139
|
-
if (replacements.length > 0) {
|
|
2140
|
-
result = {
|
|
2141
|
-
callback: callback,
|
|
2142
|
-
body: body,
|
|
2143
|
-
localNames: localNames,
|
|
2144
|
-
replacements: replacements
|
|
2145
|
-
};
|
|
2146
|
-
}
|
|
2147
|
-
}
|
|
2148
|
-
}
|
|
2149
|
-
return result;
|
|
2150
|
-
}
|
|
2151
|
-
/**
|
|
2152
|
-
* Computes the local variable name for a signal property name. Strips the
|
|
2153
|
-
* trailing `Signal` suffix when present (`xSignal` → `x`, `_configSignal` →
|
|
2154
|
-
* `_config`); otherwise returns the original name unchanged.
|
|
2155
|
-
*
|
|
2156
|
-
* A name that is purely `'Signal'` is returned as-is rather than stripped
|
|
2157
|
-
* to an empty string.
|
|
2158
|
-
*
|
|
2159
|
-
* @param signalName - The signal property name to derive the local from.
|
|
2160
|
-
* @returns The local variable name.
|
|
2161
|
-
*/ function computeLocalName(signalName) {
|
|
2162
|
-
var suffix = 'Signal';
|
|
2163
|
-
var result = signalName;
|
|
2164
|
-
if (signalName.length > suffix.length && signalName.endsWith(suffix)) {
|
|
2165
|
-
result = signalName.slice(0, signalName.length - suffix.length);
|
|
2166
|
-
}
|
|
2167
|
-
return result;
|
|
2168
|
-
}
|
|
2169
|
-
/**
|
|
2170
|
-
* Collects all identifier names introduced by `VariableDeclarator`s within
|
|
2171
|
-
* the function body's top-level statements (the only locations a hoist
|
|
2172
|
-
* could possibly shadow). Nested function bodies and inner block scopes are
|
|
2173
|
-
* not inspected — shadowing those is harmless.
|
|
2174
|
-
*
|
|
2175
|
-
* Also accepts expression-body callbacks: an expression body cannot declare
|
|
2176
|
-
* locals, so it contributes no names.
|
|
2177
|
-
*
|
|
2178
|
-
* @param body - The callback's BlockStatement or expression AST node.
|
|
2179
|
-
* @returns Identifier names already declared in the body's scope.
|
|
2180
|
-
*/ function collectExistingLocalNames(body) {
|
|
2181
|
-
var names = new Set();
|
|
2182
|
-
if ((body === null || body === void 0 ? void 0 : body.type) === 'BlockStatement') {
|
|
2183
|
-
var _body_body;
|
|
2184
|
-
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
2185
|
-
try {
|
|
2186
|
-
for(var _iterator = ((_body_body = body.body) !== null && _body_body !== void 0 ? _body_body : [])[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
2187
|
-
var statement = _step.value;
|
|
2188
|
-
if ((statement === null || statement === void 0 ? void 0 : statement.type) === 'VariableDeclaration') {
|
|
2189
|
-
var _statement_declarations;
|
|
2190
|
-
var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
|
|
2191
|
-
try {
|
|
2192
|
-
for(var _iterator1 = ((_statement_declarations = statement.declarations) !== null && _statement_declarations !== void 0 ? _statement_declarations : [])[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
|
|
2193
|
-
var declarator = _step1.value;
|
|
2194
|
-
collectPatternNames(declarator === null || declarator === void 0 ? void 0 : declarator.id, names);
|
|
2195
|
-
}
|
|
2196
|
-
} catch (err) {
|
|
2197
|
-
_didIteratorError1 = true;
|
|
2198
|
-
_iteratorError1 = err;
|
|
2199
|
-
} finally{
|
|
2200
|
-
try {
|
|
2201
|
-
if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
|
|
2202
|
-
_iterator1.return();
|
|
2203
|
-
}
|
|
2204
|
-
} finally{
|
|
2205
|
-
if (_didIteratorError1) {
|
|
2206
|
-
throw _iteratorError1;
|
|
2207
|
-
}
|
|
2208
|
-
}
|
|
2209
|
-
}
|
|
2210
|
-
}
|
|
2211
|
-
}
|
|
2212
|
-
} catch (err) {
|
|
2213
|
-
_didIteratorError = true;
|
|
2214
|
-
_iteratorError = err;
|
|
2215
|
-
} finally{
|
|
2216
|
-
try {
|
|
2217
|
-
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
2218
|
-
_iterator.return();
|
|
2219
|
-
}
|
|
2220
|
-
} finally{
|
|
2221
|
-
if (_didIteratorError) {
|
|
2222
|
-
throw _iteratorError;
|
|
2223
|
-
}
|
|
2224
|
-
}
|
|
2225
|
-
}
|
|
2226
|
-
}
|
|
2227
|
-
return names;
|
|
2228
|
-
}
|
|
2229
|
-
/**
|
|
2230
|
-
* Recursively collects identifier names introduced by a destructuring
|
|
2231
|
-
* pattern (or a plain Identifier). Handles ObjectPattern, ArrayPattern,
|
|
2232
|
-
* RestElement, and AssignmentPattern; ignores other shapes.
|
|
2233
|
-
*
|
|
2234
|
-
* @param pattern - The Identifier or destructuring pattern AST node.
|
|
2235
|
-
* @param names - The set that is mutated with discovered names.
|
|
2236
|
-
*/ function collectPatternNames(pattern, names) {
|
|
2237
|
-
if (!pattern || typeof pattern.type !== 'string') {
|
|
2238
|
-
return;
|
|
2239
|
-
}
|
|
2240
|
-
switch(pattern.type){
|
|
2241
|
-
case 'Identifier':
|
|
2242
|
-
names.add(pattern.name);
|
|
2243
|
-
break;
|
|
2244
|
-
case 'ObjectPattern':
|
|
2245
|
-
var _pattern_properties;
|
|
2246
|
-
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
2247
|
-
try {
|
|
2248
|
-
for(var _iterator = ((_pattern_properties = pattern.properties) !== null && _pattern_properties !== void 0 ? _pattern_properties : [])[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
2249
|
-
var property = _step.value;
|
|
2250
|
-
if ((property === null || property === void 0 ? void 0 : property.type) === 'Property') {
|
|
2251
|
-
collectPatternNames(property.value, names);
|
|
2252
|
-
} else if ((property === null || property === void 0 ? void 0 : property.type) === 'RestElement') {
|
|
2253
|
-
collectPatternNames(property.argument, names);
|
|
2254
|
-
}
|
|
2255
|
-
}
|
|
2256
|
-
} catch (err) {
|
|
2257
|
-
_didIteratorError = true;
|
|
2258
|
-
_iteratorError = err;
|
|
2259
|
-
} finally{
|
|
2260
|
-
try {
|
|
2261
|
-
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
2262
|
-
_iterator.return();
|
|
2263
|
-
}
|
|
2264
|
-
} finally{
|
|
2265
|
-
if (_didIteratorError) {
|
|
2266
|
-
throw _iteratorError;
|
|
2267
|
-
}
|
|
2268
|
-
}
|
|
2269
|
-
}
|
|
2270
|
-
break;
|
|
2271
|
-
case 'ArrayPattern':
|
|
2272
|
-
var _pattern_elements;
|
|
2273
|
-
var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
|
|
2274
|
-
try {
|
|
2275
|
-
for(var _iterator1 = ((_pattern_elements = pattern.elements) !== null && _pattern_elements !== void 0 ? _pattern_elements : [])[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
|
|
2276
|
-
var element = _step1.value;
|
|
2277
|
-
collectPatternNames(element, names);
|
|
2278
|
-
}
|
|
2279
|
-
} catch (err) {
|
|
2280
|
-
_didIteratorError1 = true;
|
|
2281
|
-
_iteratorError1 = err;
|
|
2282
|
-
} finally{
|
|
2283
|
-
try {
|
|
2284
|
-
if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
|
|
2285
|
-
_iterator1.return();
|
|
2286
|
-
}
|
|
2287
|
-
} finally{
|
|
2288
|
-
if (_didIteratorError1) {
|
|
2289
|
-
throw _iteratorError1;
|
|
2290
|
-
}
|
|
2291
|
-
}
|
|
2292
|
-
}
|
|
2293
|
-
break;
|
|
2294
|
-
case 'RestElement':
|
|
2295
|
-
collectPatternNames(pattern.argument, names);
|
|
2296
|
-
break;
|
|
2297
|
-
case 'AssignmentPattern':
|
|
2298
|
-
collectPatternNames(pattern.left, names);
|
|
2299
|
-
break;
|
|
2300
|
-
}
|
|
2301
|
-
}
|
|
2302
|
-
/**
|
|
2303
|
-
* Applies a fix plan, returning the list of fixer operations that hoist each
|
|
2304
|
-
* planned signal read and replace its flagged call sites.
|
|
2305
|
-
*
|
|
2306
|
-
* For BlockStatement bodies the hoists are inserted before the first
|
|
2307
|
-
* statement (or after the opening brace when the body is empty). For
|
|
2308
|
-
* expression-body callbacks the entire body expression is replaced with a
|
|
2309
|
-
* BlockStatement that contains the hoists followed by a `return <expr>;`.
|
|
2310
|
-
*
|
|
2311
|
-
* @param fixer - The ESLint RuleFixer.
|
|
2312
|
-
* @param plan - The fix plan to apply.
|
|
2313
|
-
* @param sourceCode - The ESLint sourceCode service.
|
|
2314
|
-
* @returns The list of fixer operations.
|
|
2315
|
-
*/ function applyFixPlan(fixer, plan, sourceCode) {
|
|
2316
|
-
var fixes = [];
|
|
2317
|
-
var hoistLines = [];
|
|
2318
|
-
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
2319
|
-
try {
|
|
2320
|
-
for(var _iterator = plan.localNames[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
2321
|
-
var _step_value = _sliced_to_array(_step.value, 2), signalName = _step_value[0], local = _step_value[1];
|
|
2322
|
-
var rhs = local.source === 'this' ? "this.".concat(signalName, "()") : "".concat(signalName, "()");
|
|
2323
|
-
hoistLines.push("const ".concat(local.name, " = ").concat(rhs, ";"));
|
|
2324
|
-
}
|
|
2325
|
-
} catch (err) {
|
|
2326
|
-
_didIteratorError = true;
|
|
2327
|
-
_iteratorError = err;
|
|
2328
|
-
} finally{
|
|
2329
|
-
try {
|
|
2330
|
-
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
2331
|
-
_iterator.return();
|
|
2332
|
-
}
|
|
2333
|
-
} finally{
|
|
2334
|
-
if (_didIteratorError) {
|
|
2335
|
-
throw _iteratorError;
|
|
2336
|
-
}
|
|
2337
|
-
}
|
|
2338
|
-
}
|
|
2339
|
-
if (plan.body.type === 'BlockStatement') {
|
|
2340
|
-
var _plan_body_body;
|
|
2341
|
-
var indent = getBlockIndent(plan.body, sourceCode);
|
|
2342
|
-
var firstStatement = (_plan_body_body = plan.body.body) === null || _plan_body_body === void 0 ? void 0 : _plan_body_body[0];
|
|
2343
|
-
if (firstStatement) {
|
|
2344
|
-
// `insertTextBefore(firstStatement, …)` inserts at the position of the
|
|
2345
|
-
// first non-whitespace character of the statement; the existing line
|
|
2346
|
-
// already carries the leading indent up to that position. So the first
|
|
2347
|
-
// hoist line is emitted WITHOUT a leading indent (it reuses the
|
|
2348
|
-
// existing one), every subsequent line carries the full indent, and
|
|
2349
|
-
// the trailing `\n${indent}` re-establishes the indent for the
|
|
2350
|
-
// original statement that follows.
|
|
2351
|
-
var hoistText = hoistLines.map(function(line, index) {
|
|
2352
|
-
return index === 0 ? line : "".concat(indent).concat(line);
|
|
2353
|
-
}).join('\n');
|
|
2354
|
-
fixes.push(fixer.insertTextBefore(firstStatement, "".concat(hoistText, "\n").concat(indent)));
|
|
2355
|
-
} else {
|
|
2356
|
-
var openBrace = sourceCode.getFirstToken(plan.body);
|
|
2357
|
-
if (openBrace) {
|
|
2358
|
-
var hoistText1 = hoistLines.map(function(line) {
|
|
2359
|
-
return "".concat(indent).concat(line);
|
|
2360
|
-
}).join('\n');
|
|
2361
|
-
fixes.push(fixer.insertTextAfter(openBrace, "\n".concat(hoistText1, "\n")));
|
|
2362
|
-
}
|
|
2363
|
-
}
|
|
2364
|
-
var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
|
|
2365
|
-
try {
|
|
2366
|
-
for(var _iterator1 = plan.replacements[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
|
|
2367
|
-
var replacement = _step1.value;
|
|
2368
|
-
var local1 = plan.localNames.get(replacement.signalName);
|
|
2369
|
-
if (local1) {
|
|
2370
|
-
fixes.push(fixer.replaceText(replacement.node, local1.name));
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
} catch (err) {
|
|
2374
|
-
_didIteratorError1 = true;
|
|
2375
|
-
_iteratorError1 = err;
|
|
2376
|
-
} finally{
|
|
2377
|
-
try {
|
|
2378
|
-
if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
|
|
2379
|
-
_iterator1.return();
|
|
2380
|
-
}
|
|
2381
|
-
} finally{
|
|
2382
|
-
if (_didIteratorError1) {
|
|
2383
|
-
throw _iteratorError1;
|
|
2384
|
-
}
|
|
2385
|
-
}
|
|
2386
|
-
}
|
|
2387
|
-
} else {
|
|
2388
|
-
// Expression body: rewrite `(arg) => <expr>` to
|
|
2389
|
-
// `(arg) => { …hoists; return <expr-with-substitutions>; }`. The body
|
|
2390
|
-
// replacement covers every flagged call site, so per-call replacements
|
|
2391
|
-
// are baked into the substituted expression text rather than emitted as
|
|
2392
|
-
// separate fixer operations (which would overlap the body replacement).
|
|
2393
|
-
var baseIndent = getStatementIndent(plan.callback, sourceCode);
|
|
2394
|
-
var innerIndent = "".concat(baseIndent, " ");
|
|
2395
|
-
var hoistText2 = hoistLines.map(function(line) {
|
|
2396
|
-
return "".concat(innerIndent).concat(line);
|
|
2397
|
-
}).join('\n');
|
|
2398
|
-
var substitutedExpr = substituteInExpression(plan, sourceCode);
|
|
2399
|
-
fixes.push(fixer.replaceText(plan.body, "{\n".concat(hoistText2, "\n").concat(innerIndent, "return ").concat(substitutedExpr, ";\n").concat(baseIndent, "}")));
|
|
2400
|
-
}
|
|
2401
|
-
return fixes;
|
|
2402
|
-
}
|
|
2403
|
-
/**
|
|
2404
|
-
* Returns the leading-whitespace indent of the line that contains the start
|
|
2405
|
-
* of `node`. Used to align the inserted block body with the surrounding
|
|
2406
|
-
* statement (e.g. the `readonly fooSignal = computed(() => …)` line).
|
|
2407
|
-
*
|
|
2408
|
-
* Falls back to an empty string when the node has no detectable line indent.
|
|
2409
|
-
*
|
|
2410
|
-
* @param node - The AST node whose enclosing line indent is wanted.
|
|
2411
|
-
* @param sourceCode - The ESLint sourceCode service.
|
|
2412
|
-
* @returns The indent string (spaces / tabs), or `''` when none can be derived.
|
|
2413
|
-
*/ function getStatementIndent(node, sourceCode) {
|
|
2414
|
-
var _ref, _sourceCode_text;
|
|
2415
|
-
var _sourceCode_getText;
|
|
2416
|
-
var source = (_ref = (_sourceCode_text = sourceCode.text) !== null && _sourceCode_text !== void 0 ? _sourceCode_text : (_sourceCode_getText = sourceCode.getText) === null || _sourceCode_getText === void 0 ? void 0 : _sourceCode_getText.call(sourceCode)) !== null && _ref !== void 0 ? _ref : '';
|
|
2417
|
-
var start = node.range[0];
|
|
2418
|
-
var lineStart = start;
|
|
2419
|
-
var result = '';
|
|
2420
|
-
while(lineStart > 0 && source[lineStart - 1] !== '\n'){
|
|
2421
|
-
lineStart -= 1;
|
|
2422
|
-
}
|
|
2423
|
-
var slice = source.slice(lineStart, start);
|
|
2424
|
-
var match = /^[ \t]*/.exec(slice);
|
|
2425
|
-
if (match) {
|
|
2426
|
-
result = match[0];
|
|
2427
|
-
}
|
|
2428
|
-
return result;
|
|
2429
|
-
}
|
|
2430
|
-
/**
|
|
2431
|
-
* Builds the substituted text for an expression-body callback by walking the
|
|
2432
|
-
* raw source text and swapping each flagged call's source range for its
|
|
2433
|
-
* hoisted local name. Replacements are applied right-to-left so earlier
|
|
2434
|
-
* ranges stay valid as the string is mutated.
|
|
2435
|
-
*
|
|
2436
|
-
* @param plan - The fix plan whose body is an expression.
|
|
2437
|
-
* @param sourceCode - The ESLint sourceCode service.
|
|
2438
|
-
* @returns The expression text with every flagged call site replaced.
|
|
2439
|
-
*/ function substituteInExpression(plan, sourceCode) {
|
|
2440
|
-
var bodyStart = plan.body.range[0];
|
|
2441
|
-
var ordered = _to_consumable_array$1(plan.replacements).sort(function(a, b) {
|
|
2442
|
-
return b.node.range[0] - a.node.range[0];
|
|
2443
|
-
});
|
|
2444
|
-
var result = sourceCode.getText(plan.body);
|
|
2445
|
-
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
2446
|
-
try {
|
|
2447
|
-
for(var _iterator = ordered[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
2448
|
-
var replacement = _step.value;
|
|
2449
|
-
var local = plan.localNames.get(replacement.signalName);
|
|
2450
|
-
if (local) {
|
|
2451
|
-
var start = replacement.node.range[0] - bodyStart;
|
|
2452
|
-
var end = replacement.node.range[1] - bodyStart;
|
|
2453
|
-
result = result.slice(0, start) + local.name + result.slice(end);
|
|
2454
|
-
}
|
|
2455
|
-
}
|
|
2456
|
-
} catch (err) {
|
|
2457
|
-
_didIteratorError = true;
|
|
2458
|
-
_iteratorError = err;
|
|
2459
|
-
} finally{
|
|
2460
|
-
try {
|
|
2461
|
-
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
2462
|
-
_iterator.return();
|
|
2463
|
-
}
|
|
2464
|
-
} finally{
|
|
2465
|
-
if (_didIteratorError) {
|
|
2466
|
-
throw _iteratorError;
|
|
2467
|
-
}
|
|
2468
|
-
}
|
|
2469
|
-
}
|
|
2470
|
-
return result;
|
|
2471
|
-
}
|
|
2472
|
-
/**
|
|
2473
|
-
* Returns the indent string (whitespace before the first statement) for a
|
|
2474
|
-
* BlockStatement, falling back to a sensible default (`' '`) when the
|
|
2475
|
-
* block contains no statements or no leading whitespace can be located.
|
|
2476
|
-
*
|
|
2477
|
-
* @param block - The BlockStatement AST node.
|
|
2478
|
-
* @param sourceCode - The ESLint sourceCode service.
|
|
2479
|
-
* @returns The indent string to use for inserted hoist lines.
|
|
2480
|
-
*/ function getBlockIndent(block, sourceCode) {
|
|
2481
|
-
var _block_body;
|
|
2482
|
-
var firstStatement = (_block_body = block.body) === null || _block_body === void 0 ? void 0 : _block_body[0];
|
|
2483
|
-
var result = ' ';
|
|
2484
|
-
if (firstStatement) {
|
|
2485
|
-
var _ref, _sourceCode_text;
|
|
2486
|
-
var _sourceCode_getText;
|
|
2487
|
-
var source = (_ref = (_sourceCode_text = sourceCode.text) !== null && _sourceCode_text !== void 0 ? _sourceCode_text : (_sourceCode_getText = sourceCode.getText) === null || _sourceCode_getText === void 0 ? void 0 : _sourceCode_getText.call(sourceCode)) !== null && _ref !== void 0 ? _ref : '';
|
|
2488
|
-
var textBefore = source.slice(block.range[0], firstStatement.range[0]);
|
|
2489
|
-
var match = /(?:^|\n)([ \t]*)$/.exec(textBefore);
|
|
2490
|
-
if (match) {
|
|
2491
|
-
result = match[1];
|
|
2492
|
-
}
|
|
2493
|
-
}
|
|
2494
|
-
return result;
|
|
2495
|
-
}
|
|
2496
|
-
/**
|
|
2497
|
-
* Returns a short, human-readable textual preview of the call expression,
|
|
2498
|
-
* suitable for inclusion in the diagnostic message. Falls back to `'signal'`
|
|
2499
|
-
* when the source code is unavailable.
|
|
2500
|
-
*
|
|
2501
|
-
* @param node - The CallExpression AST node.
|
|
2502
|
-
* @param context - The ESLint rule context (used to access `sourceCode`).
|
|
2503
|
-
* @returns A truncated textual preview of the call.
|
|
2504
|
-
*/ function getCallPreview(node, context) {
|
|
2505
|
-
var _context_sourceCode;
|
|
2506
|
-
var _context_getSourceCode;
|
|
2507
|
-
var sourceCode = (_context_sourceCode = context.sourceCode) !== null && _context_sourceCode !== void 0 ? _context_sourceCode : (_context_getSourceCode = context.getSourceCode) === null || _context_getSourceCode === void 0 ? void 0 : _context_getSourceCode.call(context);
|
|
2508
|
-
var preview = 'signal';
|
|
2509
|
-
if (sourceCode && node.callee) {
|
|
2510
|
-
var calleeText = sourceCode.getText(node.callee);
|
|
2511
|
-
preview = "".concat(calleeText, "()");
|
|
2512
|
-
if (preview.length > CALL_PREVIEW_MAX_LENGTH) {
|
|
2513
|
-
preview = "".concat(preview.slice(0, CALL_PREVIEW_MAX_LENGTH - 3), "...");
|
|
2514
|
-
}
|
|
2515
|
-
}
|
|
2516
|
-
return preview;
|
|
2517
|
-
}
|
|
2518
|
-
|
|
2519
|
-
function _array_like_to_array(arr, len) {
|
|
2520
|
-
if (len == null || len > arr.length) len = arr.length;
|
|
2521
|
-
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
2522
|
-
return arr2;
|
|
2523
|
-
}
|
|
2524
|
-
function _array_without_holes(arr) {
|
|
2525
|
-
if (Array.isArray(arr)) return _array_like_to_array(arr);
|
|
2526
|
-
}
|
|
2527
|
-
function _iterable_to_array(iter) {
|
|
2528
|
-
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
|
|
2529
|
-
}
|
|
2530
|
-
function _non_iterable_spread() {
|
|
2531
|
-
throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
2532
|
-
}
|
|
2533
|
-
function _to_consumable_array(arr) {
|
|
2534
|
-
return _array_without_holes(arr) || _iterable_to_array(arr) || _unsupported_iterable_to_array(arr) || _non_iterable_spread();
|
|
2535
|
-
}
|
|
2536
|
-
function _type_of(obj) {
|
|
2537
|
-
"@swc/helpers - typeof";
|
|
2538
|
-
return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
|
|
2539
|
-
}
|
|
2540
|
-
function _unsupported_iterable_to_array(o, minLen) {
|
|
2541
|
-
if (!o) return;
|
|
2542
|
-
if (typeof o === "string") return _array_like_to_array(o, minLen);
|
|
2543
|
-
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
2544
|
-
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
2545
|
-
if (n === "Map" || n === "Set") return Array.from(n);
|
|
2546
|
-
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
|
|
2547
|
-
}
|
|
2548
|
-
/**
|
|
2549
|
-
* Attribute selector that marks an element as a `dbxAction` context host.
|
|
2550
|
-
*/ var DBX_ACTION_SELECTOR = 'dbxAction';
|
|
2551
|
-
/**
|
|
2552
|
-
* Element selector form of the action context host (`<dbx-action>`).
|
|
2553
|
-
*/ var DBX_ACTION_ELEMENT_SELECTOR = 'dbx-action';
|
|
2554
|
-
/**
|
|
2555
|
-
* Selector for the action handler input (`[dbxActionHandler]`).
|
|
2556
|
-
*/ var DBX_ACTION_HANDLER_SELECTOR = 'dbxActionHandler';
|
|
2557
|
-
/**
|
|
2558
|
-
* Selector for the directive that forwards an externally-provided action store
|
|
2559
|
-
* (`[dbxActionSource]`). When present, the value/store may be supplied entirely
|
|
2560
|
-
* in TypeScript, so a static template scan cannot reason about completeness.
|
|
2561
|
-
*/ var DBX_ACTION_SOURCE_SELECTOR = 'dbxActionSource';
|
|
2562
|
-
/**
|
|
2563
|
-
* Selectors that cause the action to enter the TRIGGERED state.
|
|
2564
|
-
*/ var DBX_ACTION_TRIGGER_SELECTORS = [
|
|
2565
|
-
'dbxActionButton',
|
|
2566
|
-
'dbxActionButtonTrigger',
|
|
2567
|
-
'dbxActionKeyTrigger',
|
|
2568
|
-
'dbxActionAutoTrigger'
|
|
2569
|
-
];
|
|
2570
|
-
/**
|
|
2571
|
-
* Selectors that provide a value to the action (advancing TRIGGERED → VALUE_READY).
|
|
2572
|
-
*
|
|
2573
|
-
* Includes the bare `dbxActionValue` (which readies the empty-string sentinel on
|
|
2574
|
-
* trigger) and every directive that calls `readyValue()`/`reject()` off `triggered$`.
|
|
2575
|
-
*/ var DBX_ACTION_VALUE_SOURCE_SELECTORS = [
|
|
2576
|
-
'dbxActionValue',
|
|
2577
|
-
'dbxActionValueGetter',
|
|
2578
|
-
'dbxActionValueStream',
|
|
2579
|
-
'dbxActionForm',
|
|
2580
|
-
'dbxActionConfirm',
|
|
2581
|
-
'dbxActionDialog',
|
|
2582
|
-
'dbxActionPopover',
|
|
2583
|
-
'dbxPdfMergeUploadAction'
|
|
2584
|
-
];
|
|
2585
|
-
/**
|
|
2586
|
-
* Selectors that present/handle an action's error.
|
|
2587
|
-
*/ var DBX_ACTION_ERROR_DIRECTIVE_SELECTORS = [
|
|
2588
|
-
'dbxActionSnackbarError',
|
|
2589
|
-
'dbxActionError',
|
|
2590
|
-
'dbxActionSnackbar',
|
|
2591
|
-
'dbxActionErrorHandler'
|
|
2592
|
-
];
|
|
2593
|
-
/**
|
|
2594
|
-
* Array-valued child-bearing properties of Angular template AST nodes that a
|
|
2595
|
-
* context scan must descend into (element/template children plus the branches and
|
|
2596
|
-
* cases of `@if`/`@switch` control-flow blocks).
|
|
2597
|
-
*/ var DBX_ACTION_CHILD_LIST_KEYS = [
|
|
2598
|
-
'children',
|
|
2599
|
-
'branches',
|
|
2600
|
-
'cases'
|
|
2601
|
-
];
|
|
2602
|
-
/**
|
|
2603
|
-
* Single-node child-bearing properties of control-flow blocks (`@for` empty,
|
|
2604
|
-
* `@defer` placeholder/loading/error).
|
|
2605
|
-
*/ var DBX_ACTION_CHILD_NODE_KEYS = [
|
|
2606
|
-
'empty',
|
|
2607
|
-
'placeholder',
|
|
2608
|
-
'loading',
|
|
2609
|
-
'error'
|
|
2610
|
-
];
|
|
2611
|
-
/**
|
|
2612
|
-
* Returns the attribute/input selector names present on a template element node.
|
|
2613
|
-
*
|
|
2614
|
-
* Reads plain attributes (`node.attributes`, e.g. `dbxActionButton`), bound inputs
|
|
2615
|
-
* (`node.inputs`, e.g. `[dbxActionValue]` whose `.name` has no brackets), and the
|
|
2616
|
-
* structural template attributes (`node.templateAttrs`).
|
|
2617
|
-
*
|
|
2618
|
-
* @param node - The template AST node.
|
|
2619
|
-
* @returns The selector names found on the node.
|
|
2620
|
-
*/ function elementTokenNames(node) {
|
|
2621
|
-
var _ref, _ref1, _ref2;
|
|
2622
|
-
var attributes = (_ref = node === null || node === void 0 ? void 0 : node.attributes) !== null && _ref !== void 0 ? _ref : [];
|
|
2623
|
-
var inputs = (_ref1 = node === null || node === void 0 ? void 0 : node.inputs) !== null && _ref1 !== void 0 ? _ref1 : [];
|
|
2624
|
-
var templateAttrs = (_ref2 = node === null || node === void 0 ? void 0 : node.templateAttrs) !== null && _ref2 !== void 0 ? _ref2 : [];
|
|
2625
|
-
return _to_consumable_array(attributes).concat(_to_consumable_array(inputs), _to_consumable_array(templateAttrs)).map(function(attr) {
|
|
2626
|
-
return attr === null || attr === void 0 ? void 0 : attr.name;
|
|
2627
|
-
}).filter(function(name) {
|
|
2628
|
-
return typeof name === 'string';
|
|
2629
|
-
});
|
|
2630
|
-
}
|
|
2631
|
-
/**
|
|
2632
|
-
* Returns true when the node is a `dbxAction` context host — either the
|
|
2633
|
-
* `<dbx-action>` element or any element carrying the `dbxAction` attribute.
|
|
2634
|
-
*
|
|
2635
|
-
* @param node - The template AST node.
|
|
2636
|
-
* @returns True when the node hosts an action context.
|
|
2637
|
-
*/ function isActionHost(node) {
|
|
2638
|
-
return (node === null || node === void 0 ? void 0 : node.name) === DBX_ACTION_ELEMENT_SELECTOR || elementTokenNames(node).includes(DBX_ACTION_SELECTOR);
|
|
2639
|
-
}
|
|
2640
|
-
/**
|
|
2641
|
-
* Returns true when the given selector is present on the node itself or on any of
|
|
2642
|
-
* its ancestors (walked via the auto-populated `.parent` chain).
|
|
2643
|
-
*
|
|
2644
|
-
* @param node - The starting template AST node.
|
|
2645
|
-
* @param selector - The selector name to look for.
|
|
2646
|
-
* @returns True when the selector is found on the node or an ancestor.
|
|
2647
|
-
*/ function hasTokenOnSelfOrAncestor(node, selector) {
|
|
2648
|
-
var current = node;
|
|
2649
|
-
var found = false;
|
|
2650
|
-
while(current && !found){
|
|
2651
|
-
if (elementTokenNames(current).includes(selector)) {
|
|
2652
|
-
found = true;
|
|
2653
|
-
} else {
|
|
2654
|
-
current = current.parent;
|
|
2655
|
-
}
|
|
2656
|
-
}
|
|
2657
|
-
return found;
|
|
2658
|
-
}
|
|
2659
|
-
/**
|
|
2660
|
-
* Scans the subtree rooted at a `dbxAction` element, collecting the directive
|
|
2661
|
-
* selectors present on the element and its descendants.
|
|
2662
|
-
*
|
|
2663
|
-
* Descent steps through structural wrappers (`Template`) and control-flow blocks
|
|
2664
|
-
* (`@if`/`@for`/`@switch`/`@defer`). A nested action host re-scopes the context, so
|
|
2665
|
-
* the scan flags `nestedAction` and does not descend into it (its value source
|
|
2666
|
-
* belongs to the inner context, not this one).
|
|
2667
|
-
*
|
|
2668
|
-
* @param root - The `dbxAction` element node to scan from.
|
|
2669
|
-
* @returns The collected selector tokens and whether a nested action was found.
|
|
2670
|
-
*/ function collectActionContext(root) {
|
|
2671
|
-
var tokens = new Set();
|
|
2672
|
-
var state = {
|
|
2673
|
-
nestedAction: false
|
|
2674
|
-
};
|
|
2675
|
-
var walk = function walk1(node, isRoot) {
|
|
2676
|
-
if (!node || (typeof node === "undefined" ? "undefined" : _type_of(node)) !== 'object') {
|
|
2677
|
-
return;
|
|
2678
|
-
}
|
|
2679
|
-
if (!isRoot && isActionHost(node)) {
|
|
2680
|
-
state.nestedAction = true;
|
|
2681
|
-
return; // do not descend into a nested action context
|
|
2682
|
-
}
|
|
2683
|
-
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
2684
|
-
try {
|
|
2685
|
-
for(var _iterator = elementTokenNames(node)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
2686
|
-
var name = _step.value;
|
|
2687
|
-
tokens.add(name);
|
|
2688
|
-
}
|
|
2689
|
-
} catch (err) {
|
|
2690
|
-
_didIteratorError = true;
|
|
2691
|
-
_iteratorError = err;
|
|
2692
|
-
} finally{
|
|
2693
|
-
try {
|
|
2694
|
-
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
2695
|
-
_iterator.return();
|
|
2696
|
-
}
|
|
2697
|
-
} finally{
|
|
2698
|
-
if (_didIteratorError) {
|
|
2699
|
-
throw _iteratorError;
|
|
2700
|
-
}
|
|
2701
|
-
}
|
|
2702
|
-
}
|
|
2703
|
-
var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
|
|
2704
|
-
try {
|
|
2705
|
-
for(var _iterator1 = DBX_ACTION_CHILD_LIST_KEYS[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
|
|
2706
|
-
var key = _step1.value;
|
|
2707
|
-
var children = node[key];
|
|
2708
|
-
if (Array.isArray(children)) {
|
|
2709
|
-
var _iteratorNormalCompletion2 = true, _didIteratorError2 = false, _iteratorError2 = undefined;
|
|
2710
|
-
try {
|
|
2711
|
-
for(var _iterator2 = children[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true){
|
|
2712
|
-
var child = _step2.value;
|
|
2713
|
-
walk(child, false);
|
|
2714
|
-
}
|
|
2715
|
-
} catch (err) {
|
|
2716
|
-
_didIteratorError2 = true;
|
|
2717
|
-
_iteratorError2 = err;
|
|
2718
|
-
} finally{
|
|
2719
|
-
try {
|
|
2720
|
-
if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
|
|
2721
|
-
_iterator2.return();
|
|
2722
|
-
}
|
|
2723
|
-
} finally{
|
|
2724
|
-
if (_didIteratorError2) {
|
|
2725
|
-
throw _iteratorError2;
|
|
2726
|
-
}
|
|
2727
|
-
}
|
|
2728
|
-
}
|
|
2729
|
-
}
|
|
2730
|
-
}
|
|
2731
|
-
} catch (err) {
|
|
2732
|
-
_didIteratorError1 = true;
|
|
2733
|
-
_iteratorError1 = err;
|
|
2734
|
-
} finally{
|
|
2735
|
-
try {
|
|
2736
|
-
if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
|
|
2737
|
-
_iterator1.return();
|
|
2738
|
-
}
|
|
2739
|
-
} finally{
|
|
2740
|
-
if (_didIteratorError1) {
|
|
2741
|
-
throw _iteratorError1;
|
|
2742
|
-
}
|
|
2743
|
-
}
|
|
2744
|
-
}
|
|
2745
|
-
var _iteratorNormalCompletion3 = true, _didIteratorError3 = false, _iteratorError3 = undefined;
|
|
2746
|
-
try {
|
|
2747
|
-
for(var _iterator3 = DBX_ACTION_CHILD_NODE_KEYS[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true){
|
|
2748
|
-
var key1 = _step3.value;
|
|
2749
|
-
var child1 = node[key1];
|
|
2750
|
-
if (child1 && (typeof child1 === "undefined" ? "undefined" : _type_of(child1)) === 'object') {
|
|
2751
|
-
walk(child1, false);
|
|
2752
|
-
}
|
|
2753
|
-
}
|
|
2754
|
-
} catch (err) {
|
|
2755
|
-
_didIteratorError3 = true;
|
|
2756
|
-
_iteratorError3 = err;
|
|
2757
|
-
} finally{
|
|
2758
|
-
try {
|
|
2759
|
-
if (!_iteratorNormalCompletion3 && _iterator3.return != null) {
|
|
2760
|
-
_iterator3.return();
|
|
2761
|
-
}
|
|
2762
|
-
} finally{
|
|
2763
|
-
if (_didIteratorError3) {
|
|
2764
|
-
throw _iteratorError3;
|
|
2765
|
-
}
|
|
2766
|
-
}
|
|
2767
|
-
}
|
|
2768
|
-
};
|
|
2769
|
-
walk(root, true);
|
|
2770
|
-
return {
|
|
2771
|
-
tokens: tokens,
|
|
2772
|
-
nestedAction: state.nestedAction
|
|
2773
|
-
};
|
|
2774
|
-
}
|
|
2775
|
-
/**
|
|
2776
|
-
* Resolves the template parser services from the rule context, supporting both the
|
|
2777
|
-
* flat-config `context.sourceCode` accessor and the legacy `context.parserServices`.
|
|
2778
|
-
*
|
|
2779
|
-
* @param context - The ESLint rule context.
|
|
2780
|
-
* @returns The parser services object, or null when unavailable.
|
|
2781
|
-
*/ function getTemplateParserServices(context) {
|
|
2782
|
-
var _context_sourceCode, _ref, _ref1;
|
|
2783
|
-
var sourceCode = (_context_sourceCode = context.sourceCode) !== null && _context_sourceCode !== void 0 ? _context_sourceCode : typeof context.getSourceCode === 'function' ? context.getSourceCode() : undefined;
|
|
2784
|
-
return (_ref = (_ref1 = sourceCode === null || sourceCode === void 0 ? void 0 : sourceCode.parserServices) !== null && _ref1 !== void 0 ? _ref1 : context.parserServices) !== null && _ref !== void 0 ? _ref : null;
|
|
2785
|
-
}
|
|
2786
|
-
/**
|
|
2787
|
-
* Computes the report location for an action element from its source span.
|
|
2788
|
-
*
|
|
2789
|
-
* @param parserServices - The template parser services (must expose `convertNodeSourceSpanToLoc`).
|
|
2790
|
-
* @param node - The element AST node being reported.
|
|
2791
|
-
* @returns An ESLint `SourceLocation` for the element's opening tag.
|
|
2792
|
-
*/ function actionElementLoc(parserServices, node) {
|
|
2793
|
-
var _node_startSourceSpan;
|
|
2794
|
-
return parserServices.convertNodeSourceSpanToLoc((_node_startSourceSpan = node.startSourceSpan) !== null && _node_startSourceSpan !== void 0 ? _node_startSourceSpan : node.sourceSpan);
|
|
2795
|
-
}
|
|
2796
|
-
|
|
2797
|
-
/**
|
|
2798
|
-
* ESLint (Angular template) rule that flags a `dbxAction` whose context has a
|
|
2799
|
-
* trigger but no value source.
|
|
2800
|
-
*
|
|
2801
|
-
* Such an action hangs: clicking the trigger moves the store to TRIGGERED, but with
|
|
2802
|
-
* nothing to call `readyValue()` it never advances to VALUE_READY, so the handler
|
|
2803
|
-
* never runs.
|
|
2804
|
-
*
|
|
2805
|
-
* Targets simple, inline cases only. It bails (does not report) when the value may
|
|
2806
|
-
* be supplied in a way a static template scan cannot see:
|
|
2807
|
-
* - `[dbxActionSource]` on the element or an ancestor (store forwarded from TS), or
|
|
2808
|
-
* - a nested `dbxAction` inside the subtree (ambiguous which context owns a value source).
|
|
2809
|
-
*
|
|
2810
|
-
* For legitimate programmatic-value cases (or a demo intentionally showing the
|
|
2811
|
-
* TRIGGERED state) suppress with `<!-- eslint-disable-next-line dereekb-dbx-web/require-action-value-source -->`.
|
|
2812
|
-
*/ var DBX_WEB_REQUIRE_ACTION_VALUE_SOURCE_RULE = {
|
|
2813
|
-
meta: {
|
|
2814
|
-
type: 'problem',
|
|
2815
|
-
fixable: undefined,
|
|
2816
|
-
docs: {
|
|
2817
|
-
description: 'Require a value source on a triggered dbxAction so it cannot hang in the TRIGGERED state.',
|
|
2818
|
-
recommended: true
|
|
2819
|
-
},
|
|
2820
|
-
messages: {
|
|
2821
|
-
missingValueSource: '`dbxAction` has a trigger (`{{trigger}}`) but no value source, so triggering it hangs — the action never reaches VALUE_READY and the handler never runs. Add a value source (`dbxActionValue`, `[dbxActionValue]`, `[dbxActionValueGetter]`, `[dbxActionValueStream]`, or `[dbxActionForm]`), trigger with `triggerWithValue()`, or disable this line if the value is supplied in TypeScript.'
|
|
2822
|
-
},
|
|
2823
|
-
schema: []
|
|
2824
|
-
},
|
|
2825
|
-
create: function create(context) {
|
|
2826
|
-
var parserServices = getTemplateParserServices(context);
|
|
2827
|
-
return {
|
|
2828
|
-
Element: function Element(node) {
|
|
2829
|
-
if (!(parserServices === null || parserServices === void 0 ? void 0 : parserServices.convertNodeSourceSpanToLoc)) {
|
|
2830
|
-
return; // not an Angular template (no template parser services)
|
|
2831
|
-
}
|
|
2832
|
-
if (!isActionHost(node)) {
|
|
2833
|
-
return;
|
|
2834
|
-
}
|
|
2835
|
-
if (hasTokenOnSelfOrAncestor(node, DBX_ACTION_SOURCE_SELECTOR)) {
|
|
2836
|
-
return; // store/value may be forwarded from an external (TS) source
|
|
2837
|
-
}
|
|
2838
|
-
var _collectActionContext = collectActionContext(node), tokens = _collectActionContext.tokens, nestedAction = _collectActionContext.nestedAction;
|
|
2839
|
-
if (nestedAction) {
|
|
2840
|
-
return; // multiple action contexts — ambiguous which owns a value source
|
|
2841
|
-
}
|
|
2842
|
-
var trigger = DBX_ACTION_TRIGGER_SELECTORS.find(function(selector) {
|
|
2843
|
-
return tokens.has(selector);
|
|
2844
|
-
});
|
|
2845
|
-
if (!trigger) {
|
|
2846
|
-
return; // not trigger-driven (form/auto/programmatic) — out of scope
|
|
2847
|
-
}
|
|
2848
|
-
var hasValueSource = DBX_ACTION_VALUE_SOURCE_SELECTORS.some(function(selector) {
|
|
2849
|
-
return tokens.has(selector);
|
|
2850
|
-
});
|
|
2851
|
-
if (!hasValueSource) {
|
|
2852
|
-
context.report({
|
|
2853
|
-
loc: actionElementLoc(parserServices, node),
|
|
2854
|
-
messageId: 'missingValueSource',
|
|
2855
|
-
data: {
|
|
2856
|
-
trigger: trigger
|
|
2857
|
-
}
|
|
2858
|
-
});
|
|
2859
|
-
}
|
|
2860
|
-
}
|
|
2861
|
-
};
|
|
2862
|
-
}
|
|
2863
|
-
};
|
|
2864
|
-
|
|
2865
|
-
/**
|
|
2866
|
-
* ESLint (Angular template) rule that flags a `dbxAction` which runs work (has a
|
|
2867
|
-
* handler or a trigger) but presents no errors to the user.
|
|
2868
|
-
*
|
|
2869
|
-
* Satisfied by ANY error directive in the context: `dbxActionSnackbarError`,
|
|
2870
|
-
* `[dbxActionError]`, `[dbxActionSnackbar]`, or `[dbxActionErrorHandler]`.
|
|
2871
|
-
*
|
|
2872
|
-
* Shares the same context-scoping bail conditions as `require-action-value-source`
|
|
2873
|
-
* (`[dbxActionSource]` on self/ancestor, or a nested `dbxAction`).
|
|
2874
|
-
*/ var DBX_WEB_REQUIRE_ACTION_ERROR_HANDLER_RULE = {
|
|
2875
|
-
meta: {
|
|
2876
|
-
type: 'suggestion',
|
|
2877
|
-
fixable: undefined,
|
|
2878
|
-
docs: {
|
|
2879
|
-
description: 'Require an error-presentation directive on a dbxAction that runs a handler so failures surface to the user.',
|
|
2880
|
-
recommended: false
|
|
2881
|
-
},
|
|
2882
|
-
messages: {
|
|
2883
|
-
missingErrorHandler: '`dbxAction` runs a handler but surfaces no errors to the user. Add `dbxActionSnackbarError` (or `[dbxActionError]`, `[dbxActionSnackbar]`, or `[dbxActionErrorHandler]`) so action failures are presented.'
|
|
2884
|
-
},
|
|
2885
|
-
schema: []
|
|
2886
|
-
},
|
|
2887
|
-
create: function create(context) {
|
|
2888
|
-
var parserServices = getTemplateParserServices(context);
|
|
2889
|
-
return {
|
|
2890
|
-
Element: function Element(node) {
|
|
2891
|
-
if (!(parserServices === null || parserServices === void 0 ? void 0 : parserServices.convertNodeSourceSpanToLoc)) {
|
|
2892
|
-
return; // not an Angular template (no template parser services)
|
|
2893
|
-
}
|
|
2894
|
-
if (!isActionHost(node)) {
|
|
2895
|
-
return;
|
|
2896
|
-
}
|
|
2897
|
-
if (hasTokenOnSelfOrAncestor(node, DBX_ACTION_SOURCE_SELECTOR)) {
|
|
2898
|
-
return; // forwarded external context — error handling may live elsewhere
|
|
2899
|
-
}
|
|
2900
|
-
var _collectActionContext = collectActionContext(node), tokens = _collectActionContext.tokens, nestedAction = _collectActionContext.nestedAction;
|
|
2901
|
-
if (nestedAction) {
|
|
2902
|
-
return; // multiple action contexts — ambiguous
|
|
2903
|
-
}
|
|
2904
|
-
var hasHandlerOrTrigger = tokens.has(DBX_ACTION_HANDLER_SELECTOR) || DBX_ACTION_TRIGGER_SELECTORS.some(function(selector) {
|
|
2905
|
-
return tokens.has(selector);
|
|
2906
|
-
});
|
|
2907
|
-
if (!hasHandlerOrTrigger) {
|
|
2908
|
-
return; // nothing actionable here — skip
|
|
2909
|
-
}
|
|
2910
|
-
var hasErrorDirective = DBX_ACTION_ERROR_DIRECTIVE_SELECTORS.some(function(selector) {
|
|
2911
|
-
return tokens.has(selector);
|
|
2912
|
-
});
|
|
2913
|
-
if (!hasErrorDirective) {
|
|
2914
|
-
context.report({
|
|
2915
|
-
loc: actionElementLoc(parserServices, node),
|
|
2916
|
-
messageId: 'missingErrorHandler'
|
|
2917
|
-
});
|
|
2918
|
-
}
|
|
2919
|
-
}
|
|
2920
|
-
};
|
|
2921
|
-
}
|
|
2922
|
-
};
|
|
2923
|
-
|
|
2924
|
-
/**
|
|
2925
|
-
* ESLint plugin for dbx-web rules.
|
|
2926
|
-
*
|
|
2927
|
-
* Register as a plugin in your flat ESLint config, then enable individual rules
|
|
2928
|
-
* under the chosen plugin prefix (e.g. 'dereekb-dbx-web/require-clean-subscription').
|
|
2929
|
-
*/ var DBX_WEB_ESLINT_PLUGIN = {
|
|
2930
|
-
rules: {
|
|
2931
|
-
'require-clean-subscription': DBX_WEB_REQUIRE_CLEAN_SUBSCRIPTION_RULE,
|
|
2932
|
-
'require-complete-on-destroy': DBX_WEB_REQUIRE_COMPLETE_ON_DESTROY_RULE,
|
|
2933
|
-
'no-redundant-on-destroy': DBX_WEB_NO_REDUNDANT_ON_DESTROY_RULE,
|
|
2934
|
-
'require-computed-signal-suffix': DBX_WEB_REQUIRE_COMPUTED_SIGNAL_SUFFIX_RULE,
|
|
2935
|
-
'require-component-config-input': DBX_WEB_REQUIRE_COMPONENT_CONFIG_INPUT_RULE,
|
|
2936
|
-
'require-top-level-computed-signals': DBX_WEB_REQUIRE_TOP_LEVEL_COMPUTED_SIGNALS_RULE,
|
|
2937
|
-
'require-action-value-source': DBX_WEB_REQUIRE_ACTION_VALUE_SOURCE_RULE,
|
|
2938
|
-
'require-action-error-handler': DBX_WEB_REQUIRE_ACTION_ERROR_HANDLER_RULE
|
|
2939
|
-
}
|
|
2940
|
-
};
|
|
2941
|
-
/**
|
|
2942
|
-
* camelCase alias of {@link DBX_WEB_ESLINT_PLUGIN} matching the conventional ESLint plugin export name.
|
|
2943
|
-
*
|
|
2944
|
-
* @dbxAllowConstantName
|
|
2945
|
-
*/ var dbxWebESLintPlugin = DBX_WEB_ESLINT_PLUGIN;
|
|
2946
|
-
|
|
2947
|
-
exports.DBX_WEB_ESLINT_PLUGIN = DBX_WEB_ESLINT_PLUGIN;
|
|
2948
|
-
exports.DBX_WEB_NO_REDUNDANT_ON_DESTROY_RULE = DBX_WEB_NO_REDUNDANT_ON_DESTROY_RULE;
|
|
2949
|
-
exports.DBX_WEB_REQUIRE_ACTION_ERROR_HANDLER_RULE = DBX_WEB_REQUIRE_ACTION_ERROR_HANDLER_RULE;
|
|
2950
|
-
exports.DBX_WEB_REQUIRE_ACTION_VALUE_SOURCE_RULE = DBX_WEB_REQUIRE_ACTION_VALUE_SOURCE_RULE;
|
|
2951
|
-
exports.DBX_WEB_REQUIRE_CLEAN_SUBSCRIPTION_RULE = DBX_WEB_REQUIRE_CLEAN_SUBSCRIPTION_RULE;
|
|
2952
|
-
exports.DBX_WEB_REQUIRE_COMPLETE_ON_DESTROY_RULE = DBX_WEB_REQUIRE_COMPLETE_ON_DESTROY_RULE;
|
|
2953
|
-
exports.DBX_WEB_REQUIRE_COMPONENT_CONFIG_INPUT_RULE = DBX_WEB_REQUIRE_COMPONENT_CONFIG_INPUT_RULE;
|
|
2954
|
-
exports.DBX_WEB_REQUIRE_COMPUTED_SIGNAL_SUFFIX_RULE = DBX_WEB_REQUIRE_COMPUTED_SIGNAL_SUFFIX_RULE;
|
|
2955
|
-
exports.DBX_WEB_REQUIRE_TOP_LEVEL_COMPUTED_SIGNALS_RULE = DBX_WEB_REQUIRE_TOP_LEVEL_COMPUTED_SIGNALS_RULE;
|
|
2956
|
-
exports.dbxWebESLintPlugin = dbxWebESLintPlugin;
|