@hatem427/code-guard-ci 2.2.8 → 3.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 (73) hide show
  1. package/config/angular.config.ts +468 -27
  2. package/config/guidelines.config.ts +130 -5
  3. package/config/nextjs.config.ts +284 -11
  4. package/config/react.config.ts +440 -16
  5. package/dist/config/angular.config.d.ts.map +1 -1
  6. package/dist/config/angular.config.js +468 -26
  7. package/dist/config/angular.config.js.map +1 -1
  8. package/dist/config/guidelines.config.d.ts.map +1 -1
  9. package/dist/config/guidelines.config.js +127 -5
  10. package/dist/config/guidelines.config.js.map +1 -1
  11. package/dist/config/nextjs.config.d.ts.map +1 -1
  12. package/dist/config/nextjs.config.js +284 -11
  13. package/dist/config/nextjs.config.js.map +1 -1
  14. package/dist/config/react.config.d.ts.map +1 -1
  15. package/dist/config/react.config.js +440 -16
  16. package/dist/config/react.config.js.map +1 -1
  17. package/dist/scripts/config-generators/ai-config-generator.d.ts.map +1 -1
  18. package/dist/scripts/config-generators/ai-config-generator.js +9 -71
  19. package/dist/scripts/config-generators/ai-config-generator.js.map +1 -1
  20. package/dist/scripts/config-generators/eslint-generator.d.ts.map +1 -1
  21. package/dist/scripts/config-generators/eslint-generator.js +517 -13
  22. package/dist/scripts/config-generators/eslint-generator.js.map +1 -1
  23. package/dist/scripts/config-generators/frameworks/angular.d.ts +6 -0
  24. package/dist/scripts/config-generators/frameworks/angular.d.ts.map +1 -0
  25. package/dist/scripts/config-generators/frameworks/angular.js +81 -0
  26. package/dist/scripts/config-generators/frameworks/angular.js.map +1 -0
  27. package/dist/scripts/config-generators/frameworks/general.d.ts +6 -0
  28. package/dist/scripts/config-generators/frameworks/general.d.ts.map +1 -0
  29. package/dist/scripts/config-generators/frameworks/general.js +15 -0
  30. package/dist/scripts/config-generators/frameworks/general.js.map +1 -0
  31. package/dist/scripts/config-generators/frameworks/index.d.ts +17 -0
  32. package/dist/scripts/config-generators/frameworks/index.d.ts.map +1 -0
  33. package/dist/scripts/config-generators/frameworks/index.js +28 -0
  34. package/dist/scripts/config-generators/frameworks/index.js.map +1 -0
  35. package/dist/scripts/config-generators/frameworks/nextjs.d.ts +6 -0
  36. package/dist/scripts/config-generators/frameworks/nextjs.d.ts.map +1 -0
  37. package/dist/scripts/config-generators/frameworks/nextjs.js +115 -0
  38. package/dist/scripts/config-generators/frameworks/nextjs.js.map +1 -0
  39. package/dist/scripts/config-generators/frameworks/node.d.ts +6 -0
  40. package/dist/scripts/config-generators/frameworks/node.d.ts.map +1 -0
  41. package/dist/scripts/config-generators/frameworks/node.js +19 -0
  42. package/dist/scripts/config-generators/frameworks/node.js.map +1 -0
  43. package/dist/scripts/config-generators/frameworks/nuxt.d.ts +6 -0
  44. package/dist/scripts/config-generators/frameworks/nuxt.d.ts.map +1 -0
  45. package/dist/scripts/config-generators/frameworks/nuxt.js +18 -0
  46. package/dist/scripts/config-generators/frameworks/nuxt.js.map +1 -0
  47. package/dist/scripts/config-generators/frameworks/react.d.ts +6 -0
  48. package/dist/scripts/config-generators/frameworks/react.d.ts.map +1 -0
  49. package/dist/scripts/config-generators/frameworks/react.js +117 -0
  50. package/dist/scripts/config-generators/frameworks/react.js.map +1 -0
  51. package/dist/scripts/config-generators/frameworks/svelte.d.ts +6 -0
  52. package/dist/scripts/config-generators/frameworks/svelte.d.ts.map +1 -0
  53. package/dist/scripts/config-generators/frameworks/svelte.js +17 -0
  54. package/dist/scripts/config-generators/frameworks/svelte.js.map +1 -0
  55. package/dist/scripts/config-generators/frameworks/vue.d.ts +6 -0
  56. package/dist/scripts/config-generators/frameworks/vue.d.ts.map +1 -0
  57. package/dist/scripts/config-generators/frameworks/vue.js +19 -0
  58. package/dist/scripts/config-generators/frameworks/vue.js.map +1 -0
  59. package/dist/scripts/utils/report-generator.js +17 -5
  60. package/dist/scripts/utils/report-generator.js.map +1 -1
  61. package/package.json +1 -1
  62. package/scripts/config-generators/ai-config-generator.ts +19 -78
  63. package/scripts/config-generators/eslint-generator.ts +511 -7
  64. package/scripts/config-generators/frameworks/angular.ts +78 -0
  65. package/scripts/config-generators/frameworks/general.ts +12 -0
  66. package/scripts/config-generators/frameworks/index.ts +17 -0
  67. package/scripts/config-generators/frameworks/nextjs.ts +112 -0
  68. package/scripts/config-generators/frameworks/node.ts +16 -0
  69. package/scripts/config-generators/frameworks/nuxt.ts +15 -0
  70. package/scripts/config-generators/frameworks/react.ts +114 -0
  71. package/scripts/config-generators/frameworks/svelte.ts +14 -0
  72. package/scripts/config-generators/frameworks/vue.ts +16 -0
  73. package/scripts/utils/report-generator.ts +19 -5
@@ -16,6 +16,21 @@ import { registerRules, Rule } from './guidelines.config';
16
16
  const reactRules: Rule[] = [
17
17
  // ── NEW: React 19+ Features ────────────────────────────────────────────
18
18
 
19
+ // ─────────────────────────────────────────
20
+ // RULE: react-use-hook
21
+ // ROLE: Recommend React 19 use() hook for promise/context unwrapping
22
+ // PURPOSE: use() replaces .then() chains and useContext() with a
23
+ // single API that works in both Server and Client Components.
24
+ // It suspends the component until the promise resolves.
25
+ // EXAMPLE:
26
+ // WRONG:
27
+ // const data = await fetchData().then(res => res.json());
28
+ // const theme = useContext(ThemeContext);
29
+ // RIGHT:
30
+ // import { use } from 'react';
31
+ // const data = use(fetchData());
32
+ // const theme = use(ThemeContext);
33
+ // ─────────────────────────────────────────
19
34
  {
20
35
  id: 'react-use-hook',
21
36
  label: 'Use use() hook for unwrapping promises and context',
@@ -27,13 +42,12 @@ const reactRules: Rule[] = [
27
42
  customCheck: (file) => {
28
43
  const violations: Array<{ line: number | null; message: string }> = [];
29
44
 
30
- // Flag promise handling that could use use()
31
45
  if (/\.then\s*\(|async\s*\(\s*\).*=>.*=>/.test(file.content) &&
32
46
  !file.content.includes('use(')) {
33
47
  violations.push({
34
48
  line: null,
35
49
  message:
36
- 'Found promise handling. Use React 19 use() hook to unwrap promises directly.\n\nWhy: use() is cleaner than .then() for unwrapping server component promises.\n\n- const data = await fetchData().then(res => res.json());\n\n+ import { use } from "react";\n+ const data = use(fetchData());\n+ // Or for context\n+ const theme = use(ThemeContext);',
50
+ 'Found promise handling. Use React 19 use() hook to unwrap promises directly.',
37
51
  });
38
52
  }
39
53
  return violations;
@@ -42,6 +56,26 @@ const reactRules: Rule[] = [
42
56
  category: 'React 19',
43
57
  },
44
58
 
59
+ // ─────────────────────────────────────────
60
+ // RULE: react-use-action-state
61
+ // ROLE: Enforce React 19 form handling pattern
62
+ // PURPOSE: useActionState() eliminates manual pending/error state
63
+ // management for forms. It handles submission state, errors,
64
+ // and progressive enhancement automatically.
65
+ // EXAMPLE:
66
+ // WRONG:
67
+ // const [pending, setPending] = useState(false);
68
+ // const handleSubmit = async (e) => {
69
+ // setPending(true);
70
+ // await submitForm();
71
+ // setPending(false);
72
+ // };
73
+ // RIGHT:
74
+ // const [state, formAction, isPending] = useActionState(submit, initialState);
75
+ // <form action={formAction}>
76
+ // <button disabled={isPending}>{isPending ? 'Loading...' : 'Submit'}</button>
77
+ // </form>
78
+ // ─────────────────────────────────────────
45
79
  {
46
80
  id: 'react-use-action-state',
47
81
  label: 'Use useActionState() for form handling',
@@ -53,14 +87,13 @@ const reactRules: Rule[] = [
53
87
  customCheck: (file) => {
54
88
  const violations: Array<{ line: number | null; message: string }> = [];
55
89
 
56
- // Flag traditional form handling in client components
57
90
  if (/use\(["']use client['"], \{.*\}|<form\s+onSubmit|useState.*pending|useState.*loading/.test(file.content) &&
58
91
  !file.content.includes('useActionState') &&
59
92
  file.content.includes("'use client'")) {
60
93
  violations.push({
61
94
  line: null,
62
95
  message:
63
- 'Form with manual state management detected. Use useActionState() for simpler form handling in React 19.\n\nWhy: useActionState() handles pending states, actions, and errors automatically.\n\n- const [pending, setPending] = useState(false);\n- const handleSubmit = async (e) => {\n- setPending(true);\n- await submitForm();\n- setPending(false);\n- };\n\n+ const [state, formAction, isPending] = useActionState(submit, initialState);\n+ <form action={formAction}>\n+ <button disabled={isPending}>{isPending ? "Loading..." : "Submit"}</button>',
96
+ 'Form with manual state management detected. Use useActionState() for simpler form handling in React 19.',
64
97
  });
65
98
  }
66
99
  return violations;
@@ -69,6 +102,24 @@ const reactRules: Rule[] = [
69
102
  category: 'React 19',
70
103
  },
71
104
 
105
+ // ─────────────────────────────────────────
106
+ // RULE: react-use-transition
107
+ // ROLE: Recommend non-blocking UI updates for expensive operations
108
+ // PURPOSE: useTransition() marks state updates as low-priority so the
109
+ // UI stays responsive during expensive re-renders (filtering,
110
+ // sorting, searching large datasets).
111
+ // EXAMPLE:
112
+ // WRONG:
113
+ // const [query, setQuery] = useState('');
114
+ // const handleChange = (e) => setQuery(e.target.value);
115
+ // // UI freezes during expensive filtering
116
+ // RIGHT:
117
+ // const [isPending, startTransition] = useTransition();
118
+ // const handleChange = (e) => {
119
+ // startTransition(() => setQuery(e.target.value));
120
+ // };
121
+ // // UI stays responsive, shows pending state
122
+ // ─────────────────────────────────────────
72
123
  {
73
124
  id: 'react-use-transition',
74
125
  label: 'Use useTransition() for non-blocking updates',
@@ -80,14 +131,13 @@ const reactRules: Rule[] = [
80
131
  customCheck: (file) => {
81
132
  const violations: Array<{ line: number | null; message: string }> = [];
82
133
 
83
- // Flag expensive state updates without useTransition
84
134
  if (/filter|search|sort|debounce/.test(file.content) &&
85
135
  /useState\s*\(/.test(file.content) &&
86
136
  !file.content.includes('useTransition')) {
87
137
  violations.push({
88
138
  line: null,
89
139
  message:
90
- 'Found filter/search logic. Use useTransition() to keep UI responsive during expensive updates.\n\nWhy: useTransition marks updates as non-blocking (lower priority), preventing UI freeze.\n\n- const [query, setQuery] = useState("");\n- const handleChange = (e) => setQuery(e.target.value);\n\n+ const [isPending, startTransition] = useTransition();\n+ const handleChange = (e) => {\n+ startTransition(() => setQuery(e.target.value));\n+ };',
140
+ 'Found filter/search logic. Use useTransition() to keep UI responsive during expensive updates.',
91
141
  });
92
142
  }
93
143
  return violations;
@@ -96,6 +146,28 @@ const reactRules: Rule[] = [
96
146
  category: 'React 19',
97
147
  },
98
148
 
149
+ // ─────────────────────────────────────────
150
+ // RULE: react-use-optimistic
151
+ // ROLE: Recommend optimistic UI updates for async actions
152
+ // PURPOSE: useOptimistic() shows instant feedback to the user while
153
+ // the server processes the request. If the server fails, the
154
+ // state automatically rolls back.
155
+ // EXAMPLE:
156
+ // WRONG:
157
+ // const handleLike = async () => {
158
+ // await api.like(postId); // User waits...
159
+ // refetch();
160
+ // };
161
+ // RIGHT:
162
+ // const [optimisticLikes, addOptimistic] = useOptimistic(
163
+ // likes,
164
+ // (state, newLike) => [...state, newLike]
165
+ // );
166
+ // const handleLike = async () => {
167
+ // addOptimistic(newLike); // Instant feedback
168
+ // await api.like(postId);
169
+ // };
170
+ // ─────────────────────────────────────────
99
171
  {
100
172
  id: 'react-use-optimistic',
101
173
  label: 'Use useOptimistic() for optimistic updates',
@@ -107,7 +179,6 @@ const reactRules: Rule[] = [
107
179
  customCheck: (file) => {
108
180
  const violations: Array<{ line: number | null; message: string }> = [];
109
181
 
110
- // Flag async form handlers without useOptimistic
111
182
  const hasAsyncHandler = /const\s+handle\w+\s*=\s*async\s*\(|async\s+function\s+handle/i.test(file.content);
112
183
  const hasOptimistic = /useOptimistic/i.test(file.content);
113
184
 
@@ -116,7 +187,7 @@ const reactRules: Rule[] = [
116
187
  if (/const\s+handle\w+\s*=\s*async|async\s+function\s+handle/i.test(file.lines[i])) {
117
188
  violations.push({
118
189
  line: i + 1,
119
- message: `Async form handler found. Use useOptimistic() for instant UI feedback:\n\nExample:\nconst [optimisticItems, addOptimistic] = useOptimistic(items, (state, newItem) => [...state, newItem]);\n\nThen: addOptimistic(newItem);`,
190
+ message: `Async form handler found. Use useOptimistic() for instant UI feedback.`,
120
191
  });
121
192
  break;
122
193
  }
@@ -131,6 +202,27 @@ const reactRules: Rule[] = [
131
202
 
132
203
  // ── Server Components (Next.js 13+, React Canary) ────────────────────
133
204
 
205
+ // ─────────────────────────────────────────
206
+ // RULE: react-server-functions
207
+ // ROLE: Recommend "use server" directive for secure server logic
208
+ // PURPOSE: Server Functions run entirely on the server, enabling
209
+ // direct database access, secret handling, and mutations
210
+ // without creating API routes.
211
+ // EXAMPLE:
212
+ // WRONG:
213
+ // // pages/api/user.ts (API route approach)
214
+ // export async function POST(req) {
215
+ // const data = await req.json();
216
+ // await db.users.create(data);
217
+ // }
218
+ // RIGHT:
219
+ // // actions/user.ts
220
+ // 'use server';
221
+ // export async function createUser(formData: FormData) {
222
+ // await db.users.create(Object.fromEntries(formData));
223
+ // revalidatePath('/users');
224
+ // }
225
+ // ─────────────────────────────────────────
134
226
  {
135
227
  id: 'react-server-functions',
136
228
  label: 'Use Server Functions with "use server" directive',
@@ -143,6 +235,24 @@ const reactRules: Rule[] = [
143
235
  category: 'React Server Functions',
144
236
  },
145
237
 
238
+ // ─────────────────────────────────────────
239
+ // RULE: react-server-client-boundary
240
+ // ROLE: Validate "use client" boundary placement
241
+ // PURPOSE: Unnecessarily marking components as "use client" prevents
242
+ // server-side rendering optimizations and increases the
243
+ // JavaScript bundle sent to the browser.
244
+ // EXAMPLE:
245
+ // WRONG:
246
+ // 'use client'; // No hooks, no interactivity!
247
+ // export default function StaticCard({ title }) {
248
+ // return <div>{title}</div>;
249
+ // }
250
+ // RIGHT:
251
+ // // No directive needed — Server Component by default
252
+ // export default function StaticCard({ title }) {
253
+ // return <div>{title}</div>;
254
+ // }
255
+ // ─────────────────────────────────────────
146
256
  {
147
257
  id: 'react-server-client-boundary',
148
258
  label: 'Verify "use client" boundaries',
@@ -158,7 +268,6 @@ const reactRules: Rule[] = [
158
268
  const hasHooks = /use(State|Effect|Context|Reducer|Callback|Memo|Ref|Transition|OptimisticOptimistic)\s*\(/.test(file.content);
159
269
  const hasExport = /export\s+(default\s+)?function|export\s+(default\s+)?const/.test(file.content);
160
270
 
161
- // If no hooks but has "use client", might be misplaced
162
271
  if (hasUseClient && !hasHooks && hasExport) {
163
272
  violations.push({
164
273
  line: 1,
@@ -174,6 +283,26 @@ const reactRules: Rule[] = [
174
283
 
175
284
  // ── Hooks ──────────────────────────────────────────────────────────────
176
285
 
286
+ // ─────────────────────────────────────────
287
+ // RULE: react-no-useeffect-no-deps
288
+ // ROLE: Block useEffect without dependency array
289
+ // PURPOSE: Missing dependency array causes useEffect to run after
290
+ // EVERY render, leading to infinite loops, stale closures,
291
+ // and severe performance issues.
292
+ // EXAMPLE:
293
+ // WRONG:
294
+ // useEffect(() => {
295
+ // fetchData();
296
+ // }); // Runs on every render!
297
+ // RIGHT:
298
+ // useEffect(() => {
299
+ // fetchData();
300
+ // }, []); // Runs once on mount
301
+ // // With dependencies:
302
+ // useEffect(() => {
303
+ // fetchData(id);
304
+ // }, [id]); // Runs when id changes
305
+ // ─────────────────────────────────────────
177
306
  {
178
307
  id: 'react-no-useeffect-no-deps',
179
308
  label: 'useEffect must have dependency array',
@@ -189,9 +318,7 @@ const reactRules: Rule[] = [
189
318
  for (let i = 0; i < lines.length; i++) {
190
319
  const line = lines[i];
191
320
 
192
- // Detect useEffect( without a closing bracket on subsequent lines containing dependency array
193
321
  if (/useEffect\s*\(\s*\(\s*\)\s*=>\s*\{/.test(line)) {
194
- // Look ahead for the closing of useEffect — find , [] or , [deps])
195
322
  let foundDeps = false;
196
323
  let parenDepth = 0;
197
324
  for (let j = i; j < Math.min(i + 50, lines.length); j++) {
@@ -199,7 +326,6 @@ const reactRules: Rule[] = [
199
326
  if (ch === '(') parenDepth++;
200
327
  if (ch === ')') parenDepth--;
201
328
  }
202
- // Check if the dependency array is present
203
329
  if (/\]\s*\)\s*;?\s*$/.test(lines[j]) || /,\s*\[/.test(lines[j])) {
204
330
  foundDeps = true;
205
331
  break;
@@ -210,7 +336,7 @@ const reactRules: Rule[] = [
210
336
  if (!foundDeps) {
211
337
  violations.push({
212
338
  line: i + 1,
213
- message: 'useEffect without dependency array detected. Always specify dependencies.💡 How to fix: Missing dependency array causes infinite re-renders or stale closures.\n\n- useEffect(() => {\n- fetchData();\n- });\n\n+ useEffect(() => {\n+ fetchData();\n+ }, []);\n+\n+ // With dependencies\n+ useEffect(() => {\n+ fetchData(id);\n+ }, [id]);',
339
+ message: 'useEffect without dependency array detected. Always specify dependencies.',
214
340
  });
215
341
  }
216
342
  }
@@ -222,6 +348,18 @@ const reactRules: Rule[] = [
222
348
  category: 'React Hooks',
223
349
  },
224
350
 
351
+ // ─────────────────────────────────────────
352
+ // RULE: react-no-inline-styles
353
+ // ROLE: Block inline style objects in JSX
354
+ // PURPOSE: Inline style={{ }} creates a new object on every render,
355
+ // preventing React's reconciliation from skipping unchanged
356
+ // elements. Use Tailwind or CSS modules instead.
357
+ // EXAMPLE:
358
+ // WRONG:
359
+ // <div style={{ color: 'red', fontSize: '1rem' }}>Hello</div>
360
+ // RIGHT:
361
+ // <div className="text-red-500 text-base">Hello</div>
362
+ // ─────────────────────────────────────────
225
363
  {
226
364
  id: 'react-no-inline-styles',
227
365
  label: 'No inline style objects',
@@ -234,6 +372,21 @@ const reactRules: Rule[] = [
234
372
  category: 'Styling',
235
373
  },
236
374
 
375
+ // ─────────────────────────────────────────
376
+ // RULE: react-no-direct-dom
377
+ // ROLE: Block direct DOM manipulation in React components
378
+ // PURPOSE: React maintains a virtual DOM — direct DOM manipulation
379
+ // bypasses React's reconciliation and causes state
380
+ // inconsistencies, hydration errors, and bugs.
381
+ // EXAMPLE:
382
+ // WRONG:
383
+ // document.getElementById('input').value = 'hello';
384
+ // document.querySelector('.modal').classList.add('open');
385
+ // RIGHT:
386
+ // const inputRef = useRef<HTMLInputElement>(null);
387
+ // const [isOpen, setIsOpen] = useState(false);
388
+ // inputRef.current!.value = 'hello';
389
+ // ─────────────────────────────────────────
237
390
  {
238
391
  id: 'react-no-direct-dom',
239
392
  label: 'No direct DOM manipulation',
@@ -248,6 +401,20 @@ const reactRules: Rule[] = [
248
401
 
249
402
  // ── Component patterns ─────────────────────────────────────────────────
250
403
 
404
+ // ─────────────────────────────────────────
405
+ // RULE: react-no-react-fc
406
+ // ROLE: Block deprecated React.FC type
407
+ // PURPOSE: React.FC implicitly includes children, has issues with
408
+ // generics, and doesn't work well with defaultProps. Direct
409
+ // prop typing is cleaner and more explicit.
410
+ // EXAMPLE:
411
+ // WRONG:
412
+ // const UserCard: React.FC<Props> = ({ name }) => { ... };
413
+ // RIGHT:
414
+ // function UserCard({ name }: Props): React.ReactNode { ... }
415
+ // // Or:
416
+ // const UserCard = ({ name }: Props) => { ... };
417
+ // ─────────────────────────────────────────
251
418
  {
252
419
  id: 'react-no-react-fc',
253
420
  label: 'Do not use React.FC',
@@ -260,6 +427,17 @@ const reactRules: Rule[] = [
260
427
  category: 'TypeScript',
261
428
  },
262
429
 
430
+ // ─────────────────────────────────────────
431
+ // RULE: react-explicit-return-types
432
+ // ROLE: Recommend explicit return types for component functions
433
+ // PURPOSE: Explicit return types catch accidental non-JSX returns,
434
+ // prevent undefined leaks, and improve code documentation.
435
+ // EXAMPLE:
436
+ // WRONG:
437
+ // export function UserCard(props: Props) { ... }
438
+ // RIGHT:
439
+ // export function UserCard(props: Props): React.ReactNode { ... }
440
+ // ─────────────────────────────────────────
263
441
  {
264
442
  id: 'react-explicit-return-types',
265
443
  label: 'Add explicit return types for components',
@@ -272,6 +450,18 @@ const reactRules: Rule[] = [
272
450
  category: 'TypeScript',
273
451
  },
274
452
 
453
+ // ─────────────────────────────────────────
454
+ // RULE: react-no-prop-spreading
455
+ // ROLE: Discourage prop spreading for API clarity
456
+ // PURPOSE: Spreading props ({...props}) hides the component API,
457
+ // passes unintended props, and breaks TypeScript strictness.
458
+ // Explicitly passing required props is safer and self-documenting.
459
+ // EXAMPLE:
460
+ // WRONG:
461
+ // <Button {...props} />
462
+ // RIGHT:
463
+ // <Button onClick={props.onClick} disabled={props.disabled} label={props.label} />
464
+ // ─────────────────────────────────────────
275
465
  {
276
466
  id: 'react-no-prop-spreading',
277
467
  label: 'Avoid prop spreading ({...props})',
@@ -289,6 +479,20 @@ const reactRules: Rule[] = [
289
479
  // NOTE: 'react-no-dangerously-set-html' removed — covered by ESLint 'react/no-danger' rule
290
480
  // NOTE: 'no-eval' removed — covered by ESLint 'no-eval' rule
291
481
 
482
+ // ─────────────────────────────────────────
483
+ // RULE: no-hardcoded-credentials
484
+ // ROLE: Block hardcoded secrets in source code
485
+ // PURPOSE: Hardcoded API keys, passwords, and tokens get committed to
486
+ // git, leaked in public repos, and exposed in client bundles.
487
+ // Always use environment variables (.env).
488
+ // EXAMPLE:
489
+ // WRONG:
490
+ // const apiKey = 'sk-1234567890abcdef';
491
+ // const password = 'admin123';
492
+ // RIGHT:
493
+ // const apiKey = process.env.API_KEY;
494
+ // const password = process.env.ADMIN_PASSWORD;
495
+ // ─────────────────────────────────────────
292
496
  {
293
497
  id: 'no-hardcoded-credentials',
294
498
  label: 'No hardcoded credentials',
@@ -327,6 +531,20 @@ const reactRules: Rule[] = [
327
531
  category: 'Security',
328
532
  },
329
533
 
534
+ // ─────────────────────────────────────────
535
+ // RULE: require-https
536
+ // ROLE: Block insecure HTTP URLs in source code
537
+ // PURPOSE: HTTP transmits data in plain text — passwords, tokens, and
538
+ // user data can be intercepted. HTTPS is mandatory for all
539
+ // production API calls and external resources.
540
+ // EXAMPLE:
541
+ // WRONG:
542
+ // fetch('http://api.example.com/users');
543
+ // RIGHT:
544
+ // fetch('https://api.example.com/users');
545
+ // // localhost is exempt:
546
+ // fetch('http://localhost:3000/api');
547
+ // ─────────────────────────────────────────
330
548
  {
331
549
  id: 'require-https',
332
550
  label: 'Require HTTPS URLs',
@@ -341,6 +559,19 @@ const reactRules: Rule[] = [
341
559
 
342
560
  // ── Performance Rules ──────────────────────────────────────────────────
343
561
 
562
+ // ─────────────────────────────────────────
563
+ // RULE: react-no-anonymous-functions-in-render
564
+ // ROLE: Block anonymous functions in JSX event handlers
565
+ // PURPOSE: Arrow functions in props create a new function reference on
566
+ // every render, defeating React.memo and causing child
567
+ // components to re-render unnecessarily.
568
+ // EXAMPLE:
569
+ // WRONG:
570
+ // <Button onClick={() => handleClick(id)} />
571
+ // RIGHT:
572
+ // const handleButtonClick = useCallback(() => handleClick(id), [id]);
573
+ // <Button onClick={handleButtonClick} />
574
+ // ─────────────────────────────────────────
344
575
  {
345
576
  id: 'react-no-anonymous-functions-in-render',
346
577
  label: 'No anonymous functions in JSX props',
@@ -353,6 +584,19 @@ const reactRules: Rule[] = [
353
584
  category: 'Performance',
354
585
  },
355
586
 
587
+ // ─────────────────────────────────────────
588
+ // RULE: react-missing-key-prop
589
+ // ROLE: Block list rendering without key props
590
+ // PURPOSE: React uses key to identify which items changed, were added,
591
+ // or removed. Missing keys cause UI bugs and poor performance.
592
+ // Array index as key causes issues with reordering.
593
+ // EXAMPLE:
594
+ // WRONG:
595
+ // items.map(item => <Card title={item.name} />);
596
+ // items.map((item, i) => <Card key={i} title={item.name} />);
597
+ // RIGHT:
598
+ // items.map(item => <Card key={item.id} title={item.name} />);
599
+ // ─────────────────────────────────────────
356
600
  {
357
601
  id: 'react-missing-key-prop',
358
602
  label: 'Missing key prop in list',
@@ -367,9 +611,7 @@ const reactRules: Rule[] = [
367
611
 
368
612
  for (let i = 0; i < lines.length; i++) {
369
613
  const line = lines[i];
370
- // Detect .map( with JSX but no key prop nearby
371
614
  if (/\.\s*map\s*\(/.test(line)) {
372
- // Look ahead for JSX opening tag
373
615
  let foundKey = false;
374
616
  for (let j = i; j < Math.min(i + 10, lines.length); j++) {
375
617
  if (/key\s*=/.test(lines[j])) {
@@ -378,7 +620,6 @@ const reactRules: Rule[] = [
378
620
  }
379
621
  }
380
622
 
381
- // Check if there's JSX in the map
382
623
  let hasJsx = false;
383
624
  for (let j = i; j < Math.min(i + 10, lines.length); j++) {
384
625
  if (/<[A-Z]/.test(lines[j]) || /<[a-z]/.test(lines[j])) {
@@ -402,6 +643,20 @@ const reactRules: Rule[] = [
402
643
  category: 'Performance',
403
644
  },
404
645
 
646
+ // ─────────────────────────────────────────
647
+ // RULE: no-large-bundle-imports
648
+ // ROLE: Block large library wildcard imports
649
+ // PURPOSE: Importing entire lodash (~70KB) or moment (~290KB) when
650
+ // you only need one function wastes bandwidth. Use specific
651
+ // imports for tree-shaking support.
652
+ // EXAMPLE:
653
+ // WRONG:
654
+ // import _ from 'lodash';
655
+ // import moment from 'moment';
656
+ // RIGHT:
657
+ // import get from 'lodash/get';
658
+ // import { format } from 'date-fns';
659
+ // ─────────────────────────────────────────
405
660
  {
406
661
  id: 'no-large-bundle-imports',
407
662
  label: 'Avoid large library imports',
@@ -418,6 +673,19 @@ const reactRules: Rule[] = [
418
673
 
419
674
  // ── Accessibility Rules ────────────────────────────────────────────────
420
675
 
676
+ // ─────────────────────────────────────────
677
+ // RULE: require-button-type
678
+ // ROLE: Enforce type attribute on all button elements
679
+ // PURPOSE: Buttons default to type="submit", which triggers form
680
+ // submission unexpectedly. Always specify type explicitly
681
+ // to prevent accidental form submissions.
682
+ // EXAMPLE:
683
+ // WRONG:
684
+ // <button onClick={handleClick}>Click me</button>
685
+ // RIGHT:
686
+ // <button type="button" onClick={handleClick}>Click me</button>
687
+ // <button type="submit">Submit</button>
688
+ // ─────────────────────────────────────────
421
689
  {
422
690
  id: 'require-button-type',
423
691
  label: 'Buttons must have type attribute',
@@ -448,6 +716,21 @@ const reactRules: Rule[] = [
448
716
 
449
717
  // ── Additional Performance Rules ───────────────────────────────────────
450
718
 
719
+ // ─────────────────────────────────────────
720
+ // RULE: react-prefer-lazy-loading
721
+ // ROLE: Recommend code splitting for route components
722
+ // PURPOSE: Eagerly importing all page components increases the initial
723
+ // bundle size. React.lazy() + Suspense splits code by route,
724
+ // loading pages only when navigated to.
725
+ // EXAMPLE:
726
+ // WRONG:
727
+ // import Dashboard from '../pages/Dashboard';
728
+ // import Settings from '../pages/Settings';
729
+ // RIGHT:
730
+ // const Dashboard = lazy(() => import('../pages/Dashboard'));
731
+ // const Settings = lazy(() => import('../pages/Settings'));
732
+ // <Suspense fallback={<Spinner />}><Dashboard /></Suspense>
733
+ // ─────────────────────────────────────────
451
734
  {
452
735
  id: 'react-prefer-lazy-loading',
453
736
  label: 'Consider lazy loading for routes',
@@ -460,6 +743,22 @@ const reactRules: Rule[] = [
460
743
  category: 'Performance',
461
744
  },
462
745
 
746
+ // ─────────────────────────────────────────
747
+ // RULE: react-memo-expensive-components
748
+ // ROLE: Recommend React.memo for large render trees
749
+ // PURPOSE: Components with expensive render logic (>500 chars of JSX)
750
+ // should be wrapped in React.memo to skip re-renders when
751
+ // props haven't changed.
752
+ // EXAMPLE:
753
+ // WRONG:
754
+ // export function DataTable({ rows }) {
755
+ // // ... 500+ lines of complex rendering
756
+ // }
757
+ // RIGHT:
758
+ // export const DataTable = memo(function DataTable({ rows }) {
759
+ // // ... 500+ lines of complex rendering
760
+ // });
761
+ // ─────────────────────────────────────────
463
762
  {
464
763
  id: 'react-memo-expensive-components',
465
764
  label: 'Consider React.memo for expensive renders',
@@ -472,6 +771,21 @@ const reactRules: Rule[] = [
472
771
  category: 'Performance',
473
772
  },
474
773
 
774
+ // ─────────────────────────────────────────
775
+ // RULE: no-object-array-in-deps
776
+ // ROLE: Block unstable references in hook dependency arrays
777
+ // PURPOSE: Objects and arrays create new references on every render.
778
+ // Putting them in dependency arrays triggers hooks on every
779
+ // render, defeating the purpose of the dependency array.
780
+ // EXAMPLE:
781
+ // WRONG:
782
+ // useEffect(() => { ... }, [{ id: 1 }]);
783
+ // useMemo(() => { ... }, [[1, 2, 3]]);
784
+ // RIGHT:
785
+ // useEffect(() => { ... }, [id]);
786
+ // const configRef = useRef(config); // stable reference
787
+ // useEffect(() => { ... }, [configRef]);
788
+ // ─────────────────────────────────────────
475
789
  {
476
790
  id: 'no-object-array-in-deps',
477
791
  label: 'Avoid objects/arrays in dependency arrays',
@@ -486,6 +800,18 @@ const reactRules: Rule[] = [
486
800
 
487
801
  // ── Additional Best Practice Rules ────────────────────────────────────
488
802
 
803
+ // ─────────────────────────────────────────
804
+ // RULE: react-use-fragments
805
+ // ROLE: Recommend React fragments over wrapper divs
806
+ // PURPOSE: Unnecessary wrapper <div>s add DOM nodes, break CSS layouts
807
+ // (flexbox, grid), increase memory usage, and cause
808
+ // accessibility issues with screen readers.
809
+ // EXAMPLE:
810
+ // WRONG:
811
+ // return (<div><Header /><Content /></div>);
812
+ // RIGHT:
813
+ // return (<><Header /><Content /></>);
814
+ // ─────────────────────────────────────────
489
815
  {
490
816
  id: 'react-use-fragments',
491
817
  label: 'Use React fragments instead of divs',
@@ -498,6 +824,18 @@ const reactRules: Rule[] = [
498
824
  category: 'Best Practices',
499
825
  },
500
826
 
827
+ // ─────────────────────────────────────────
828
+ // RULE: prefer-optional-chaining
829
+ // ROLE: Recommend modern null-safe access patterns
830
+ // PURPOSE: Chained && checks are verbose and error-prone. Optional
831
+ // chaining (?.) is built into the language, more readable,
832
+ // and handles null/undefined safely.
833
+ // EXAMPLE:
834
+ // WRONG:
835
+ // user && user.profile && user.profile.name
836
+ // RIGHT:
837
+ // user?.profile?.name
838
+ // ─────────────────────────────────────────
501
839
  {
502
840
  id: 'prefer-optional-chaining',
503
841
  label: 'Use optional chaining',
@@ -510,6 +848,18 @@ const reactRules: Rule[] = [
510
848
  category: 'Modern Syntax',
511
849
  },
512
850
 
851
+ // ─────────────────────────────────────────
852
+ // RULE: prefer-nullish-coalescing
853
+ // ROLE: Recommend ?? over || for default values
854
+ // PURPOSE: || treats 0, '', and false as falsy and replaces them.
855
+ // ?? only replaces null/undefined, preserving valid falsy
856
+ // values like 0 (count) and '' (empty string).
857
+ // EXAMPLE:
858
+ // WRONG:
859
+ // const count = data.count || 10; // 0 becomes 10!
860
+ // RIGHT:
861
+ // const count = data.count ?? 10; // 0 stays 0
862
+ // ─────────────────────────────────────────
513
863
  {
514
864
  id: 'prefer-nullish-coalescing',
515
865
  label: 'Use nullish coalescing (??)',
@@ -522,6 +872,24 @@ const reactRules: Rule[] = [
522
872
  category: 'Modern Syntax',
523
873
  },
524
874
 
875
+ // ─────────────────────────────────────────
876
+ // RULE: react-error-boundary-usage
877
+ // ROLE: Recommend error boundaries for crash prevention
878
+ // PURPOSE: Without error boundaries, a single thrown error crashes
879
+ // the entire React app. Error boundaries catch render errors
880
+ // and display fallback UI instead.
881
+ // EXAMPLE:
882
+ // WRONG:
883
+ // <App>
884
+ // <BuggyComponent /> {/* Crash kills entire app */}
885
+ // </App>
886
+ // RIGHT:
887
+ // <App>
888
+ // <ErrorBoundary fallback={<ErrorPage />}>
889
+ // <BuggyComponent /> {/* Crash is contained */}
890
+ // </ErrorBoundary>
891
+ // </App>
892
+ // ─────────────────────────────────────────
525
893
  {
526
894
  id: 'react-error-boundary-usage',
527
895
  label: 'Consider error boundaries',
@@ -534,6 +902,19 @@ const reactRules: Rule[] = [
534
902
  category: 'Error Handling',
535
903
  },
536
904
 
905
+ // ─────────────────────────────────────────
906
+ // RULE: no-sync-external-calls
907
+ // ROLE: Block synchronous external API calls
908
+ // PURPOSE: Synchronous fetch/XHR blocks the main thread, freezing
909
+ // the UI completely. Always use async/await for API calls,
910
+ // file operations, and external services.
911
+ // EXAMPLE:
912
+ // WRONG:
913
+ // const data = fetch('/api/data'); // No await!
914
+ // RIGHT:
915
+ // const data = await fetch('/api/data');
916
+ // const json = await data.json();
917
+ // ─────────────────────────────────────────
537
918
  {
538
919
  id: 'no-sync-external-calls',
539
920
  label: 'Avoid synchronous external calls',
@@ -546,6 +927,18 @@ const reactRules: Rule[] = [
546
927
  category: 'Async Patterns',
547
928
  },
548
929
 
930
+ // ─────────────────────────────────────────
931
+ // RULE: react-prefer-controlled-inputs
932
+ // ROLE: Recommend controlled components over uncontrolled
933
+ // PURPOSE: Controlled components (value + onChange) keep React as the
934
+ // single source of truth. Uncontrolled (defaultValue + ref)
935
+ // creates two sources of truth and makes validation harder.
936
+ // EXAMPLE:
937
+ // WRONG:
938
+ // <input defaultValue="hello" ref={inputRef} />
939
+ // RIGHT:
940
+ // <input value={name} onChange={(e) => setName(e.target.value)} />
941
+ // ─────────────────────────────────────────
549
942
  {
550
943
  id: 'react-prefer-controlled-inputs',
551
944
  label: 'Prefer controlled components',
@@ -560,6 +953,23 @@ const reactRules: Rule[] = [
560
953
 
561
954
  // NOTE: 'no-index-as-key' removed — covered by ESLint 'react/no-array-index-key' rule
562
955
 
956
+ // ─────────────────────────────────────────
957
+ // RULE: react-cleanup-effects
958
+ // ROLE: Enforce cleanup functions in side-effect hooks
959
+ // PURPOSE: useEffect with subscriptions, timers, or event listeners
960
+ // must return a cleanup function. Without it, listeners
961
+ // accumulate on every re-render, causing memory leaks.
962
+ // EXAMPLE:
963
+ // WRONG:
964
+ // useEffect(() => {
965
+ // window.addEventListener('resize', handleResize);
966
+ // }, []);
967
+ // RIGHT:
968
+ // useEffect(() => {
969
+ // window.addEventListener('resize', handleResize);
970
+ // return () => window.removeEventListener('resize', handleResize);
971
+ // }, []);
972
+ // ─────────────────────────────────────────
563
973
  {
564
974
  id: 'react-cleanup-effects',
565
975
  label: 'Cleanup side effects',
@@ -572,6 +982,20 @@ const reactRules: Rule[] = [
572
982
  category: 'Memory Management',
573
983
  },
574
984
 
985
+ // ─────────────────────────────────────────
986
+ // RULE: prefer-const-assertion
987
+ // ROLE: Recommend const assertions for literal types
988
+ // PURPOSE: "as const" narrows types to literal values and makes
989
+ // objects/arrays readonly, improving type safety and
990
+ // enabling discriminated unions.
991
+ // EXAMPLE:
992
+ // WRONG:
993
+ // const ROLES: string[] = ['admin', 'user', 'viewer'];
994
+ // // Type: string[] — loses literal type info
995
+ // RIGHT:
996
+ // const ROLES = ['admin', 'user', 'viewer'] as const;
997
+ // // Type: readonly ['admin', 'user', 'viewer']
998
+ // ─────────────────────────────────────────
575
999
  {
576
1000
  id: 'prefer-const-assertion',
577
1001
  label: 'Use const assertions for literal types',