@hatem427/code-guard-ci 3.3.0 → 3.4.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 (86) hide show
  1. package/config/angular.config.ts +29 -708
  2. package/config/guidelines.config.ts +5 -130
  3. package/config/nextjs.config.ts +27 -511
  4. package/config/react.config.ts +19 -614
  5. package/dist/config/angular.config.d.ts +5 -8
  6. package/dist/config/angular.config.d.ts.map +1 -1
  7. package/dist/config/angular.config.js +28 -666
  8. package/dist/config/angular.config.js.map +1 -1
  9. package/dist/config/guidelines.config.d.ts.map +1 -1
  10. package/dist/config/guidelines.config.js +5 -127
  11. package/dist/config/guidelines.config.js.map +1 -1
  12. package/dist/config/nextjs.config.d.ts +7 -9
  13. package/dist/config/nextjs.config.d.ts.map +1 -1
  14. package/dist/config/nextjs.config.js +26 -472
  15. package/dist/config/nextjs.config.js.map +1 -1
  16. package/dist/config/react.config.d.ts +4 -5
  17. package/dist/config/react.config.d.ts.map +1 -1
  18. package/dist/config/react.config.js +19 -586
  19. package/dist/config/react.config.js.map +1 -1
  20. package/dist/scripts/auto-fix.d.ts +0 -5
  21. package/dist/scripts/auto-fix.d.ts.map +1 -1
  22. package/dist/scripts/auto-fix.js +0 -5
  23. package/dist/scripts/auto-fix.js.map +1 -1
  24. package/dist/scripts/cli.js +211 -415
  25. package/dist/scripts/cli.js.map +1 -1
  26. package/dist/scripts/config-generators/ai-config-generator.d.ts.map +1 -1
  27. package/dist/scripts/config-generators/ai-config-generator.js +71 -15
  28. package/dist/scripts/config-generators/ai-config-generator.js.map +1 -1
  29. package/dist/scripts/config-generators/eslint-generator.d.ts.map +1 -1
  30. package/dist/scripts/config-generators/eslint-generator.js +13 -625
  31. package/dist/scripts/config-generators/eslint-generator.js.map +1 -1
  32. package/dist/scripts/config-generators/index.d.ts +0 -1
  33. package/dist/scripts/config-generators/index.d.ts.map +1 -1
  34. package/dist/scripts/config-generators/index.js +1 -5
  35. package/dist/scripts/config-generators/index.js.map +1 -1
  36. package/dist/scripts/config-generators/typescript-generator.d.ts.map +1 -1
  37. package/dist/scripts/config-generators/typescript-generator.js +0 -33
  38. package/dist/scripts/config-generators/typescript-generator.js.map +1 -1
  39. package/dist/scripts/config-generators/vscode-generator.d.ts.map +1 -1
  40. package/dist/scripts/config-generators/vscode-generator.js +28 -171
  41. package/dist/scripts/config-generators/vscode-generator.js.map +1 -1
  42. package/dist/scripts/generate-pr-checklist.d.ts +0 -5
  43. package/dist/scripts/generate-pr-checklist.d.ts.map +1 -1
  44. package/dist/scripts/generate-pr-checklist.js +1 -6
  45. package/dist/scripts/generate-pr-checklist.js.map +1 -1
  46. package/dist/scripts/postinstall.js +0 -38
  47. package/dist/scripts/postinstall.js.map +1 -1
  48. package/dist/scripts/precommit-check.d.ts +0 -5
  49. package/dist/scripts/precommit-check.d.ts.map +1 -1
  50. package/dist/scripts/precommit-check.js +92 -149
  51. package/dist/scripts/precommit-check.js.map +1 -1
  52. package/dist/scripts/utils/naming-validator.d.ts.map +1 -1
  53. package/dist/scripts/utils/naming-validator.js +2 -96
  54. package/dist/scripts/utils/naming-validator.js.map +1 -1
  55. package/dist/scripts/utils/project-detector.d.ts +9 -12
  56. package/dist/scripts/utils/project-detector.d.ts.map +1 -1
  57. package/dist/scripts/utils/project-detector.js +11 -63
  58. package/dist/scripts/utils/project-detector.js.map +1 -1
  59. package/dist/scripts/utils/report-generator.js +5 -17
  60. package/dist/scripts/utils/report-generator.js.map +1 -1
  61. package/dist/scripts/utils/structure-validator.d.ts.map +1 -1
  62. package/dist/scripts/utils/structure-validator.js +0 -50
  63. package/dist/scripts/utils/structure-validator.js.map +1 -1
  64. package/package.json +1 -12
  65. package/scripts/auto-fix.ts +0 -5
  66. package/scripts/cli.ts +226 -451
  67. package/scripts/config-generators/ai-config-generator.ts +78 -28
  68. package/scripts/config-generators/eslint-generator.ts +7 -621
  69. package/scripts/config-generators/index.ts +0 -1
  70. package/scripts/config-generators/typescript-generator.ts +0 -36
  71. package/scripts/config-generators/vscode-generator.ts +40 -178
  72. package/scripts/generate-pr-checklist.ts +1 -6
  73. package/scripts/postinstall.ts +0 -38
  74. package/scripts/precommit-check.ts +113 -278
  75. package/scripts/utils/naming-validator.ts +2 -104
  76. package/scripts/utils/project-detector.ts +11 -78
  77. package/scripts/utils/report-generator.ts +5 -19
  78. package/scripts/utils/structure-validator.ts +0 -54
  79. package/config/fastify.config.ts +0 -326
  80. package/config/hono.config.ts +0 -331
  81. package/config/nestjs.config.ts +0 -500
  82. package/config/python.config.ts +0 -512
  83. package/templates/feature-doc-api.md +0 -101
  84. package/templates/feature-doc-backend.md +0 -114
  85. package/templates/feature-doc-service.md +0 -113
  86. package/templates/feature-doc-ui.md +0 -91
@@ -1,126 +1,20 @@
1
1
  "use strict";
2
2
  /**
3
3
  * ============================================================================
4
- * angular.config.ts — Angular-specific coding rules (v21)
4
+ * angular.config.ts — Angular-specific coding rules
5
5
  * ============================================================================
6
6
  *
7
7
  * Registers rules that only apply to Angular projects:
8
- * - New control flow syntax (@if, @for, @switch, @let)
9
- * - Signals and computed() patterns
10
- * - Effect management
11
- * - input(), output(), model() functions
12
- * - Zoneless change detection
13
- * - Server-side rendering (hydration)
14
- * - RxJS best practices
8
+ * - ngOnInit misuse
9
+ * - Deprecated APIs (::ng-deep, CommonModule, ngClass, ngStyle)
10
+ * - Function calls in templates
11
+ * - RxJS best practices (async pipe, takeUntilDestroyed, signals)
15
12
  */
16
13
  Object.defineProperty(exports, "__esModule", { value: true });
17
14
  exports.angularRules = void 0;
18
15
  const guidelines_config_1 = require("./guidelines.config");
19
16
  const angularRules = [
20
- // ── NEW: Control Flow Syntax (Angular 17+) ────────────────────────────
21
- // ─────────────────────────────────────────
22
- // RULE: angular-use-new-control-flow
23
- // ROLE: Enforce Angular 17+ @if/@for/@switch syntax
24
- // PURPOSE: Prevents legacy *ngIf, *ngFor, *ngSwitch usage — the new
25
- // control flow is built-in, tree-shakeable, and has better
26
- // type narrowing than structural directives.
27
- // EXAMPLE:
28
- // WRONG:
29
- // <div *ngIf="isVisible">
30
- // <li *ngFor="let item of items">{{ item }}</li>
31
- // </div>
32
- // RIGHT:
33
- // @if (isVisible) {
34
- // @for (item of items; track item.id) {
35
- // <li>{{ item }}</li>
36
- // }
37
- // }
38
- // ─────────────────────────────────────────
39
- {
40
- id: 'angular-use-new-control-flow',
41
- label: 'Use @if, @for, @switch instead of *ngIf, *ngFor',
42
- description: 'Angular 17+ introduces new control flow syntax: @if, @for, @switch, @let. They are more performant and syntactically cleaner than structural directives.',
43
- severity: 'warning',
44
- fileExtensions: ['html'],
45
- pattern: null,
46
- customCheck: (file) => {
47
- const violations = [];
48
- const lines = file.lines;
49
- for (let i = 0; i < lines.length; i++) {
50
- if (/\*ngIf|\*ngFor|\*ngSwitch/.test(lines[i])) {
51
- violations.push({
52
- line: i + 1,
53
- message: 'Found structural directive. Use new Angular control flow syntax for better performance and readability.',
54
- });
55
- }
56
- }
57
- return violations;
58
- },
59
- applicableTo: ['angular'],
60
- category: 'Template Syntax',
61
- },
62
- // ─────────────────────────────────────────
63
- // RULE: angular-use-let-syntax
64
- // ROLE: Recommend Angular 17+ @let template variables
65
- // PURPOSE: Simplifies complex template expressions by extracting them
66
- // into named variables, improving readability and avoiding
67
- // repeated evaluation.
68
- // EXAMPLE:
69
- // WRONG:
70
- // {{ user?.profile?.name ? user.profile.name : 'Guest' }}
71
- // {{ user?.profile?.name ? user.profile.name : 'Guest' }}
72
- // RIGHT:
73
- // @let displayName = user?.profile?.name ?? 'Guest';
74
- // {{ displayName }}
75
- // {{ displayName }}
76
- // ─────────────────────────────────────────
77
- {
78
- id: 'angular-use-let-syntax',
79
- label: 'Use @let for template variables',
80
- description: 'Angular 17+ @let syntax enables simple variable binding in templates without getters. Example: @let myVar = expression;',
81
- severity: 'info',
82
- fileExtensions: ['html'],
83
- pattern: null,
84
- customCheck: (file) => {
85
- // This is informational - flag templates with complex expressions that could use @let
86
- const violations = [];
87
- const hasComplexExpr = /\{\{\s*[\w\.]+\s*\?\s*[\w\.]+\s*:\s*[\w\.]+\s*\}\}/g.test(file.content);
88
- if (hasComplexExpr && !file.content.includes('@let')) {
89
- violations.push({
90
- line: null,
91
- message: 'Consider using @let syntax to simplify complex template expressions.',
92
- });
93
- }
94
- return violations;
95
- },
96
- applicableTo: ['angular'],
97
- category: 'Template Syntax',
98
- },
99
17
  // ── Lifecycle ──────────────────────────────────────────────────────────
100
- // ─────────────────────────────────────────
101
- // RULE: angular-no-ngoninit
102
- // ROLE: Enforce modern Angular initialization patterns
103
- // PURPOSE: ngOnInit was needed when constructor injection was the norm.
104
- // With inject() + signals, field initializers and effect()
105
- // cover most use cases without lifecycle hooks.
106
- // EXAMPLE:
107
- // WRONG:
108
- // export class UserComponent implements OnInit {
109
- // private userService = inject(UserService);
110
- // user: User | null = null;
111
- // ngOnInit() {
112
- // this.user = this.userService.getUser();
113
- // }
114
- // }
115
- // RIGHT:
116
- // export class UserComponent {
117
- // private userService = inject(UserService);
118
- // user = signal<User | null>(null);
119
- // constructor() {
120
- // effect(() => this.user.set(this.userService.getUser()));
121
- // }
122
- // }
123
- // ─────────────────────────────────────────
124
18
  {
125
19
  id: 'angular-no-ngoninit',
126
20
  label: 'Avoid ngOnInit for simple initialization',
@@ -132,20 +26,6 @@ const angularRules = [
132
26
  category: 'Angular Lifecycle',
133
27
  },
134
28
  // ── Deprecated APIs ────────────────────────────────────────────────────
135
- // ─────────────────────────────────────────
136
- // RULE: angular-no-ng-deep
137
- // ROLE: Block deprecated view encapsulation piercing
138
- // PURPOSE: ::ng-deep breaks component encapsulation and is officially
139
- // deprecated. It leads to unpredictable style leakage and
140
- // makes refactoring dangerous.
141
- // EXAMPLE:
142
- // WRONG:
143
- // :host ::ng-deep .mat-card { background: red; }
144
- // RIGHT:
145
- // :host { --card-bg: red; }
146
- // /* In child component CSS: */
147
- // .mat-card { background: var(--card-bg); }
148
- // ─────────────────────────────────────────
149
29
  {
150
30
  id: 'angular-no-ng-deep',
151
31
  label: 'No ::ng-deep',
@@ -156,25 +36,6 @@ const angularRules = [
156
36
  applicableTo: ['angular'],
157
37
  category: 'Angular Deprecated APIs',
158
38
  },
159
- // ─────────────────────────────────────────
160
- // RULE: angular-no-common-module
161
- // ROLE: Enforce tree-shakeable standalone imports
162
- // PURPOSE: CommonModule imports NgIf, NgFor, NgClass, AsyncPipe, and
163
- // 30+ other directives/pipes. Standalone components should
164
- // import ONLY what they use — or better, use @if/@for.
165
- // EXAMPLE:
166
- // WRONG:
167
- // @Component({
168
- // standalone: true,
169
- // imports: [CommonModule],
170
- // })
171
- // RIGHT:
172
- // @Component({
173
- // standalone: true,
174
- // imports: [NgClass, NgTemplateOutlet],
175
- // })
176
- // // Or even better — use @if/@for and import nothing
177
- // ─────────────────────────────────────────
178
39
  {
179
40
  id: 'angular-no-common-module',
180
41
  label: 'No CommonModule import in standalone components',
@@ -185,20 +46,6 @@ const angularRules = [
185
46
  applicableTo: ['angular'],
186
47
  category: 'Angular Deprecated APIs',
187
48
  },
188
- // ─────────────────────────────────────────
189
- // RULE: angular-no-ngclass
190
- // ROLE: Enforce modern class binding patterns
191
- // PURPOSE: [ngClass] requires importing NgClass and has complex object
192
- // syntax. [class.name] binding is simpler, and Tailwind
193
- // pseudo-classes (disabled:, hover:) are even cleaner.
194
- // EXAMPLE:
195
- // WRONG:
196
- // <button [ngClass]="{ 'btn-active': isActive, 'btn-disabled': isDisabled }">
197
- // RIGHT:
198
- // <button [class.btn-active]="isActive()" [class.btn-disabled]="isDisabled()">
199
- // <!-- Or with Tailwind: -->
200
- // <button class="not-disabled:bg-blue-500 disabled:bg-gray-300">
201
- // ─────────────────────────────────────────
202
49
  {
203
50
  id: 'angular-no-ngclass',
204
51
  label: 'No [ngClass] — use [class] binding or Tailwind',
@@ -209,20 +56,6 @@ const angularRules = [
209
56
  applicableTo: ['angular'],
210
57
  category: 'Angular Deprecated APIs',
211
58
  },
212
- // ─────────────────────────────────────────
213
- // RULE: angular-no-ngstyle
214
- // ROLE: Enforce modern style binding patterns
215
- // PURPOSE: [ngStyle] is verbose and requires importing NgStyle.
216
- // Direct [style.prop] binding or Tailwind utilities are
217
- // simpler and more performant.
218
- // EXAMPLE:
219
- // WRONG:
220
- // <div [ngStyle]="{ 'background-color': bgColor, 'font-size': fontSize + 'px' }">
221
- // RIGHT:
222
- // <div [style.background-color]="bgColor" [style.font-size.rem]="fontSize">
223
- // <!-- Or with Tailwind: -->
224
- // <div class="bg-primary text-base">
225
- // ─────────────────────────────────────────
226
59
  {
227
60
  id: 'angular-no-ngstyle',
228
61
  label: 'No [ngStyle] — use [style] binding or Tailwind',
@@ -234,22 +67,6 @@ const angularRules = [
234
67
  category: 'Angular Deprecated APIs',
235
68
  },
236
69
  // ── Template best practices ────────────────────────────────────────────
237
- // ─────────────────────────────────────────
238
- // RULE: angular-no-function-in-template
239
- // ROLE: Block function calls in template bindings
240
- // PURPOSE: Functions in templates execute on EVERY change detection
241
- // cycle, causing severe performance degradation. Signals and
242
- // computed() are memoized and only re-evaluate when deps change.
243
- // EXAMPLE:
244
- // WRONG:
245
- // <span>{{ getFullName() }}</span>
246
- // <div [class.active]="isItemActive(item)">
247
- // RIGHT:
248
- // fullName = computed(() => this.firstName() + ' ' + this.lastName());
249
- // <span>{{ fullName() }}</span>
250
- // <!-- Or use a pipe: -->
251
- // <span>{{ user | fullName }}</span>
252
- // ─────────────────────────────────────────
253
70
  {
254
71
  id: 'angular-no-function-in-template',
255
72
  label: 'No function calls in templates',
@@ -259,6 +76,16 @@ const angularRules = [
259
76
  pattern: null,
260
77
  customCheck: (file) => {
261
78
  const violations = [];
79
+ /**
80
+ * Regex explanation:
81
+ * Match patterns like {{ someFunc() }} or (click)="handler()" but EXCLUDE:
82
+ * - Event handlers: (click)="...", (submit)="...", on*="..."
83
+ * - Known safe pipes: | async, | date, | json, etc.
84
+ * - Structural directives: *ngIf, *ngFor, @if, @for
85
+ *
86
+ * We look for interpolation bindings {{ expr() }} and property bindings [prop]="expr()"
87
+ * that contain function calls which are NOT event bindings.
88
+ */
262
89
  const interpolationRegex = /\{\{[^}]*\w+\s*\([^)]*\)[^}]*\}\}/g;
263
90
  const propertyBindingRegex = /\[(?!click|submit|keyup|keydown|change|input|focus|blur)\w+\]\s*=\s*"[^"]*\w+\s*\([^)]*\)[^"]*"/g;
264
91
  for (let i = 0; i < file.lines.length; i++) {
@@ -266,6 +93,7 @@ const angularRules = [
266
93
  // Check interpolation bindings {{ func() }}
267
94
  interpolationRegex.lastIndex = 0;
268
95
  if (interpolationRegex.test(line)) {
96
+ // Exclude pipe usages like {{ value | pipeName }}
269
97
  const trimmed = line.trim();
270
98
  if (!trimmed.includes(' | ')) {
271
99
  violations.push({
@@ -289,24 +117,6 @@ const angularRules = [
289
117
  category: 'Angular Templates',
290
118
  },
291
119
  // ── RxJS best practices ────────────────────────────────────────────────
292
- // ─────────────────────────────────────────
293
- // RULE: angular-use-async-pipe
294
- // ROLE: Prevent manual subscription memory leaks
295
- // PURPOSE: Manual .subscribe() in components is the #1 cause of memory
296
- // leaks in Angular apps. The async pipe or toSignal()
297
- // auto-unsubscribes when the component is destroyed.
298
- // EXAMPLE:
299
- // WRONG:
300
- // ngOnInit() {
301
- // this.userService.getUser().subscribe(user => this.user = user);
302
- // }
303
- // RIGHT:
304
- // user$ = this.userService.getUser();
305
- // <!-- In template: -->
306
- // {{ user$ | async }}
307
- // <!-- Or convert to signal: -->
308
- // user = toSignal(this.userService.getUser());
309
- // ─────────────────────────────────────────
310
120
  {
311
121
  id: 'angular-use-async-pipe',
312
122
  label: 'Use async pipe or toSignal() instead of manual subscribe',
@@ -317,26 +127,6 @@ const angularRules = [
317
127
  applicableTo: ['angular'],
318
128
  category: 'RxJS',
319
129
  },
320
- // ─────────────────────────────────────────
321
- // RULE: angular-use-takeuntildestroyed
322
- // ROLE: Enforce subscription cleanup in Angular classes
323
- // PURPOSE: If subscribe() is unavoidable, takeUntilDestroyed() from
324
- // @angular/core/rxjs-interop handles cleanup automatically
325
- // tied to the component's DestroyRef lifecycle.
326
- // EXAMPLE:
327
- // WRONG:
328
- // ngOnInit() {
329
- // this.data$.subscribe(d => this.process(d));
330
- // }
331
- // // Memory leak — never unsubscribes!
332
- // RIGHT:
333
- // private destroyRef = inject(DestroyRef);
334
- // ngOnInit() {
335
- // this.data$.pipe(
336
- // takeUntilDestroyed(this.destroyRef)
337
- // ).subscribe(d => this.process(d));
338
- // }
339
- // ─────────────────────────────────────────
340
130
  {
341
131
  id: 'angular-use-takeuntildestroyed',
342
132
  label: 'Use takeUntilDestroyed() for subscriptions',
@@ -346,6 +136,7 @@ const angularRules = [
346
136
  pattern: null,
347
137
  customCheck: (file) => {
348
138
  const violations = [];
139
+ // Only check Angular component/directive/service files
349
140
  if (!file.content.includes('@Component') &&
350
141
  !file.content.includes('@Directive') &&
351
142
  !file.content.includes('@Injectable')) {
@@ -365,20 +156,6 @@ const angularRules = [
365
156
  applicableTo: ['angular'],
366
157
  category: 'RxJS',
367
158
  },
368
- // ─────────────────────────────────────────
369
- // RULE: angular-prefer-signals
370
- // ROLE: Recommend migration from BehaviorSubject to Signals
371
- // PURPOSE: Angular Signals (signal(), computed(), effect()) are simpler,
372
- // synchronous, and deeply integrated with change detection.
373
- // BehaviorSubject is async and requires subscription management.
374
- // EXAMPLE:
375
- // WRONG:
376
- // count$ = new BehaviorSubject<number>(0);
377
- // increment() { this.count$.next(this.count$.value + 1); }
378
- // RIGHT:
379
- // count = signal(0);
380
- // increment() { this.count.update(v => v + 1); }
381
- // ─────────────────────────────────────────
382
159
  {
383
160
  id: 'angular-prefer-signals',
384
161
  label: 'Consider using Angular Signals',
@@ -387,6 +164,7 @@ const angularRules = [
387
164
  fileExtensions: ['ts'],
388
165
  pattern: null,
389
166
  customCheck: (file) => {
167
+ // Only flag if the component uses BehaviorSubject but no signals
390
168
  const hasBehaviorSubject = file.content.includes('BehaviorSubject');
391
169
  const hasSignal = file.content.includes('signal(') || file.content.includes('computed(');
392
170
  if (hasBehaviorSubject && !hasSignal) {
@@ -402,28 +180,7 @@ const angularRules = [
402
180
  applicableTo: ['angular'],
403
181
  category: 'Angular Signals',
404
182
  },
405
- // ─────────────────────────────────────────
406
- // RULE: angular-prefer-inject
407
- // ROLE: Enforce inject() function over constructor DI
408
- // PURPOSE: inject() enables dependency injection at field declaration
409
- // level, works with standalone components, and doesn't require
410
- // constructor parameter boilerplate.
411
- // EXAMPLE:
412
- // WRONG:
413
- // export class UserComponent {
414
- // constructor(
415
- // private userService: UserService,
416
- // private router: Router,
417
- // private http: HttpClient
418
- // ) {}
419
- // }
420
- // RIGHT:
421
- // export class UserComponent {
422
- // private userService = inject(UserService);
423
- // private router = inject(Router);
424
- // private http = inject(HttpClient);
425
- // }
426
- // ─────────────────────────────────────────
183
+ // ── Dependency Injection ────────────────────────────────────────────────
427
184
  {
428
185
  id: 'angular-prefer-inject',
429
186
  label: 'Use inject() instead of constructor injection',
@@ -433,13 +190,17 @@ const angularRules = [
433
190
  pattern: null,
434
191
  customCheck: (file) => {
435
192
  const violations = [];
193
+ // Only check Angular component/directive/service files
436
194
  if (!file.content.includes('@Component') &&
437
195
  !file.content.includes('@Directive') &&
438
196
  !file.content.includes('@Injectable')) {
439
197
  return [];
440
198
  }
199
+ // Check for constructor with dependency injection parameters
200
+ // Matches: constructor(private service: Type) or constructor(public readonly http: HttpClient)
441
201
  const constructorPattern = /constructor\s*\(\s*(?:private|public|protected|readonly|\s)+\w+\s*:\s*\w+/;
442
202
  if (constructorPattern.test(file.content)) {
203
+ // Find the line number
443
204
  for (let i = 0; i < file.lines.length; i++) {
444
205
  if (constructorPattern.test(file.lines[i])) {
445
206
  violations.push({
@@ -455,222 +216,7 @@ const angularRules = [
455
216
  applicableTo: ['angular'],
456
217
  category: 'Angular Dependency Injection',
457
218
  },
458
- // ── NEW: Signals & Signal-based Features (Angular 16+, essential in v21) ────
459
- // ─────────────────────────────────────────
460
- // RULE: angular-use-signals
461
- // ROLE: Enforce Signal-based reactive state
462
- // PURPOSE: Signals are the future of Angular reactivity. They are
463
- // synchronous, automatically tracked by change detection,
464
- // and eliminate the need for most RxJS subscriptions.
465
- // EXAMPLE:
466
- // WRONG:
467
- // import { BehaviorSubject } from 'rxjs';
468
- // counter = new BehaviorSubject(0);
469
- // counter.next(counter.value + 1);
470
- // RIGHT:
471
- // import { signal } from '@angular/core';
472
- // counter = signal(0);
473
- // counter.update(val => val + 1);
474
- // ─────────────────────────────────────────
475
- {
476
- id: 'angular-use-signals',
477
- label: 'Use Signals for reactive state',
478
- description: 'Angular Signals replace RxJS in many cases. Use signal(), computed(), and effect() for simpler, more performant reactivity. Consider migrating from BehaviorSubject.',
479
- severity: 'warning',
480
- fileExtensions: ['ts'],
481
- pattern: null,
482
- customCheck: (file) => {
483
- const violations = [];
484
- if (!file.content.includes('@Component') &&
485
- !file.content.includes('@Injectable') &&
486
- !file.content.includes('export')) {
487
- return [];
488
- }
489
- const hasBehaviorSubject = /BehaviorSubject|Subject/.test(file.content);
490
- const hasSignal = /signal\(|computed\(|effect\(/.test(file.content);
491
- const hasRxJS = /rxjs|Observable<|\.pipe\(/.test(file.content);
492
- if (hasBehaviorSubject && !hasSignal && !hasRxJS) {
493
- violations.push({
494
- line: null,
495
- message: 'BehaviorSubject detected. Migrate to Angular Signals for cleaner reactive state.\n\nWhy: Signals are simpler, more performant, and better integrated with Angular\'s change detection.\n\n- import { BehaviorSubject } from "rxjs";\n- counter = new BehaviorSubject(0);\n- counter.next(counter.value + 1);\n\n+ import { signal } from "@angular/core";\n+ counter = signal(0);\n+ counter.update(val => val + 1);',
496
- });
497
- }
498
- return violations;
499
- },
500
- applicableTo: ['angular'],
501
- category: 'Angular Signals',
502
- },
503
- // ─────────────────────────────────────────
504
- // RULE: angular-use-computed
505
- // ROLE: Enforce computed() for derived state
506
- // PURPOSE: computed() is memoized and only recalculates when its
507
- // signal dependencies change. Getters re-evaluate on every
508
- // change detection cycle.
509
- // EXAMPLE:
510
- // WRONG:
511
- // get totalPrice(): number {
512
- // return this.items.reduce((sum, item) => sum + item.price, 0);
513
- // }
514
- // RIGHT:
515
- // totalPrice = computed(() =>
516
- // this.items().reduce((sum, item) => sum + item.price, 0)
517
- // );
518
- // ─────────────────────────────────────────
519
- {
520
- id: 'angular-use-computed',
521
- label: 'Use computed() for derived state',
522
- description: 'Instead of getters or pipes, use computed() from @angular/core to create derived signal state. Better type safety and automatic memoization.',
523
- severity: 'info',
524
- fileExtensions: ['ts'],
525
- pattern: null,
526
- customCheck: (file) => {
527
- const violations = [];
528
- if (/get\s+\w+\s*\(\s*\).*\{[\s\S]*?return.*signal|this\.\w+\(\)/.test(file.content)) {
529
- violations.push({
530
- line: null,
531
- message: 'Found getter function. Use computed() from signals for automatic memoization and better performance.',
532
- });
533
- }
534
- return violations;
535
- },
536
- applicableTo: ['angular'],
537
- category: 'Angular Signals',
538
- },
539
- // ─────────────────────────────────────────
540
- // RULE: angular-use-effect
541
- // ROLE: Enforce effect() for signal-driven side effects
542
- // PURPOSE: effect() automatically tracks signal dependencies and
543
- // re-runs when they change, replacing ngOnInit-based
544
- // subscription patterns with declarative reactivity.
545
- // EXAMPLE:
546
- // WRONG:
547
- // ngOnInit() {
548
- // this.data$.subscribe(data => this.processData(data));
549
- // }
550
- // RIGHT:
551
- // constructor() {
552
- // effect(() => this.processData(this.data()));
553
- // }
554
- // ─────────────────────────────────────────
555
- {
556
- id: 'angular-use-effect',
557
- label: 'Use effect() for side effects instead of ngOnInit',
558
- description: 'Use effect() from @angular/core for side effects that depend on signals. It automatically tracks dependencies and runs when signals change.',
559
- severity: 'info',
560
- fileExtensions: ['ts'],
561
- pattern: null,
562
- customCheck: (file) => {
563
- const violations = [];
564
- if (/ngOnInit\s*\(\)[\s\S]*?\{[\s\S]*?(?:subscribe|addEventListener|setTimeout)/.test(file.content)) {
565
- violations.push({
566
- line: null,
567
- message: 'Found ngOnInit with side effects. Use effect() from signals for cleaner dependency tracking.',
568
- });
569
- }
570
- return violations;
571
- },
572
- applicableTo: ['angular'],
573
- category: 'Angular Signals',
574
- },
575
- // ─────────────────────────────────────────
576
- // RULE: angular-use-input-output-functions
577
- // ROLE: Enforce signal-based input/output API
578
- // PURPOSE: input(), output(), and model() functions provide better
579
- // null-safety, type inference, and signal integration than
580
- // @Input/@Output decorators.
581
- // EXAMPLE:
582
- // WRONG:
583
- // @Input() title: string = '';
584
- // @Output() clicked = new EventEmitter<void>();
585
- // RIGHT:
586
- // title = input<string>('');
587
- // clicked = output<void>();
588
- // // Two-way binding:
589
- // name = model<string>('');
590
- // ─────────────────────────────────────────
591
- {
592
- id: 'angular-use-input-output-functions',
593
- label: 'Use input(), output(), model() functions',
594
- description: 'Angular 17+ supports input(), output(), and model() functions instead of @Input/@Output decorators. They provide better null-safety and type inference.',
595
- severity: 'warning',
596
- fileExtensions: ['ts'],
597
- pattern: /@Input|@Output/g,
598
- applicableTo: ['angular'],
599
- category: 'Angular Components',
600
- },
601
- // ─────────────────────────────────────────
602
- // RULE: angular-signal-based-viewchild
603
- // ROLE: Recommend signal-based queries
604
- // PURPOSE: viewChild() and viewChildren() return signals, eliminating
605
- // the need for AfterViewInit lifecycle hooks and providing
606
- // automatic change detection integration.
607
- // EXAMPLE:
608
- // WRONG:
609
- // @ViewChild('myInput') myInput!: ElementRef;
610
- // ngAfterViewInit() { this.myInput.nativeElement.focus(); }
611
- // RIGHT:
612
- // myInput = viewChild<ElementRef>('myInput');
613
- // constructor() {
614
- // afterNextRender(() => this.myInput()?.nativeElement.focus());
615
- // }
616
- // ─────────────────────────────────────────
617
- {
618
- id: 'angular-signal-based-viewchild',
619
- label: 'Use signal-based @ViewChild where applicable',
620
- description: 'For @ViewChild queries, consider using signal queries with viewChild() function for better type safety and automatic change detection.',
621
- severity: 'info',
622
- fileExtensions: ['ts'],
623
- pattern: /@ViewChild\s*\(|@ViewChildren\s*\(/g,
624
- applicableTo: ['angular'],
625
- category: 'Angular Templates',
626
- },
627
- // ── NEW: Zoneless Change Detection (Angular 18+, recommended in v21) ───
628
- // ─────────────────────────────────────────
629
- // RULE: angular-consider-zoneless
630
- // ROLE: Recommend zoneless change detection
631
- // PURPOSE: Zone.js patches every async API (setTimeout, Promise, XHR)
632
- // adding ~100KB to the bundle. Signals make Zone.js optional
633
- // since they notify Angular directly of changes.
634
- // EXAMPLE:
635
- // WRONG:
636
- // // app.config.ts (with Zone.js)
637
- // provideZoneChangeDetection({ eventCoalescing: true })
638
- // RIGHT:
639
- // // app.config.ts (zoneless)
640
- // provideExperimentalZonelessChangeDetection()
641
- // // angular.json
642
- // "polyfills": [] // remove zone.js
643
- // ─────────────────────────────────────────
644
- {
645
- id: 'angular-consider-zoneless',
646
- label: 'Consider zoneless change detection',
647
- description: 'Angular 18+ supports zoneless applications, which improves performance by removing Zone.js. Set "zone": "none" in component config when possible.',
648
- severity: 'info',
649
- fileExtensions: ['ts'],
650
- pattern: null,
651
- applicableTo: ['angular'],
652
- category: 'Performance',
653
- },
654
219
  // ── Performance Rules ──────────────────────────────────────────────────
655
- // ─────────────────────────────────────────
656
- // RULE: angular-onpush-change-detection
657
- // ROLE: Recommend OnPush change detection strategy
658
- // PURPOSE: Default change detection runs on EVERY browser event.
659
- // OnPush only checks when @Input references change or signals
660
- // update, dramatically reducing unnecessary checks.
661
- // EXAMPLE:
662
- // WRONG:
663
- // @Component({
664
- // selector: 'app-user-card',
665
- // templateUrl: './user-card.component.html',
666
- // })
667
- // RIGHT:
668
- // @Component({
669
- // selector: 'app-user-card',
670
- // templateUrl: './user-card.component.html',
671
- // changeDetection: ChangeDetectionStrategy.OnPush,
672
- // })
673
- // ─────────────────────────────────────────
674
220
  {
675
221
  id: 'angular-onpush-change-detection',
676
222
  label: 'Consider OnPush change detection',
@@ -681,176 +227,27 @@ const angularRules = [
681
227
  applicableTo: ['angular'],
682
228
  category: 'Performance',
683
229
  },
684
- // ─────────────────────────────────────────
685
- // RULE: angular-trackby-for-loops
686
- // ROLE: Enforce proper DOM identity tracking in loops
687
- // PURPOSE: Without track, Angular destroys and recreates ALL DOM nodes
688
- // on every list change. track item.id lets Angular reuse
689
- // existing DOM nodes, improving performance 10-100x for lists.
690
- // EXAMPLE:
691
- // WRONG:
692
- // <li *ngFor="let item of items">{{ item.name }}</li>
693
- // @for (item of items) { <li>{{ item.name }}</li> }
694
- // RIGHT:
695
- // @for (item of items; track item.id) {
696
- // <li>{{ item.name }}</li>
697
- // }
698
- // ─────────────────────────────────────────
699
230
  {
700
- id: 'angular-trackby-for-loops',
701
- label: 'Use trackBy with *ngFor or proper @for iteration',
702
- description: 'With *ngFor, always provide trackBy. With @for, use @for (item of items; track item.id) for proper identity tracking.',
231
+ id: 'angular-trackby-ngfor',
232
+ label: 'Use trackBy with *ngFor',
233
+ description: 'Always provide trackBy function for *ngFor to prevent unnecessary DOM re-renders when data changes.',
703
234
  severity: 'warning',
704
235
  fileExtensions: ['html'],
705
- pattern: /\*ngFor\s*=\s*"[^"]*"(?![^<]*trackBy)|@for\s*\([^)]*\)\s*\{(?![^}]*track)/g,
236
+ pattern: /\*ngFor\s*=\s*"[^"]*"(?![^<]*trackBy)/g,
706
237
  applicableTo: ['angular'],
707
238
  category: 'Performance',
708
239
  },
709
- // ─────────────────────────────────────────
710
- // RULE: angular-no-unused-imports
711
- // ROLE: Flag unnecessary RxJS imports when Signals are available
712
- // PURPOSE: Importing Observable/Subject when Signals could be used
713
- // adds unnecessary bundle weight and cognitive overhead.
714
- // Prefer signals for simple reactive state.
715
- // EXAMPLE:
716
- // WRONG:
717
- // import { Observable, BehaviorSubject } from 'rxjs';
718
- // data$ = new BehaviorSubject<string[]>([]);
719
- // RIGHT:
720
- // import { signal } from '@angular/core';
721
- // data = signal<string[]>([]);
722
- // ─────────────────────────────────────────
723
- {
724
- id: 'angular-no-unused-imports',
725
- label: 'Avoid importing RxJS operators not used',
726
- description: 'Remove unused RxJS imports (Observable, Subject). Prefer Signals if you are not heavily using RxJS pipelines.',
727
- severity: 'info',
728
- fileExtensions: ['ts'],
729
- pattern: /import\s*\{[^}]*Observable[^}]*\}\s*from\s+['"]rxjs['"]/g,
730
- applicableTo: ['angular'],
731
- category: 'Best Practices',
732
- },
733
- // ── NEW: Server-Side Rendering Hydration (Angular 17+) ────────────────
734
- // ─────────────────────────────────────────
735
- // RULE: angular-ssr-hydration
736
- // ROLE: Block direct DOM access in SSR-compatible components
737
- // PURPOSE: document.*, window.*, localStorage do not exist on the
738
- // server. Direct usage crashes SSR pre-rendering. Always
739
- // guard with isPlatformBrowser() or use afterNextRender().
740
- // EXAMPLE:
741
- // WRONG:
742
- // ngOnInit() {
743
- // const token = localStorage.getItem('token');
744
- // document.title = 'My App';
745
- // }
746
- // RIGHT:
747
- // constructor() {
748
- // afterNextRender(() => {
749
- // const token = localStorage.getItem('token');
750
- // document.title = 'My App';
751
- // });
752
- // }
753
- // ─────────────────────────────────────────
754
- {
755
- id: 'angular-ssr-hydration',
756
- label: 'Ensure SSR hydration compatibility',
757
- description: 'When using SSR, ensure components are hydration-compatible. Avoid direct DOM manipulation (document.*, window.*) in server contexts.',
758
- severity: 'warning',
759
- fileExtensions: ['ts'],
760
- pattern: /(?:document\.|window\.|localStorage|sessionStorage)/g,
761
- applicableTo: ['angular'],
762
- category: 'Server-Side Rendering',
763
- },
764
- // ─────────────────────────────────────────
765
- // RULE: angular-ssr-safe-initialization
766
- // ROLE: Enforce browser API guards for SSR safety
767
- // PURPOSE: Browser-only code (navigator, localStorage, window) must
768
- // be wrapped in platform guards to prevent server crashes
769
- // during pre-rendering.
770
- // EXAMPLE:
771
- // WRONG:
772
- // const lang = navigator.language;
773
- // window.scrollTo(0, 0);
774
- // RIGHT:
775
- // private platformId = inject(PLATFORM_ID);
776
- // constructor() {
777
- // if (isPlatformBrowser(this.platformId)) {
778
- // const lang = navigator.language;
779
- // window.scrollTo(0, 0);
780
- // }
781
- // }
782
- // ─────────────────────────────────────────
783
- {
784
- id: 'angular-ssr-safe-initialization',
785
- label: 'Guard browser-only APIs in SSR',
786
- description: 'Wrap browser-only code in isPlatformBrowser() or effect() guards to prevent errors during SSR pre-rendering.',
787
- severity: 'warning',
788
- fileExtensions: ['ts'],
789
- pattern: null,
790
- customCheck: (file) => {
791
- const violations = [];
792
- const hasBrowserAPI = /(?:document\.|window\.|localStorage|navigator|sessionStorage)/.test(file.content);
793
- const hasGuard = /isPlatformBrowser|platformBrowser|isBrowser/.test(file.content);
794
- if (hasBrowserAPI && !hasGuard && !file.relativePath.includes('client')) {
795
- violations.push({
796
- line: null,
797
- message: 'Browser-only API detected without SSR guard. Wrap in isPlatformBrowser() or use effect() for safe execution.',
798
- });
799
- }
800
- return violations;
801
- },
802
- applicableTo: ['angular'],
803
- category: 'Server-Side Rendering',
804
- },
805
- // ─────────────────────────────────────────
806
- // RULE: angular-lazy-load-modules
807
- // ROLE: Enforce lazy loading for route modules
808
- // PURPOSE: Eagerly loading all routes increases the initial bundle
809
- // size and slows Time-to-Interactive. Lazy loading defers
810
- // feature code until the user navigates to it.
811
- // EXAMPLE:
812
- // WRONG:
813
- // import { UsersComponent } from './users/users.component';
814
- // { path: 'users', component: UsersComponent }
815
- // RIGHT:
816
- // {
817
- // path: 'users',
818
- // loadComponent: () => import('./users/users.component')
819
- // .then(m => m.UsersComponent)
820
- // }
821
- // ─────────────────────────────────────────
822
240
  {
823
241
  id: 'angular-lazy-load-modules',
824
242
  label: 'Use lazy loading for routes',
825
243
  description: 'Lazy load feature modules in routing to reduce initial bundle size and improve load time.',
826
244
  severity: 'info',
827
245
  fileExtensions: ['ts'],
828
- pattern: /loadChildren:\s*\(\)\s*=>\s*import\(/g,
246
+ pattern: /loadChildren:\s*\(\)\s*=>\s*import\(['"]\.['"]\)/g,
829
247
  applicableTo: ['angular'],
830
248
  category: 'Performance',
831
249
  },
832
250
  // ── Best Practices ─────────────────────────────────────────────────────
833
- // ─────────────────────────────────────────
834
- // RULE: angular-standalone-components
835
- // ROLE: Enforce standalone component architecture
836
- // PURPOSE: NgModules add indirection and make dependency tracking hard.
837
- // Standalone components declare their own imports, are
838
- // tree-shakeable, and are the recommended Angular architecture.
839
- // EXAMPLE:
840
- // WRONG:
841
- // @NgModule({
842
- // declarations: [UserComponent],
843
- // imports: [CommonModule],
844
- // })
845
- // export class UserModule {}
846
- // RIGHT:
847
- // @Component({
848
- // standalone: true,
849
- // imports: [NgClass],
850
- // selector: 'app-user',
851
- // })
852
- // export class UserComponent {}
853
- // ─────────────────────────────────────────
854
251
  {
855
252
  id: 'angular-standalone-components',
856
253
  label: 'Prefer standalone components',
@@ -861,24 +258,6 @@ const angularRules = [
861
258
  applicableTo: ['angular'],
862
259
  category: 'Best Practices',
863
260
  },
864
- // ─────────────────────────────────────────
865
- // RULE: angular-strict-templates
866
- // ROLE: Enforce strict template type checking
867
- // PURPOSE: Without strictTemplates, Angular templates are essentially
868
- // untyped — typos in property names, wrong types, and missing
869
- // fields are silently ignored at compile time.
870
- // EXAMPLE:
871
- // WRONG (tsconfig.json):
872
- // "angularCompilerOptions": {
873
- // "strictTemplates": false
874
- // }
875
- // RIGHT (tsconfig.json):
876
- // "angularCompilerOptions": {
877
- // "strictTemplates": true,
878
- // "strictInjectionParameters": true,
879
- // "strictInputAccessModifiers": true
880
- // }
881
- // ─────────────────────────────────────────
882
261
  {
883
262
  id: 'angular-strict-templates',
884
263
  label: 'Enable strict template checking',
@@ -889,23 +268,6 @@ const angularRules = [
889
268
  applicableTo: ['angular'],
890
269
  category: 'TypeScript',
891
270
  },
892
- // ─────────────────────────────────────────
893
- // RULE: angular-defer-for-lazy
894
- // ROLE: Recommend @defer for template-level lazy loading
895
- // PURPOSE: @defer blocks let you lazy-load sections of a template
896
- // (heavy charts, tables, modals) without component-level
897
- // code splitting. Triggers include viewport, interaction, etc.
898
- // EXAMPLE:
899
- // WRONG:
900
- // <app-heavy-chart [data]="chartData()"></app-heavy-chart>
901
- // <!-- Always loaded, even if below the fold -->
902
- // RIGHT:
903
- // @defer (on viewport) {
904
- // <app-heavy-chart [data]="chartData()"></app-heavy-chart>
905
- // } @placeholder {
906
- // <div class="h-64 animate-pulse bg-gray-200"></div>
907
- // }
908
- // ─────────────────────────────────────────
909
271
  {
910
272
  id: 'angular-defer-for-lazy',
911
273
  label: 'Use @defer for lazy views',