@kernlang/review 3.1.8 → 3.2.3
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/dist/cache.js +4 -0
- package/dist/cache.js.map +1 -1
- package/dist/file-context.d.ts +6 -0
- package/dist/file-context.js +6 -1
- package/dist/file-context.js.map +1 -1
- package/dist/rules/a11y.d.ts +10 -0
- package/dist/rules/a11y.js +294 -0
- package/dist/rules/a11y.js.map +1 -0
- package/dist/rules/async.d.ts +8 -0
- package/dist/rules/async.js +154 -0
- package/dist/rules/async.js.map +1 -0
- package/dist/rules/base.js +29 -0
- package/dist/rules/base.js.map +1 -1
- package/dist/rules/index.d.ts +12 -0
- package/dist/rules/index.js +283 -4
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/ink.js +41 -0
- package/dist/rules/ink.js.map +1 -1
- package/dist/rules/kern-source.js +94 -14
- package/dist/rules/kern-source.js.map +1 -1
- package/dist/rules/nextjs-app-router.d.ts +11 -0
- package/dist/rules/nextjs-app-router.js +277 -0
- package/dist/rules/nextjs-app-router.js.map +1 -0
- package/dist/rules/nextjs.js +77 -1
- package/dist/rules/nextjs.js.map +1 -1
- package/dist/rules/perf.d.ts +11 -0
- package/dist/rules/perf.js +131 -0
- package/dist/rules/perf.js.map +1 -0
- package/dist/rules/react-composition.d.ts +12 -0
- package/dist/rules/react-composition.js +360 -0
- package/dist/rules/react-composition.js.map +1 -0
- package/dist/rules/react-hooks.d.ts +11 -0
- package/dist/rules/react-hooks.js +380 -0
- package/dist/rules/react-hooks.js.map +1 -0
- package/dist/rules/security-v5.d.ts +11 -0
- package/dist/rules/security-v5.js +200 -0
- package/dist/rules/security-v5.js.map +1 -0
- package/dist/rules/utils.d.ts +16 -0
- package/dist/rules/utils.js +46 -0
- package/dist/rules/utils.js.map +1 -1
- package/dist/taint-ast.js +32 -6
- package/dist/taint-ast.js.map +1 -1
- package/dist/taint-findings.js +3 -0
- package/dist/taint-findings.js.map +1 -1
- package/dist/taint-types.d.ts +2 -2
- package/dist/taint-types.js +38 -4
- package/dist/taint-types.js.map +1 -1
- package/dist/types.d.ts +20 -0
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React hooks correctness — Wave 2 net-new rules.
|
|
3
|
+
*
|
|
4
|
+
* Intentionally conservative: each rule only fires when the pattern is
|
|
5
|
+
* unambiguous. eslint-plugin-react-hooks is the authority for exhaustive
|
|
6
|
+
* correctness; this layer catches the common footguns with high precision.
|
|
7
|
+
*/
|
|
8
|
+
import { Node, SyntaxKind } from 'ts-morph';
|
|
9
|
+
import { finding, nodeSpan } from './utils.js';
|
|
10
|
+
const EFFECT_HOOKS = new Set(['useEffect', 'useLayoutEffect']);
|
|
11
|
+
const MEMO_HOOKS = new Set(['useMemo', 'useCallback']);
|
|
12
|
+
const REACT_HOOK_NAMES = new Set([
|
|
13
|
+
'useState',
|
|
14
|
+
'useEffect',
|
|
15
|
+
'useLayoutEffect',
|
|
16
|
+
'useRef',
|
|
17
|
+
'useCallback',
|
|
18
|
+
'useMemo',
|
|
19
|
+
'useReducer',
|
|
20
|
+
'useContext',
|
|
21
|
+
'useTransition',
|
|
22
|
+
'useDeferredValue',
|
|
23
|
+
'useImperativeHandle',
|
|
24
|
+
'useSyncExternalStore',
|
|
25
|
+
'useId',
|
|
26
|
+
'useDebugValue',
|
|
27
|
+
]);
|
|
28
|
+
const GLOBAL_NAMES = new Set([
|
|
29
|
+
'console',
|
|
30
|
+
'window',
|
|
31
|
+
'document',
|
|
32
|
+
'globalThis',
|
|
33
|
+
'process',
|
|
34
|
+
'Math',
|
|
35
|
+
'Date',
|
|
36
|
+
'JSON',
|
|
37
|
+
'Object',
|
|
38
|
+
'Array',
|
|
39
|
+
'String',
|
|
40
|
+
'Number',
|
|
41
|
+
'Boolean',
|
|
42
|
+
'Symbol',
|
|
43
|
+
'Promise',
|
|
44
|
+
'Error',
|
|
45
|
+
'TypeError',
|
|
46
|
+
'RangeError',
|
|
47
|
+
'RegExp',
|
|
48
|
+
'Map',
|
|
49
|
+
'Set',
|
|
50
|
+
'WeakMap',
|
|
51
|
+
'WeakSet',
|
|
52
|
+
'Proxy',
|
|
53
|
+
'Reflect',
|
|
54
|
+
'setTimeout',
|
|
55
|
+
'setInterval',
|
|
56
|
+
'clearTimeout',
|
|
57
|
+
'clearInterval',
|
|
58
|
+
'requestAnimationFrame',
|
|
59
|
+
'cancelAnimationFrame',
|
|
60
|
+
'queueMicrotask',
|
|
61
|
+
'structuredClone',
|
|
62
|
+
'fetch',
|
|
63
|
+
'URL',
|
|
64
|
+
'URLSearchParams',
|
|
65
|
+
'localStorage',
|
|
66
|
+
'sessionStorage',
|
|
67
|
+
'navigator',
|
|
68
|
+
'history',
|
|
69
|
+
'location',
|
|
70
|
+
'alert',
|
|
71
|
+
'confirm',
|
|
72
|
+
'prompt',
|
|
73
|
+
'undefined',
|
|
74
|
+
'null',
|
|
75
|
+
'true',
|
|
76
|
+
'false',
|
|
77
|
+
'NaN',
|
|
78
|
+
'Infinity',
|
|
79
|
+
'React',
|
|
80
|
+
]);
|
|
81
|
+
/**
|
|
82
|
+
* Collect "stable" identifier names inside the enclosing function:
|
|
83
|
+
* - setters from `const [x, setX] = useState(...)`
|
|
84
|
+
* - dispatch from `const [state, dispatch] = useReducer(...)`
|
|
85
|
+
* - refs from `const foo = useRef(...)`
|
|
86
|
+
*
|
|
87
|
+
* These are guaranteed stable across renders and don't need to appear
|
|
88
|
+
* in dependency arrays.
|
|
89
|
+
*/
|
|
90
|
+
function collectStableNames(root) {
|
|
91
|
+
const setters = new Set();
|
|
92
|
+
const refs = new Set();
|
|
93
|
+
for (const decl of root.getDescendantsOfKind(SyntaxKind.VariableDeclaration)) {
|
|
94
|
+
const init = decl.getInitializer();
|
|
95
|
+
if (!init || !Node.isCallExpression(init))
|
|
96
|
+
continue;
|
|
97
|
+
const callee = init.getExpression().getText();
|
|
98
|
+
const calleeName = callee.includes('.') ? callee.split('.').pop() : callee;
|
|
99
|
+
const nameNode = decl.getNameNode();
|
|
100
|
+
if (calleeName === 'useState' || calleeName === 'useReducer') {
|
|
101
|
+
// Destructured tuple: const [state, setter] = useState(...)
|
|
102
|
+
if (Node.isArrayBindingPattern(nameNode)) {
|
|
103
|
+
const elements = nameNode.getElements();
|
|
104
|
+
if (elements.length >= 2) {
|
|
105
|
+
const second = elements[1];
|
|
106
|
+
if (Node.isBindingElement(second)) {
|
|
107
|
+
setters.add(second.getNameNode().getText());
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else if (calleeName === 'useRef') {
|
|
113
|
+
if (Node.isIdentifier(nameNode)) {
|
|
114
|
+
refs.add(nameNode.getText());
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return { setters, refs };
|
|
119
|
+
}
|
|
120
|
+
/** Extract identifiers from a deps array literal — returns names only. */
|
|
121
|
+
function extractDepNames(depsExpr) {
|
|
122
|
+
const names = new Set();
|
|
123
|
+
if (!Node.isArrayLiteralExpression(depsExpr))
|
|
124
|
+
return names;
|
|
125
|
+
for (const el of depsExpr.getElements()) {
|
|
126
|
+
if (Node.isIdentifier(el)) {
|
|
127
|
+
names.add(el.getText());
|
|
128
|
+
}
|
|
129
|
+
else if (Node.isPropertyAccessExpression(el)) {
|
|
130
|
+
// Capture the root: `foo.bar.baz` → `foo`
|
|
131
|
+
let cur = el;
|
|
132
|
+
while (Node.isPropertyAccessExpression(cur)) {
|
|
133
|
+
cur = cur.getExpression();
|
|
134
|
+
}
|
|
135
|
+
if (Node.isIdentifier(cur))
|
|
136
|
+
names.add(cur.getText());
|
|
137
|
+
names.add(el.getText()); // also allow exact match
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return names;
|
|
141
|
+
}
|
|
142
|
+
// ── Rule: exhaustive-deps ────────────────────────────────────────────────
|
|
143
|
+
function exhaustiveDeps(ctx) {
|
|
144
|
+
const findings = [];
|
|
145
|
+
for (const call of ctx.sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
146
|
+
const calleeText = call.getExpression().getText();
|
|
147
|
+
const calleeName = calleeText.includes('.') ? calleeText.split('.').pop() : calleeText;
|
|
148
|
+
const isEffect = EFFECT_HOOKS.has(calleeName);
|
|
149
|
+
const isMemo = MEMO_HOOKS.has(calleeName);
|
|
150
|
+
if (!isEffect && !isMemo)
|
|
151
|
+
continue;
|
|
152
|
+
const args = call.getArguments();
|
|
153
|
+
if (args.length < 2)
|
|
154
|
+
continue; // handled by missing-memo-deps
|
|
155
|
+
const fnArg = args[0];
|
|
156
|
+
const depsArg = args[1];
|
|
157
|
+
if (!Node.isArrayLiteralExpression(depsArg))
|
|
158
|
+
continue;
|
|
159
|
+
if (!Node.isArrowFunction(fnArg) && !Node.isFunctionExpression(fnArg))
|
|
160
|
+
continue;
|
|
161
|
+
// Find enclosing React component function to collect stable names
|
|
162
|
+
const enclosingFn = call.getFirstAncestorByKind(SyntaxKind.FunctionDeclaration) ||
|
|
163
|
+
call.getFirstAncestorByKind(SyntaxKind.ArrowFunction) ||
|
|
164
|
+
call.getFirstAncestorByKind(SyntaxKind.FunctionExpression);
|
|
165
|
+
if (!enclosingFn)
|
|
166
|
+
continue;
|
|
167
|
+
const { setters, refs } = collectStableNames(enclosingFn);
|
|
168
|
+
const depNames = extractDepNames(depsArg);
|
|
169
|
+
const body = fnArg.getBody();
|
|
170
|
+
if (!body)
|
|
171
|
+
continue;
|
|
172
|
+
// Collect identifiers defined INSIDE the hook body itself — they are local.
|
|
173
|
+
// Must cover: const x = ..., const { a, b } = obj, const [x, y] = arr.
|
|
174
|
+
const locallyDeclared = new Set();
|
|
175
|
+
for (const decl of body.getDescendantsOfKind(SyntaxKind.VariableDeclaration)) {
|
|
176
|
+
const nameNode = decl.getNameNode();
|
|
177
|
+
if (Node.isIdentifier(nameNode)) {
|
|
178
|
+
locallyDeclared.add(nameNode.getText());
|
|
179
|
+
}
|
|
180
|
+
else if (Node.isObjectBindingPattern(nameNode) || Node.isArrayBindingPattern(nameNode)) {
|
|
181
|
+
for (const el of nameNode.getDescendantsOfKind(SyntaxKind.BindingElement)) {
|
|
182
|
+
const n = el.getNameNode();
|
|
183
|
+
if (Node.isIdentifier(n))
|
|
184
|
+
locallyDeclared.add(n.getText());
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
for (const param of fnArg.getParameters()) {
|
|
189
|
+
locallyDeclared.add(param.getName());
|
|
190
|
+
}
|
|
191
|
+
const missing = new Set();
|
|
192
|
+
for (const id of body.getDescendantsOfKind(SyntaxKind.Identifier)) {
|
|
193
|
+
const name = id.getText();
|
|
194
|
+
if (GLOBAL_NAMES.has(name))
|
|
195
|
+
continue;
|
|
196
|
+
if (REACT_HOOK_NAMES.has(name))
|
|
197
|
+
continue;
|
|
198
|
+
if (setters.has(name))
|
|
199
|
+
continue;
|
|
200
|
+
if (refs.has(name))
|
|
201
|
+
continue;
|
|
202
|
+
if (locallyDeclared.has(name))
|
|
203
|
+
continue;
|
|
204
|
+
if (depNames.has(name))
|
|
205
|
+
continue;
|
|
206
|
+
// Skip identifiers that are the property name in a PropertyAccessExpression
|
|
207
|
+
// (we only care about the root object, which is the expression side)
|
|
208
|
+
const parent = id.getParent();
|
|
209
|
+
if (parent && Node.isPropertyAccessExpression(parent) && parent.getNameNode() === id)
|
|
210
|
+
continue;
|
|
211
|
+
// Skip property assignment keys (but NOT shorthand — shorthand IS a read of the identifier)
|
|
212
|
+
if (parent && Node.isPropertyAssignment(parent) && parent.getNameNode() === id)
|
|
213
|
+
continue;
|
|
214
|
+
// Note: shorthand property assignments like `{ userId }` inside a hook body
|
|
215
|
+
// ARE reads of `userId` and must be checked — do NOT skip them.
|
|
216
|
+
// Skip import specifiers / binding elements (declarations, not references)
|
|
217
|
+
if (parent && (Node.isImportSpecifier(parent) || Node.isBindingElement(parent)))
|
|
218
|
+
continue;
|
|
219
|
+
// Skip type references
|
|
220
|
+
if (parent && Node.isTypeReference(parent))
|
|
221
|
+
continue;
|
|
222
|
+
// Skip function/variable declaration names
|
|
223
|
+
if (parent && Node.isVariableDeclaration(parent) && parent.getNameNode() === id)
|
|
224
|
+
continue;
|
|
225
|
+
if (parent && Node.isFunctionDeclaration(parent) && parent.getNameNode() === id)
|
|
226
|
+
continue;
|
|
227
|
+
if (parent && Node.isParameterDeclaration(parent))
|
|
228
|
+
continue;
|
|
229
|
+
// Check if it's defined outside the enclosing function (import, module-level)
|
|
230
|
+
const sym = id.getSymbol();
|
|
231
|
+
if (!sym)
|
|
232
|
+
continue;
|
|
233
|
+
const decls = sym.getDeclarations();
|
|
234
|
+
if (decls.length === 0)
|
|
235
|
+
continue;
|
|
236
|
+
let definedInEnclosing = false;
|
|
237
|
+
for (const d of decls) {
|
|
238
|
+
const ancestor = d.getFirstAncestor((a) => a === enclosingFn);
|
|
239
|
+
if (ancestor) {
|
|
240
|
+
definedInEnclosing = true;
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (!definedInEnclosing)
|
|
245
|
+
continue;
|
|
246
|
+
missing.add(name);
|
|
247
|
+
}
|
|
248
|
+
if (missing.size > 0) {
|
|
249
|
+
const hookName = calleeName;
|
|
250
|
+
const names = [...missing].sort().join(', ');
|
|
251
|
+
// Autofix: rebuild the dependency array to include the missing names.
|
|
252
|
+
// Preserve existing deps in their original order, then append missing
|
|
253
|
+
// in sorted order. Marked as "review before applying" because adding
|
|
254
|
+
// deps can introduce render loops when a dep is a non-memoized object.
|
|
255
|
+
const existingTexts = depsArg.getElements().map((e) => e.getText());
|
|
256
|
+
const missingSorted = [...missing].sort();
|
|
257
|
+
const newDepsText = `[${[...existingTexts, ...missingSorted].join(', ')}]`;
|
|
258
|
+
findings.push(finding('exhaustive-deps', 'warning', 'bug', `${hookName} references ${names} but ${missing.size === 1 ? 'it is' : 'they are'} missing from the dependency array — will use stale ${missing.size === 1 ? 'value' : 'values'} across renders`, ctx.filePath, depsArg.getStartLineNumber(), 1, {
|
|
259
|
+
suggestion: `Add ${names} to the dependency array, or move ${missing.size === 1 ? 'it' : 'them'} out of the enclosing closure`,
|
|
260
|
+
autofix: {
|
|
261
|
+
type: 'replace',
|
|
262
|
+
span: nodeSpan(depsArg, ctx.filePath),
|
|
263
|
+
replacement: newDepsText,
|
|
264
|
+
description: `Add ${names} to the dependency array — REVIEW before applying: adding a non-memoized object/function dep can trigger a render loop`,
|
|
265
|
+
},
|
|
266
|
+
}));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return findings;
|
|
270
|
+
}
|
|
271
|
+
// ── Rule: ref-in-deps ────────────────────────────────────────────────────
|
|
272
|
+
// A ref created with useRef() is stable across renders — putting it in a
|
|
273
|
+
// dependency array is pointless noise and usually indicates a misunderstanding.
|
|
274
|
+
function refInDeps(ctx) {
|
|
275
|
+
const findings = [];
|
|
276
|
+
for (const call of ctx.sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
277
|
+
const calleeText = call.getExpression().getText();
|
|
278
|
+
const calleeName = calleeText.includes('.') ? calleeText.split('.').pop() : calleeText;
|
|
279
|
+
if (!EFFECT_HOOKS.has(calleeName) && !MEMO_HOOKS.has(calleeName))
|
|
280
|
+
continue;
|
|
281
|
+
const args = call.getArguments();
|
|
282
|
+
if (args.length < 2)
|
|
283
|
+
continue;
|
|
284
|
+
const depsArg = args[1];
|
|
285
|
+
if (!Node.isArrayLiteralExpression(depsArg))
|
|
286
|
+
continue;
|
|
287
|
+
const enclosingFn = call.getFirstAncestorByKind(SyntaxKind.FunctionDeclaration) ||
|
|
288
|
+
call.getFirstAncestorByKind(SyntaxKind.ArrowFunction) ||
|
|
289
|
+
call.getFirstAncestorByKind(SyntaxKind.FunctionExpression);
|
|
290
|
+
if (!enclosingFn)
|
|
291
|
+
continue;
|
|
292
|
+
const { refs } = collectStableNames(enclosingFn);
|
|
293
|
+
// Collect the ref names present in the deps array, then emit one finding
|
|
294
|
+
// per ref with an autofix that rewrites the deps array without ANY of the
|
|
295
|
+
// refs. That way applying the first autofix also resolves the others,
|
|
296
|
+
// and we avoid emitting stale "remove X" fixes after the user accepted one.
|
|
297
|
+
const depElements = depsArg.getElements();
|
|
298
|
+
const refElementsInDeps = depElements.filter((el) => Node.isIdentifier(el) && refs.has(el.getText()));
|
|
299
|
+
if (refElementsInDeps.length === 0)
|
|
300
|
+
continue;
|
|
301
|
+
const refNamesInDeps = new Set(refElementsInDeps.map((e) => e.getText()));
|
|
302
|
+
const filteredElements = depElements.filter((el) => !(Node.isIdentifier(el) && refNamesInDeps.has(el.getText())));
|
|
303
|
+
const newDepsText = `[${filteredElements.map((e) => e.getText()).join(', ')}]`;
|
|
304
|
+
for (const el of refElementsInDeps) {
|
|
305
|
+
findings.push(finding('ref-in-deps', 'warning', 'pattern', `'${el.getText()}' is a ref from useRef — refs are stable across renders, so including them in a dependency array has no effect`, ctx.filePath, el.getStartLineNumber(), 1, {
|
|
306
|
+
suggestion: `Remove '${el.getText()}' from the dependency array. If you want to react to ref.current changes, you need a different pattern (state or a callback ref).`,
|
|
307
|
+
autofix: {
|
|
308
|
+
type: 'replace',
|
|
309
|
+
span: nodeSpan(depsArg, ctx.filePath),
|
|
310
|
+
replacement: newDepsText,
|
|
311
|
+
description: `Remove ref(s) from the dependency array`,
|
|
312
|
+
},
|
|
313
|
+
}));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return findings;
|
|
317
|
+
}
|
|
318
|
+
// ── Rule: state-derived-from-props ───────────────────────────────────────
|
|
319
|
+
// `const [x, setX] = useState(props.y)` — classic stale-state antipattern.
|
|
320
|
+
// The state is initialized from props once, then drifts if props.y changes.
|
|
321
|
+
function stateDerivedFromProps(ctx) {
|
|
322
|
+
const findings = [];
|
|
323
|
+
for (const call of ctx.sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
324
|
+
const calleeText = call.getExpression().getText();
|
|
325
|
+
const calleeName = calleeText.includes('.') ? calleeText.split('.').pop() : calleeText;
|
|
326
|
+
if (calleeName !== 'useState')
|
|
327
|
+
continue;
|
|
328
|
+
const args = call.getArguments();
|
|
329
|
+
if (args.length === 0)
|
|
330
|
+
continue;
|
|
331
|
+
const initArg = args[0];
|
|
332
|
+
// Pattern A: useState(props.y) / useState(props.y.z)
|
|
333
|
+
// Pattern B: useState(someProp) where someProp is a destructured prop
|
|
334
|
+
let flagged = false;
|
|
335
|
+
let label = '';
|
|
336
|
+
if (Node.isPropertyAccessExpression(initArg)) {
|
|
337
|
+
let root = initArg;
|
|
338
|
+
while (Node.isPropertyAccessExpression(root)) {
|
|
339
|
+
root = root.getExpression();
|
|
340
|
+
}
|
|
341
|
+
if (Node.isIdentifier(root) && root.getText() === 'props') {
|
|
342
|
+
flagged = true;
|
|
343
|
+
label = initArg.getText();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
else if (Node.isIdentifier(initArg)) {
|
|
347
|
+
// Check if this identifier is a destructured prop
|
|
348
|
+
const name = initArg.getText();
|
|
349
|
+
const enclosingFn = call.getFirstAncestorByKind(SyntaxKind.FunctionDeclaration) ||
|
|
350
|
+
call.getFirstAncestorByKind(SyntaxKind.ArrowFunction) ||
|
|
351
|
+
call.getFirstAncestorByKind(SyntaxKind.FunctionExpression);
|
|
352
|
+
if (enclosingFn) {
|
|
353
|
+
const params = enclosingFn.getParameters();
|
|
354
|
+
// Component signature: `function Foo({ y, z })` — first param is an object binding pattern
|
|
355
|
+
if (params.length > 0) {
|
|
356
|
+
const firstParam = params[0];
|
|
357
|
+
const paramName = firstParam.getNameNode();
|
|
358
|
+
if (Node.isObjectBindingPattern(paramName)) {
|
|
359
|
+
for (const el of paramName.getElements()) {
|
|
360
|
+
if (el.getName() === name) {
|
|
361
|
+
flagged = true;
|
|
362
|
+
label = `destructured prop '${name}'`;
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (flagged) {
|
|
371
|
+
findings.push(finding('state-derived-from-props', 'warning', 'bug', `useState initialized from ${label} — state will not update when the prop changes, causing stale UI`, ctx.filePath, call.getStartLineNumber(), 1, {
|
|
372
|
+
suggestion: 'Use the prop directly, derive with useMemo, or use a key prop on the parent to force remount when the source changes',
|
|
373
|
+
}));
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return findings;
|
|
377
|
+
}
|
|
378
|
+
// ── Exported React Hooks Rules ───────────────────────────────────────────
|
|
379
|
+
export const reactHooksRules = [exhaustiveDeps, refInDeps, stateDerivedFromProps];
|
|
380
|
+
//# sourceMappingURL=react-hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react-hooks.js","sourceRoot":"","sources":["../../src/rules/react-hooks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security v5 — XSS attribute surface, javascript: URLs, crypto misuse.
|
|
3
|
+
*
|
|
4
|
+
* Note: ssrf-fetch and sql-string-concat are handled automatically by the
|
|
5
|
+
* shared taint engine via new sinks registered in taint-types.ts (Wave 0).
|
|
6
|
+
* This file holds only the rules that can't be expressed as a sink category.
|
|
7
|
+
*/
|
|
8
|
+
import type { ReviewFinding, RuleContext } from '../types.js';
|
|
9
|
+
declare function xssHrefJavascript(ctx: RuleContext): ReviewFinding[];
|
|
10
|
+
export declare const securityV5Rules: (typeof xssHrefJavascript)[];
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security v5 — XSS attribute surface, javascript: URLs, crypto misuse.
|
|
3
|
+
*
|
|
4
|
+
* Note: ssrf-fetch and sql-string-concat are handled automatically by the
|
|
5
|
+
* shared taint engine via new sinks registered in taint-types.ts (Wave 0).
|
|
6
|
+
* This file holds only the rules that can't be expressed as a sink category.
|
|
7
|
+
*/
|
|
8
|
+
import { Node, SyntaxKind } from 'ts-morph';
|
|
9
|
+
import { finding, nodeSpan } from './utils.js';
|
|
10
|
+
// ── Rule: xss-href-javascript ────────────────────────────────────────────
|
|
11
|
+
// JSX href / src attribute set to a string starting with `javascript:`,
|
|
12
|
+
// or to an expression whose value can be traced to a literal javascript: URL.
|
|
13
|
+
function xssHrefJavascript(ctx) {
|
|
14
|
+
const findings = [];
|
|
15
|
+
for (const attr of ctx.sourceFile.getDescendantsOfKind(SyntaxKind.JsxAttribute)) {
|
|
16
|
+
const name = attr.getNameNode().getText();
|
|
17
|
+
if (name !== 'href' && name !== 'src' && name !== 'action' && name !== 'formAction')
|
|
18
|
+
continue;
|
|
19
|
+
const init = attr.getInitializer();
|
|
20
|
+
if (!init)
|
|
21
|
+
continue;
|
|
22
|
+
// Literal string attribute: href="javascript:alert(1)"
|
|
23
|
+
// Autofix: replace the string literal with "#". That's the standard
|
|
24
|
+
// "inert link" marker — safe default, user can change to a real URL later.
|
|
25
|
+
if (Node.isStringLiteral(init)) {
|
|
26
|
+
const value = init.getLiteralValue();
|
|
27
|
+
if (/^\s*javascript:/i.test(value)) {
|
|
28
|
+
findings.push(finding('xss-href-javascript', 'error', 'bug', `${name} uses a javascript: URL — executes arbitrary script on click`, ctx.filePath, attr.getStartLineNumber(), 1, {
|
|
29
|
+
suggestion: 'Replace javascript: URL with an onClick handler or a safe href',
|
|
30
|
+
autofix: {
|
|
31
|
+
type: 'replace',
|
|
32
|
+
span: nodeSpan(init, ctx.filePath),
|
|
33
|
+
replacement: '"#"',
|
|
34
|
+
description: 'Replace javascript: URL with inert "#" — wire an onClick handler for real behavior',
|
|
35
|
+
},
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
// Expression attribute: href={someVar} — flag when the expression is a
|
|
41
|
+
// template literal or string literal starting with javascript:
|
|
42
|
+
if (Node.isJsxExpression(init)) {
|
|
43
|
+
const expr = init.getExpression();
|
|
44
|
+
if (!expr)
|
|
45
|
+
continue;
|
|
46
|
+
if (Node.isStringLiteral(expr)) {
|
|
47
|
+
if (/^\s*javascript:/i.test(expr.getLiteralValue())) {
|
|
48
|
+
findings.push(finding('xss-href-javascript', 'error', 'bug', `${name} uses a javascript: URL — executes arbitrary script on click`, ctx.filePath, attr.getStartLineNumber(), 1, {
|
|
49
|
+
suggestion: 'Replace javascript: URL with an onClick handler or a safe href',
|
|
50
|
+
autofix: {
|
|
51
|
+
type: 'replace',
|
|
52
|
+
span: nodeSpan(expr, ctx.filePath),
|
|
53
|
+
replacement: '"#"',
|
|
54
|
+
description: 'Replace javascript: URL with inert "#"',
|
|
55
|
+
},
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else if (Node.isTemplateExpression(expr) || Node.isNoSubstitutionTemplateLiteral(expr)) {
|
|
60
|
+
const text = expr.getText();
|
|
61
|
+
if (/^[`'"]\s*javascript:/i.test(text)) {
|
|
62
|
+
findings.push(finding('xss-href-javascript', 'error', 'bug', `${name} is a template literal starting with javascript: — executes arbitrary script on click`, ctx.filePath, attr.getStartLineNumber(), 1, { suggestion: 'Replace javascript: URL with an onClick handler or a safe href' }));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return findings;
|
|
68
|
+
}
|
|
69
|
+
// ── Rule: crypto-iv-reuse ────────────────────────────────────────────────
|
|
70
|
+
// createCipheriv(algo, key, iv) with a literal/constant IV.
|
|
71
|
+
// A reused IV on GCM is catastrophic; on CBC it's merely broken.
|
|
72
|
+
function isLiteralOrConstant(node) {
|
|
73
|
+
if (!node)
|
|
74
|
+
return false;
|
|
75
|
+
if (Node.isStringLiteral(node))
|
|
76
|
+
return true;
|
|
77
|
+
if (Node.isNumericLiteral(node))
|
|
78
|
+
return true;
|
|
79
|
+
if (Node.isNoSubstitutionTemplateLiteral(node))
|
|
80
|
+
return true;
|
|
81
|
+
// Buffer.from("constant"), Buffer.from([1,2,3]), Buffer.alloc(16)
|
|
82
|
+
if (Node.isCallExpression(node)) {
|
|
83
|
+
const callee = node.getExpression().getText();
|
|
84
|
+
if (callee === 'Buffer.from' || callee === 'Buffer.alloc') {
|
|
85
|
+
const arg = node.getArguments()[0];
|
|
86
|
+
if (!arg)
|
|
87
|
+
return false;
|
|
88
|
+
// Buffer.alloc(16) — all zeros, always unsafe
|
|
89
|
+
if (callee === 'Buffer.alloc' && Node.isNumericLiteral(arg))
|
|
90
|
+
return true;
|
|
91
|
+
if (Node.isStringLiteral(arg))
|
|
92
|
+
return true;
|
|
93
|
+
if (Node.isNumericLiteral(arg))
|
|
94
|
+
return true;
|
|
95
|
+
if (Node.isArrayLiteralExpression(arg)) {
|
|
96
|
+
return arg.getElements().every((el) => Node.isNumericLiteral(el));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
/** Resolve a variable reference to its initializer if it is a top-level const. */
|
|
103
|
+
function resolveConstInitializer(node) {
|
|
104
|
+
if (!Node.isIdentifier(node))
|
|
105
|
+
return undefined;
|
|
106
|
+
const decls = node.getSymbol()?.getDeclarations() ?? [];
|
|
107
|
+
for (const d of decls) {
|
|
108
|
+
if (Node.isVariableDeclaration(d)) {
|
|
109
|
+
// Must be a `const` declaration
|
|
110
|
+
const statement = d.getVariableStatement();
|
|
111
|
+
if (statement && statement.getDeclarationKind() === 'const') {
|
|
112
|
+
return d.getInitializer();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
function cryptoIvReuse(ctx) {
|
|
119
|
+
const findings = [];
|
|
120
|
+
for (const call of ctx.sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
121
|
+
const callee = call.getExpression().getText();
|
|
122
|
+
if (callee !== 'createCipheriv' && callee !== 'crypto.createCipheriv')
|
|
123
|
+
continue;
|
|
124
|
+
const args = call.getArguments();
|
|
125
|
+
if (args.length < 3)
|
|
126
|
+
continue;
|
|
127
|
+
const ivArg = args[2];
|
|
128
|
+
let unsafe = isLiteralOrConstant(ivArg);
|
|
129
|
+
let reason = 'IV is a compile-time constant';
|
|
130
|
+
if (!unsafe && Node.isIdentifier(ivArg)) {
|
|
131
|
+
const init = resolveConstInitializer(ivArg);
|
|
132
|
+
if (init && isLiteralOrConstant(init)) {
|
|
133
|
+
unsafe = true;
|
|
134
|
+
reason = `IV resolves to constant '${ivArg.getText()}' (declared as const with a literal initializer)`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (unsafe) {
|
|
138
|
+
findings.push(finding('crypto-iv-reuse', 'error', 'bug', `createCipheriv called with a constant IV — ${reason}. Reusing an IV on AES-GCM leaks the key stream; on CBC it enables chosen-plaintext attacks.`, ctx.filePath, call.getStartLineNumber(), 1, {
|
|
139
|
+
suggestion: 'Generate a fresh IV per encryption: `const iv = crypto.randomBytes(ivLength)` and prepend it to the ciphertext for decryption',
|
|
140
|
+
}));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return findings;
|
|
144
|
+
}
|
|
145
|
+
// ── Rule: crypto-weak-kdf ────────────────────────────────────────────────
|
|
146
|
+
// pbkdf2(password, salt, iterations, keylen, digest) with iterations below
|
|
147
|
+
// the current OWASP minimum (600_000 for SHA-256 as of 2023, 210_000 for
|
|
148
|
+
// SHA-512). We use 100_000 as a hard floor to avoid flagging historical
|
|
149
|
+
// but still-passing callers; anything lower is indefensible.
|
|
150
|
+
const PBKDF2_MIN_ITERATIONS = 100_000;
|
|
151
|
+
function cryptoWeakKdf(ctx) {
|
|
152
|
+
const findings = [];
|
|
153
|
+
for (const call of ctx.sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
154
|
+
const callee = call.getExpression().getText();
|
|
155
|
+
if (callee !== 'pbkdf2' &&
|
|
156
|
+
callee !== 'pbkdf2Sync' &&
|
|
157
|
+
callee !== 'crypto.pbkdf2' &&
|
|
158
|
+
callee !== 'crypto.pbkdf2Sync') {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const args = call.getArguments();
|
|
162
|
+
if (args.length < 3)
|
|
163
|
+
continue;
|
|
164
|
+
const iterArg = args[2];
|
|
165
|
+
let iterations;
|
|
166
|
+
if (Node.isNumericLiteral(iterArg)) {
|
|
167
|
+
iterations = Number(iterArg.getLiteralValue());
|
|
168
|
+
}
|
|
169
|
+
else if (Node.isIdentifier(iterArg)) {
|
|
170
|
+
const init = resolveConstInitializer(iterArg);
|
|
171
|
+
if (init && Node.isNumericLiteral(init)) {
|
|
172
|
+
iterations = Number(init.getLiteralValue());
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (iterations !== undefined && iterations < PBKDF2_MIN_ITERATIONS) {
|
|
176
|
+
// Autofix: replace the iterations literal with 600_000 (OWASP 2023 min
|
|
177
|
+
// for SHA-256). Only when the arg is a direct literal — don't try to
|
|
178
|
+
// rewrite a referenced constant, that requires resolving and patching
|
|
179
|
+
// the declaration.
|
|
180
|
+
const canAutofix = Node.isNumericLiteral(iterArg);
|
|
181
|
+
findings.push(finding('crypto-weak-kdf', 'error', 'bug', `pbkdf2 called with only ${iterations} iterations — well below the OWASP minimum (600,000 for SHA-256, 210,000 for SHA-512). This key derivation can be brute-forced.`, ctx.filePath, call.getStartLineNumber(), 1, {
|
|
182
|
+
suggestion: 'Use argon2id via `argon2` or increase iterations to at least 600,000 for SHA-256 / 210,000 for SHA-512',
|
|
183
|
+
...(canAutofix
|
|
184
|
+
? {
|
|
185
|
+
autofix: {
|
|
186
|
+
type: 'replace',
|
|
187
|
+
span: nodeSpan(iterArg, ctx.filePath),
|
|
188
|
+
replacement: '600_000',
|
|
189
|
+
description: 'Bump iteration count to 600,000 (OWASP 2023 minimum for SHA-256)',
|
|
190
|
+
},
|
|
191
|
+
}
|
|
192
|
+
: {}),
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return findings;
|
|
197
|
+
}
|
|
198
|
+
// ── Exported Security v5 Rules ───────────────────────────────────────────
|
|
199
|
+
export const securityV5Rules = [xssHrefJavascript, cryptoIvReuse, cryptoWeakKdf];
|
|
200
|
+
//# sourceMappingURL=security-v5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security-v5.js","sourceRoot":"","sources":["../../src/rules/security-v5.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE5C,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE/C,4EAA4E;AAC5E,wEAAwE;AACxE,8EAA8E;AAE9E,SAAS,iBAAiB,CAAC,GAAgB;IACzC,MAAM,QAAQ,GAAoB,EAAE,CAAC;IAErC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,oBAAoB,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAChF,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,CAAC;QAC1C,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,YAAY;YAAE,SAAS;QAE9F,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACnC,IAAI,CAAC,IAAI;YAAE,SAAS;QAEpB,uDAAuD;QACvD,oEAAoE;QACpE,2EAA2E;QAC3E,IAAI,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;YACrC,IAAI,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;gBACnC,QAAQ,CAAC,IAAI,CACX,OAAO,CACL,qBAAqB,EACrB,OAAO,EACP,KAAK,EACL,GAAG,IAAI,8DAA8D,EACrE,GAAG,CAAC,QAAQ,EACZ,IAAI,CAAC,kBAAkB,EAAE,EACzB,CAAC,EACD;oBACE,UAAU,EAAE,gEAAgE;oBAC5E,OAAO,EAAE;wBACP,IAAI,EAAE,SAAS;wBACf,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC;wBAClC,WAAW,EAAE,KAAK;wBAClB,WAAW,EAAE,oFAAoF;qBAClG;iBACF,CACF,CACF,CAAC;YACJ,CAAC;YACD,SAAS;QACX,CAAC;QAED,uEAAuE;QACvE,+DAA+D;QAC/D,IAAI,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;YAClC,IAAI,CAAC,IAAI;gBAAE,SAAS;YAEpB,IAAI,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC/B,IAAI,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC,EAAE,CAAC;oBACpD,QAAQ,CAAC,IAAI,CACX,OAAO,CACL,qBAAqB,EACrB,OAAO,EACP,KAAK,EACL,GAAG,IAAI,8DAA8D,EACrE,GAAG,CAAC,QAAQ,EACZ,IAAI,CAAC,kBAAkB,EAAE,EACzB,CAAC,EACD;wBACE,UAAU,EAAE,gEAAgE;wBAC5E,OAAO,EAAE;4BACP,IAAI,EAAE,SAAS;4BACf,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC;4BAClC,WAAW,EAAE,KAAK;4BAClB,WAAW,EAAE,wCAAwC;yBACtD;qBACF,CACF,CACF,CAAC;gBACJ,CAAC;YACH,CAAC;iBAAM,IAAI,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,+BAA+B,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzF,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;gBAC5B,IAAI,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBACvC,QAAQ,CAAC,IAAI,CACX,OAAO,CACL,qBAAqB,EACrB,OAAO,EACP,KAAK,EACL,GAAG,IAAI,uFAAuF,EAC9F,GAAG,CAAC,QAAQ,EACZ,IAAI,CAAC,kBAAkB,EAAE,EACzB,CAAC,EACD,EAAE,UAAU,EAAE,gEAAgE,EAAE,CACjF,CACF,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,4EAA4E;AAC5E,4DAA4D;AAC5D,iEAAiE;AAEjE,SAAS,mBAAmB,CAAC,IAAsB;IACjD,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5C,IAAI,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,IAAI,IAAI,CAAC,+BAA+B,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5D,kEAAkE;IAClE,IAAI,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,CAAC;QAC9C,IAAI,MAAM,KAAK,aAAa,IAAI,MAAM,KAAK,cAAc,EAAE,CAAC;YAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC;YACnC,IAAI,CAAC,GAAG;gBAAE,OAAO,KAAK,CAAC;YACvB,8CAA8C;YAC9C,IAAI,MAAM,KAAK,cAAc,IAAI,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC;gBAAE,OAAO,IAAI,CAAC;YACzE,IAAI,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC;gBAAE,OAAO,IAAI,CAAC;YAC3C,IAAI,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC;gBAAE,OAAO,IAAI,CAAC;YAC5C,IAAI,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC,EAAE,CAAC;gBACvC,OAAO,GAAG,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC,CAAC;YACpE,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,kFAAkF;AAClF,SAAS,uBAAuB,CAAC,IAAU;IACzC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC;IACxD,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,IAAI,CAAC,qBAAqB,CAAC,CAAC,CAAC,EAAE,CAAC;YAClC,gCAAgC;YAChC,MAAM,SAAS,GAAG,CAAC,CAAC,oBAAoB,EAAE,CAAC;YAC3C,IAAI,SAAS,IAAI,SAAS,CAAC,kBAAkB,EAAE,KAAK,OAAO,EAAE,CAAC;gBAC5D,OAAO,CAAC,CAAC,cAAc,EAAE,CAAC;YAC5B,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,aAAa,CAAC,GAAgB;IACrC,MAAM,QAAQ,GAAoB,EAAE,CAAC;IAErC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,oBAAoB,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QAClF,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,CAAC;QAC9C,IAAI,MAAM,KAAK,gBAAgB,IAAI,MAAM,KAAK,uBAAuB;YAAE,SAAS;QAEhF,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QACjC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,SAAS;QAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAEtB,IAAI,MAAM,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,MAAM,GAAG,+BAA+B,CAAC;QAE7C,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;YACxC,MAAM,IAAI,GAAG,uBAAuB,CAAC,KAAK,CAAC,CAAC;YAC5C,IAAI,IAAI,IAAI,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;gBACtC,MAAM,GAAG,IAAI,CAAC;gBACd,MAAM,GAAG,4BAA4B,KAAK,CAAC,OAAO,EAAE,kDAAkD,CAAC;YACzG,CAAC;QACH,CAAC;QAED,IAAI,MAAM,EAAE,CAAC;YACX,QAAQ,CAAC,IAAI,CACX,OAAO,CACL,iBAAiB,EACjB,OAAO,EACP,KAAK,EACL,8CAA8C,MAAM,8FAA8F,EAClJ,GAAG,CAAC,QAAQ,EACZ,IAAI,CAAC,kBAAkB,EAAE,EACzB,CAAC,EACD;gBACE,UAAU,EACR,+HAA+H;aAClI,CACF,CACF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,4EAA4E;AAC5E,2EAA2E;AAC3E,yEAAyE;AACzE,wEAAwE;AACxE,6DAA6D;AAE7D,MAAM,qBAAqB,GAAG,OAAO,CAAC;AAEtC,SAAS,aAAa,CAAC,GAAgB;IACrC,MAAM,QAAQ,GAAoB,EAAE,CAAC;IAErC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,oBAAoB,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QAClF,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,CAAC;QAC9C,IACE,MAAM,KAAK,QAAQ;YACnB,MAAM,KAAK,YAAY;YACvB,MAAM,KAAK,eAAe;YAC1B,MAAM,KAAK,mBAAmB,EAC9B,CAAC;YACD,SAAS;QACX,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QACjC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,SAAS;QAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAExB,IAAI,UAA8B,CAAC;QAEnC,IAAI,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;YACnC,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;QACjD,CAAC;aAAM,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;YAC9C,IAAI,IAAI,IAAI,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;gBACxC,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC;YAC9C,CAAC;QACH,CAAC;QAED,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,GAAG,qBAAqB,EAAE,CAAC;YACnE,uEAAuE;YACvE,qEAAqE;YACrE,sEAAsE;YACtE,mBAAmB;YACnB,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;YAClD,QAAQ,CAAC,IAAI,CACX,OAAO,CACL,iBAAiB,EACjB,OAAO,EACP,KAAK,EACL,2BAA2B,UAAU,iIAAiI,EACtK,GAAG,CAAC,QAAQ,EACZ,IAAI,CAAC,kBAAkB,EAAE,EACzB,CAAC,EACD;gBACE,UAAU,EACR,wGAAwG;gBAC1G,GAAG,CAAC,UAAU;oBACZ,CAAC,CAAC;wBACE,OAAO,EAAE;4BACP,IAAI,EAAE,SAAkB;4BACxB,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,QAAQ,CAAC;4BACrC,WAAW,EAAE,SAAS;4BACtB,WAAW,EAAE,kEAAkE;yBAChF;qBACF;oBACH,CAAC,CAAC,EAAE,CAAC;aACR,CACF,CACF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,4EAA4E;AAE5E,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,iBAAiB,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC"}
|
package/dist/rules/utils.d.ts
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
* Shared helpers for review rules — eliminates duplication of span() and finding()
|
|
3
3
|
* across base.ts, react.ts, nextjs.ts, express.ts, security.ts, vue.ts, dead-logic.ts.
|
|
4
4
|
*/
|
|
5
|
+
import type { Node } from 'ts-morph';
|
|
5
6
|
import type { ReviewFinding, SourceSpan } from '../types.js';
|
|
6
7
|
export declare function span(file: string, line: number, col?: number, endLine?: number, endCol?: number): SourceSpan;
|
|
8
|
+
/**
|
|
9
|
+
* Compute a precise SourceSpan for a ts-morph Node, using 1-based line/column.
|
|
10
|
+
* Used by autofix rules that need character-accurate replacement coordinates.
|
|
11
|
+
*/
|
|
12
|
+
export declare function nodeSpan(node: Node, file: string): SourceSpan;
|
|
13
|
+
/**
|
|
14
|
+
* Compute a SourceSpan for the insertion point immediately before a node.
|
|
15
|
+
* For use with FixAction.type === 'insert-before'.
|
|
16
|
+
*/
|
|
17
|
+
export declare function insertBeforeSpan(node: Node, file: string): SourceSpan;
|
|
18
|
+
/**
|
|
19
|
+
* Compute a SourceSpan for the insertion point immediately after a node.
|
|
20
|
+
* For use with FixAction.type === 'insert-after'.
|
|
21
|
+
*/
|
|
22
|
+
export declare function insertAfterSpan(node: Node, file: string): SourceSpan;
|
|
7
23
|
export declare function finding(ruleId: string, severity: 'error' | 'warning' | 'info', category: ReviewFinding['category'], message: string, file: string, line: number, col?: number, extra?: Partial<ReviewFinding>): ReviewFinding;
|