@corbat-tech/coding-standards-mcp 1.0.3 → 2.0.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 (99) hide show
  1. package/README.md +233 -337
  2. package/dist/agent.d.ts +5 -6
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +95 -217
  5. package/dist/agent.js.map +1 -1
  6. package/dist/analysis/code-analyzer.d.ts +44 -0
  7. package/dist/analysis/code-analyzer.d.ts.map +1 -0
  8. package/dist/analysis/code-analyzer.js +528 -0
  9. package/dist/analysis/code-analyzer.js.map +1 -0
  10. package/dist/errors.d.ts +58 -0
  11. package/dist/errors.d.ts.map +1 -0
  12. package/dist/errors.js +112 -0
  13. package/dist/errors.js.map +1 -0
  14. package/dist/guardrails.d.ts +35 -0
  15. package/dist/guardrails.d.ts.map +1 -0
  16. package/dist/guardrails.js +303 -0
  17. package/dist/guardrails.js.map +1 -0
  18. package/dist/index.js +1 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/logger.d.ts +36 -0
  21. package/dist/logger.d.ts.map +1 -0
  22. package/dist/logger.js +63 -0
  23. package/dist/logger.js.map +1 -0
  24. package/dist/metrics.d.ts +40 -0
  25. package/dist/metrics.d.ts.map +1 -0
  26. package/dist/metrics.js +97 -0
  27. package/dist/metrics.js.map +1 -0
  28. package/dist/profiles.d.ts +1 -1
  29. package/dist/profiles.d.ts.map +1 -1
  30. package/dist/profiles.js +239 -108
  31. package/dist/profiles.js.map +1 -1
  32. package/dist/prompts.js +1 -1
  33. package/dist/prompts.js.map +1 -1
  34. package/dist/tools/definitions.d.ts +143 -0
  35. package/dist/tools/definitions.d.ts.map +1 -0
  36. package/dist/tools/definitions.js +229 -0
  37. package/dist/tools/definitions.js.map +1 -0
  38. package/dist/tools/handlers/get-context.d.ts +12 -0
  39. package/dist/tools/handlers/get-context.d.ts.map +1 -0
  40. package/dist/tools/handlers/get-context.js +233 -0
  41. package/dist/tools/handlers/get-context.js.map +1 -0
  42. package/dist/tools/handlers/health.d.ts +11 -0
  43. package/dist/tools/handlers/health.d.ts.map +1 -0
  44. package/dist/tools/handlers/health.js +57 -0
  45. package/dist/tools/handlers/health.js.map +1 -0
  46. package/dist/tools/handlers/index.d.ts +12 -0
  47. package/dist/tools/handlers/index.d.ts.map +1 -0
  48. package/dist/tools/handlers/index.js +12 -0
  49. package/dist/tools/handlers/index.js.map +1 -0
  50. package/dist/tools/handlers/init.d.ts +12 -0
  51. package/dist/tools/handlers/init.d.ts.map +1 -0
  52. package/dist/tools/handlers/init.js +102 -0
  53. package/dist/tools/handlers/init.js.map +1 -0
  54. package/dist/tools/handlers/profiles.d.ts +11 -0
  55. package/dist/tools/handlers/profiles.d.ts.map +1 -0
  56. package/dist/tools/handlers/profiles.js +25 -0
  57. package/dist/tools/handlers/profiles.js.map +1 -0
  58. package/dist/tools/handlers/search.d.ts +12 -0
  59. package/dist/tools/handlers/search.d.ts.map +1 -0
  60. package/dist/tools/handlers/search.js +58 -0
  61. package/dist/tools/handlers/search.js.map +1 -0
  62. package/dist/tools/handlers/validate.d.ts +15 -0
  63. package/dist/tools/handlers/validate.d.ts.map +1 -0
  64. package/dist/tools/handlers/validate.js +71 -0
  65. package/dist/tools/handlers/validate.js.map +1 -0
  66. package/dist/tools/handlers/verify.d.ts +38 -0
  67. package/dist/tools/handlers/verify.d.ts.map +1 -0
  68. package/dist/tools/handlers/verify.js +172 -0
  69. package/dist/tools/handlers/verify.js.map +1 -0
  70. package/dist/tools/index.d.ts +22 -0
  71. package/dist/tools/index.d.ts.map +1 -0
  72. package/dist/tools/index.js +75 -0
  73. package/dist/tools/index.js.map +1 -0
  74. package/dist/tools/schemas.d.ts +29 -0
  75. package/dist/tools/schemas.d.ts.map +1 -0
  76. package/dist/tools/schemas.js +20 -0
  77. package/dist/tools/schemas.js.map +1 -0
  78. package/dist/tools.js +2 -2
  79. package/dist/tools.js.map +1 -1
  80. package/dist/types.d.ts +141 -71
  81. package/dist/types.d.ts.map +1 -1
  82. package/dist/types.js +92 -40
  83. package/dist/types.js.map +1 -1
  84. package/package.json +2 -2
  85. package/profiles/examples/microservice-kafka.yaml +122 -0
  86. package/profiles/examples/startup-fast.yaml +67 -0
  87. package/profiles/examples/strict-enterprise.yaml +62 -0
  88. package/profiles/templates/angular.yaml +614 -0
  89. package/profiles/templates/csharp-dotnet.yaml +529 -0
  90. package/profiles/templates/flutter.yaml +547 -0
  91. package/profiles/templates/go.yaml +1276 -0
  92. package/profiles/templates/java-spring-backend.yaml +326 -0
  93. package/profiles/templates/kotlin-spring.yaml +417 -0
  94. package/profiles/templates/nextjs.yaml +536 -0
  95. package/profiles/templates/nodejs.yaml +594 -0
  96. package/profiles/templates/python.yaml +546 -0
  97. package/profiles/templates/react.yaml +456 -0
  98. package/profiles/templates/rust.yaml +508 -0
  99. package/profiles/templates/vue.yaml +483 -0
@@ -492,3 +492,617 @@ forms:
492
492
  #
493
493
  # e2e/ # End-to-end tests
494
494
  # └── specs/
495
+
496
+ # ----------------------------------------------------------------------------
497
+ # CODE EXAMPLES
498
+ # ----------------------------------------------------------------------------
499
+ codeExamples:
500
+ standaloneComponent:
501
+ description: "Modern standalone component with signals"
502
+ code: |
503
+ // features/users/user-card.component.ts
504
+ import { Component, input, output, computed } from '@angular/core';
505
+ import { RouterLink } from '@angular/router';
506
+ import type { User } from '@/core/models/user.model';
507
+
508
+ @Component({
509
+ selector: 'app-user-card',
510
+ standalone: true,
511
+ imports: [RouterLink],
512
+ template: `
513
+ @if (user(); as u) {
514
+ <article class="user-card" [class.active]="isActive()">
515
+ <img [src]="u.avatar" [alt]="fullName() + '\\'s avatar'" />
516
+ <h3>{{ fullName() }}</h3>
517
+ <p>{{ u.email }}</p>
518
+ <button (click)="handleSelect()">Select</button>
519
+ <a [routerLink]="['/users', u.id]">View Profile</a>
520
+ </article>
521
+ }
522
+ `,
523
+ styles: [`
524
+ .user-card { @apply p-4 rounded-lg shadow; }
525
+ .user-card.active { @apply ring-2 ring-primary; }
526
+ `]
527
+ })
528
+ export class UserCardComponent {
529
+ // Signal-based inputs
530
+ user = input.required<User>();
531
+ isActive = input(false);
532
+
533
+ // Signal-based output
534
+ selected = output<string>();
535
+
536
+ // Computed signal
537
+ fullName = computed(() => {
538
+ const u = this.user();
539
+ return `${u.firstName} ${u.lastName}`;
540
+ });
541
+
542
+ handleSelect() {
543
+ this.selected.emit(this.user().id);
544
+ }
545
+ }
546
+
547
+ signalState:
548
+ description: "Component state with signals"
549
+ code: |
550
+ // features/counter/counter.component.ts
551
+ import { Component, signal, computed, effect } from '@angular/core';
552
+
553
+ @Component({
554
+ selector: 'app-counter',
555
+ standalone: true,
556
+ template: `
557
+ <div class="counter">
558
+ <p>Count: {{ count() }}</p>
559
+ <p>Double: {{ doubleCount() }}</p>
560
+ <button (click)="increment()">+</button>
561
+ <button (click)="decrement()">-</button>
562
+ <button (click)="reset()">Reset</button>
563
+ </div>
564
+ `
565
+ })
566
+ export class CounterComponent {
567
+ // Writable signal
568
+ count = signal(0);
569
+
570
+ // Computed signal (derived state)
571
+ doubleCount = computed(() => this.count() * 2);
572
+
573
+ constructor() {
574
+ // Effect for side effects (logging, localStorage, etc.)
575
+ effect(() => {
576
+ console.log(`Count changed to: ${this.count()}`);
577
+ });
578
+ }
579
+
580
+ increment() {
581
+ this.count.update(c => c + 1);
582
+ }
583
+
584
+ decrement() {
585
+ this.count.update(c => c - 1);
586
+ }
587
+
588
+ reset() {
589
+ this.count.set(0);
590
+ }
591
+ }
592
+
593
+ service:
594
+ description: "Injectable service with signals and HttpClient"
595
+ code: |
596
+ // core/services/user.service.ts
597
+ import { Injectable, inject, signal, computed } from '@angular/core';
598
+ import { HttpClient } from '@angular/common/http';
599
+ import { toSignal } from '@angular/core/rxjs-interop';
600
+ import type { User } from '@/core/models/user.model';
601
+
602
+ @Injectable({ providedIn: 'root' })
603
+ export class UserService {
604
+ private http = inject(HttpClient);
605
+
606
+ private usersSignal = signal<User[]>([]);
607
+ private loadingSignal = signal(false);
608
+ private errorSignal = signal<Error | null>(null);
609
+
610
+ // Public readonly signals
611
+ readonly users = this.usersSignal.asReadonly();
612
+ readonly isLoading = this.loadingSignal.asReadonly();
613
+ readonly error = this.errorSignal.asReadonly();
614
+
615
+ // Computed
616
+ readonly userCount = computed(() => this.usersSignal().length);
617
+
618
+ async loadUsers(): Promise<void> {
619
+ this.loadingSignal.set(true);
620
+ this.errorSignal.set(null);
621
+
622
+ try {
623
+ const users = await firstValueFrom(
624
+ this.http.get<User[]>('/api/users')
625
+ );
626
+ this.usersSignal.set(users);
627
+ } catch (error) {
628
+ this.errorSignal.set(error as Error);
629
+ } finally {
630
+ this.loadingSignal.set(false);
631
+ }
632
+ }
633
+
634
+ getUserById(id: string): User | undefined {
635
+ return this.usersSignal().find(u => u.id === id);
636
+ }
637
+ }
638
+
639
+ functionalGuard:
640
+ description: "Functional route guard"
641
+ code: |
642
+ // core/guards/auth.guard.ts
643
+ import { inject } from '@angular/core';
644
+ import { Router, type CanActivateFn } from '@angular/router';
645
+ import { AuthService } from '@/core/services/auth.service';
646
+
647
+ export const authGuard: CanActivateFn = (route, state) => {
648
+ const authService = inject(AuthService);
649
+ const router = inject(Router);
650
+
651
+ if (authService.isAuthenticated()) {
652
+ return true;
653
+ }
654
+
655
+ // Redirect to login with return URL
656
+ return router.createUrlTree(['/login'], {
657
+ queryParams: { returnUrl: state.url }
658
+ });
659
+ };
660
+
661
+ // Usage in routes
662
+ export const routes: Routes = [
663
+ {
664
+ path: 'dashboard',
665
+ loadComponent: () => import('./dashboard/dashboard.component'),
666
+ canActivate: [authGuard]
667
+ }
668
+ ];
669
+
670
+ functionalInterceptor:
671
+ description: "Functional HTTP interceptor"
672
+ code: |
673
+ // core/interceptors/auth.interceptor.ts
674
+ import { inject } from '@angular/core';
675
+ import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
676
+ import { catchError, throwError } from 'rxjs';
677
+ import { AuthService } from '@/core/services/auth.service';
678
+ import { Router } from '@angular/router';
679
+
680
+ export const authInterceptor: HttpInterceptorFn = (req, next) => {
681
+ const authService = inject(AuthService);
682
+ const router = inject(Router);
683
+
684
+ const token = authService.token();
685
+
686
+ // Add auth header if token exists
687
+ if (token) {
688
+ req = req.clone({
689
+ setHeaders: {
690
+ Authorization: `Bearer ${token}`
691
+ }
692
+ });
693
+ }
694
+
695
+ return next(req).pipe(
696
+ catchError((error: HttpErrorResponse) => {
697
+ if (error.status === 401) {
698
+ authService.logout();
699
+ router.navigate(['/login']);
700
+ }
701
+ return throwError(() => error);
702
+ })
703
+ );
704
+ };
705
+
706
+ // Register in app.config.ts
707
+ export const appConfig: ApplicationConfig = {
708
+ providers: [
709
+ provideHttpClient(withInterceptors([authInterceptor]))
710
+ ]
711
+ };
712
+
713
+ signalStore:
714
+ description: "NgRx SignalStore for feature state"
715
+ code: |
716
+ // features/products/products.store.ts
717
+ import { signalStore, withState, withComputed, withMethods, patchState } from '@ngrx/signals';
718
+ import { inject } from '@angular/core';
719
+ import { ProductService } from './products.service';
720
+ import type { Product } from '@/core/models/product.model';
721
+
722
+ interface ProductsState {
723
+ products: Product[];
724
+ selectedId: string | null;
725
+ isLoading: boolean;
726
+ error: string | null;
727
+ }
728
+
729
+ const initialState: ProductsState = {
730
+ products: [],
731
+ selectedId: null,
732
+ isLoading: false,
733
+ error: null
734
+ };
735
+
736
+ export const ProductsStore = signalStore(
737
+ withState(initialState),
738
+
739
+ withComputed((store) => ({
740
+ selectedProduct: computed(() => {
741
+ const id = store.selectedId();
742
+ return store.products().find(p => p.id === id) ?? null;
743
+ }),
744
+ productCount: computed(() => store.products().length)
745
+ })),
746
+
747
+ withMethods((store, productService = inject(ProductService)) => ({
748
+ async loadProducts() {
749
+ patchState(store, { isLoading: true, error: null });
750
+ try {
751
+ const products = await productService.getAll();
752
+ patchState(store, { products, isLoading: false });
753
+ } catch (error) {
754
+ patchState(store, { error: (error as Error).message, isLoading: false });
755
+ }
756
+ },
757
+
758
+ selectProduct(id: string) {
759
+ patchState(store, { selectedId: id });
760
+ },
761
+
762
+ clearSelection() {
763
+ patchState(store, { selectedId: null });
764
+ }
765
+ }))
766
+ );
767
+
768
+ componentTest:
769
+ description: "Component test with Angular Testing Library"
770
+ code: |
771
+ // features/users/user-card.component.spec.ts
772
+ import { render, screen } from '@testing-library/angular';
773
+ import userEvent from '@testing-library/user-event';
774
+ import { UserCardComponent } from './user-card.component';
775
+
776
+ describe('UserCardComponent', () => {
777
+ const mockUser = {
778
+ id: '1',
779
+ firstName: 'John',
780
+ lastName: 'Doe',
781
+ email: 'john@example.com',
782
+ avatar: '/avatar.png'
783
+ };
784
+
785
+ it('displays user information', async () => {
786
+ await render(UserCardComponent, {
787
+ inputs: { user: mockUser }
788
+ });
789
+
790
+ expect(screen.getByText('John Doe')).toBeInTheDocument();
791
+ expect(screen.getByText('john@example.com')).toBeInTheDocument();
792
+ });
793
+
794
+ it('emits selected event when button clicked', async () => {
795
+ const user = userEvent.setup();
796
+ const selectedSpy = jest.fn();
797
+
798
+ await render(UserCardComponent, {
799
+ inputs: { user: mockUser },
800
+ on: { selected: selectedSpy }
801
+ });
802
+
803
+ await user.click(screen.getByRole('button', { name: /select/i }));
804
+
805
+ expect(selectedSpy).toHaveBeenCalledWith('1');
806
+ });
807
+
808
+ it('applies active class when isActive is true', async () => {
809
+ await render(UserCardComponent, {
810
+ inputs: { user: mockUser, isActive: true }
811
+ });
812
+
813
+ expect(screen.getByRole('article')).toHaveClass('active');
814
+ });
815
+ });
816
+
817
+ deferBlock:
818
+ description: "@defer for lazy loading and performance"
819
+ code: |
820
+ // features/dashboard/dashboard.component.ts
821
+ @Component({
822
+ selector: 'app-dashboard',
823
+ standalone: true,
824
+ imports: [HeavyChartComponent, DataTableComponent],
825
+ template: `
826
+ <main>
827
+ <h1>Dashboard</h1>
828
+
829
+ <!-- Load chart when visible in viewport -->
830
+ @defer (on viewport) {
831
+ <app-heavy-chart [data]="chartData()" />
832
+ } @placeholder {
833
+ <div class="chart-placeholder">Loading chart...</div>
834
+ } @loading (minimum 300ms) {
835
+ <app-skeleton type="chart" />
836
+ }
837
+
838
+ <!-- Load table when user scrolls down -->
839
+ @defer (on viewport; prefetch on idle) {
840
+ <app-data-table [rows]="tableData()" />
841
+ } @placeholder {
842
+ <p>Scroll to see data table</p>
843
+ }
844
+
845
+ <!-- Load on interaction -->
846
+ @defer (on interaction) {
847
+ <app-comments [postId]="postId()" />
848
+ } @placeholder {
849
+ <button>Click to load comments</button>
850
+ }
851
+
852
+ <!-- Conditional defer -->
853
+ @defer (when showAdvanced()) {
854
+ <app-advanced-settings />
855
+ }
856
+ </main>
857
+ `
858
+ })
859
+ export class DashboardComponent {
860
+ chartData = signal<ChartData[]>([]);
861
+ tableData = signal<TableRow[]>([]);
862
+ postId = input.required<string>();
863
+ showAdvanced = signal(false);
864
+ }
865
+
866
+ # ----------------------------------------------------------------------------
867
+ # ANTI-PATTERNS
868
+ # ----------------------------------------------------------------------------
869
+ antiPatterns:
870
+ ngModulesForNewCode:
871
+ name: "Using NgModules for New Components"
872
+ description: "NgModules are legacy; use standalone components"
873
+ bad: |
874
+ // ❌ NgModule-based component
875
+ @NgModule({
876
+ declarations: [UserCardComponent],
877
+ imports: [CommonModule],
878
+ exports: [UserCardComponent]
879
+ })
880
+ export class UserModule {}
881
+
882
+ @Component({
883
+ selector: 'app-user-card',
884
+ templateUrl: './user-card.component.html'
885
+ })
886
+ export class UserCardComponent {}
887
+ good: |
888
+ // ✅ Standalone component
889
+ @Component({
890
+ selector: 'app-user-card',
891
+ standalone: true,
892
+ imports: [RouterLink],
893
+ template: `...`
894
+ })
895
+ export class UserCardComponent {}
896
+
897
+ constructorInjection:
898
+ name: "Constructor Injection Instead of inject()"
899
+ description: "Use inject() function for cleaner dependency injection"
900
+ bad: |
901
+ // ❌ Constructor injection
902
+ @Component({ ... })
903
+ export class UserListComponent {
904
+ constructor(
905
+ private userService: UserService,
906
+ private router: Router,
907
+ private route: ActivatedRoute
908
+ ) {}
909
+ }
910
+ good: |
911
+ // ✅ inject() function
912
+ @Component({ ... })
913
+ export class UserListComponent {
914
+ private userService = inject(UserService);
915
+ private router = inject(Router);
916
+ private route = inject(ActivatedRoute);
917
+ }
918
+
919
+ oldControlFlow:
920
+ name: "Using *ngIf/*ngFor Instead of @if/@for"
921
+ description: "Use new built-in control flow (@if, @for, @switch)"
922
+ bad: |
923
+ // ❌ Old structural directives
924
+ <div *ngIf="user; else loading">{{ user.name }}</div>
925
+ <ng-template #loading>Loading...</ng-template>
926
+
927
+ <li *ngFor="let item of items; trackBy: trackById">
928
+ {{ item.name }}
929
+ </li>
930
+ good: |
931
+ // ✅ New control flow
932
+ @if (user(); as u) {
933
+ <div>{{ u.name }}</div>
934
+ } @else {
935
+ <span>Loading...</span>
936
+ }
937
+
938
+ @for (item of items(); track item.id) {
939
+ <li>{{ item.name }}</li>
940
+ } @empty {
941
+ <li>No items found</li>
942
+ }
943
+
944
+ behaviorSubjectOverSignals:
945
+ name: "Using BehaviorSubject When Signals Are Better"
946
+ description: "For synchronous state, prefer signals over RxJS"
947
+ bad: |
948
+ // ❌ BehaviorSubject for simple state
949
+ @Injectable({ providedIn: 'root' })
950
+ export class CounterService {
951
+ private count$ = new BehaviorSubject(0);
952
+ readonly count = this.count$.asObservable();
953
+
954
+ increment() {
955
+ this.count$.next(this.count$.value + 1);
956
+ }
957
+ }
958
+ good: |
959
+ // ✅ Signals for synchronous state
960
+ @Injectable({ providedIn: 'root' })
961
+ export class CounterService {
962
+ private _count = signal(0);
963
+ readonly count = this._count.asReadonly();
964
+
965
+ increment() {
966
+ this._count.update(c => c + 1);
967
+ }
968
+ }
969
+
970
+ subscribeWithoutCleanup:
971
+ name: "Manual Subscribe Without Cleanup"
972
+ description: "Use takeUntilDestroyed() or async pipe instead"
973
+ bad: |
974
+ // ❌ Manual subscribe leaks memory
975
+ @Component({ ... })
976
+ export class UserComponent implements OnInit {
977
+ user: User | null = null;
978
+
979
+ ngOnInit() {
980
+ this.userService.getUser().subscribe(user => {
981
+ this.user = user;
982
+ });
983
+ }
984
+ }
985
+ good: |
986
+ // ✅ Use toSignal() or takeUntilDestroyed()
987
+ @Component({ ... })
988
+ export class UserComponent {
989
+ private userService = inject(UserService);
990
+
991
+ // Option 1: toSignal (preferred)
992
+ user = toSignal(this.userService.getUser());
993
+
994
+ // Option 2: takeUntilDestroyed
995
+ private destroyRef = inject(DestroyRef);
996
+
997
+ ngOnInit() {
998
+ this.userService.getUser().pipe(
999
+ takeUntilDestroyed(this.destroyRef)
1000
+ ).subscribe(user => {
1001
+ this.userSignal.set(user);
1002
+ });
1003
+ }
1004
+ }
1005
+
1006
+ defaultChangeDetection:
1007
+ name: "Using Default Change Detection"
1008
+ description: "OnPush with signals is more performant"
1009
+ bad: |
1010
+ // ❌ Default change detection
1011
+ @Component({
1012
+ selector: 'app-user-list',
1013
+ template: `...`
1014
+ // No changeDetection specified = Default
1015
+ })
1016
+ export class UserListComponent {
1017
+ users: User[] = [];
1018
+ }
1019
+ good: |
1020
+ // ✅ OnPush with signals
1021
+ @Component({
1022
+ selector: 'app-user-list',
1023
+ changeDetection: ChangeDetectionStrategy.OnPush,
1024
+ template: `
1025
+ @for (user of users(); track user.id) {
1026
+ <app-user-card [user]="user" />
1027
+ }
1028
+ `
1029
+ })
1030
+ export class UserListComponent {
1031
+ users = signal<User[]>([]);
1032
+ }
1033
+
1034
+ classBasedGuards:
1035
+ name: "Class-Based Guards and Resolvers"
1036
+ description: "Use functional guards and resolvers instead"
1037
+ bad: |
1038
+ // ❌ Class-based guard
1039
+ @Injectable({ providedIn: 'root' })
1040
+ export class AuthGuard implements CanActivate {
1041
+ constructor(private auth: AuthService, private router: Router) {}
1042
+
1043
+ canActivate(): boolean {
1044
+ if (this.auth.isAuthenticated()) return true;
1045
+ this.router.navigate(['/login']);
1046
+ return false;
1047
+ }
1048
+ }
1049
+ good: |
1050
+ // ✅ Functional guard
1051
+ export const authGuard: CanActivateFn = () => {
1052
+ const auth = inject(AuthService);
1053
+ const router = inject(Router);
1054
+
1055
+ return auth.isAuthenticated()
1056
+ ? true
1057
+ : router.createUrlTree(['/login']);
1058
+ };
1059
+
1060
+ templateFunctionCalls:
1061
+ name: "Function Calls in Templates"
1062
+ description: "Use computed signals instead of functions in templates"
1063
+ bad: |
1064
+ // ❌ Function called on every change detection
1065
+ <p>{{ getFullName() }}</p>
1066
+ <p>{{ calculateTotal(items) }}</p>
1067
+ good: |
1068
+ // ✅ Use computed signals
1069
+ fullName = computed(() => `${this.user().firstName} ${this.user().lastName}`);
1070
+ total = computed(() => this.items().reduce((sum, i) => sum + i.price, 0));
1071
+
1072
+ // Template
1073
+ <p>{{ fullName() }}</p>
1074
+ <p>{{ total() }}</p>
1075
+
1076
+ nestedSubscribes:
1077
+ name: "Nested Subscribes"
1078
+ description: "Use higher-order mapping operators instead"
1079
+ bad: |
1080
+ // ❌ Nested subscribes
1081
+ this.route.params.subscribe(params => {
1082
+ this.userService.getUser(params['id']).subscribe(user => {
1083
+ this.user = user;
1084
+ });
1085
+ });
1086
+ good: |
1087
+ // ✅ Use switchMap
1088
+ user = toSignal(
1089
+ this.route.params.pipe(
1090
+ switchMap(params => this.userService.getUser(params['id']))
1091
+ )
1092
+ );
1093
+
1094
+ hardcodedStringsInTemplates:
1095
+ name: "Hardcoded Strings in Templates"
1096
+ description: "Use i18n or constants for user-facing strings"
1097
+ bad: |
1098
+ // ❌ Hardcoded strings
1099
+ <button>Submit</button>
1100
+ <p>No users found</p>
1101
+ <h1>User Management</h1>
1102
+ good: |
1103
+ // ✅ Use i18n
1104
+ <button i18n>Submit</button>
1105
+ <p i18n>No users found</p>
1106
+
1107
+ // ✅ Or constants file
1108
+ <button>{{ LABELS.submit }}</button>