@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.
- package/LICENSE +661 -0
- package/dist/differ.d.ts +11 -0
- package/dist/differ.js +132 -0
- package/dist/differ.js.map +1 -0
- package/dist/external-tools.d.ts +32 -0
- package/dist/external-tools.js +173 -0
- package/dist/external-tools.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +98 -0
- package/dist/index.js.map +1 -0
- package/dist/inferrer.d.ts +15 -0
- package/dist/inferrer.js +502 -0
- package/dist/inferrer.js.map +1 -0
- package/dist/llm-review.d.ts +24 -0
- package/dist/llm-review.js +197 -0
- package/dist/llm-review.js.map +1 -0
- package/dist/quality-rules.d.ts +12 -0
- package/dist/quality-rules.js +28 -0
- package/dist/quality-rules.js.map +1 -0
- package/dist/reporter.d.ts +15 -0
- package/dist/reporter.js +217 -0
- package/dist/reporter.js.map +1 -0
- package/dist/rules/base.d.ts +10 -0
- package/dist/rules/base.js +556 -0
- package/dist/rules/base.js.map +1 -0
- package/dist/rules/express.d.ts +9 -0
- package/dist/rules/express.js +107 -0
- package/dist/rules/express.js.map +1 -0
- package/dist/rules/index.d.ts +16 -0
- package/dist/rules/index.js +38 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/nextjs.d.ts +9 -0
- package/dist/rules/nextjs.js +128 -0
- package/dist/rules/nextjs.js.map +1 -0
- package/dist/rules/react.d.ts +9 -0
- package/dist/rules/react.js +252 -0
- package/dist/rules/react.js.map +1 -0
- package/dist/rules/vue.d.ts +9 -0
- package/dist/rules/vue.js +198 -0
- package/dist/rules/vue.js.map +1 -0
- package/dist/template-detector.d.ts +12 -0
- package/dist/template-detector.js +225 -0
- package/dist/template-detector.js.map +1 -0
- package/dist/types.d.ts +152 -0
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- 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 {};
|