@kernlang/review 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 (47) hide show
  1. package/LICENSE +661 -0
  2. package/dist/differ.d.ts +11 -0
  3. package/dist/differ.js +132 -0
  4. package/dist/differ.js.map +1 -0
  5. package/dist/external-tools.d.ts +32 -0
  6. package/dist/external-tools.js +173 -0
  7. package/dist/external-tools.js.map +1 -0
  8. package/dist/index.d.ts +33 -0
  9. package/dist/index.js +98 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/inferrer.d.ts +15 -0
  12. package/dist/inferrer.js +502 -0
  13. package/dist/inferrer.js.map +1 -0
  14. package/dist/llm-review.d.ts +24 -0
  15. package/dist/llm-review.js +197 -0
  16. package/dist/llm-review.js.map +1 -0
  17. package/dist/quality-rules.d.ts +12 -0
  18. package/dist/quality-rules.js +28 -0
  19. package/dist/quality-rules.js.map +1 -0
  20. package/dist/reporter.d.ts +15 -0
  21. package/dist/reporter.js +217 -0
  22. package/dist/reporter.js.map +1 -0
  23. package/dist/rules/base.d.ts +10 -0
  24. package/dist/rules/base.js +556 -0
  25. package/dist/rules/base.js.map +1 -0
  26. package/dist/rules/express.d.ts +9 -0
  27. package/dist/rules/express.js +107 -0
  28. package/dist/rules/express.js.map +1 -0
  29. package/dist/rules/index.d.ts +16 -0
  30. package/dist/rules/index.js +38 -0
  31. package/dist/rules/index.js.map +1 -0
  32. package/dist/rules/nextjs.d.ts +9 -0
  33. package/dist/rules/nextjs.js +128 -0
  34. package/dist/rules/nextjs.js.map +1 -0
  35. package/dist/rules/react.d.ts +9 -0
  36. package/dist/rules/react.js +252 -0
  37. package/dist/rules/react.js.map +1 -0
  38. package/dist/rules/vue.d.ts +9 -0
  39. package/dist/rules/vue.js +198 -0
  40. package/dist/rules/vue.js.map +1 -0
  41. package/dist/template-detector.d.ts +12 -0
  42. package/dist/template-detector.js +225 -0
  43. package/dist/template-detector.js.map +1 -0
  44. package/dist/types.d.ts +152 -0
  45. package/dist/types.js +17 -0
  46. package/dist/types.js.map +1 -0
  47. package/package.json +23 -0
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Express review rules — active when target = express.
3
+ *
4
+ * Catches common Express security and performance issues.
5
+ */
6
+ import { createFingerprint } from '../types.js';
7
+ function span(file, line, col = 1) {
8
+ return { file, startLine: line, startCol: col, endLine: line, endCol: col };
9
+ }
10
+ function finding(ruleId, severity, category, message, file, line, extra) {
11
+ return {
12
+ source: 'kern',
13
+ ruleId,
14
+ severity,
15
+ category,
16
+ message,
17
+ primarySpan: span(file, line),
18
+ fingerprint: createFingerprint(ruleId, line, 1),
19
+ ...extra,
20
+ };
21
+ }
22
+ // ── Rule 24: unvalidated-input ───────────────────────────────────────────
23
+ // req.body/params used without validation
24
+ function unvalidatedInput(ctx) {
25
+ const findings = [];
26
+ const fullText = ctx.sourceFile.getFullText();
27
+ const lines = fullText.split('\n');
28
+ // Track if file has any validation library imports
29
+ const hasZod = fullText.includes('from \'zod\'') || fullText.includes('from "zod"');
30
+ const hasJoi = fullText.includes('from \'joi\'') || fullText.includes('from "joi"');
31
+ const hasYup = fullText.includes('from \'yup\'') || fullText.includes('from "yup"');
32
+ const hasValidation = hasZod || hasJoi || hasYup || fullText.includes('validate(');
33
+ if (hasValidation)
34
+ return findings; // file uses a validation library
35
+ // Check for req.body access patterns
36
+ const reqBodyRegex = /\breq\.(body|params|query)(?:\.\w+|\[)/g;
37
+ let match;
38
+ while ((match = reqBodyRegex.exec(fullText)) !== null) {
39
+ const line = fullText.substring(0, match.index).split('\n').length;
40
+ const lineText = lines[line - 1] || '';
41
+ // Skip if there's a type assertion or validation nearby
42
+ if (lineText.includes(' as ') || lineText.includes('typeof') ||
43
+ lineText.includes('validate') || lineText.includes('.parse(') ||
44
+ lineText.includes('schema'))
45
+ continue;
46
+ findings.push(finding('unvalidated-input', 'error', 'bug', `req.${match[1]} used without validation — potential injection vector`, ctx.filePath, line, { suggestion: 'Validate with zod, joi, or express-validator before using request data' }));
47
+ }
48
+ return findings;
49
+ }
50
+ // ── Rule 25: missing-error-middleware ─────────────────────────────────────
51
+ // Express app without error handler (4-param middleware)
52
+ function missingErrorMiddleware(ctx) {
53
+ const findings = [];
54
+ const fullText = ctx.sourceFile.getFullText();
55
+ // Check if this file creates an Express app
56
+ const hasApp = /(?:const|let)\s+\w+\s*=\s*express\s*\(\s*\)/g.test(fullText);
57
+ if (!hasApp)
58
+ return findings;
59
+ // Check for error middleware (4-parameter function: err, req, res, next)
60
+ const has4ParamMiddleware = /app\.use\s*\(\s*(?:function\s*)?\(\s*\w+\s*,\s*\w+\s*,\s*\w+\s*,\s*\w+\s*\)/g.test(fullText);
61
+ const hasErrorHandler = has4ParamMiddleware || fullText.includes('errorHandler') || fullText.includes('error-handler');
62
+ if (!hasErrorHandler) {
63
+ // Find the app declaration line
64
+ const appMatch = fullText.match(/(?:const|let)\s+(\w+)\s*=\s*express\s*\(\s*\)/);
65
+ if (appMatch) {
66
+ const line = fullText.substring(0, (appMatch.index ?? 0)).split('\n').length;
67
+ findings.push(finding('missing-error-middleware', 'warning', 'pattern', 'Express app has no error handling middleware — unhandled errors will crash the server', ctx.filePath, line, { suggestion: 'app.use((err, req, res, next) => { res.status(500).json({ error: err.message }); })' }));
68
+ }
69
+ }
70
+ return findings;
71
+ }
72
+ // ── Rule 26: sync-in-handler ─────────────────────────────────────────────
73
+ // Synchronous fs/crypto operations in request handlers
74
+ function syncInHandler(ctx) {
75
+ const findings = [];
76
+ const fullText = ctx.sourceFile.getFullText();
77
+ const syncOps = [
78
+ { pattern: /\breadFileSync\s*\(/g, name: 'readFileSync', async: 'readFile' },
79
+ { pattern: /\bwriteFileSync\s*\(/g, name: 'writeFileSync', async: 'writeFile' },
80
+ { pattern: /\bexistsSync\s*\(/g, name: 'existsSync', async: 'access' },
81
+ { pattern: /\bmkdirSync\s*\(/g, name: 'mkdirSync', async: 'mkdir' },
82
+ { pattern: /\breaddirSync\s*\(/g, name: 'readdirSync', async: 'readdir' },
83
+ { pattern: /\bstatSync\s*\(/g, name: 'statSync', async: 'stat' },
84
+ { pattern: /\bcrypto\.pbkdf2Sync\s*\(/g, name: 'pbkdf2Sync', async: 'pbkdf2' },
85
+ { pattern: /\bcrypto\.scryptSync\s*\(/g, name: 'scryptSync', async: 'scrypt' },
86
+ { pattern: /\bcrypto\.randomBytes\s*\(/g, name: 'randomBytes (sync)', async: 'randomBytes (callback)' },
87
+ ];
88
+ // Check if we're in a route handler context
89
+ const isRouteFile = /(?:app|router)\.(get|post|put|delete|patch|use)\s*\(/.test(fullText);
90
+ if (!isRouteFile)
91
+ return findings;
92
+ for (const { pattern, name, async: asyncName } of syncOps) {
93
+ let match;
94
+ while ((match = pattern.exec(fullText)) !== null) {
95
+ const line = fullText.substring(0, match.index).split('\n').length;
96
+ findings.push(finding('sync-in-handler', 'warning', 'pattern', `${name} in request handler blocks the event loop — use ${asyncName} instead`, ctx.filePath, line, { suggestion: `Replace ${name} with async ${asyncName}` }));
97
+ }
98
+ }
99
+ return findings;
100
+ }
101
+ // ── Exported Express Rules ───────────────────────────────────────────────
102
+ export const expressRules = [
103
+ unvalidatedInput,
104
+ missingErrorMiddleware,
105
+ syncInHandler,
106
+ ];
107
+ //# sourceMappingURL=express.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/rules/express.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Rule layer loader — reads config.target and returns active review rules.
3
+ *
4
+ * Layers:
5
+ * [base] Always active — universal TS/KERN rules
6
+ * [react] Active when target = nextjs | tailwind | web | native
7
+ * [vue] Active when target = vue | nuxt
8
+ * [express] Active when target = express
9
+ * [nextjs] Active when target = nextjs (on top of react)
10
+ */
11
+ import type { ReviewRule } from '../types.js';
12
+ /**
13
+ * Get all active review rules for a given target.
14
+ * Base rules are always active; framework rules activate by target.
15
+ */
16
+ export declare function getActiveRules(target?: string): ReviewRule[];
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Rule layer loader — reads config.target and returns active review rules.
3
+ *
4
+ * Layers:
5
+ * [base] Always active — universal TS/KERN rules
6
+ * [react] Active when target = nextjs | tailwind | web | native
7
+ * [vue] Active when target = vue | nuxt
8
+ * [express] Active when target = express
9
+ * [nextjs] Active when target = nextjs (on top of react)
10
+ */
11
+ import { baseRules } from './base.js';
12
+ import { reactRules } from './react.js';
13
+ import { vueRules } from './vue.js';
14
+ import { nextjsRules } from './nextjs.js';
15
+ import { expressRules } from './express.js';
16
+ const REACT_TARGETS = new Set(['nextjs', 'tailwind', 'web', 'native']);
17
+ const VUE_TARGETS = new Set(['vue', 'nuxt']);
18
+ /**
19
+ * Get all active review rules for a given target.
20
+ * Base rules are always active; framework rules activate by target.
21
+ */
22
+ export function getActiveRules(target) {
23
+ const rules = [...baseRules];
24
+ if (target && REACT_TARGETS.has(target)) {
25
+ rules.push(...reactRules);
26
+ }
27
+ if (target && VUE_TARGETS.has(target)) {
28
+ rules.push(...vueRules);
29
+ }
30
+ if (target === 'nextjs') {
31
+ rules.push(...nextjsRules);
32
+ }
33
+ if (target === 'express') {
34
+ rules.push(...expressRules);
35
+ }
36
+ return rules;
37
+ }
38
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;AACvE,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;AAE7C;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,MAAe;IAC5C,MAAM,KAAK,GAAiB,CAAC,GAAG,SAAS,CAAC,CAAC;IAE3C,IAAI,MAAM,IAAI,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QACxC,KAAK,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,CAAC;IAC5B,CAAC;IAED,IAAI,MAAM,IAAI,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,KAAK,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC;IAC1B,CAAC;IAED,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;QACxB,KAAK,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,CAAC;IAC7B,CAAC;IAED,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,CAAC;IAC9B,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Next.js review rules — active when target = nextjs (on top of React rules).
3
+ *
4
+ * Catches Server Component / App Router pitfalls.
5
+ */
6
+ import type { ReviewFinding, RuleContext } from '../types.js';
7
+ declare function serverHook(ctx: RuleContext): ReviewFinding[];
8
+ export declare const nextjsRules: (typeof serverHook)[];
9
+ export {};
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Next.js review rules — active when target = nextjs (on top of React rules).
3
+ *
4
+ * Catches Server Component / App Router pitfalls.
5
+ */
6
+ import { createFingerprint } from '../types.js';
7
+ function span(file, line, col = 1) {
8
+ return { file, startLine: line, startCol: col, endLine: line, endCol: col };
9
+ }
10
+ function finding(ruleId, severity, category, message, file, line, extra) {
11
+ return {
12
+ source: 'kern',
13
+ ruleId,
14
+ severity,
15
+ category,
16
+ message,
17
+ primarySpan: span(file, line),
18
+ fingerprint: createFingerprint(ruleId, line, 1),
19
+ ...extra,
20
+ };
21
+ }
22
+ function isClientComponent(fullText) {
23
+ // Check for 'use client' directive (must be at the top of the file)
24
+ return /^['"]use client['"];?\s*$/m.test(fullText.substring(0, 200));
25
+ }
26
+ // ── Rule 21: server-hook ─────────────────────────────────────────────────
27
+ // React hooks (useState, useEffect, etc.) in a Server Component
28
+ function serverHook(ctx) {
29
+ const findings = [];
30
+ const fullText = ctx.sourceFile.getFullText();
31
+ if (isClientComponent(fullText))
32
+ return findings;
33
+ const clientHooks = ['useState', 'useEffect', 'useRef', 'useCallback', 'useMemo',
34
+ 'useReducer', 'useContext', 'useLayoutEffect', 'useTransition',
35
+ 'useDeferredValue', 'useImperativeHandle', 'useSyncExternalStore'];
36
+ for (const hook of clientHooks) {
37
+ const hookRegex = new RegExp(`\\b${hook}\\s*[<(]`, 'g');
38
+ let match;
39
+ while ((match = hookRegex.exec(fullText)) !== null) {
40
+ const line = fullText.substring(0, match.index).split('\n').length;
41
+ findings.push(finding('server-hook', 'error', 'bug', `'${hook}' used in Server Component — add 'use client' directive or move to a Client Component`, ctx.filePath, line, { suggestion: "Add 'use client' at the top of the file" }));
42
+ }
43
+ }
44
+ return findings;
45
+ }
46
+ // ── Rule 22: hydration-mismatch ──────────────────────────────────────────
47
+ // Nondeterministic expressions (Date.now, Math.random, new Date) in render
48
+ function hydrationMismatch(ctx) {
49
+ const findings = [];
50
+ const fullText = ctx.sourceFile.getFullText();
51
+ // Build a set of character ranges that are inside useEffect/useMemo/event handlers
52
+ const safeRanges = [];
53
+ const safeCallRegex = /(?:useEffect|useMemo|useCallback|onClick|onSubmit)\s*\(\s*/g;
54
+ let safeMatch;
55
+ while ((safeMatch = safeCallRegex.exec(fullText)) !== null) {
56
+ const startIdx = safeMatch.index + safeMatch[0].length;
57
+ let depth = 0;
58
+ let rangeEnd = startIdx;
59
+ for (let i = startIdx; i < fullText.length; i++) {
60
+ if (fullText[i] === '(')
61
+ depth++;
62
+ if (fullText[i] === '{')
63
+ depth++;
64
+ if (fullText[i] === ')') {
65
+ if (depth === 0) {
66
+ rangeEnd = i;
67
+ break;
68
+ }
69
+ depth--;
70
+ }
71
+ if (fullText[i] === '}')
72
+ depth--;
73
+ }
74
+ safeRanges.push([safeMatch.index, rangeEnd]);
75
+ }
76
+ const isInSafeRange = (idx) => safeRanges.some(([s, e]) => idx >= s && idx <= e);
77
+ const nondeterministic = [
78
+ { pattern: /\bDate\.now\s*\(\s*\)/g, name: 'Date.now()' },
79
+ { pattern: /\bMath\.random\s*\(\s*\)/g, name: 'Math.random()' },
80
+ { pattern: /\bnew\s+Date\s*\(\s*\)/g, name: 'new Date()' },
81
+ { pattern: /\bcrypto\.randomUUID\s*\(\s*\)/g, name: 'crypto.randomUUID()' },
82
+ ];
83
+ for (const { pattern, name } of nondeterministic) {
84
+ let match;
85
+ while ((match = pattern.exec(fullText)) !== null) {
86
+ // Skip if inside useEffect, useMemo, event handler, or server action
87
+ if (isInSafeRange(match.index))
88
+ continue;
89
+ const line = fullText.substring(0, match.index).split('\n').length;
90
+ const lineText = fullText.split('\n')[line - 1] || '';
91
+ if (lineText.includes("'use server'"))
92
+ continue;
93
+ findings.push(finding('hydration-mismatch', 'warning', 'bug', `${name} in render produces different values on server vs client — hydration mismatch`, ctx.filePath, line, { suggestion: `Move to useEffect or use a stable seed. For IDs, use React.useId()` }));
94
+ }
95
+ }
96
+ return findings;
97
+ }
98
+ // ── Rule 23: missing-use-client ──────────────────────────────────────────
99
+ // Event handlers (onClick, onChange, etc.) without 'use client' directive
100
+ function missingUseClient(ctx) {
101
+ const findings = [];
102
+ const fullText = ctx.sourceFile.getFullText();
103
+ if (isClientComponent(fullText))
104
+ return findings;
105
+ const eventHandlers = ['onClick', 'onChange', 'onSubmit', 'onKeyDown', 'onKeyUp',
106
+ 'onMouseEnter', 'onMouseLeave', 'onFocus', 'onBlur', 'onInput',
107
+ 'onTouchStart', 'onTouchEnd', 'onScroll', 'onDrag'];
108
+ const found = new Set();
109
+ for (const handler of eventHandlers) {
110
+ const regex = new RegExp(`\\b${handler}=\\{`, 'g');
111
+ let match;
112
+ while ((match = regex.exec(fullText)) !== null) {
113
+ if (found.has(handler))
114
+ continue;
115
+ found.add(handler);
116
+ const line = fullText.substring(0, match.index).split('\n').length;
117
+ findings.push(finding('missing-use-client', 'warning', 'pattern', `'${handler}' in Server Component — needs 'use client' directive`, ctx.filePath, line, { suggestion: "Add 'use client' at the top of the file, or extract to a Client Component" }));
118
+ }
119
+ }
120
+ return findings;
121
+ }
122
+ // ── Exported Next.js Rules ───────────────────────────────────────────────
123
+ export const nextjsRules = [
124
+ serverHook,
125
+ hydrationMismatch,
126
+ missingUseClient,
127
+ ];
128
+ //# sourceMappingURL=nextjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nextjs.js","sourceRoot":"","sources":["../../src/rules/nextjs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * React review rules — active when target = nextjs | tailwind | web | native.
3
+ *
4
+ * Catches React-specific bugs that KERN IR + AST can detect mechanically.
5
+ */
6
+ import type { ReviewFinding, RuleContext } from '../types.js';
7
+ declare function asyncEffect(ctx: RuleContext): ReviewFinding[];
8
+ export declare const reactRules: (typeof asyncEffect)[];
9
+ export {};
@@ -0,0 +1,252 @@
1
+ /**
2
+ * React review rules — active when target = nextjs | tailwind | web | native.
3
+ *
4
+ * Catches React-specific bugs that KERN IR + AST can detect mechanically.
5
+ */
6
+ import { SyntaxKind } from 'ts-morph';
7
+ import { createFingerprint } from '../types.js';
8
+ function span(file, line, col = 1) {
9
+ return { file, startLine: line, startCol: col, endLine: line, endCol: col };
10
+ }
11
+ function finding(ruleId, severity, category, message, file, line, extra) {
12
+ return {
13
+ source: 'kern',
14
+ ruleId,
15
+ severity,
16
+ category,
17
+ message,
18
+ primarySpan: span(file, line),
19
+ fingerprint: createFingerprint(ruleId, line, 1),
20
+ ...extra,
21
+ };
22
+ }
23
+ // ── Rule 11: async-effect ────────────────────────────────────────────────
24
+ // useEffect(async () => ...) — React doesn't support async effect callbacks
25
+ function asyncEffect(ctx) {
26
+ const findings = [];
27
+ const fullText = ctx.sourceFile.getFullText();
28
+ const asyncEffectRegex = /useEffect\s*\(\s*async\s/g;
29
+ let match;
30
+ while ((match = asyncEffectRegex.exec(fullText)) !== null) {
31
+ const line = fullText.substring(0, match.index).split('\n').length;
32
+ findings.push(finding('async-effect', 'error', 'bug', 'useEffect callback must not be async — use an inner async function instead', ctx.filePath, line, { suggestion: 'useEffect(() => { async function run() { ... } run(); }, [])' }));
33
+ }
34
+ return findings;
35
+ }
36
+ // ── Rule 12: render-side-effect ──────────────────────────────────────────
37
+ // setState or fetch called directly in render body (outside hooks/handlers)
38
+ function renderSideEffect(ctx) {
39
+ const findings = [];
40
+ // AST-based: walk function component bodies and find side effects
41
+ for (const fn of ctx.sourceFile.getFunctions()) {
42
+ const name = fn.getName() || '';
43
+ if (!name || name[0] !== name[0].toUpperCase())
44
+ continue; // not a component
45
+ const body = fn.getBody();
46
+ if (!body || body.getKind() !== SyntaxKind.Block)
47
+ continue;
48
+ const block = body;
49
+ // Walk top-level statements in the function body (not nested in hooks)
50
+ for (const stmt of block.getStatements()) {
51
+ // Skip return statements and variable declarations (hooks)
52
+ if (stmt.getKind() === SyntaxKind.ReturnStatement)
53
+ continue;
54
+ if (stmt.getKind() === SyntaxKind.VariableStatement) {
55
+ const text = stmt.getText();
56
+ if (/\buse[A-Z]/.test(text))
57
+ continue; // hook declaration
58
+ }
59
+ if (stmt.getKind() !== SyntaxKind.ExpressionStatement)
60
+ continue;
61
+ const exprStmt = stmt;
62
+ const exprText = exprStmt.getExpression().getText();
63
+ // setState call outside hooks — line number is exact from AST
64
+ if (/\bset[A-Z]\w*\(/.test(exprText) && !exprText.includes('useState')) {
65
+ findings.push(finding('render-side-effect', 'error', 'bug', `setState called in render body of '${name}' — move to useEffect or event handler`, ctx.filePath, stmt.getStartLineNumber()));
66
+ }
67
+ // fetch call in render body
68
+ if (/\bfetch\s*\(/.test(exprText)) {
69
+ findings.push(finding('render-side-effect', 'error', 'bug', `fetch() called in render body of '${name}' — move to useEffect or event handler`, ctx.filePath, stmt.getStartLineNumber()));
70
+ }
71
+ }
72
+ }
73
+ return findings;
74
+ }
75
+ // ── Rule 13: unstable-key ────────────────────────────────────────────────
76
+ // Missing key or key={index} in .map() JSX expressions
77
+ function unstableKey(ctx) {
78
+ const findings = [];
79
+ // AST-based: walk CallExpressions where callee is .map()
80
+ for (const call of ctx.sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
81
+ const callee = call.getExpression();
82
+ if (callee.getKind() !== SyntaxKind.PropertyAccessExpression)
83
+ continue;
84
+ const propAccess = callee;
85
+ if (propAccess.getName() !== 'map')
86
+ continue;
87
+ // Get first argument — should be ArrowFunction or FunctionExpression
88
+ const args = call.getArguments();
89
+ if (args.length === 0)
90
+ continue;
91
+ const callback = args[0];
92
+ if (callback.getKind() !== SyntaxKind.ArrowFunction &&
93
+ callback.getKind() !== SyntaxKind.FunctionExpression)
94
+ continue;
95
+ // Get the index parameter (second param of the callback)
96
+ const params = callback.getKind() === SyntaxKind.ArrowFunction
97
+ ? callback.getParameters()
98
+ : callback.getParameters();
99
+ const indexParam = params.length >= 2 ? params[1].getName() : null;
100
+ // Walk callback descendants for JSX elements
101
+ const jsxElements = [
102
+ ...callback.getDescendantsOfKind(SyntaxKind.JsxOpeningElement),
103
+ ...callback.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement),
104
+ ];
105
+ if (jsxElements.length === 0)
106
+ continue; // No JSX → skip (fixes non-JSX .map() FP)
107
+ // Check the FIRST (root) JSX element for key prop
108
+ const firstJsx = jsxElements.sort((a, b) => a.getStart() - b.getStart())[0];
109
+ const line = call.getStartLineNumber();
110
+ // Get attributes from the first JSX element
111
+ const attributes = firstJsx.getKind() === SyntaxKind.JsxSelfClosingElement
112
+ ? firstJsx.getAttributes()
113
+ : firstJsx.getAttributes();
114
+ let hasKey = false;
115
+ let usesIndexKey = false;
116
+ for (const attr of attributes) {
117
+ if (attr.getKind() !== SyntaxKind.JsxAttribute)
118
+ continue;
119
+ const jsxAttr = attr;
120
+ if (jsxAttr.getNameNode().getText() !== 'key')
121
+ continue;
122
+ hasKey = true;
123
+ // Check if key={indexVar}
124
+ if (indexParam) {
125
+ const init = jsxAttr.getInitializer();
126
+ if (init && init.getKind() === SyntaxKind.JsxExpression) {
127
+ const exprText = init.getExpression()?.getText();
128
+ if (exprText === indexParam) {
129
+ usesIndexKey = true;
130
+ }
131
+ }
132
+ }
133
+ break;
134
+ }
135
+ if (usesIndexKey) {
136
+ findings.push(finding('unstable-key', 'warning', 'bug', `key={${indexParam}} uses array index — use a stable identifier instead`, ctx.filePath, line, { suggestion: 'Use a unique ID from the data (e.g., key={item.id})' }));
137
+ }
138
+ else if (!hasKey) {
139
+ findings.push(finding('unstable-key', 'warning', 'bug', 'JSX in .map() is missing a key prop', ctx.filePath, line, { suggestion: 'Add key={item.id} to the root JSX element in .map()' }));
140
+ }
141
+ }
142
+ return findings;
143
+ }
144
+ // ── Rule 14: stale-closure ───────────────────────────────────────────────
145
+ // Timer captures state not in dependency array
146
+ function staleClosure(ctx) {
147
+ const findings = [];
148
+ // AST-based: find useEffect() calls and analyze deps + timer usage
149
+ for (const call of ctx.sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
150
+ const callee = call.getExpression();
151
+ if (callee.getText() !== 'useEffect')
152
+ continue;
153
+ const args = call.getArguments();
154
+ if (args.length < 2)
155
+ continue;
156
+ // First arg: the effect callback
157
+ const callbackText = args[0].getText();
158
+ // Second arg: deps array
159
+ const depsArg = args[1];
160
+ if (depsArg.getKind() !== SyntaxKind.ArrayLiteralExpression)
161
+ continue;
162
+ const depsArray = depsArg;
163
+ const deps = depsArray.getElements();
164
+ // Empty deps [] + timer in callback = stale closure risk
165
+ if (deps.length === 0) {
166
+ const hasTimer = /\b(?:setInterval|setTimeout)\s*\(/.test(callbackText);
167
+ if (hasTimer) {
168
+ findings.push(finding('stale-closure', 'warning', 'bug', 'Timer in useEffect with empty deps [] may capture stale state', ctx.filePath, call.getStartLineNumber(), { suggestion: 'Use a ref for the latest value or add dependencies' }));
169
+ }
170
+ }
171
+ }
172
+ return findings;
173
+ }
174
+ // ── Rule 15: state-explosion ─────────────────────────────────────────────
175
+ // >5 useState calls in a single component — should be useReducer or machine
176
+ function stateExplosion(ctx) {
177
+ const findings = [];
178
+ const fullText = ctx.sourceFile.getFullText();
179
+ for (const fn of ctx.sourceFile.getFunctions()) {
180
+ const name = fn.getName() || '';
181
+ if (!name || name[0] !== name[0].toUpperCase())
182
+ continue;
183
+ const body = fn.getBody()?.getText() || '';
184
+ const useStateCount = (body.match(/useState\s*[<(]/g) || []).length;
185
+ if (useStateCount > 5) {
186
+ findings.push(finding('state-explosion', 'warning', 'pattern', `Component '${name}' has ${useStateCount} useState calls — consider useReducer or a state machine`, ctx.filePath, fn.getStartLineNumber(), { suggestion: 'Use useReducer for complex state, or a KERN machine node for state transitions' }));
187
+ }
188
+ }
189
+ // Also check arrow function components
190
+ for (const stmt of ctx.sourceFile.getVariableStatements()) {
191
+ for (const decl of stmt.getDeclarations()) {
192
+ const name = decl.getName();
193
+ if (!name || name[0] !== name[0].toUpperCase())
194
+ continue;
195
+ const init = decl.getInitializer()?.getText() || '';
196
+ if (!init.includes('=>'))
197
+ continue;
198
+ const useStateCount = (init.match(/useState\s*[<(]/g) || []).length;
199
+ if (useStateCount > 5) {
200
+ findings.push(finding('state-explosion', 'warning', 'pattern', `Component '${name}' has ${useStateCount} useState calls — consider useReducer or a state machine`, ctx.filePath, stmt.getStartLineNumber()));
201
+ }
202
+ }
203
+ }
204
+ return findings;
205
+ }
206
+ // ── Rule 16: hook-order ──────────────────────────────────────────────────
207
+ // Conditional hook calls (hooks inside if/loop/early return)
208
+ const HOOK_NAMES = new Set(['useState', 'useEffect', 'useCallback', 'useMemo', 'useRef',
209
+ 'useContext', 'useReducer', 'useLayoutEffect', 'useImperativeHandle',
210
+ 'useDebugValue', 'useDeferredValue', 'useTransition', 'useId',
211
+ 'useSyncExternalStore', 'useInsertionEffect']);
212
+ function hookOrder(ctx) {
213
+ const findings = [];
214
+ // Collect all control-flow nodes (if/for/while/do)
215
+ const controlFlowNodes = [
216
+ ...ctx.sourceFile.getDescendantsOfKind(SyntaxKind.IfStatement),
217
+ ...ctx.sourceFile.getDescendantsOfKind(SyntaxKind.ForStatement),
218
+ ...ctx.sourceFile.getDescendantsOfKind(SyntaxKind.ForOfStatement),
219
+ ...ctx.sourceFile.getDescendantsOfKind(SyntaxKind.ForInStatement),
220
+ ...ctx.sourceFile.getDescendantsOfKind(SyntaxKind.WhileStatement),
221
+ ...ctx.sourceFile.getDescendantsOfKind(SyntaxKind.DoStatement),
222
+ ];
223
+ for (const cfNode of controlFlowNodes) {
224
+ const isConditional = cfNode.getKind() === SyntaxKind.IfStatement;
225
+ const label = isConditional ? 'conditional' : 'loop';
226
+ // Walk actual CallExpression descendants — not string matches
227
+ const reported = new Set();
228
+ for (const callExpr of cfNode.getDescendantsOfKind(SyntaxKind.CallExpression)) {
229
+ const callee = callExpr.getExpression();
230
+ if (callee.getKind() !== SyntaxKind.Identifier)
231
+ continue;
232
+ const hookName = callee.getText();
233
+ if (!HOOK_NAMES.has(hookName))
234
+ continue;
235
+ if (reported.has(hookName))
236
+ continue; // one finding per hook per block
237
+ reported.add(hookName);
238
+ findings.push(finding('hook-order', 'error', 'bug', `Hook '${hookName}' called inside ${label} — violates Rules of Hooks`, ctx.filePath, cfNode.getStartLineNumber(), { suggestion: 'Move hook call to top level of component' }));
239
+ }
240
+ }
241
+ return findings;
242
+ }
243
+ // ── Exported React Rules ─────────────────────────────────────────────────
244
+ export const reactRules = [
245
+ asyncEffect,
246
+ renderSideEffect,
247
+ unstableKey,
248
+ staleClosure,
249
+ stateExplosion,
250
+ hookOrder,
251
+ ];
252
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.js","sourceRoot":"","sources":["../../src/rules/react.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Vue review rules — active when target = vue | nuxt.
3
+ *
4
+ * Catches Vue 3 Composition API pitfalls.
5
+ */
6
+ import type { ReviewFinding, RuleContext } from '../types.js';
7
+ declare function missingRefValue(ctx: RuleContext): ReviewFinding[];
8
+ export declare const vueRules: (typeof missingRefValue)[];
9
+ export {};