lint-suite 1.4.0 → 1.6.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.cjs +623 -45
- package/eslint.cjs.map +4 -4
- package/eslint.js +636 -42
- package/eslint.js.map +4 -4
- package/lib/angular-template.d.ts +1 -2
- package/lib/angular-template.d.ts.map +1 -1
- package/lib/angular.d.ts +1 -2
- package/lib/angular.d.ts.map +1 -1
- package/lib/base.d.ts +1 -1
- package/lib/base.d.ts.map +1 -1
- package/lib/boundaries.d.ts +1 -1
- package/lib/boundaries.d.ts.map +1 -1
- package/lib/javascript.d.ts +1 -2
- package/lib/javascript.d.ts.map +1 -1
- package/lib/json.d.ts +1 -1
- package/lib/json.d.ts.map +1 -1
- package/lib/playwright.d.ts +1 -1
- package/lib/playwright.d.ts.map +1 -1
- package/lib/prettier.d.ts +1 -1
- package/lib/prettier.d.ts.map +1 -1
- package/lib/rules/no-unused-angular-instance-fields/angular-class-fields.d.ts +8 -0
- package/lib/rules/no-unused-angular-instance-fields/angular-class-fields.d.ts.map +1 -0
- package/lib/rules/no-unused-angular-instance-fields/angular-metadata-reads.d.ts +3 -0
- package/lib/rules/no-unused-angular-instance-fields/angular-metadata-reads.d.ts.map +1 -0
- package/lib/rules/no-unused-angular-instance-fields/common/no-unused-angular-instance-fields.type.d.ts +10 -0
- package/lib/rules/no-unused-angular-instance-fields/common/no-unused-angular-instance-fields.type.d.ts.map +1 -0
- package/lib/rules/no-unused-angular-instance-fields/no-unused-angular-instance-fields.d.ts +7 -0
- package/lib/rules/no-unused-angular-instance-fields/no-unused-angular-instance-fields.d.ts.map +1 -0
- package/lib/rules/no-unused-angular-instance-fields/no-unused-instance-fields.spec.d.ts +2 -0
- package/lib/rules/no-unused-angular-instance-fields/no-unused-instance-fields.spec.d.ts.map +1 -0
- package/lib/rules/no-unused-angular-instance-fields/utils/angular-expression-reads.util.d.ts +6 -0
- package/lib/rules/no-unused-angular-instance-fields/utils/angular-expression-reads.util.d.ts.map +1 -0
- package/lib/rules/no-unused-angular-instance-fields/utils/typescript-field-reads.util.d.ts +7 -0
- package/lib/rules/no-unused-angular-instance-fields/utils/typescript-field-reads.util.d.ts.map +1 -0
- package/lib/rxjs.d.ts +1 -1
- package/lib/rxjs.d.ts.map +1 -1
- package/lib/storybook.d.ts +1 -1
- package/lib/storybook.d.ts.map +1 -1
- package/lib/typescript.d.ts +1 -2
- package/lib/typescript.d.ts.map +1 -1
- package/lib/vitest.d.ts +1 -1
- package/lib/vitest.d.ts.map +1 -1
- package/package.json +23 -22
- package/stylelint.cjs +3 -0
- package/stylelint.cjs.map +2 -2
- package/stylelint.d.ts.map +1 -1
- package/stylelint.js +3 -0
- package/stylelint.js.map +2 -2
package/eslint.js
CHANGED
|
@@ -79,10 +79,604 @@ var angularTemplate = defineConfig([
|
|
|
79
79
|
import globals from "globals";
|
|
80
80
|
import { configs as configs2, processInlineTemplates } from "angular-eslint";
|
|
81
81
|
import { defineConfig as defineConfig2 } from "eslint/config";
|
|
82
|
+
|
|
83
|
+
// packages/lint-suite/src/lib/rules/no-unused-angular-instance-fields/no-unused-angular-instance-fields.ts
|
|
84
|
+
import { ESLintUtils, TSESTree as TSESTree4 } from "@typescript-eslint/utils";
|
|
85
|
+
|
|
86
|
+
// packages/lint-suite/src/lib/rules/no-unused-angular-instance-fields/angular-class-fields.ts
|
|
87
|
+
import { TSESTree as TSESTree2 } from "@typescript-eslint/utils";
|
|
88
|
+
|
|
89
|
+
// packages/lint-suite/src/lib/rules/no-unused-angular-instance-fields/angular-metadata-reads.ts
|
|
90
|
+
import { readFileSync, statSync } from "node:fs";
|
|
91
|
+
import { dirname, resolve } from "node:path";
|
|
92
|
+
import {
|
|
93
|
+
Lexer,
|
|
94
|
+
ParseLocation,
|
|
95
|
+
ParseSourceFile,
|
|
96
|
+
ParseSourceSpan,
|
|
97
|
+
Parser,
|
|
98
|
+
R3TargetBinder,
|
|
99
|
+
parseTemplate
|
|
100
|
+
} from "@angular/compiler";
|
|
101
|
+
import { TSESTree } from "@typescript-eslint/utils";
|
|
102
|
+
|
|
103
|
+
// packages/lint-suite/src/lib/rules/no-unused-angular-instance-fields/utils/angular-expression-reads.util.ts
|
|
104
|
+
import {
|
|
105
|
+
CombinedRecursiveAstVisitor,
|
|
106
|
+
ImplicitReceiver,
|
|
107
|
+
KeyedRead,
|
|
108
|
+
PropertyRead,
|
|
109
|
+
SafeKeyedRead,
|
|
110
|
+
SafePropertyRead,
|
|
111
|
+
ThisReceiver
|
|
112
|
+
} from "@angular/compiler";
|
|
113
|
+
var ReadCollector = class extends CombinedRecursiveAstVisitor {
|
|
114
|
+
constructor(boundTarget, action) {
|
|
115
|
+
super();
|
|
116
|
+
this.boundTarget = boundTarget;
|
|
117
|
+
this.action = action;
|
|
118
|
+
}
|
|
119
|
+
boundTarget;
|
|
120
|
+
action;
|
|
121
|
+
reads = /* @__PURE__ */ new Set();
|
|
122
|
+
record(node) {
|
|
123
|
+
if (node.receiver instanceof ThisReceiver) {
|
|
124
|
+
this.reads.add(node.name);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (node.receiver instanceof ImplicitReceiver && (!this.action || node.name !== "$event") && !this.boundTarget?.getExpressionTarget(node)) {
|
|
128
|
+
this.reads.add(node.name);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
visitBinary(node, context) {
|
|
132
|
+
if (node.operation !== "=") {
|
|
133
|
+
return super.visitBinary(node, context);
|
|
134
|
+
}
|
|
135
|
+
if (node.left instanceof PropertyRead || node.left instanceof SafePropertyRead || node.left instanceof KeyedRead || node.left instanceof SafeKeyedRead) {
|
|
136
|
+
this.visit(node.left.receiver);
|
|
137
|
+
}
|
|
138
|
+
if (node.left instanceof KeyedRead || node.left instanceof SafeKeyedRead) {
|
|
139
|
+
this.visit(node.left.key);
|
|
140
|
+
}
|
|
141
|
+
this.visit(node.right);
|
|
142
|
+
return void 0;
|
|
143
|
+
}
|
|
144
|
+
visitPropertyRead(node, context) {
|
|
145
|
+
this.record(node);
|
|
146
|
+
return super.visitPropertyRead(node, context);
|
|
147
|
+
}
|
|
148
|
+
visitSafePropertyRead(node, context) {
|
|
149
|
+
this.record(node);
|
|
150
|
+
return super.visitSafePropertyRead(node, context);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
var collectAngularExpressionReads = (nodes, boundTarget, action) => {
|
|
154
|
+
const collector = new ReadCollector(boundTarget, action);
|
|
155
|
+
for (const node of nodes) {
|
|
156
|
+
collector.visit(node);
|
|
157
|
+
}
|
|
158
|
+
return collector.reads;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// packages/lint-suite/src/lib/rules/no-unused-angular-instance-fields/angular-metadata-reads.ts
|
|
162
|
+
var externalTemplates = /* @__PURE__ */ new Map();
|
|
163
|
+
var parser = new Parser(new Lexer());
|
|
164
|
+
var templateBinder = new R3TargetBinder(null);
|
|
165
|
+
var text = (node) => {
|
|
166
|
+
if (node?.type === TSESTree.AST_NODE_TYPES.Literal && typeof node.value === "string") {
|
|
167
|
+
return node.value;
|
|
168
|
+
}
|
|
169
|
+
return node?.type === TSESTree.AST_NODE_TYPES.TemplateLiteral && node.expressions.length === 0 ? node.quasis[0].value.cooked : null;
|
|
170
|
+
};
|
|
171
|
+
var templateReads = (template, filename) => {
|
|
172
|
+
try {
|
|
173
|
+
const result = parseTemplate(template, filename);
|
|
174
|
+
if (result.errors?.length) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
return collectAngularExpressionReads(
|
|
178
|
+
result.nodes,
|
|
179
|
+
templateBinder.bind({ template: result.nodes }),
|
|
180
|
+
false
|
|
181
|
+
);
|
|
182
|
+
} catch {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
var externalTemplateReads = (filename) => {
|
|
187
|
+
try {
|
|
188
|
+
const stats = statSync(filename, { bigint: true });
|
|
189
|
+
const version = `${stats.mtimeNs}:${stats.size}`;
|
|
190
|
+
const cached = externalTemplates.get(filename);
|
|
191
|
+
if (cached?.version === version) {
|
|
192
|
+
return cached.reads;
|
|
193
|
+
}
|
|
194
|
+
const reads = templateReads(readFileSync(filename, "utf8"), filename);
|
|
195
|
+
externalTemplates.set(filename, { reads, version });
|
|
196
|
+
return reads;
|
|
197
|
+
} catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
var expressionReads = (expression, filename, action) => {
|
|
202
|
+
try {
|
|
203
|
+
const file = new ParseSourceFile(expression, filename);
|
|
204
|
+
const start = new ParseLocation(file, 0, 0, 0);
|
|
205
|
+
const span = new ParseSourceSpan(start, start.moveBy(expression.length));
|
|
206
|
+
const result = action ? parser.parseAction(expression, span, 0) : parser.parseBinding(expression, span, 0);
|
|
207
|
+
if (result.errors.length > 0) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
return collectAngularExpressionReads([result], void 0, action);
|
|
211
|
+
} catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
var addReads = (target, source) => {
|
|
216
|
+
if (!source) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
for (const name of source) {
|
|
220
|
+
target.add(name);
|
|
221
|
+
}
|
|
222
|
+
return true;
|
|
223
|
+
};
|
|
224
|
+
var key = (property) => {
|
|
225
|
+
if (property.type !== TSESTree.AST_NODE_TYPES.Property || property.computed) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
return property.key.type === TSESTree.AST_NODE_TYPES.Identifier ? property.key.name : text(property.key);
|
|
229
|
+
};
|
|
230
|
+
var metadataValue = (metadata, name) => {
|
|
231
|
+
for (const property of metadata.properties) {
|
|
232
|
+
if (property.type === TSESTree.AST_NODE_TYPES.Property && key(property) === name) {
|
|
233
|
+
return property.value;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return void 0;
|
|
237
|
+
};
|
|
238
|
+
var hostPropertyReads = (property, reads, filename) => {
|
|
239
|
+
const name = key(property);
|
|
240
|
+
const expression = text(property.value);
|
|
241
|
+
if (name === null || expression === null) {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
if (name.startsWith("[")) {
|
|
245
|
+
return addReads(reads, expressionReads(expression, filename, false));
|
|
246
|
+
}
|
|
247
|
+
if (name.startsWith("(")) {
|
|
248
|
+
return addReads(reads, expressionReads(expression, filename, true));
|
|
249
|
+
}
|
|
250
|
+
return true;
|
|
251
|
+
};
|
|
252
|
+
var hostReads = (host, reads, filename) => {
|
|
253
|
+
if (!host) {
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
if (host.type !== TSESTree.AST_NODE_TYPES.ObjectExpression || host.properties.some((property) => key(property) === null)) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
for (const property of host.properties) {
|
|
260
|
+
if (property.type !== TSESTree.AST_NODE_TYPES.Property || !hostPropertyReads(property, reads, filename)) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return true;
|
|
265
|
+
};
|
|
266
|
+
var inlineTemplateReads = (template, reads, filename) => {
|
|
267
|
+
if (!template) {
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
const source = text(template);
|
|
271
|
+
return source !== null && addReads(reads, templateReads(source, filename));
|
|
272
|
+
};
|
|
273
|
+
var externalTemplatePropertyReads = (templateUrl, reads, filename) => {
|
|
274
|
+
if (!templateUrl) {
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
const url = text(templateUrl);
|
|
278
|
+
return url !== null && addReads(reads, externalTemplateReads(resolve(dirname(filename), url)));
|
|
279
|
+
};
|
|
280
|
+
var componentTemplateReads = (metadata, reads, filename) => {
|
|
281
|
+
const template = metadataValue(metadata, "template");
|
|
282
|
+
const templateUrl = metadataValue(metadata, "templateUrl");
|
|
283
|
+
return inlineTemplateReads(template, reads, filename) && externalTemplatePropertyReads(templateUrl, reads, filename);
|
|
284
|
+
};
|
|
285
|
+
var metadataReads = (metadata, component, filename, remainingNames) => {
|
|
286
|
+
if (metadata.properties.some((property) => key(property) === null)) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
const reads = /* @__PURE__ */ new Set();
|
|
290
|
+
if (!hostReads(metadataValue(metadata, "host"), reads, filename)) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
if (component && remainingNames.every((name) => reads.has(name)) || !component || componentTemplateReads(metadata, reads, filename)) {
|
|
294
|
+
return reads;
|
|
295
|
+
}
|
|
296
|
+
return null;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// packages/lint-suite/src/lib/rules/no-unused-angular-instance-fields/angular-class-fields.ts
|
|
300
|
+
var managedApis = {
|
|
301
|
+
input: true,
|
|
302
|
+
model: true,
|
|
303
|
+
output: true
|
|
304
|
+
};
|
|
305
|
+
var angularName = (node, imports) => {
|
|
306
|
+
if (node.type === TSESTree2.AST_NODE_TYPES.Identifier) {
|
|
307
|
+
return imports.get(node.name);
|
|
308
|
+
}
|
|
309
|
+
if (node.type !== TSESTree2.AST_NODE_TYPES.MemberExpression || node.computed || node.property.type !== TSESTree2.AST_NODE_TYPES.Identifier) {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
if (node.object.type === TSESTree2.AST_NODE_TYPES.Identifier && imports.get(node.object.name) === null) {
|
|
313
|
+
return node.property.name;
|
|
314
|
+
}
|
|
315
|
+
return angularName(node.object, imports);
|
|
316
|
+
};
|
|
317
|
+
var angularClass = (node, imports) => {
|
|
318
|
+
for (const decorator of node.decorators) {
|
|
319
|
+
if (decorator.expression.type !== TSESTree2.AST_NODE_TYPES.CallExpression) {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const kind = angularName(decorator.expression.callee, imports);
|
|
323
|
+
const metadata = decorator.expression.arguments[0];
|
|
324
|
+
if ((kind === "Component" || kind === "Directive") && metadata?.type === TSESTree2.AST_NODE_TYPES.ObjectExpression) {
|
|
325
|
+
return {
|
|
326
|
+
component: kind === "Component",
|
|
327
|
+
metadata
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return null;
|
|
332
|
+
};
|
|
333
|
+
var isInstanceField = (node) => node.type === TSESTree2.AST_NODE_TYPES.PropertyDefinition && node.key.type === TSESTree2.AST_NODE_TYPES.Identifier;
|
|
334
|
+
var lifecycleHooks = {
|
|
335
|
+
ngOnChanges: true,
|
|
336
|
+
ngOnInit: true,
|
|
337
|
+
ngDoCheck: true,
|
|
338
|
+
ngAfterContentInit: true,
|
|
339
|
+
ngAfterContentChecked: true,
|
|
340
|
+
ngAfterViewInit: true,
|
|
341
|
+
ngAfterViewChecked: true,
|
|
342
|
+
ngOnDestroy: true
|
|
343
|
+
};
|
|
344
|
+
var isInstanceMethod = (node) => node.type === TSESTree2.AST_NODE_TYPES.MethodDefinition && node.key.type === TSESTree2.AST_NODE_TYPES.Identifier;
|
|
345
|
+
var isExcludedField = (node, component) => node.static || node.declare || node.override || node.decorators.length > 0 || !component && node.accessibility !== "private";
|
|
346
|
+
var isManagedField = (node, imports) => {
|
|
347
|
+
if (node.value?.type !== TSESTree2.AST_NODE_TYPES.CallExpression) {
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
const name = angularName(node.value.callee, imports);
|
|
351
|
+
return typeof name === "string" && managedApis[name] === true;
|
|
352
|
+
};
|
|
353
|
+
var field = (node, imports, component) => {
|
|
354
|
+
if (isInstanceField(node) && !isExcludedField(node, component) && !isManagedField(node, imports)) {
|
|
355
|
+
return { messageId: "unusedField", name: node.key.name, node };
|
|
356
|
+
}
|
|
357
|
+
return null;
|
|
358
|
+
};
|
|
359
|
+
var isExcludedMethod = (node, component) => node.static || node.override || node.decorators.length > 0 || node.computed || node.kind !== "method" || node.value.body === null || lifecycleHooks[node.key.name] === true || !component && node.accessibility !== "private";
|
|
360
|
+
var method = (node, component) => {
|
|
361
|
+
if (isInstanceMethod(node) && !isExcludedMethod(node, component)) {
|
|
362
|
+
return { messageId: "unusedMethod", name: node.key.name, node };
|
|
363
|
+
}
|
|
364
|
+
return null;
|
|
365
|
+
};
|
|
366
|
+
var addAngularImport = (node, imports) => {
|
|
367
|
+
if (node.source.value !== "@angular/core" || node.importKind === "type") {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
for (const specifier of node.specifiers) {
|
|
371
|
+
if (specifier.type === TSESTree2.AST_NODE_TYPES.ImportNamespaceSpecifier) {
|
|
372
|
+
imports.set(specifier.local.name, null);
|
|
373
|
+
} else if (specifier.type === TSESTree2.AST_NODE_TYPES.ImportSpecifier && specifier.importKind !== "type") {
|
|
374
|
+
const importedName = specifier.imported.type === TSESTree2.AST_NODE_TYPES.Identifier ? specifier.imported.name : specifier.imported.value;
|
|
375
|
+
imports.set(specifier.local.name, importedName);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
var reportUnusedMembers = (context, imports, classes, dynamicClasses) => {
|
|
380
|
+
for (const entry of classes) {
|
|
381
|
+
const ngClass = angularClass(entry.node, imports);
|
|
382
|
+
if (!ngClass || dynamicClasses.has(entry)) {
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
const members = [];
|
|
386
|
+
for (const node of entry.node.body.body) {
|
|
387
|
+
const candidate = field(node, imports, ngClass.component) ?? method(node, ngClass.component);
|
|
388
|
+
if (candidate) {
|
|
389
|
+
members.push(candidate);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (members.length === 0) {
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
const unreadMembers = members.filter(
|
|
396
|
+
(candidate) => !entry.reads.has(candidate.name)
|
|
397
|
+
);
|
|
398
|
+
if (unreadMembers.length === 0) {
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
const reads = metadataReads(
|
|
402
|
+
ngClass.metadata,
|
|
403
|
+
ngClass.component,
|
|
404
|
+
context.filename,
|
|
405
|
+
unreadMembers.map((candidate) => candidate.name)
|
|
406
|
+
);
|
|
407
|
+
if (!reads) {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
for (const candidate of unreadMembers) {
|
|
411
|
+
if (!reads.has(candidate.name)) {
|
|
412
|
+
context.report({
|
|
413
|
+
data: { name: candidate.name },
|
|
414
|
+
messageId: candidate.messageId,
|
|
415
|
+
node: candidate.node.key
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
// packages/lint-suite/src/lib/rules/no-unused-angular-instance-fields/utils/typescript-field-reads.util.ts
|
|
423
|
+
import { TSESTree as TSESTree3 } from "@typescript-eslint/utils";
|
|
424
|
+
var isTypeScriptExpressionWrapper = (node) => {
|
|
425
|
+
switch (node.type) {
|
|
426
|
+
case TSESTree3.AST_NODE_TYPES.TSAsExpression:
|
|
427
|
+
case TSESTree3.AST_NODE_TYPES.TSInstantiationExpression:
|
|
428
|
+
case TSESTree3.AST_NODE_TYPES.TSNonNullExpression:
|
|
429
|
+
case TSESTree3.AST_NODE_TYPES.TSSatisfiesExpression:
|
|
430
|
+
case TSESTree3.AST_NODE_TYPES.TSTypeAssertion:
|
|
431
|
+
return true;
|
|
432
|
+
default:
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
var unwrapTypeScriptExpression = (node) => {
|
|
437
|
+
let current = node;
|
|
438
|
+
while (current && isTypeScriptExpressionWrapper(current)) {
|
|
439
|
+
current = current.expression;
|
|
440
|
+
}
|
|
441
|
+
return current;
|
|
442
|
+
};
|
|
443
|
+
var isThisExpression = (node) => unwrapTypeScriptExpression(node)?.type === TSESTree3.AST_NODE_TYPES.ThisExpression;
|
|
444
|
+
var isPatternTarget = (parent, child) => {
|
|
445
|
+
switch (parent?.type) {
|
|
446
|
+
case TSESTree3.AST_NODE_TYPES.ArrayPattern:
|
|
447
|
+
return parent.elements.some((element) => element === child);
|
|
448
|
+
case TSESTree3.AST_NODE_TYPES.AssignmentPattern:
|
|
449
|
+
return parent.left === child;
|
|
450
|
+
case TSESTree3.AST_NODE_TYPES.ObjectPattern:
|
|
451
|
+
return parent.properties.some((property) => property === child);
|
|
452
|
+
case TSESTree3.AST_NODE_TYPES.Property:
|
|
453
|
+
return parent.value === child && parent.parent.type === TSESTree3.AST_NODE_TYPES.ObjectPattern;
|
|
454
|
+
case TSESTree3.AST_NODE_TYPES.RestElement:
|
|
455
|
+
return parent.argument === child;
|
|
456
|
+
default:
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
var isSimpleAssignmentTarget = (parent, current) => parent?.type === TSESTree3.AST_NODE_TYPES.AssignmentExpression && parent.left === current && parent.operator === "=";
|
|
461
|
+
var isLoopTarget = (parent, current) => (parent?.type === TSESTree3.AST_NODE_TYPES.ForInStatement || parent?.type === TSESTree3.AST_NODE_TYPES.ForOfStatement) && parent.left === current;
|
|
462
|
+
var isDirectWrite = (node) => {
|
|
463
|
+
let current = node;
|
|
464
|
+
let parent = node.parent;
|
|
465
|
+
while (parent && isTypeScriptExpressionWrapper(parent) && parent.expression === current) {
|
|
466
|
+
current = parent;
|
|
467
|
+
parent = parent.parent;
|
|
468
|
+
}
|
|
469
|
+
return isSimpleAssignmentTarget(parent, current) || parent?.type === TSESTree3.AST_NODE_TYPES.UnaryExpression && parent.operator === "delete";
|
|
470
|
+
};
|
|
471
|
+
var isDestructuringOrLoopTarget = (node) => {
|
|
472
|
+
let current = node;
|
|
473
|
+
let parent = node.parent;
|
|
474
|
+
let patternTarget = false;
|
|
475
|
+
while (parent) {
|
|
476
|
+
const wrapper = isTypeScriptExpressionWrapper(parent) && parent.expression === current;
|
|
477
|
+
const pattern = isPatternTarget(parent, current);
|
|
478
|
+
if (!wrapper && !pattern) {
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
if (pattern) {
|
|
482
|
+
patternTarget = true;
|
|
483
|
+
}
|
|
484
|
+
current = parent;
|
|
485
|
+
parent = parent.parent;
|
|
486
|
+
}
|
|
487
|
+
return patternTarget && isSimpleAssignmentTarget(parent, current) || isLoopTarget(parent, current);
|
|
488
|
+
};
|
|
489
|
+
var isWriteOnly = (node) => isDirectWrite(node) || isDestructuringOrLoopTarget(node);
|
|
490
|
+
var destructuringParts = (node) => {
|
|
491
|
+
if (node.type === TSESTree3.AST_NODE_TYPES.VariableDeclarator) {
|
|
492
|
+
return { pattern: node.id, source: node.init };
|
|
493
|
+
}
|
|
494
|
+
if (node.operator === "=") {
|
|
495
|
+
return { pattern: node.left, source: node.right };
|
|
496
|
+
}
|
|
497
|
+
return null;
|
|
498
|
+
};
|
|
499
|
+
var propertyName = (property) => {
|
|
500
|
+
if (property.type !== TSESTree3.AST_NODE_TYPES.Property || property.computed) {
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
if (property.key.type === TSESTree3.AST_NODE_TYPES.Identifier) {
|
|
504
|
+
return property.key.name;
|
|
505
|
+
}
|
|
506
|
+
return property.key.type === TSESTree3.AST_NODE_TYPES.Literal && typeof property.key.value === "string" ? property.key.value : null;
|
|
507
|
+
};
|
|
508
|
+
var destructuredThisReads = (node) => {
|
|
509
|
+
const parts = destructuringParts(node);
|
|
510
|
+
if (!parts || parts.pattern.type !== TSESTree3.AST_NODE_TYPES.ObjectPattern || !isThisExpression(parts.source)) {
|
|
511
|
+
return void 0;
|
|
512
|
+
}
|
|
513
|
+
const reads = [];
|
|
514
|
+
for (const property of parts.pattern.properties) {
|
|
515
|
+
const name = propertyName(property);
|
|
516
|
+
if (name === null) {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
reads.push(name);
|
|
520
|
+
}
|
|
521
|
+
return reads;
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
// packages/lint-suite/src/lib/rules/no-unused-angular-instance-fields/no-unused-angular-instance-fields.ts
|
|
525
|
+
var createRule = ESLintUtils.RuleCreator(
|
|
526
|
+
() => "https://eslint.org/docs/latest/rules/no-unused-private-class-members"
|
|
527
|
+
);
|
|
528
|
+
var enterClass = (node, classes, stack, thisStack) => {
|
|
529
|
+
const entry = {
|
|
530
|
+
node,
|
|
531
|
+
reads: /* @__PURE__ */ new Set()
|
|
532
|
+
};
|
|
533
|
+
classes.push(entry);
|
|
534
|
+
stack.push(entry);
|
|
535
|
+
thisStack.push(true);
|
|
536
|
+
};
|
|
537
|
+
var componentThis = (node, thisStack) => node.type === TSESTree4.AST_NODE_TYPES.ArrowFunctionExpression ? thisStack.at(-1) ?? false : node.parent.type === TSESTree4.AST_NODE_TYPES.MethodDefinition && !node.parent.static;
|
|
538
|
+
var trackMemberRead = (node, stack, thisStack, dynamicClasses) => {
|
|
539
|
+
const current = stack.at(-1);
|
|
540
|
+
if (!current || !thisStack.at(-1)) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
const receiverIsThis = isThisExpression(node.object);
|
|
544
|
+
if (node.computed && receiverIsThis) {
|
|
545
|
+
dynamicClasses.add(current);
|
|
546
|
+
}
|
|
547
|
+
if (receiverIsThis && !node.computed && node.property.type === TSESTree4.AST_NODE_TYPES.Identifier && !isWriteOnly(node)) {
|
|
548
|
+
current.reads.add(node.property.name);
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
var trackDestructuringRead = (node, stack, thisStack, dynamicClasses) => {
|
|
552
|
+
const current = stack.at(-1);
|
|
553
|
+
if (!current || !thisStack.at(-1)) {
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
const reads = destructuredThisReads(node);
|
|
557
|
+
if (reads === void 0) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
if (reads === null) {
|
|
561
|
+
dynamicClasses.add(current);
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
for (const name of reads) {
|
|
565
|
+
current.reads.add(name);
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
var lexicalThisVisitor = (thisStack) => ({
|
|
569
|
+
FunctionDeclaration(node) {
|
|
570
|
+
thisStack.push(componentThis(node, thisStack));
|
|
571
|
+
},
|
|
572
|
+
FunctionExpression(node) {
|
|
573
|
+
thisStack.push(componentThis(node, thisStack));
|
|
574
|
+
},
|
|
575
|
+
ArrowFunctionExpression(node) {
|
|
576
|
+
thisStack.push(componentThis(node, thisStack));
|
|
577
|
+
},
|
|
578
|
+
"FunctionDeclaration:exit"() {
|
|
579
|
+
thisStack.pop();
|
|
580
|
+
},
|
|
581
|
+
"FunctionExpression:exit"() {
|
|
582
|
+
thisStack.pop();
|
|
583
|
+
},
|
|
584
|
+
"ArrowFunctionExpression:exit"() {
|
|
585
|
+
thisStack.pop();
|
|
586
|
+
},
|
|
587
|
+
StaticBlock() {
|
|
588
|
+
thisStack.push(false);
|
|
589
|
+
},
|
|
590
|
+
"StaticBlock:exit"() {
|
|
591
|
+
thisStack.pop();
|
|
592
|
+
},
|
|
593
|
+
PropertyDefinition(node) {
|
|
594
|
+
if (node.static) {
|
|
595
|
+
thisStack.push(false);
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
"PropertyDefinition:exit"(node) {
|
|
599
|
+
if (node.static) {
|
|
600
|
+
thisStack.pop();
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
var visitor = (context, imports, classes, stack, dynamicClasses) => {
|
|
605
|
+
const thisStack = [];
|
|
606
|
+
return {
|
|
607
|
+
ClassDeclaration(node) {
|
|
608
|
+
enterClass(node, classes, stack, thisStack);
|
|
609
|
+
},
|
|
610
|
+
ClassExpression(node) {
|
|
611
|
+
enterClass(node, classes, stack, thisStack);
|
|
612
|
+
},
|
|
613
|
+
"ClassDeclaration:exit"() {
|
|
614
|
+
stack.pop();
|
|
615
|
+
thisStack.pop();
|
|
616
|
+
},
|
|
617
|
+
"ClassExpression:exit"() {
|
|
618
|
+
stack.pop();
|
|
619
|
+
thisStack.pop();
|
|
620
|
+
},
|
|
621
|
+
...lexicalThisVisitor(thisStack),
|
|
622
|
+
AssignmentExpression(node) {
|
|
623
|
+
trackDestructuringRead(node, stack, thisStack, dynamicClasses);
|
|
624
|
+
},
|
|
625
|
+
MemberExpression(node) {
|
|
626
|
+
trackMemberRead(node, stack, thisStack, dynamicClasses);
|
|
627
|
+
},
|
|
628
|
+
VariableDeclarator(node) {
|
|
629
|
+
trackDestructuringRead(node, stack, thisStack, dynamicClasses);
|
|
630
|
+
},
|
|
631
|
+
"Program:exit"() {
|
|
632
|
+
reportUnusedMembers(context, imports, classes, dynamicClasses);
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
};
|
|
636
|
+
var no_unused_angular_instance_fields_default = createRule({
|
|
637
|
+
name: "no-unused-instance-fields",
|
|
638
|
+
meta: {
|
|
639
|
+
type: "problem",
|
|
640
|
+
docs: {
|
|
641
|
+
description: "Disallow unread instance fields and methods in Angular components and directives"
|
|
642
|
+
},
|
|
643
|
+
messages: {
|
|
644
|
+
unusedField: "Angular instance field '{{name}}' is never read.",
|
|
645
|
+
unusedMethod: "Angular instance method '{{name}}' is never read."
|
|
646
|
+
},
|
|
647
|
+
schema: []
|
|
648
|
+
},
|
|
649
|
+
defaultOptions: [],
|
|
650
|
+
create(context) {
|
|
651
|
+
const imports = /* @__PURE__ */ new Map();
|
|
652
|
+
for (const node of context.sourceCode.ast.body) {
|
|
653
|
+
if (node.type === TSESTree4.AST_NODE_TYPES.ImportDeclaration) {
|
|
654
|
+
addAngularImport(node, imports);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (imports.size === 0) {
|
|
658
|
+
return {};
|
|
659
|
+
}
|
|
660
|
+
const classes = [];
|
|
661
|
+
const stack = [];
|
|
662
|
+
const dynamicClasses = /* @__PURE__ */ new Set();
|
|
663
|
+
return visitor(context, imports, classes, stack, dynamicClasses);
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
// packages/lint-suite/src/lib/angular.ts
|
|
668
|
+
var lintSuiteAngularPlugin = {
|
|
669
|
+
rules: {
|
|
670
|
+
"no-unused-instance-fields": no_unused_angular_instance_fields_default
|
|
671
|
+
}
|
|
672
|
+
};
|
|
82
673
|
var angular = defineConfig2([
|
|
83
674
|
{
|
|
84
675
|
files: ["**/*.ts"],
|
|
85
676
|
extends: [...configs2.tsRecommended],
|
|
677
|
+
plugins: {
|
|
678
|
+
"lint-suite-angular": lintSuiteAngularPlugin
|
|
679
|
+
},
|
|
86
680
|
languageOptions: {
|
|
87
681
|
globals: {
|
|
88
682
|
...globals.browser,
|
|
@@ -92,7 +686,9 @@ var angular = defineConfig2([
|
|
|
92
686
|
},
|
|
93
687
|
processor: processInlineTemplates,
|
|
94
688
|
rules: {
|
|
689
|
+
"lint-suite-angular/no-unused-instance-fields": "error",
|
|
95
690
|
"@angular-eslint/prefer-on-push-component-change-detection": "error",
|
|
691
|
+
// v22: flags only explicit opt-out of OnPush (omission now means OnPush)
|
|
96
692
|
"@angular-eslint/prefer-signals": "error",
|
|
97
693
|
"@angular-eslint/use-injectable-provided-in": "error",
|
|
98
694
|
"@angular-eslint/prefer-signal-model": "error",
|
|
@@ -110,8 +706,6 @@ var angular = defineConfig2([
|
|
|
110
706
|
// Consistent lifecycle method ordering
|
|
111
707
|
"@angular-eslint/computed-must-return": "error",
|
|
112
708
|
// Catch silent computed() bugs missing a return
|
|
113
|
-
"@angular-eslint/no-conflicting-lifecycle": "error",
|
|
114
|
-
// Block incompatible hook combinations (OnChanges + DoCheck)
|
|
115
709
|
"@angular-eslint/no-duplicates-in-metadata-arrays": "error",
|
|
116
710
|
// Catch duplicate imports/providers in @Component/@NgModule
|
|
117
711
|
"@angular-eslint/no-lifecycle-call": "error",
|
|
@@ -477,8 +1071,8 @@ import importPluginX2 from "eslint-plugin-import-x";
|
|
|
477
1071
|
import { defineConfig as defineConfig9 } from "eslint/config";
|
|
478
1072
|
|
|
479
1073
|
// packages/lint-suite/src/lib/rules/explicit-accessibility.ts
|
|
480
|
-
import { ESLintUtils, TSESTree } from "@typescript-eslint/utils";
|
|
481
|
-
var
|
|
1074
|
+
import { ESLintUtils as ESLintUtils2, TSESTree as TSESTree5 } from "@typescript-eslint/utils";
|
|
1075
|
+
var createRule2 = ESLintUtils2.RuleCreator(
|
|
482
1076
|
() => "https://eslint.org/docs/latest/rules/no-unused-private-class-members"
|
|
483
1077
|
);
|
|
484
1078
|
var defaultOptions = {
|
|
@@ -489,10 +1083,10 @@ var getNodeName = (node) => {
|
|
|
489
1083
|
return "<unknown>";
|
|
490
1084
|
}
|
|
491
1085
|
if ("key" in node && node.key) {
|
|
492
|
-
if (node.key.type ===
|
|
1086
|
+
if (node.key.type === TSESTree5.AST_NODE_TYPES.Identifier) {
|
|
493
1087
|
return node.key.name;
|
|
494
1088
|
}
|
|
495
|
-
if (node.key.type ===
|
|
1089
|
+
if (node.key.type === TSESTree5.AST_NODE_TYPES.PrivateIdentifier) {
|
|
496
1090
|
return `#${node.key.name}`;
|
|
497
1091
|
}
|
|
498
1092
|
return "<computed/complex key>";
|
|
@@ -500,13 +1094,13 @@ var getNodeName = (node) => {
|
|
|
500
1094
|
if ("kind" in node && node.kind === "constructor") {
|
|
501
1095
|
return "constructor";
|
|
502
1096
|
}
|
|
503
|
-
if (node.type ===
|
|
1097
|
+
if (node.type === TSESTree5.AST_NODE_TYPES.TSParameterProperty) {
|
|
504
1098
|
const param = node.parameter;
|
|
505
|
-
if (param.type ===
|
|
1099
|
+
if (param.type === TSESTree5.AST_NODE_TYPES.Identifier) {
|
|
506
1100
|
return param.name;
|
|
507
1101
|
}
|
|
508
|
-
if (param.type ===
|
|
509
|
-
if (param.left.type ===
|
|
1102
|
+
if (param.type === TSESTree5.AST_NODE_TYPES.AssignmentPattern) {
|
|
1103
|
+
if (param.left.type === TSESTree5.AST_NODE_TYPES.Identifier) {
|
|
510
1104
|
return param.left.name;
|
|
511
1105
|
}
|
|
512
1106
|
return "<destructured parameter>";
|
|
@@ -515,7 +1109,7 @@ var getNodeName = (node) => {
|
|
|
515
1109
|
}
|
|
516
1110
|
return "<unknown>";
|
|
517
1111
|
};
|
|
518
|
-
var explicit_accessibility_default =
|
|
1112
|
+
var explicit_accessibility_default = createRule2({
|
|
519
1113
|
name: "explicit-accessibility",
|
|
520
1114
|
meta: {
|
|
521
1115
|
type: "suggestion",
|
|
@@ -550,13 +1144,13 @@ var explicit_accessibility_default = createRule({
|
|
|
550
1144
|
}
|
|
551
1145
|
let reportNode = node;
|
|
552
1146
|
const name = getNodeName(node);
|
|
553
|
-
if (node.type ===
|
|
1147
|
+
if (node.type === TSESTree5.AST_NODE_TYPES.MethodDefinition || node.type === TSESTree5.AST_NODE_TYPES.PropertyDefinition) {
|
|
554
1148
|
reportNode = node.key;
|
|
555
1149
|
} else {
|
|
556
1150
|
const param = node.parameter;
|
|
557
|
-
if (param.type ===
|
|
1151
|
+
if (param.type === TSESTree5.AST_NODE_TYPES.Identifier) {
|
|
558
1152
|
reportNode = param;
|
|
559
|
-
} else if (param.type ===
|
|
1153
|
+
} else if (param.type === TSESTree5.AST_NODE_TYPES.AssignmentPattern && param.left.type === TSESTree5.AST_NODE_TYPES.Identifier) {
|
|
560
1154
|
reportNode = param.left;
|
|
561
1155
|
}
|
|
562
1156
|
}
|
|
@@ -573,7 +1167,7 @@ var explicit_accessibility_default = createRule({
|
|
|
573
1167
|
} else {
|
|
574
1168
|
tokenToInsertBefore = sourceCode.getFirstToken(node);
|
|
575
1169
|
}
|
|
576
|
-
if (node.type ===
|
|
1170
|
+
if (node.type === TSESTree5.AST_NODE_TYPES.TSParameterProperty && node.readonly) {
|
|
577
1171
|
const firstToken = sourceCode.getFirstToken(node);
|
|
578
1172
|
if (firstToken?.value === "readonly") {
|
|
579
1173
|
tokenToInsertBefore = firstToken;
|
|
@@ -878,7 +1472,7 @@ var typescript = defineConfig9([
|
|
|
878
1472
|
],
|
|
879
1473
|
rules: {
|
|
880
1474
|
"@typescript-eslint/no-empty-function": "off",
|
|
881
|
-
"@typescript-eslint/
|
|
1475
|
+
"@typescript-eslint/unbound-method": "off",
|
|
882
1476
|
"@typescript-eslint/consistent-type-assertions": "off"
|
|
883
1477
|
}
|
|
884
1478
|
},
|
|
@@ -1000,53 +1594,53 @@ var boundaries = defineConfig12([
|
|
|
1000
1594
|
"error",
|
|
1001
1595
|
{
|
|
1002
1596
|
default: "disallow",
|
|
1003
|
-
message: "
|
|
1004
|
-
|
|
1597
|
+
message: "{{to.element.types}} is not allowed to be imported in {{from.element.types}}",
|
|
1598
|
+
policies: [
|
|
1005
1599
|
{
|
|
1006
|
-
from: { type: "feature" },
|
|
1600
|
+
from: { element: { type: "feature" } },
|
|
1007
1601
|
allow: [
|
|
1008
|
-
{ to: { type: "feature" } },
|
|
1009
|
-
{ to: { type: "ui" } },
|
|
1010
|
-
{ to: { type: "domain-logic" } },
|
|
1011
|
-
{ to: { type: "utils" } },
|
|
1012
|
-
{ to: { type: "common" } }
|
|
1602
|
+
{ to: { element: { type: "feature" } } },
|
|
1603
|
+
{ to: { element: { type: "ui" } } },
|
|
1604
|
+
{ to: { element: { type: "domain-logic" } } },
|
|
1605
|
+
{ to: { element: { type: "utils" } } },
|
|
1606
|
+
{ to: { element: { type: "common" } } }
|
|
1013
1607
|
]
|
|
1014
1608
|
},
|
|
1015
1609
|
{
|
|
1016
|
-
from: { type: "domain-logic" },
|
|
1610
|
+
from: { element: { type: "domain-logic" } },
|
|
1017
1611
|
allow: [
|
|
1018
|
-
{ to: { type: "domain-logic" } },
|
|
1019
|
-
{ to: { type: "data-access" } },
|
|
1020
|
-
{ to: { type: "utils" } },
|
|
1021
|
-
{ to: { type: "common" } }
|
|
1612
|
+
{ to: { element: { type: "domain-logic" } } },
|
|
1613
|
+
{ to: { element: { type: "data-access" } } },
|
|
1614
|
+
{ to: { element: { type: "utils" } } },
|
|
1615
|
+
{ to: { element: { type: "common" } } }
|
|
1022
1616
|
]
|
|
1023
1617
|
},
|
|
1024
1618
|
{
|
|
1025
|
-
from: { type: "data-access" },
|
|
1619
|
+
from: { element: { type: "data-access" } },
|
|
1026
1620
|
allow: [
|
|
1027
|
-
{ to: { type: "data-access" } },
|
|
1028
|
-
{ to: { type: "utils" } },
|
|
1029
|
-
{ to: { type: "common" } }
|
|
1621
|
+
{ to: { element: { type: "data-access" } } },
|
|
1622
|
+
{ to: { element: { type: "utils" } } },
|
|
1623
|
+
{ to: { element: { type: "common" } } }
|
|
1030
1624
|
]
|
|
1031
1625
|
},
|
|
1032
1626
|
{
|
|
1033
|
-
from: { type: "ui" },
|
|
1627
|
+
from: { element: { type: "ui" } },
|
|
1034
1628
|
allow: [
|
|
1035
|
-
{ to: { type: "ui" } },
|
|
1036
|
-
{ to: { type: "utils" } },
|
|
1037
|
-
{ to: { type: "common" } }
|
|
1629
|
+
{ to: { element: { type: "ui" } } },
|
|
1630
|
+
{ to: { element: { type: "utils" } } },
|
|
1631
|
+
{ to: { element: { type: "common" } } }
|
|
1038
1632
|
]
|
|
1039
1633
|
},
|
|
1040
1634
|
{
|
|
1041
|
-
from: { type: "utils" },
|
|
1635
|
+
from: { element: { type: "utils" } },
|
|
1042
1636
|
allow: [
|
|
1043
|
-
{ to: { type: "utils" } },
|
|
1044
|
-
{ to: { type: "common" } }
|
|
1637
|
+
{ to: { element: { type: "utils" } } },
|
|
1638
|
+
{ to: { element: { type: "common" } } }
|
|
1045
1639
|
]
|
|
1046
1640
|
},
|
|
1047
1641
|
{
|
|
1048
|
-
from: { type: "common" },
|
|
1049
|
-
allow: [{ to: { type: "common" } }]
|
|
1642
|
+
from: { element: { type: "common" } },
|
|
1643
|
+
allow: [{ to: { element: { type: "common" } } }]
|
|
1050
1644
|
}
|
|
1051
1645
|
]
|
|
1052
1646
|
}
|