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.
Files changed (48) hide show
  1. package/eslint.cjs +623 -45
  2. package/eslint.cjs.map +4 -4
  3. package/eslint.js +636 -42
  4. package/eslint.js.map +4 -4
  5. package/lib/angular-template.d.ts +1 -2
  6. package/lib/angular-template.d.ts.map +1 -1
  7. package/lib/angular.d.ts +1 -2
  8. package/lib/angular.d.ts.map +1 -1
  9. package/lib/base.d.ts +1 -1
  10. package/lib/base.d.ts.map +1 -1
  11. package/lib/boundaries.d.ts +1 -1
  12. package/lib/boundaries.d.ts.map +1 -1
  13. package/lib/javascript.d.ts +1 -2
  14. package/lib/javascript.d.ts.map +1 -1
  15. package/lib/json.d.ts +1 -1
  16. package/lib/json.d.ts.map +1 -1
  17. package/lib/playwright.d.ts +1 -1
  18. package/lib/playwright.d.ts.map +1 -1
  19. package/lib/prettier.d.ts +1 -1
  20. package/lib/prettier.d.ts.map +1 -1
  21. package/lib/rules/no-unused-angular-instance-fields/angular-class-fields.d.ts +8 -0
  22. package/lib/rules/no-unused-angular-instance-fields/angular-class-fields.d.ts.map +1 -0
  23. package/lib/rules/no-unused-angular-instance-fields/angular-metadata-reads.d.ts +3 -0
  24. package/lib/rules/no-unused-angular-instance-fields/angular-metadata-reads.d.ts.map +1 -0
  25. package/lib/rules/no-unused-angular-instance-fields/common/no-unused-angular-instance-fields.type.d.ts +10 -0
  26. package/lib/rules/no-unused-angular-instance-fields/common/no-unused-angular-instance-fields.type.d.ts.map +1 -0
  27. package/lib/rules/no-unused-angular-instance-fields/no-unused-angular-instance-fields.d.ts +7 -0
  28. package/lib/rules/no-unused-angular-instance-fields/no-unused-angular-instance-fields.d.ts.map +1 -0
  29. package/lib/rules/no-unused-angular-instance-fields/no-unused-instance-fields.spec.d.ts +2 -0
  30. package/lib/rules/no-unused-angular-instance-fields/no-unused-instance-fields.spec.d.ts.map +1 -0
  31. package/lib/rules/no-unused-angular-instance-fields/utils/angular-expression-reads.util.d.ts +6 -0
  32. package/lib/rules/no-unused-angular-instance-fields/utils/angular-expression-reads.util.d.ts.map +1 -0
  33. package/lib/rules/no-unused-angular-instance-fields/utils/typescript-field-reads.util.d.ts +7 -0
  34. package/lib/rules/no-unused-angular-instance-fields/utils/typescript-field-reads.util.d.ts.map +1 -0
  35. package/lib/rxjs.d.ts +1 -1
  36. package/lib/rxjs.d.ts.map +1 -1
  37. package/lib/storybook.d.ts +1 -1
  38. package/lib/storybook.d.ts.map +1 -1
  39. package/lib/typescript.d.ts +1 -2
  40. package/lib/typescript.d.ts.map +1 -1
  41. package/lib/vitest.d.ts +1 -1
  42. package/lib/vitest.d.ts.map +1 -1
  43. package/package.json +23 -22
  44. package/stylelint.cjs +3 -0
  45. package/stylelint.cjs.map +2 -2
  46. package/stylelint.d.ts.map +1 -1
  47. package/stylelint.js +3 -0
  48. 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 createRule = ESLintUtils.RuleCreator(
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 === TSESTree.AST_NODE_TYPES.Identifier) {
1086
+ if (node.key.type === TSESTree5.AST_NODE_TYPES.Identifier) {
493
1087
  return node.key.name;
494
1088
  }
495
- if (node.key.type === TSESTree.AST_NODE_TYPES.PrivateIdentifier) {
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 === TSESTree.AST_NODE_TYPES.TSParameterProperty) {
1097
+ if (node.type === TSESTree5.AST_NODE_TYPES.TSParameterProperty) {
504
1098
  const param = node.parameter;
505
- if (param.type === TSESTree.AST_NODE_TYPES.Identifier) {
1099
+ if (param.type === TSESTree5.AST_NODE_TYPES.Identifier) {
506
1100
  return param.name;
507
1101
  }
508
- if (param.type === TSESTree.AST_NODE_TYPES.AssignmentPattern) {
509
- if (param.left.type === TSESTree.AST_NODE_TYPES.Identifier) {
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 = createRule({
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 === TSESTree.AST_NODE_TYPES.MethodDefinition || node.type === TSESTree.AST_NODE_TYPES.PropertyDefinition) {
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 === TSESTree.AST_NODE_TYPES.Identifier) {
1151
+ if (param.type === TSESTree5.AST_NODE_TYPES.Identifier) {
558
1152
  reportNode = param;
559
- } else if (param.type === TSESTree.AST_NODE_TYPES.AssignmentPattern && param.left.type === TSESTree.AST_NODE_TYPES.Identifier) {
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 === TSESTree.AST_NODE_TYPES.TSParameterProperty && node.readonly) {
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/no-unbound-method": "off",
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: "${dependency.type} is not allowed to be imported in ${file.type}",
1004
- rules: [
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
  }