@hatem427/code-guard-ci 1.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 (107) hide show
  1. package/.husky/pre-commit +27 -0
  2. package/LICENSE +21 -0
  3. package/README.md +646 -0
  4. package/config/angular.config.ts +223 -0
  5. package/config/guidelines.config.ts +229 -0
  6. package/config/nextjs.config.ts +160 -0
  7. package/config/react.config.ts +330 -0
  8. package/dist/config/angular.config.d.ts +15 -0
  9. package/dist/config/angular.config.d.ts.map +1 -0
  10. package/dist/config/angular.config.js +187 -0
  11. package/dist/config/angular.config.js.map +1 -0
  12. package/dist/config/guidelines.config.d.ts +63 -0
  13. package/dist/config/guidelines.config.d.ts.map +1 -0
  14. package/dist/config/guidelines.config.js +167 -0
  15. package/dist/config/guidelines.config.js.map +1 -0
  16. package/dist/config/nextjs.config.d.ts +18 -0
  17. package/dist/config/nextjs.config.d.ts.map +1 -0
  18. package/dist/config/nextjs.config.js +133 -0
  19. package/dist/config/nextjs.config.js.map +1 -0
  20. package/dist/config/react.config.d.ts +15 -0
  21. package/dist/config/react.config.d.ts.map +1 -0
  22. package/dist/config/react.config.js +287 -0
  23. package/dist/config/react.config.js.map +1 -0
  24. package/dist/scripts/auto-fix.d.ts +16 -0
  25. package/dist/scripts/auto-fix.d.ts.map +1 -0
  26. package/dist/scripts/auto-fix.js +130 -0
  27. package/dist/scripts/auto-fix.js.map +1 -0
  28. package/dist/scripts/cli.d.ts +17 -0
  29. package/dist/scripts/cli.d.ts.map +1 -0
  30. package/dist/scripts/cli.js +255 -0
  31. package/dist/scripts/cli.js.map +1 -0
  32. package/dist/scripts/delete-bypass-logs.d.ts +17 -0
  33. package/dist/scripts/delete-bypass-logs.d.ts.map +1 -0
  34. package/dist/scripts/delete-bypass-logs.js +242 -0
  35. package/dist/scripts/delete-bypass-logs.js.map +1 -0
  36. package/dist/scripts/generate-doc.d.ts +18 -0
  37. package/dist/scripts/generate-doc.d.ts.map +1 -0
  38. package/dist/scripts/generate-doc.js +300 -0
  39. package/dist/scripts/generate-doc.js.map +1 -0
  40. package/dist/scripts/generate-pr-checklist.d.ts +20 -0
  41. package/dist/scripts/generate-pr-checklist.d.ts.map +1 -0
  42. package/dist/scripts/generate-pr-checklist.js +276 -0
  43. package/dist/scripts/generate-pr-checklist.js.map +1 -0
  44. package/dist/scripts/precommit-check.d.ts +23 -0
  45. package/dist/scripts/precommit-check.d.ts.map +1 -0
  46. package/dist/scripts/precommit-check.js +331 -0
  47. package/dist/scripts/precommit-check.js.map +1 -0
  48. package/dist/scripts/set-admin-password.d.ts +14 -0
  49. package/dist/scripts/set-admin-password.d.ts.map +1 -0
  50. package/dist/scripts/set-admin-password.js +116 -0
  51. package/dist/scripts/set-admin-password.js.map +1 -0
  52. package/dist/scripts/set-bypass-password.d.ts +11 -0
  53. package/dist/scripts/set-bypass-password.d.ts.map +1 -0
  54. package/dist/scripts/set-bypass-password.js +106 -0
  55. package/dist/scripts/set-bypass-password.js.map +1 -0
  56. package/dist/scripts/utils/auto-fixer.d.ts +28 -0
  57. package/dist/scripts/utils/auto-fixer.d.ts.map +1 -0
  58. package/dist/scripts/utils/auto-fixer.js +177 -0
  59. package/dist/scripts/utils/auto-fixer.js.map +1 -0
  60. package/dist/scripts/utils/bypass-manager.d.ts +101 -0
  61. package/dist/scripts/utils/bypass-manager.d.ts.map +1 -0
  62. package/dist/scripts/utils/bypass-manager.js +496 -0
  63. package/dist/scripts/utils/bypass-manager.js.map +1 -0
  64. package/dist/scripts/utils/code-analyzer.d.ts +34 -0
  65. package/dist/scripts/utils/code-analyzer.d.ts.map +1 -0
  66. package/dist/scripts/utils/code-analyzer.js +323 -0
  67. package/dist/scripts/utils/code-analyzer.js.map +1 -0
  68. package/dist/scripts/utils/file-checker.d.ts +93 -0
  69. package/dist/scripts/utils/file-checker.d.ts.map +1 -0
  70. package/dist/scripts/utils/file-checker.js +248 -0
  71. package/dist/scripts/utils/file-checker.js.map +1 -0
  72. package/dist/scripts/utils/logger.d.ts +26 -0
  73. package/dist/scripts/utils/logger.d.ts.map +1 -0
  74. package/dist/scripts/utils/logger.js +86 -0
  75. package/dist/scripts/utils/logger.js.map +1 -0
  76. package/dist/scripts/utils/project-detector.d.ts +34 -0
  77. package/dist/scripts/utils/project-detector.d.ts.map +1 -0
  78. package/dist/scripts/utils/project-detector.js +124 -0
  79. package/dist/scripts/utils/project-detector.js.map +1 -0
  80. package/dist/scripts/utils/rule-engine.d.ts +57 -0
  81. package/dist/scripts/utils/rule-engine.d.ts.map +1 -0
  82. package/dist/scripts/utils/rule-engine.js +158 -0
  83. package/dist/scripts/utils/rule-engine.js.map +1 -0
  84. package/dist/scripts/view-bypass-log.d.ts +13 -0
  85. package/dist/scripts/view-bypass-log.d.ts.map +1 -0
  86. package/dist/scripts/view-bypass-log.js +117 -0
  87. package/dist/scripts/view-bypass-log.js.map +1 -0
  88. package/package.json +74 -0
  89. package/scripts/auto-fix.ts +115 -0
  90. package/scripts/cli.ts +246 -0
  91. package/scripts/delete-bypass-logs.ts +253 -0
  92. package/scripts/generate-doc.ts +317 -0
  93. package/scripts/generate-pr-checklist.ts +285 -0
  94. package/scripts/precommit-check.ts +349 -0
  95. package/scripts/set-admin-password.ts +90 -0
  96. package/scripts/set-bypass-password.ts +80 -0
  97. package/scripts/utils/auto-fixer.ts +181 -0
  98. package/scripts/utils/bypass-manager.ts +566 -0
  99. package/scripts/utils/code-analyzer.ts +341 -0
  100. package/scripts/utils/file-checker.ts +253 -0
  101. package/scripts/utils/logger.ts +88 -0
  102. package/scripts/utils/project-detector.ts +115 -0
  103. package/scripts/utils/rule-engine.ts +186 -0
  104. package/scripts/view-bypass-log.ts +92 -0
  105. package/templates/feature-doc-api.md +101 -0
  106. package/templates/feature-doc-service.md +113 -0
  107. package/templates/feature-doc-ui.md +91 -0
@@ -0,0 +1,223 @@
1
+ /**
2
+ * ============================================================================
3
+ * angular.config.ts — Angular-specific coding rules
4
+ * ============================================================================
5
+ *
6
+ * Registers rules that only apply to Angular projects:
7
+ * - ngOnInit misuse
8
+ * - Deprecated APIs (::ng-deep, CommonModule, ngClass, ngStyle)
9
+ * - Function calls in templates
10
+ * - RxJS best practices (async pipe, takeUntilDestroyed, signals)
11
+ */
12
+
13
+ import { registerRules, Rule } from './guidelines.config';
14
+
15
+ const angularRules: Rule[] = [
16
+ // ── Lifecycle ──────────────────────────────────────────────────────────
17
+
18
+ {
19
+ id: 'angular-no-ngoninit',
20
+ label: 'Avoid ngOnInit for simple initialization',
21
+ description:
22
+ 'Prefer constructor injection and field initializers. Use ngOnInit only when you need DOM access or @Input values. Modern Angular recommends inject() + signals.',
23
+ severity: 'warning',
24
+ fileExtensions: ['ts'],
25
+ pattern: /ngOnInit\s*\(\s*\)/g,
26
+ applicableTo: ['angular'],
27
+ category: 'Angular Lifecycle',
28
+ },
29
+
30
+ // ── Deprecated APIs ────────────────────────────────────────────────────
31
+
32
+ {
33
+ id: 'angular-no-ng-deep',
34
+ label: 'No ::ng-deep',
35
+ description:
36
+ '::ng-deep is deprecated. Use :host, CSS custom properties, or component styling APIs instead.',
37
+ severity: 'error',
38
+ fileExtensions: ['css', 'scss', 'sass', 'less', 'ts'],
39
+ pattern: /::ng-deep/g,
40
+ applicableTo: ['angular'],
41
+ category: 'Angular Deprecated APIs',
42
+ },
43
+
44
+ {
45
+ id: 'angular-no-common-module',
46
+ label: 'No CommonModule import in standalone components',
47
+ description:
48
+ 'Standalone components should import specific directives (NgIf, NgFor, etc.) instead of the entire CommonModule. Better yet, use @if/@for control flow.',
49
+ severity: 'warning',
50
+ fileExtensions: ['ts'],
51
+ pattern: /CommonModule/g,
52
+ applicableTo: ['angular'],
53
+ category: 'Angular Deprecated APIs',
54
+ },
55
+
56
+ {
57
+ id: 'angular-no-ngclass',
58
+ label: 'No [ngClass] — use [class] binding or Tailwind',
59
+ description:
60
+ 'Prefer [class.name]="condition" binding or Tailwind dynamic classes over [ngClass]. It is cleaner and more performant.',
61
+ severity: 'warning',
62
+ fileExtensions: ['html', 'ts'],
63
+ pattern: /\[ngClass\]/g,
64
+ applicableTo: ['angular'],
65
+ category: 'Angular Deprecated APIs',
66
+ },
67
+
68
+ {
69
+ id: 'angular-no-ngstyle',
70
+ label: 'No [ngStyle] — use [style] binding or Tailwind',
71
+ description:
72
+ 'Prefer [style.property]="value" binding or Tailwind utilities over [ngStyle].',
73
+ severity: 'warning',
74
+ fileExtensions: ['html', 'ts'],
75
+ pattern: /\[ngStyle\]/g,
76
+ applicableTo: ['angular'],
77
+ category: 'Angular Deprecated APIs',
78
+ },
79
+
80
+ // ── Template best practices ────────────────────────────────────────────
81
+
82
+ {
83
+ id: 'angular-no-function-in-template',
84
+ label: 'No function calls in templates',
85
+ description:
86
+ 'Avoid calling functions in Angular templates — they run on every change detection cycle. Use pipes, computed signals, or memoized getters instead.',
87
+ severity: 'error',
88
+ fileExtensions: ['html'],
89
+ pattern: null,
90
+ customCheck: (file) => {
91
+ const violations: Array<{ line: number | null; message: string }> = [];
92
+
93
+ /**
94
+ * Regex explanation:
95
+ * Match patterns like {{ someFunc() }} or (click)="handler()" but EXCLUDE:
96
+ * - Event handlers: (click)="...", (submit)="...", on*="..."
97
+ * - Known safe pipes: | async, | date, | json, etc.
98
+ * - Structural directives: *ngIf, *ngFor, @if, @for
99
+ *
100
+ * We look for interpolation bindings {{ expr() }} and property bindings [prop]="expr()"
101
+ * that contain function calls which are NOT event bindings.
102
+ */
103
+ const interpolationRegex = /\{\{[^}]*\w+\s*\([^)]*\)[^}]*\}\}/g;
104
+ const propertyBindingRegex = /\[(?!click|submit|keyup|keydown|change|input|focus|blur)\w+\]\s*=\s*"[^"]*\w+\s*\([^)]*\)[^"]*"/g;
105
+
106
+ for (let i = 0; i < file.lines.length; i++) {
107
+ const line = file.lines[i];
108
+
109
+ // Check interpolation bindings {{ func() }}
110
+ interpolationRegex.lastIndex = 0;
111
+ if (interpolationRegex.test(line)) {
112
+ // Exclude pipe usages like {{ value | pipeName }}
113
+ const trimmed = line.trim();
114
+ if (!trimmed.includes(' | ')) {
115
+ violations.push({
116
+ line: i + 1,
117
+ message: `Function call in interpolation: "${trimmed}". Use a pipe or signal instead.`,
118
+ });
119
+ }
120
+ }
121
+
122
+ // Check property bindings [prop]="func()"
123
+ propertyBindingRegex.lastIndex = 0;
124
+ if (propertyBindingRegex.test(line)) {
125
+ violations.push({
126
+ line: i + 1,
127
+ message: `Function call in property binding: "${line.trim()}". Use a signal or memoized getter.`,
128
+ });
129
+ }
130
+ }
131
+
132
+ return violations;
133
+ },
134
+ applicableTo: ['angular'],
135
+ category: 'Angular Templates',
136
+ },
137
+
138
+ // ── RxJS best practices ────────────────────────────────────────────────
139
+
140
+ {
141
+ id: 'angular-use-async-pipe',
142
+ label: 'Use async pipe or toSignal() instead of manual subscribe',
143
+ description:
144
+ 'Manual .subscribe() in components leads to memory leaks. Use the async pipe in templates or convert to signals with toSignal().',
145
+ severity: 'warning',
146
+ fileExtensions: ['ts'],
147
+ pattern: /\.subscribe\s*\(/g,
148
+ applicableTo: ['angular'],
149
+ category: 'RxJS',
150
+ },
151
+
152
+ {
153
+ id: 'angular-use-takeuntildestroyed',
154
+ label: 'Use takeUntilDestroyed() for subscriptions',
155
+ description:
156
+ 'If you must subscribe manually, use takeUntilDestroyed() from @angular/core/rxjs-interop to auto-unsubscribe.',
157
+ severity: 'warning',
158
+ fileExtensions: ['ts'],
159
+ pattern: null,
160
+ customCheck: (file) => {
161
+ const violations: Array<{ line: number | null; message: string }> = [];
162
+
163
+ // Only check Angular component/directive/service files
164
+ if (
165
+ !file.content.includes('@Component') &&
166
+ !file.content.includes('@Directive') &&
167
+ !file.content.includes('@Injectable')
168
+ ) {
169
+ return [];
170
+ }
171
+
172
+ const hasSubscribe = /\.subscribe\s*\(/.test(file.content);
173
+ const hasTakeUntilDestroyed = file.content.includes('takeUntilDestroyed');
174
+ const hasAsyncPipe = file.content.includes('| async') || file.content.includes('toSignal');
175
+
176
+ if (hasSubscribe && !hasTakeUntilDestroyed && !hasAsyncPipe) {
177
+ violations.push({
178
+ line: null,
179
+ message:
180
+ 'Manual .subscribe() found without takeUntilDestroyed(). Add pipe(takeUntilDestroyed()) or use async pipe / toSignal().',
181
+ });
182
+ }
183
+
184
+ return violations;
185
+ },
186
+ applicableTo: ['angular'],
187
+ category: 'RxJS',
188
+ },
189
+
190
+ {
191
+ id: 'angular-prefer-signals',
192
+ label: 'Consider using Angular Signals',
193
+ description:
194
+ 'Angular 16+ supports Signals for reactive state. Consider using signal(), computed(), and effect() for simpler reactivity.',
195
+ severity: 'info',
196
+ fileExtensions: ['ts'],
197
+ pattern: null,
198
+ customCheck: (file) => {
199
+ // Only flag if the component uses BehaviorSubject but no signals
200
+ const hasBehaviorSubject = file.content.includes('BehaviorSubject');
201
+ const hasSignal = file.content.includes('signal(') || file.content.includes('computed(');
202
+
203
+ if (hasBehaviorSubject && !hasSignal) {
204
+ return [
205
+ {
206
+ line: null,
207
+ message:
208
+ 'BehaviorSubject detected. Consider migrating to Angular Signals (signal(), computed()) for simpler state management.',
209
+ },
210
+ ];
211
+ }
212
+
213
+ return [];
214
+ },
215
+ applicableTo: ['angular'],
216
+ category: 'Angular Signals',
217
+ },
218
+ ];
219
+
220
+ // Register all Angular-specific rules
221
+ registerRules('angular', angularRules);
222
+
223
+ export { angularRules };
@@ -0,0 +1,229 @@
1
+ /**
2
+ * ============================================================================
3
+ * guidelines.config.ts — Central guidelines configuration
4
+ * ============================================================================
5
+ *
6
+ * Defines the shape of a rule, severity levels, and the shared rule registry.
7
+ * Each project-type config (angular, react, nextjs) plugs into this registry.
8
+ */
9
+
10
+ import { ProjectType } from '../scripts/utils/project-detector';
11
+
12
+ // ── Severity ────────────────────────────────────────────────────────────────
13
+
14
+ export type Severity = 'error' | 'warning' | 'info';
15
+
16
+ // ── Rule definition ─────────────────────────────────────────────────────────
17
+
18
+ export interface Rule {
19
+ /** Unique identifier — used in reports and inline suppressions */
20
+ id: string;
21
+
22
+ /** Human-readable label for logs and checklists */
23
+ label: string;
24
+
25
+ /** Detailed description of what the rule checks */
26
+ description: string;
27
+
28
+ /** Severity: 'error' blocks commit, 'warning' allows but logs, 'info' is advisory */
29
+ severity: Severity;
30
+
31
+ /**
32
+ * Which file extensions this rule applies to.
33
+ * Empty array = all files.
34
+ */
35
+ fileExtensions: string[];
36
+
37
+ /**
38
+ * Regex pattern to search for inside file content.
39
+ * If set, the rule engine will scan each line for this pattern.
40
+ * `null` means the rule uses a custom checker function instead.
41
+ */
42
+ pattern: RegExp | null;
43
+
44
+ /**
45
+ * Optional custom checker function for complex rules that cannot be
46
+ * expressed as a simple regex. Receives the file info and returns
47
+ * an array of violation objects.
48
+ */
49
+ customCheck?: (file: any) => Array<{ line: number | null; message: string }>;
50
+
51
+ /**
52
+ * Which project types this rule applies to.
53
+ * Empty array = all project types.
54
+ */
55
+ applicableTo: ProjectType[];
56
+
57
+ /** Category for grouping in the PR checklist */
58
+ category: string;
59
+ }
60
+
61
+ // ── Shared rules (framework-agnostic) ───────────────────────────────────────
62
+
63
+ export const sharedRules: Rule[] = [
64
+ {
65
+ id: 'no-console-log',
66
+ label: 'No console.log',
67
+ description: 'Remove all console.log statements before committing. Use a proper logging service instead.',
68
+ severity: 'error',
69
+ fileExtensions: ['ts', 'tsx', 'js', 'jsx'],
70
+ pattern: /console\.log\s*\(/g,
71
+ applicableTo: [],
72
+ category: 'Code Quality',
73
+ },
74
+ {
75
+ id: 'no-any-type',
76
+ label: 'No `any` type',
77
+ description: 'Avoid using `any` type. Use proper typing, generics, or `unknown` instead.',
78
+ severity: 'error',
79
+ fileExtensions: ['ts', 'tsx'],
80
+ pattern: /:\s*any\b|<any>|as\s+any\b/g,
81
+ applicableTo: [],
82
+ category: 'TypeScript',
83
+ },
84
+ {
85
+ id: 'no-settimeout',
86
+ label: 'No setTimeout',
87
+ description: 'Avoid using setTimeout. Use RxJS timer(), delay(), or framework-specific alternatives.',
88
+ severity: 'warning',
89
+ fileExtensions: ['ts', 'tsx', 'js', 'jsx'],
90
+ pattern: /setTimeout\s*\(/g,
91
+ applicableTo: [],
92
+ category: 'Async Patterns',
93
+ },
94
+ {
95
+ id: 'max-component-lines',
96
+ label: 'Component size ≤ 400 lines',
97
+ description: 'Components should not exceed 400 lines. Split large components into smaller, focused ones.',
98
+ severity: 'error',
99
+ fileExtensions: ['ts', 'tsx', 'jsx'],
100
+ pattern: null,
101
+ customCheck: (file) => {
102
+ if (file.lineCount > 400) {
103
+ return [
104
+ {
105
+ line: null,
106
+ message: `File has ${file.lineCount} lines (max 400). Consider splitting into smaller components.`,
107
+ },
108
+ ];
109
+ }
110
+ return [];
111
+ },
112
+ applicableTo: [],
113
+ category: 'Architecture',
114
+ },
115
+ {
116
+ id: 'no-custom-css',
117
+ label: 'Use Tailwind instead of custom CSS',
118
+ description: 'Prefer Tailwind utility classes over writing custom CSS. Custom styles should be rare and justified.',
119
+ severity: 'warning',
120
+ fileExtensions: ['css', 'scss', 'sass', 'less'],
121
+ pattern: null,
122
+ customCheck: (file) => {
123
+ // Allow Tailwind config files and global resets
124
+ const allowedFiles = [
125
+ 'tailwind.css',
126
+ 'globals.css',
127
+ 'global.css',
128
+ 'styles.css',
129
+ 'variables.css',
130
+ 'reset.css',
131
+ ];
132
+ const basename = file.relativePath.split('/').pop() || '';
133
+ if (allowedFiles.includes(basename)) return [];
134
+
135
+ // Check for custom CSS declarations (property: value patterns)
136
+ const cssDeclarationRegex = /^\s*[a-z-]+\s*:\s*[^;]+;/gm;
137
+ const matches: Array<{ line: number | null; message: string }> = [];
138
+ const lines: string[] = file.lines;
139
+
140
+ for (let i = 0; i < lines.length; i++) {
141
+ const trimmed = lines[i].trim();
142
+ // Skip @apply (Tailwind), @import, @tailwind directives, comments, and CSS custom properties
143
+ if (
144
+ trimmed.startsWith('@apply') ||
145
+ trimmed.startsWith('@import') ||
146
+ trimmed.startsWith('@tailwind') ||
147
+ trimmed.startsWith('//') ||
148
+ trimmed.startsWith('/*') ||
149
+ trimmed.startsWith('*') ||
150
+ trimmed.startsWith('--') // CSS custom properties
151
+ ) {
152
+ continue;
153
+ }
154
+
155
+ cssDeclarationRegex.lastIndex = 0;
156
+ if (cssDeclarationRegex.test(lines[i])) {
157
+ matches.push({
158
+ line: i + 1,
159
+ message: `Custom CSS found: "${trimmed}". Prefer Tailwind utilities.`,
160
+ });
161
+ }
162
+ }
163
+
164
+ // Only report if there are a meaningful number of custom declarations
165
+ if (matches.length > 3) {
166
+ return [
167
+ {
168
+ line: null,
169
+ message: `File contains ${matches.length} custom CSS declarations. Consider using Tailwind utilities instead.`,
170
+ },
171
+ ];
172
+ }
173
+
174
+ return [];
175
+ },
176
+ applicableTo: [],
177
+ category: 'Styling',
178
+ },
179
+ ];
180
+
181
+ // ── Rule registry (populated by project-specific configs) ───────────────────
182
+
183
+ const ruleRegistry: Map<ProjectType, Rule[]> = new Map();
184
+
185
+ /**
186
+ * Register rules for a specific project type.
187
+ * Called by angular.config.ts, react.config.ts, nextjs.config.ts.
188
+ */
189
+ export function registerRules(projectType: ProjectType, rules: Rule[]): void {
190
+ const existing = ruleRegistry.get(projectType) || [];
191
+ ruleRegistry.set(projectType, [...existing, ...rules]);
192
+ }
193
+
194
+ /**
195
+ * Get all applicable rules for a detected project type.
196
+ * Merges shared rules with project-specific rules.
197
+ */
198
+ export function getRulesForProject(projectType: ProjectType): Rule[] {
199
+ const projectRules = ruleRegistry.get(projectType) || [];
200
+
201
+ // Filter shared rules: include if applicableTo is empty (all) or includes this project type
202
+ const applicableShared = sharedRules.filter(
203
+ (r) => r.applicableTo.length === 0 || r.applicableTo.includes(projectType)
204
+ );
205
+
206
+ return [...applicableShared, ...projectRules];
207
+ }
208
+
209
+ /**
210
+ * Get ALL rules across all project types (used for PR checklist generation).
211
+ */
212
+ export function getAllRules(): Rule[] {
213
+ const allProjectRules: Rule[] = [];
214
+ for (const rules of ruleRegistry.values()) {
215
+ allProjectRules.push(...rules);
216
+ }
217
+
218
+ // Deduplicate by rule id
219
+ const seen = new Set<string>();
220
+ const deduped: Rule[] = [];
221
+ for (const rule of [...sharedRules, ...allProjectRules]) {
222
+ if (!seen.has(rule.id)) {
223
+ seen.add(rule.id);
224
+ deduped.push(rule);
225
+ }
226
+ }
227
+
228
+ return deduped;
229
+ }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * ============================================================================
3
+ * nextjs.config.ts — Next.js-specific coding rules
4
+ * ============================================================================
5
+ *
6
+ * Registers rules that only apply to Next.js projects:
7
+ * - App Router patterns
8
+ * - Server/Client component boundaries
9
+ * - Image optimization
10
+ * - API route conventions
11
+ *
12
+ * Note: Next.js projects also inherit React rules automatically
13
+ * because the React config registers rules for 'nextjs' too.
14
+ */
15
+
16
+ import { registerRules, Rule } from './guidelines.config';
17
+
18
+ const nextjsRules: Rule[] = [
19
+ // ── App Router ─────────────────────────────────────────────────────────
20
+
21
+ {
22
+ id: 'nextjs-use-client-directive',
23
+ label: 'Verify "use client" / "use server" directives',
24
+ description:
25
+ 'Components using hooks, event handlers, or browser APIs must have "use client" at the top. Server components must not have it.',
26
+ severity: 'warning',
27
+ fileExtensions: ['tsx', 'jsx', 'ts', 'js'],
28
+ pattern: null,
29
+ customCheck: (file) => {
30
+ const violations: Array<{ line: number | null; message: string }> = [];
31
+
32
+ const hasUseClient = file.content.includes("'use client'") || file.content.includes('"use client"');
33
+ const hasHooks = /use(State|Effect|Context|Reducer|Callback|Memo|Ref)\s*\(/.test(file.content);
34
+ const hasEventHandlers = /on(Click|Change|Submit|KeyDown|KeyUp|Focus|Blur)\s*=/.test(file.content);
35
+
36
+ // If file uses hooks or event handlers but lacks "use client"
37
+ if ((hasHooks || hasEventHandlers) && !hasUseClient) {
38
+ // Only flag if this looks like a component file (not a utility/hook file)
39
+ const isComponent = /export\s+(default\s+)?function\s+[A-Z]/.test(file.content) ||
40
+ /const\s+[A-Z]\w+\s*[:=]/.test(file.content);
41
+ if (isComponent) {
42
+ violations.push({
43
+ line: 1,
44
+ message: 'Component uses hooks/events but missing "use client" directive at the top.',
45
+ });
46
+ }
47
+ }
48
+
49
+ return violations;
50
+ },
51
+ applicableTo: ['nextjs'],
52
+ category: 'Next.js App Router',
53
+ },
54
+
55
+ // ── Image optimization ─────────────────────────────────────────────────
56
+
57
+ {
58
+ id: 'nextjs-use-image-component',
59
+ label: 'Use next/image instead of <img>',
60
+ description:
61
+ 'Always use the Next.js <Image> component for automatic optimization, lazy loading, and proper sizing.',
62
+ severity: 'warning',
63
+ fileExtensions: ['tsx', 'jsx'],
64
+ pattern: /<img\s/g,
65
+ applicableTo: ['nextjs'],
66
+ category: 'Next.js Performance',
67
+ },
68
+
69
+ // ── Link component ────────────────────────────────────────────────────
70
+
71
+ {
72
+ id: 'nextjs-use-link-component',
73
+ label: 'Use next/link instead of <a>',
74
+ description:
75
+ 'Use the Next.js <Link> component for client-side navigation. Avoid plain <a> tags for internal links.',
76
+ severity: 'warning',
77
+ fileExtensions: ['tsx', 'jsx'],
78
+ pattern: /<a\s+href\s*=\s*["']\/(?!\/)/g,
79
+ applicableTo: ['nextjs'],
80
+ category: 'Next.js Navigation',
81
+ },
82
+
83
+ // ── API routes ─────────────────────────────────────────────────────────
84
+
85
+ {
86
+ id: 'nextjs-api-error-handling',
87
+ label: 'API routes must have error handling',
88
+ description:
89
+ 'All API route handlers (GET, POST, etc.) must wrap logic in try/catch and return proper error responses.',
90
+ severity: 'warning',
91
+ fileExtensions: ['ts', 'js'],
92
+ pattern: null,
93
+ customCheck: (file) => {
94
+ const violations: Array<{ line: number | null; message: string }> = [];
95
+
96
+ // Only check files in api/ or route.ts/route.js paths
97
+ if (
98
+ !file.relativePath.includes('/api/') &&
99
+ !file.relativePath.includes('route.ts') &&
100
+ !file.relativePath.includes('route.js')
101
+ ) {
102
+ return [];
103
+ }
104
+
105
+ const hasExportedHandler = /export\s+(async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH)/.test(file.content);
106
+ const hasTryCatch = file.content.includes('try {') || file.content.includes('try{');
107
+
108
+ if (hasExportedHandler && !hasTryCatch) {
109
+ violations.push({
110
+ line: null,
111
+ message: 'API route handler missing try/catch error handling. Wrap logic in try/catch.',
112
+ });
113
+ }
114
+
115
+ return violations;
116
+ },
117
+ applicableTo: ['nextjs'],
118
+ category: 'Next.js API Routes',
119
+ },
120
+
121
+ // ── Metadata ──────────────────────────────────────────────────────────
122
+
123
+ {
124
+ id: 'nextjs-page-metadata',
125
+ label: 'Pages should export metadata',
126
+ description:
127
+ 'Page components should export a metadata object or generateMetadata function for SEO.',
128
+ severity: 'info',
129
+ fileExtensions: ['tsx', 'ts'],
130
+ pattern: null,
131
+ customCheck: (file) => {
132
+ const violations: Array<{ line: number | null; message: string }> = [];
133
+
134
+ // Only check page.tsx files
135
+ const basename = file.relativePath.split('/').pop() || '';
136
+ if (basename !== 'page.tsx' && basename !== 'page.ts') return [];
137
+
138
+ const hasMetadata =
139
+ file.content.includes('export const metadata') ||
140
+ file.content.includes('export function generateMetadata') ||
141
+ file.content.includes('export async function generateMetadata');
142
+
143
+ if (!hasMetadata) {
144
+ violations.push({
145
+ line: null,
146
+ message: 'Page is missing metadata export. Add `export const metadata` or `export function generateMetadata`.',
147
+ });
148
+ }
149
+
150
+ return violations;
151
+ },
152
+ applicableTo: ['nextjs'],
153
+ category: 'Next.js SEO',
154
+ },
155
+ ];
156
+
157
+ // Register all Next.js-specific rules
158
+ registerRules('nextjs', nextjsRules);
159
+
160
+ export { nextjsRules };