sourceverity 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +188 -0
- package/bin/sourceverity.js +12 -0
- package/dist/analysis/controlFlow.d.ts +10 -0
- package/dist/analysis/controlFlow.d.ts.map +1 -0
- package/dist/analysis/controlFlow.js +278 -0
- package/dist/analysis/controlFlow.js.map +1 -0
- package/dist/analysis/frameworks.d.ts +11 -0
- package/dist/analysis/frameworks.d.ts.map +1 -0
- package/dist/analysis/frameworks.js +189 -0
- package/dist/analysis/frameworks.js.map +1 -0
- package/dist/analysis/functions.d.ts +27 -0
- package/dist/analysis/functions.d.ts.map +1 -0
- package/dist/analysis/functions.js +628 -0
- package/dist/analysis/functions.js.map +1 -0
- package/dist/analysis/promiseFlow.d.ts +44 -0
- package/dist/analysis/promiseFlow.d.ts.map +1 -0
- package/dist/analysis/promiseFlow.js +261 -0
- package/dist/analysis/promiseFlow.js.map +1 -0
- package/dist/analysis/values.d.ts +24 -0
- package/dist/analysis/values.d.ts.map +1 -0
- package/dist/analysis/values.js +189 -0
- package/dist/analysis/values.js.map +1 -0
- package/dist/baseline/manager.d.ts +20 -0
- package/dist/baseline/manager.d.ts.map +1 -0
- package/dist/baseline/manager.js +77 -0
- package/dist/baseline/manager.js.map +1 -0
- package/dist/baseline/types.d.ts +20 -0
- package/dist/baseline/types.d.ts.map +1 -0
- package/dist/baseline/types.js +2 -0
- package/dist/baseline/types.js.map +1 -0
- package/dist/cli/args.d.ts +26 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +126 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/exitCodes.d.ts +8 -0
- package/dist/cli/exitCodes.d.ts.map +1 -0
- package/dist/cli/exitCodes.js +7 -0
- package/dist/cli/exitCodes.js.map +1 -0
- package/dist/cli/help.d.ts +5 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +46 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/main.d.ts +7 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +156 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/core/fingerprint.d.ts +18 -0
- package/dist/core/fingerprint.d.ts.map +1 -0
- package/dist/core/fingerprint.js +26 -0
- package/dist/core/fingerprint.js.map +1 -0
- package/dist/core/paths.d.ts +10 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +30 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/suppressions.d.ts +24 -0
- package/dist/core/suppressions.d.ts.map +1 -0
- package/dist/core/suppressions.js +100 -0
- package/dist/core/suppressions.js.map +1 -0
- package/dist/core/terminal.d.ts +18 -0
- package/dist/core/terminal.d.ts.map +1 -0
- package/dist/core/terminal.js +39 -0
- package/dist/core/terminal.js.map +1 -0
- package/dist/core/types.d.ts +119 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/engine/context.d.ts +16 -0
- package/dist/engine/context.d.ts.map +1 -0
- package/dist/engine/context.js +57 -0
- package/dist/engine/context.js.map +1 -0
- package/dist/engine/registry.d.ts +26 -0
- package/dist/engine/registry.d.ts.map +1 -0
- package/dist/engine/registry.js +38 -0
- package/dist/engine/registry.js.map +1 -0
- package/dist/engine/runner.d.ts +14 -0
- package/dist/engine/runner.d.ts.map +1 -0
- package/dist/engine/runner.js +166 -0
- package/dist/engine/runner.js.map +1 -0
- package/dist/engine/symbols.d.ts +46 -0
- package/dist/engine/symbols.d.ts.map +1 -0
- package/dist/engine/symbols.js +278 -0
- package/dist/engine/symbols.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/reporters/agent.d.ts +45 -0
- package/dist/reporters/agent.d.ts.map +1 -0
- package/dist/reporters/agent.js +40 -0
- package/dist/reporters/agent.js.map +1 -0
- package/dist/reporters/json.d.ts +6 -0
- package/dist/reporters/json.d.ts.map +1 -0
- package/dist/reporters/json.js +7 -0
- package/dist/reporters/json.js.map +1 -0
- package/dist/reporters/sarif.d.ts +6 -0
- package/dist/reporters/sarif.d.ts.map +1 -0
- package/dist/reporters/sarif.js +60 -0
- package/dist/reporters/sarif.js.map +1 -0
- package/dist/reporters/terminal.d.ts +10 -0
- package/dist/reporters/terminal.d.ts.map +1 -0
- package/dist/reporters/terminal.js +105 -0
- package/dist/reporters/terminal.js.map +1 -0
- package/dist/repository/detector.d.ts +6 -0
- package/dist/repository/detector.d.ts.map +1 -0
- package/dist/repository/detector.js +231 -0
- package/dist/repository/detector.js.map +1 -0
- package/dist/repository/fileFilter.d.ts +10 -0
- package/dist/repository/fileFilter.d.ts.map +1 -0
- package/dist/repository/fileFilter.js +78 -0
- package/dist/repository/fileFilter.js.map +1 -0
- package/dist/repository/git.d.ts +7 -0
- package/dist/repository/git.d.ts.map +1 -0
- package/dist/repository/git.js +53 -0
- package/dist/repository/git.js.map +1 -0
- package/dist/repository/tsLoader.d.ts +15 -0
- package/dist/repository/tsLoader.d.ts.map +1 -0
- package/dist/repository/tsLoader.js +27 -0
- package/dist/repository/tsLoader.js.map +1 -0
- package/dist/repository/types.d.ts +38 -0
- package/dist/repository/types.d.ts.map +1 -0
- package/dist/repository/types.js +2 -0
- package/dist/repository/types.js.map +1 -0
- package/dist/rules/async/asyncForeach.d.ts +3 -0
- package/dist/rules/async/asyncForeach.d.ts.map +1 -0
- package/dist/rules/async/asyncForeach.js +76 -0
- package/dist/rules/async/asyncForeach.js.map +1 -0
- package/dist/rules/async/floatingPromise.d.ts +3 -0
- package/dist/rules/async/floatingPromise.d.ts.map +1 -0
- package/dist/rules/async/floatingPromise.js +359 -0
- package/dist/rules/async/floatingPromise.js.map +1 -0
- package/dist/rules/browser/eventListenerCleanup.d.ts +3 -0
- package/dist/rules/browser/eventListenerCleanup.d.ts.map +1 -0
- package/dist/rules/browser/eventListenerCleanup.js +378 -0
- package/dist/rules/browser/eventListenerCleanup.js.map +1 -0
- package/dist/rules/browser/observerCleanup.d.ts +3 -0
- package/dist/rules/browser/observerCleanup.d.ts.map +1 -0
- package/dist/rules/browser/observerCleanup.js +184 -0
- package/dist/rules/browser/observerCleanup.js.map +1 -0
- package/dist/rules/browser/timerCleanup.d.ts +3 -0
- package/dist/rules/browser/timerCleanup.d.ts.map +1 -0
- package/dist/rules/browser/timerCleanup.js +115 -0
- package/dist/rules/browser/timerCleanup.js.map +1 -0
- package/dist/rules/index.d.ts +19 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +41 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/network/fetchStatusUnchecked.d.ts +3 -0
- package/dist/rules/network/fetchStatusUnchecked.d.ts.map +1 -0
- package/dist/rules/network/fetchStatusUnchecked.js +96 -0
- package/dist/rules/network/fetchStatusUnchecked.js.map +1 -0
- package/dist/rules/react/asyncEffectCallback.d.ts +3 -0
- package/dist/rules/react/asyncEffectCallback.d.ts.map +1 -0
- package/dist/rules/react/asyncEffectCallback.js +62 -0
- package/dist/rules/react/asyncEffectCallback.js.map +1 -0
- package/dist/rules/react/derivedStateEffect.d.ts +3 -0
- package/dist/rules/react/derivedStateEffect.d.ts.map +1 -0
- package/dist/rules/react/derivedStateEffect.js +172 -0
- package/dist/rules/react/derivedStateEffect.js.map +1 -0
- package/dist/rules/react/missingEffectCleanup.d.ts +3 -0
- package/dist/rules/react/missingEffectCleanup.d.ts.map +1 -0
- package/dist/rules/react/missingEffectCleanup.js +86 -0
- package/dist/rules/react/missingEffectCleanup.js.map +1 -0
- package/dist/rules/typescript/nonNullAssertionRisk.d.ts +3 -0
- package/dist/rules/typescript/nonNullAssertionRisk.d.ts.map +1 -0
- package/dist/rules/typescript/nonNullAssertionRisk.js +65 -0
- package/dist/rules/typescript/nonNullAssertionRisk.js.map +1 -0
- package/dist/rules/typescript/uncheckedIndexAccess.d.ts +3 -0
- package/dist/rules/typescript/uncheckedIndexAccess.d.ts.map +1 -0
- package/dist/rules/typescript/uncheckedIndexAccess.js +79 -0
- package/dist/rules/typescript/uncheckedIndexAccess.js.map +1 -0
- package/dist/rules/typescript/unsafeUnvalidatedAssertion.d.ts +3 -0
- package/dist/rules/typescript/unsafeUnvalidatedAssertion.d.ts.map +1 -0
- package/dist/rules/typescript/unsafeUnvalidatedAssertion.js +94 -0
- package/dist/rules/typescript/unsafeUnvalidatedAssertion.js.map +1 -0
- package/dist/strictness/evaluator.d.ts +28 -0
- package/dist/strictness/evaluator.d.ts.map +1 -0
- package/dist/strictness/evaluator.js +141 -0
- package/dist/strictness/evaluator.js.map +1 -0
- package/dist/strictness/matrix.d.ts +8 -0
- package/dist/strictness/matrix.d.ts.map +1 -0
- package/dist/strictness/matrix.js +63 -0
- package/dist/strictness/matrix.js.map +1 -0
- package/dist/typescript/programManager.d.ts +18 -0
- package/dist/typescript/programManager.d.ts.map +1 -0
- package/dist/typescript/programManager.js +46 -0
- package/dist/typescript/programManager.js.map +1 -0
- package/dist/typescript/projectResolver.d.ts +17 -0
- package/dist/typescript/projectResolver.d.ts.map +1 -0
- package/dist/typescript/projectResolver.js +75 -0
- package/dist/typescript/projectResolver.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { getNodeSourceRange, resolveReactHook } from "../../engine/symbols.js";
|
|
2
|
+
export const timerCleanupRule = {
|
|
3
|
+
meta: {
|
|
4
|
+
id: "browser/timer-cleanup",
|
|
5
|
+
category: "browser",
|
|
6
|
+
defaultSeverity: "error",
|
|
7
|
+
defaultConfidence: "high",
|
|
8
|
+
description: "Detects timers registered in lifecycle hooks without corresponding cleanup.",
|
|
9
|
+
requiresTypeInformation: false,
|
|
10
|
+
},
|
|
11
|
+
analyze(context) {
|
|
12
|
+
const { sourceFile, checker, ts } = context;
|
|
13
|
+
if (!ts)
|
|
14
|
+
return;
|
|
15
|
+
context.visitNodes((node) => {
|
|
16
|
+
if (!ts.isCallExpression(node))
|
|
17
|
+
return;
|
|
18
|
+
let isLifecycleHook = false;
|
|
19
|
+
if (checker && (resolveReactHook(node, checker, "useEffect") || resolveReactHook(node, checker, "useLayoutEffect"))) {
|
|
20
|
+
isLifecycleHook = true;
|
|
21
|
+
}
|
|
22
|
+
else if (ts.isIdentifier(node.expression) && (node.expression.text === "useEffect" || node.expression.text === "useLayoutEffect")) {
|
|
23
|
+
isLifecycleHook = true;
|
|
24
|
+
}
|
|
25
|
+
if (!isLifecycleHook)
|
|
26
|
+
return;
|
|
27
|
+
const effectCallback = node.arguments[0];
|
|
28
|
+
if (!effectCallback || (!ts.isArrowFunction(effectCallback) && !ts.isFunctionExpression(effectCallback))) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const timers = [];
|
|
32
|
+
const cleanups = new Set();
|
|
33
|
+
let hasCleanupReturn = false;
|
|
34
|
+
function inspectEffectBody(n) {
|
|
35
|
+
// Look for: const id = setInterval(...) or setInterval(...)
|
|
36
|
+
if (ts && ts.isCallExpression(n) && ts.isIdentifier(n.expression)) {
|
|
37
|
+
const fnName = n.expression.text;
|
|
38
|
+
if (fnName === "setInterval" || fnName === "setTimeout") {
|
|
39
|
+
let varName;
|
|
40
|
+
if (n.parent && ts.isVariableDeclaration(n.parent) && ts.isIdentifier(n.parent.name)) {
|
|
41
|
+
varName = n.parent.name.text;
|
|
42
|
+
}
|
|
43
|
+
timers.push({
|
|
44
|
+
node: n,
|
|
45
|
+
timerKind: fnName,
|
|
46
|
+
varName,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Check for return statement
|
|
51
|
+
if (ts && ts.isReturnStatement(n) && n.expression) {
|
|
52
|
+
const retExpr = n.expression;
|
|
53
|
+
if (ts.isArrowFunction(retExpr) || ts.isFunctionExpression(retExpr)) {
|
|
54
|
+
hasCleanupReturn = true;
|
|
55
|
+
inspectCleanupBody(retExpr);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (ts && n !== effectCallback && (ts.isFunctionDeclaration(n) || ts.isArrowFunction(n) || ts.isFunctionExpression(n))) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
n.forEachChild(inspectEffectBody);
|
|
62
|
+
}
|
|
63
|
+
function inspectCleanupBody(cleanupFn) {
|
|
64
|
+
function walkCleanup(cn) {
|
|
65
|
+
if (ts && ts.isCallExpression(cn) && ts.isIdentifier(cn.expression)) {
|
|
66
|
+
const fnName = cn.expression.text;
|
|
67
|
+
if (fnName === "clearInterval" || fnName === "clearTimeout") {
|
|
68
|
+
cleanups.add(fnName);
|
|
69
|
+
const arg = cn.arguments[0];
|
|
70
|
+
if (arg && ts.isIdentifier(arg)) {
|
|
71
|
+
cleanups.add(arg.text);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
cn.forEachChild(walkCleanup);
|
|
76
|
+
}
|
|
77
|
+
cleanupFn.forEachChild(walkCleanup);
|
|
78
|
+
}
|
|
79
|
+
inspectEffectBody(effectCallback.body);
|
|
80
|
+
for (const timer of timers) {
|
|
81
|
+
let isCleaned = false;
|
|
82
|
+
if (hasCleanupReturn) {
|
|
83
|
+
const expectedClear = timer.timerKind === "setInterval" ? "clearInterval" : "clearTimeout";
|
|
84
|
+
if (cleanups.has(expectedClear)) {
|
|
85
|
+
if (!timer.varName || cleanups.has(timer.varName)) {
|
|
86
|
+
isCleaned = true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (!isCleaned) {
|
|
91
|
+
const range = getNodeSourceRange(timer.node, sourceFile);
|
|
92
|
+
const isInterval = timer.timerKind === "setInterval";
|
|
93
|
+
context.report({
|
|
94
|
+
ruleId: "browser/timer-cleanup",
|
|
95
|
+
category: "browser",
|
|
96
|
+
severity: isInterval ? "error" : "warning",
|
|
97
|
+
confidence: isInterval ? "high" : "medium",
|
|
98
|
+
message: `${timer.timerKind} registered in lifecycle hook is missing matching ${isInterval ? "clearInterval" : "clearTimeout"} cleanup.`,
|
|
99
|
+
file: sourceFile.fileName,
|
|
100
|
+
range,
|
|
101
|
+
evidence: [
|
|
102
|
+
{
|
|
103
|
+
message: `Active ${timer.timerKind} timers that outlive the component lifecycle cause state leaks and background CPU consumption.`,
|
|
104
|
+
range,
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
suggestedAction: `Clear the timer in the returned cleanup function: return () => ${isInterval ? "clearInterval" : "clearTimeout"}(id);`,
|
|
108
|
+
safeAutomaticFix: false,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
//# sourceMappingURL=timerCleanup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timerCleanup.js","sourceRoot":"","sources":["../../../src/rules/browser/timerCleanup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { asyncForeachRule } from "./async/asyncForeach.js";
|
|
2
|
+
import { floatingPromiseRule } from "./async/floatingPromise.js";
|
|
3
|
+
import { unsafeUnvalidatedAssertionRule } from "./typescript/unsafeUnvalidatedAssertion.js";
|
|
4
|
+
import { nonNullAssertionRiskRule } from "./typescript/nonNullAssertionRisk.js";
|
|
5
|
+
import { uncheckedIndexAccessRule } from "./typescript/uncheckedIndexAccess.js";
|
|
6
|
+
import { fetchStatusUncheckedRule } from "./network/fetchStatusUnchecked.js";
|
|
7
|
+
import { eventListenerCleanupRule } from "./browser/eventListenerCleanup.js";
|
|
8
|
+
import { timerCleanupRule } from "./browser/timerCleanup.js";
|
|
9
|
+
import { observerCleanupRule } from "./browser/observerCleanup.js";
|
|
10
|
+
import { asyncEffectCallbackRule } from "./react/asyncEffectCallback.js";
|
|
11
|
+
import { derivedStateEffectRule } from "./react/derivedStateEffect.js";
|
|
12
|
+
import { missingEffectCleanupRule } from "./react/missingEffectCleanup.js";
|
|
13
|
+
export declare const BUILTIN_RULES: readonly [import("../index.js").Rule, import("../index.js").Rule, import("../index.js").Rule, import("../index.js").Rule, import("../index.js").Rule, import("../index.js").Rule, import("../index.js").Rule, import("../index.js").Rule, import("../index.js").Rule, import("../index.js").Rule, import("../index.js").Rule, import("../index.js").Rule];
|
|
14
|
+
/**
|
|
15
|
+
* Registers all 12 built-in production rules into the global registry.
|
|
16
|
+
*/
|
|
17
|
+
export declare function registerBuiltinRules(): void;
|
|
18
|
+
export { asyncForeachRule, floatingPromiseRule, unsafeUnvalidatedAssertionRule, nonNullAssertionRiskRule, uncheckedIndexAccessRule, fetchStatusUncheckedRule, eventListenerCleanupRule, timerCleanupRule, observerCleanupRule, asyncEffectCallbackRule, derivedStateEffectRule, missingEffectCleanupRule, };
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,8BAA8B,EAAE,MAAM,4CAA4C,CAAC;AAC5F,OAAO,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAC;AAChF,OAAO,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAC;AAChF,OAAO,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAE3E,eAAO,MAAM,aAAa,2VAahB,CAAC;AAEX;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAM3C;AAKD,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,8BAA8B,EAC9B,wBAAwB,EACxB,wBAAwB,EACxB,wBAAwB,EACxB,wBAAwB,EACxB,gBAAgB,EAChB,mBAAmB,EACnB,uBAAuB,EACvB,sBAAsB,EACtB,wBAAwB,GACzB,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { globalRuleRegistry } from "../engine/registry.js";
|
|
2
|
+
import { asyncForeachRule } from "./async/asyncForeach.js";
|
|
3
|
+
import { floatingPromiseRule } from "./async/floatingPromise.js";
|
|
4
|
+
import { unsafeUnvalidatedAssertionRule } from "./typescript/unsafeUnvalidatedAssertion.js";
|
|
5
|
+
import { nonNullAssertionRiskRule } from "./typescript/nonNullAssertionRisk.js";
|
|
6
|
+
import { uncheckedIndexAccessRule } from "./typescript/uncheckedIndexAccess.js";
|
|
7
|
+
import { fetchStatusUncheckedRule } from "./network/fetchStatusUnchecked.js";
|
|
8
|
+
import { eventListenerCleanupRule } from "./browser/eventListenerCleanup.js";
|
|
9
|
+
import { timerCleanupRule } from "./browser/timerCleanup.js";
|
|
10
|
+
import { observerCleanupRule } from "./browser/observerCleanup.js";
|
|
11
|
+
import { asyncEffectCallbackRule } from "./react/asyncEffectCallback.js";
|
|
12
|
+
import { derivedStateEffectRule } from "./react/derivedStateEffect.js";
|
|
13
|
+
import { missingEffectCleanupRule } from "./react/missingEffectCleanup.js";
|
|
14
|
+
export const BUILTIN_RULES = [
|
|
15
|
+
asyncForeachRule,
|
|
16
|
+
floatingPromiseRule,
|
|
17
|
+
unsafeUnvalidatedAssertionRule,
|
|
18
|
+
nonNullAssertionRiskRule,
|
|
19
|
+
uncheckedIndexAccessRule,
|
|
20
|
+
fetchStatusUncheckedRule,
|
|
21
|
+
eventListenerCleanupRule,
|
|
22
|
+
timerCleanupRule,
|
|
23
|
+
observerCleanupRule,
|
|
24
|
+
asyncEffectCallbackRule,
|
|
25
|
+
derivedStateEffectRule,
|
|
26
|
+
missingEffectCleanupRule,
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* Registers all 12 built-in production rules into the global registry.
|
|
30
|
+
*/
|
|
31
|
+
export function registerBuiltinRules() {
|
|
32
|
+
for (const rule of BUILTIN_RULES) {
|
|
33
|
+
if (!globalRuleRegistry.get(rule.meta.id)) {
|
|
34
|
+
globalRuleRegistry.register(rule);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Auto-register built-ins on module load
|
|
39
|
+
registerBuiltinRules();
|
|
40
|
+
export { asyncForeachRule, floatingPromiseRule, unsafeUnvalidatedAssertionRule, nonNullAssertionRiskRule, uncheckedIndexAccessRule, fetchStatusUncheckedRule, eventListenerCleanupRule, timerCleanupRule, observerCleanupRule, asyncEffectCallbackRule, derivedStateEffectRule, missingEffectCleanupRule, };
|
|
41
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,8BAA8B,EAAE,MAAM,4CAA4C,CAAC;AAC5F,OAAO,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAC;AAChF,OAAO,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAC;AAChF,OAAO,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAE3E,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,gBAAgB;IAChB,mBAAmB;IACnB,8BAA8B;IAC9B,wBAAwB;IACxB,wBAAwB;IACxB,wBAAwB;IACxB,wBAAwB;IACxB,gBAAgB;IAChB,mBAAmB;IACnB,uBAAuB;IACvB,sBAAsB;IACtB,wBAAwB;CAChB,CAAC;AAEX;;GAEG;AACH,MAAM,UAAU,oBAAoB;IAClC,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QACjC,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YAC1C,kBAAkB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;AACH,CAAC;AAED,yCAAyC;AACzC,oBAAoB,EAAE,CAAC;AAEvB,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,8BAA8B,EAC9B,wBAAwB,EACxB,wBAAwB,EACxB,wBAAwB,EACxB,wBAAwB,EACxB,gBAAgB,EAChB,mBAAmB,EACnB,uBAAuB,EACvB,sBAAsB,EACtB,wBAAwB,GACzB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetchStatusUnchecked.d.ts","sourceRoot":"","sources":["../../../src/rules/network/fetchStatusUnchecked.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAe,MAAM,qBAAqB,CAAC;AAE7D,eAAO,MAAM,wBAAwB,EAAE,IAkGtC,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { getNodeSourceRange, isDOMGlobal } from "../../engine/symbols.js";
|
|
2
|
+
export const fetchStatusUncheckedRule = {
|
|
3
|
+
meta: {
|
|
4
|
+
id: "network/fetch-status-unchecked",
|
|
5
|
+
category: "network",
|
|
6
|
+
defaultSeverity: "warning",
|
|
7
|
+
defaultConfidence: "high",
|
|
8
|
+
description: "Detects consumption of fetch() response bodies without checking response.ok or status.",
|
|
9
|
+
requiresTypeInformation: true,
|
|
10
|
+
},
|
|
11
|
+
analyze(context) {
|
|
12
|
+
const { sourceFile, checker, ts } = context;
|
|
13
|
+
if (!checker || !ts)
|
|
14
|
+
return;
|
|
15
|
+
context.visitNodes((node) => {
|
|
16
|
+
// Look for VariableDeclaration: const res = await fetch(...)
|
|
17
|
+
if (!ts.isVariableDeclaration(node))
|
|
18
|
+
return;
|
|
19
|
+
if (!ts.isIdentifier(node.name))
|
|
20
|
+
return;
|
|
21
|
+
const resVarName = node.name.text;
|
|
22
|
+
const init = node.initializer;
|
|
23
|
+
if (!init)
|
|
24
|
+
return;
|
|
25
|
+
let callExpr;
|
|
26
|
+
if (ts.isAwaitExpression(init) && ts.isCallExpression(init.expression)) {
|
|
27
|
+
callExpr = init.expression;
|
|
28
|
+
}
|
|
29
|
+
else if (ts.isCallExpression(init)) {
|
|
30
|
+
callExpr = init;
|
|
31
|
+
}
|
|
32
|
+
if (!callExpr)
|
|
33
|
+
return;
|
|
34
|
+
// Check if call target is global DOM fetch
|
|
35
|
+
let isFetchCall = false;
|
|
36
|
+
if (ts.isIdentifier(callExpr.expression) && callExpr.expression.text === "fetch") {
|
|
37
|
+
if (isDOMGlobal(callExpr.expression, checker, "fetch")) {
|
|
38
|
+
isFetchCall = true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else if (ts.isPropertyAccessExpression(callExpr.expression) &&
|
|
42
|
+
callExpr.expression.name.text === "fetch" &&
|
|
43
|
+
ts.isIdentifier(callExpr.expression.expression) &&
|
|
44
|
+
(callExpr.expression.expression.text === "window" || callExpr.expression.expression.text === "globalThis")) {
|
|
45
|
+
isFetchCall = true;
|
|
46
|
+
}
|
|
47
|
+
if (!isFetchCall)
|
|
48
|
+
return;
|
|
49
|
+
// Find enclosing block or function
|
|
50
|
+
const scope = context.findEnclosingScope(node);
|
|
51
|
+
if (!scope)
|
|
52
|
+
return;
|
|
53
|
+
let hasStatusCheck = false;
|
|
54
|
+
let bodyConsumptionNode;
|
|
55
|
+
// Walk scope to look for usages of resVarName
|
|
56
|
+
function inspectScope(n) {
|
|
57
|
+
// Check for res.ok or res.status access
|
|
58
|
+
if (ts && ts.isPropertyAccessExpression(n)) {
|
|
59
|
+
if (ts.isIdentifier(n.expression) && n.expression.text === resVarName) {
|
|
60
|
+
const prop = n.name.text;
|
|
61
|
+
if (prop === "ok" || prop === "status" || prop === "statusText") {
|
|
62
|
+
hasStatusCheck = true;
|
|
63
|
+
}
|
|
64
|
+
if (prop === "json" || prop === "text" || prop === "blob" || prop === "arrayBuffer" || prop === "formData") {
|
|
65
|
+
bodyConsumptionNode = n;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
n.forEachChild(inspectScope);
|
|
70
|
+
}
|
|
71
|
+
inspectScope(scope);
|
|
72
|
+
// If body was consumed but status was never checked in scope
|
|
73
|
+
if (bodyConsumptionNode && !hasStatusCheck) {
|
|
74
|
+
const range = getNodeSourceRange(bodyConsumptionNode, sourceFile);
|
|
75
|
+
context.report({
|
|
76
|
+
ruleId: "network/fetch-status-unchecked",
|
|
77
|
+
category: "network",
|
|
78
|
+
severity: "warning",
|
|
79
|
+
confidence: "high",
|
|
80
|
+
message: `Response body consumed via .${bodyConsumptionNode.name.text}() without checking response.ok or response.status.`,
|
|
81
|
+
file: sourceFile.fileName,
|
|
82
|
+
range,
|
|
83
|
+
evidence: [
|
|
84
|
+
{
|
|
85
|
+
message: "fetch() does not reject on HTTP 4xx or 5xx status codes.",
|
|
86
|
+
range,
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
suggestedAction: "Check `if (response.ok)` or verify `response.status` before consuming the response body.",
|
|
90
|
+
safeAutomaticFix: false,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=fetchStatusUnchecked.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetchStatusUnchecked.js","sourceRoot":"","sources":["../../../src/rules/network/fetchStatusUnchecked.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asyncEffectCallback.d.ts","sourceRoot":"","sources":["../../../src/rules/react/asyncEffectCallback.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAe,MAAM,qBAAqB,CAAC;AAE7D,eAAO,MAAM,uBAAuB,EAAE,IA8DrC,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { getNodeSourceRange, resolveReactHook } from "../../engine/symbols.js";
|
|
2
|
+
export const asyncEffectCallbackRule = {
|
|
3
|
+
meta: {
|
|
4
|
+
id: "react/async-effect-callback",
|
|
5
|
+
category: "react",
|
|
6
|
+
defaultSeverity: "error",
|
|
7
|
+
defaultConfidence: "high",
|
|
8
|
+
description: "Detects async functions passed directly as React useEffect callbacks.",
|
|
9
|
+
requiresTypeInformation: false,
|
|
10
|
+
},
|
|
11
|
+
analyze(context) {
|
|
12
|
+
const { sourceFile, checker, ts } = context;
|
|
13
|
+
if (!ts)
|
|
14
|
+
return;
|
|
15
|
+
context.visitNodes((node) => {
|
|
16
|
+
if (!ts.isCallExpression(node))
|
|
17
|
+
return;
|
|
18
|
+
let isEffectHook = false;
|
|
19
|
+
if (checker && (resolveReactHook(node, checker, "useEffect") || resolveReactHook(node, checker, "useLayoutEffect"))) {
|
|
20
|
+
isEffectHook = true;
|
|
21
|
+
}
|
|
22
|
+
else if (ts.isIdentifier(node.expression) && (node.expression.text === "useEffect" || node.expression.text === "useLayoutEffect")) {
|
|
23
|
+
isEffectHook = true;
|
|
24
|
+
}
|
|
25
|
+
if (!isEffectHook)
|
|
26
|
+
return;
|
|
27
|
+
const args = node.arguments;
|
|
28
|
+
if (!args || args.length === 0)
|
|
29
|
+
return;
|
|
30
|
+
const callback = args[0];
|
|
31
|
+
if (!callback)
|
|
32
|
+
return;
|
|
33
|
+
let isAsync = false;
|
|
34
|
+
if (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) {
|
|
35
|
+
if (callback.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword)) {
|
|
36
|
+
isAsync = true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (isAsync) {
|
|
40
|
+
const range = getNodeSourceRange(callback, sourceFile);
|
|
41
|
+
context.report({
|
|
42
|
+
ruleId: "react/async-effect-callback",
|
|
43
|
+
category: "react",
|
|
44
|
+
severity: "error",
|
|
45
|
+
confidence: "high",
|
|
46
|
+
message: "Effect setup callback must not be an async function.",
|
|
47
|
+
file: sourceFile.fileName,
|
|
48
|
+
range,
|
|
49
|
+
evidence: [
|
|
50
|
+
{
|
|
51
|
+
message: "React expects useEffect setup functions to return undefined or a synchronous cleanup function; async functions return a Promise.",
|
|
52
|
+
range,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
suggestedAction: "Define an async function inside the effect and call it synchronously: useEffect(() => { async function load() { ... } load(); }, []).",
|
|
56
|
+
safeAutomaticFix: false,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=asyncEffectCallback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asyncEffectCallback.js","sourceRoot":"","sources":["../../../src/rules/react/asyncEffectCallback.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAG/E,MAAM,CAAC,MAAM,uBAAuB,GAAS;IAC3C,IAAI,EAAE;QACJ,EAAE,EAAE,6BAA6B;QACjC,QAAQ,EAAE,OAAO;QACjB,eAAe,EAAE,OAAO;QACxB,iBAAiB,EAAE,MAAM;QACzB,WAAW,EAAE,uEAAuE;QACpF,uBAAuB,EAAE,KAAK;KAC/B;IAED,OAAO,CAAC,OAAoB;QAC1B,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC;QAC5C,IAAI,CAAC,EAAE;YAAE,OAAO;QAEhB,OAAO,CAAC,UAAU,CAAC,CAAC,IAAiB,EAAE,EAAE;YACvC,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC;gBAAE,OAAO;YAEvC,IAAI,YAAY,GAAG,KAAK,CAAC;YACzB,IAAI,OAAO,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,WAAW,CAAC,IAAI,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC;gBACpH,YAAY,GAAG,IAAI,CAAC;YACtB,CAAC;iBAAM,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,WAAW,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,iBAAiB,CAAC,EAAE,CAAC;gBACpI,YAAY,GAAG,IAAI,CAAC;YACtB,CAAC;YAED,IAAI,CAAC,YAAY;gBAAE,OAAO;YAE1B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;YAC5B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO;YAEvC,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YACzB,IAAI,CAAC,QAAQ;gBAAE,OAAO;YACtB,IAAI,OAAO,GAAG,KAAK,CAAC;YAEpB,IAAI,EAAE,CAAC,eAAe,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,oBAAoB,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACtE,IAAI,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;oBAC3E,OAAO,GAAG,IAAI,CAAC;gBACjB,CAAC;YACH,CAAC;YAED,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,KAAK,GAAG,kBAAkB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;gBACvD,OAAO,CAAC,MAAM,CAAC;oBACb,MAAM,EAAE,6BAA6B;oBACrC,QAAQ,EAAE,OAAO;oBACjB,QAAQ,EAAE,OAAO;oBACjB,UAAU,EAAE,MAAM;oBAClB,OAAO,EAAE,sDAAsD;oBAC/D,IAAI,EAAE,UAAU,CAAC,QAAQ;oBACzB,KAAK;oBACL,QAAQ,EAAE;wBACR;4BACE,OAAO,EAAE,kIAAkI;4BAC3I,KAAK;yBACN;qBACF;oBACD,eAAe,EACb,uIAAuI;oBACzI,gBAAgB,EAAE,KAAK;iBACxB,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derivedStateEffect.d.ts","sourceRoot":"","sources":["../../../src/rules/react/derivedStateEffect.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAe,MAAM,qBAAqB,CAAC;AA0D7D,eAAO,MAAM,sBAAsB,EAAE,IAkIpC,CAAC"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { getNodeSourceRange, resolveReactHook } from "../../engine/symbols.js";
|
|
2
|
+
import { isResetValue, resolveConstantValue, unwrapExpression } from "../../analysis/values.js";
|
|
3
|
+
/**
|
|
4
|
+
* Collects all identifier names referenced within an AST node, following local const variable initializers.
|
|
5
|
+
*/
|
|
6
|
+
function collectReferencedIdentifiers(node, ts, checker) {
|
|
7
|
+
const names = new Set();
|
|
8
|
+
const visitedSymbols = new Set();
|
|
9
|
+
function walk(n) {
|
|
10
|
+
if (ts.isIdentifier(n)) {
|
|
11
|
+
names.add(n.text);
|
|
12
|
+
if (checker) {
|
|
13
|
+
const sym = checker.getSymbolAtLocation(n);
|
|
14
|
+
if (sym && !visitedSymbols.has(sym)) {
|
|
15
|
+
visitedSymbols.add(sym);
|
|
16
|
+
const decl = sym.valueDeclaration ?? sym.declarations?.[0];
|
|
17
|
+
if (decl && ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
18
|
+
walk(decl.initializer);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
n.forEachChild(walk);
|
|
24
|
+
}
|
|
25
|
+
walk(node);
|
|
26
|
+
return names;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Extracts dependency identifier names from a React hook dependency array (e.g. `[a, b.c, d]`).
|
|
30
|
+
*/
|
|
31
|
+
function extractDependencyNames(depsArg, ts) {
|
|
32
|
+
const depNames = new Set();
|
|
33
|
+
const unwrapped = unwrapExpression(depsArg, ts);
|
|
34
|
+
if (ts.isArrayLiteralExpression(unwrapped)) {
|
|
35
|
+
for (const elem of unwrapped.elements) {
|
|
36
|
+
const u = unwrapExpression(elem, ts);
|
|
37
|
+
if (ts.isIdentifier(u)) {
|
|
38
|
+
depNames.add(u.text);
|
|
39
|
+
}
|
|
40
|
+
else if (ts.isPropertyAccessExpression(u)) {
|
|
41
|
+
// e.g. props.value -> collect 'props' and 'value'
|
|
42
|
+
const base = unwrapExpression(u.expression, ts);
|
|
43
|
+
if (ts.isIdentifier(base)) {
|
|
44
|
+
depNames.add(base.text);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return depNames;
|
|
50
|
+
}
|
|
51
|
+
export const derivedStateEffectRule = {
|
|
52
|
+
meta: {
|
|
53
|
+
id: "react/derived-state-effect",
|
|
54
|
+
category: "react",
|
|
55
|
+
defaultSeverity: "warning",
|
|
56
|
+
defaultConfidence: "high",
|
|
57
|
+
description: "Detects Effects used exclusively to derive state from render inputs.",
|
|
58
|
+
requiresTypeInformation: false,
|
|
59
|
+
},
|
|
60
|
+
analyze(context) {
|
|
61
|
+
const { sourceFile, checker, ts } = context;
|
|
62
|
+
if (!ts)
|
|
63
|
+
return;
|
|
64
|
+
context.visitNodes((node) => {
|
|
65
|
+
if (!ts.isCallExpression(node))
|
|
66
|
+
return;
|
|
67
|
+
let isEffectHook = false;
|
|
68
|
+
if (checker && (resolveReactHook(node, checker, "useEffect") || resolveReactHook(node, checker, "useLayoutEffect"))) {
|
|
69
|
+
isEffectHook = true;
|
|
70
|
+
}
|
|
71
|
+
else if (ts.isIdentifier(node.expression) && (node.expression.text === "useEffect" || node.expression.text === "useLayoutEffect")) {
|
|
72
|
+
isEffectHook = true;
|
|
73
|
+
}
|
|
74
|
+
if (!isEffectHook)
|
|
75
|
+
return;
|
|
76
|
+
const args = node.arguments;
|
|
77
|
+
if (!args || args.length === 0)
|
|
78
|
+
return;
|
|
79
|
+
const callback = args[0];
|
|
80
|
+
if (!callback || (!ts.isArrowFunction(callback) && !ts.isFunctionExpression(callback))) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Inspect statements in effect body
|
|
84
|
+
const body = callback.body;
|
|
85
|
+
let setterCallExpr;
|
|
86
|
+
let setterName = "";
|
|
87
|
+
if (ts.isBlock(body)) {
|
|
88
|
+
const stmts = body.statements;
|
|
89
|
+
if (stmts.length === 1 && stmts[0] && ts.isExpressionStatement(stmts[0])) {
|
|
90
|
+
const expr = unwrapExpression(stmts[0].expression, ts);
|
|
91
|
+
if (ts.isCallExpression(expr) && ts.isIdentifier(expr.expression)) {
|
|
92
|
+
const fnName = expr.expression.text;
|
|
93
|
+
if (/^set[A-Z]/.test(fnName)) {
|
|
94
|
+
setterCallExpr = expr;
|
|
95
|
+
setterName = fnName;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else if (ts.isCallExpression(body)) {
|
|
101
|
+
const unwrappedBody = unwrapExpression(body, ts);
|
|
102
|
+
if (ts.isCallExpression(unwrappedBody) && ts.isIdentifier(unwrappedBody.expression)) {
|
|
103
|
+
const fnName = unwrappedBody.expression.text;
|
|
104
|
+
if (/^set[A-Z]/.test(fnName)) {
|
|
105
|
+
setterCallExpr = unwrappedBody;
|
|
106
|
+
setterName = fnName;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!setterCallExpr)
|
|
111
|
+
return;
|
|
112
|
+
const setterArgs = setterCallExpr.arguments;
|
|
113
|
+
if (setterArgs.length === 0)
|
|
114
|
+
return;
|
|
115
|
+
const setterArg = setterArgs[0];
|
|
116
|
+
if (!setterArg)
|
|
117
|
+
return;
|
|
118
|
+
// 1. Exclude functional state updates: setState(prev => prev + 1)
|
|
119
|
+
const unwrappedArg = unwrapExpression(setterArg, ts);
|
|
120
|
+
if (ts.isArrowFunction(unwrappedArg) || ts.isFunctionExpression(unwrappedArg)) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// 2. Exclude state resets: setState(0), setState(""), setState(null), setState(DEFAULT_STATE where DEFAULT_STATE is static)
|
|
124
|
+
if (isResetValue(unwrappedArg, ts, checker)) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
// 3. Exclude static literals/constants that do not depend on effect dependencies
|
|
128
|
+
const constantVal = resolveConstantValue(unwrappedArg, ts, checker);
|
|
129
|
+
if (constantVal.isStatic) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// 4. If dependency array exists, check whether setter argument actually references any dependency (including through local aliases)
|
|
133
|
+
const depsArg = args[1];
|
|
134
|
+
if (depsArg) {
|
|
135
|
+
const deps = extractDependencyNames(depsArg, ts);
|
|
136
|
+
const referencedInArg = collectReferencedIdentifiers(unwrappedArg, ts, checker);
|
|
137
|
+
// Check if any referenced variable in the setter expression is part of the dependencies
|
|
138
|
+
let dependsOnDependencies = false;
|
|
139
|
+
for (const ref of referencedInArg) {
|
|
140
|
+
if (deps.has(ref)) {
|
|
141
|
+
dependsOnDependencies = true;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// If the setter value does not depend on any Effect dependency, it is a state reset / trigger,
|
|
146
|
+
// not a derived state synchronization.
|
|
147
|
+
if (!dependsOnDependencies && deps.size > 0) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const range = getNodeSourceRange(node, sourceFile);
|
|
152
|
+
context.report({
|
|
153
|
+
ruleId: "react/derived-state-effect",
|
|
154
|
+
category: "react",
|
|
155
|
+
severity: "warning",
|
|
156
|
+
confidence: "high",
|
|
157
|
+
message: `State setter '${setterName}' is called unconditionally inside useEffect to derive state.`,
|
|
158
|
+
file: sourceFile.fileName,
|
|
159
|
+
range,
|
|
160
|
+
evidence: [
|
|
161
|
+
{
|
|
162
|
+
message: "Synchronizing derived state in useEffect causes an extra render cycle and potential UI flicker.",
|
|
163
|
+
range,
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
suggestedAction: "Compute the derived value directly during render instead of storing it in useState and syncing with useEffect.",
|
|
167
|
+
safeAutomaticFix: false,
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
//# sourceMappingURL=derivedStateEffect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derivedStateEffect.js","sourceRoot":"","sources":["../../../src/rules/react/derivedStateEffect.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAGhG;;GAEG;AACH,SAAS,4BAA4B,CACnC,IAAiB,EACjB,EAAiB,EACjB,OAAwC;IAExC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,MAAM,cAAc,GAAG,IAAI,GAAG,EAAiB,CAAC;IAEhD,SAAS,IAAI,CAAC,CAAc;QAC1B,IAAI,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;YACvB,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAClB,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,GAAG,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;gBAC3C,IAAI,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;oBACpC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;oBACxB,MAAM,IAAI,GAAG,GAAG,CAAC,gBAAgB,IAAI,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC;oBAC3D,IAAI,IAAI,IAAI,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;wBAC/D,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;oBACzB,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QACD,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,CAAC;IACX,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,SAAS,sBAAsB,CAAC,OAA0B,EAAE,EAAiB;IAC3E,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAEhD,IAAI,EAAE,CAAC,wBAAwB,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3C,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,QAAQ,EAAE,CAAC;YACtC,MAAM,CAAC,GAAG,gBAAgB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACrC,IAAI,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;gBACvB,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACvB,CAAC;iBAAM,IAAI,EAAE,CAAC,0BAA0B,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC5C,kDAAkD;gBAClD,MAAM,IAAI,GAAG,gBAAgB,CAAC,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;gBAChD,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC1B,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC1B,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAS;IAC1C,IAAI,EAAE;QACJ,EAAE,EAAE,4BAA4B;QAChC,QAAQ,EAAE,OAAO;QACjB,eAAe,EAAE,SAAS;QAC1B,iBAAiB,EAAE,MAAM;QACzB,WAAW,EAAE,sEAAsE;QACnF,uBAAuB,EAAE,KAAK;KAC/B;IAED,OAAO,CAAC,OAAoB;QAC1B,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC;QAC5C,IAAI,CAAC,EAAE;YAAE,OAAO;QAEhB,OAAO,CAAC,UAAU,CAAC,CAAC,IAAiB,EAAE,EAAE;YACvC,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC;gBAAE,OAAO;YAEvC,IAAI,YAAY,GAAG,KAAK,CAAC;YACzB,IAAI,OAAO,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,WAAW,CAAC,IAAI,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC;gBACpH,YAAY,GAAG,IAAI,CAAC;YACtB,CAAC;iBAAM,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,WAAW,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,iBAAiB,CAAC,EAAE,CAAC;gBACpI,YAAY,GAAG,IAAI,CAAC;YACtB,CAAC;YAED,IAAI,CAAC,YAAY;gBAAE,OAAO;YAE1B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;YAC5B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO;YAEvC,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YACzB,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,EAAE,CAAC,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;gBACvF,OAAO;YACT,CAAC;YAED,oCAAoC;YACpC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;YAC3B,IAAI,cAAiD,CAAC;YACtD,IAAI,UAAU,GAAG,EAAE,CAAC;YAEpB,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gBACrB,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC;gBAC9B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;oBACzE,MAAM,IAAI,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;oBACvD,IAAI,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;wBAClE,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;wBACpC,IAAI,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;4BAC7B,cAAc,GAAG,IAAI,CAAC;4BACtB,UAAU,GAAG,MAAM,CAAC;wBACtB,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;iBAAM,IAAI,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;gBACrC,MAAM,aAAa,GAAG,gBAAgB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBACjD,IAAI,EAAE,CAAC,gBAAgB,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,YAAY,CAAC,aAAa,CAAC,UAAU,CAAC,EAAE,CAAC;oBACpF,MAAM,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC;oBAC7C,IAAI,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;wBAC7B,cAAc,GAAG,aAAa,CAAC;wBAC/B,UAAU,GAAG,MAAM,CAAC;oBACtB,CAAC;gBACH,CAAC;YACH,CAAC;YAED,IAAI,CAAC,cAAc;gBAAE,OAAO;YAE5B,MAAM,UAAU,GAAG,cAAc,CAAC,SAAS,CAAC;YAC5C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO;YAEpC,MAAM,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YAChC,IAAI,CAAC,SAAS;gBAAE,OAAO;YAEvB,kEAAkE;YAClE,MAAM,YAAY,GAAG,gBAAgB,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;YACrD,IAAI,EAAE,CAAC,eAAe,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,oBAAoB,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC9E,OAAO;YACT,CAAC;YAED,4HAA4H;YAC5H,IAAI,YAAY,CAAC,YAAY,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC;gBAC5C,OAAO;YACT,CAAC;YAED,iFAAiF;YACjF,MAAM,WAAW,GAAG,oBAAoB,CAAC,YAAY,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;YACpE,IAAI,WAAW,CAAC,QAAQ,EAAE,CAAC;gBACzB,OAAO;YACT,CAAC;YAED,oIAAoI;YACpI,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YACxB,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,IAAI,GAAG,sBAAsB,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;gBACjD,MAAM,eAAe,GAAG,4BAA4B,CAAC,YAAY,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;gBAEhF,wFAAwF;gBACxF,IAAI,qBAAqB,GAAG,KAAK,CAAC;gBAClC,KAAK,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;oBAClC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;wBAClB,qBAAqB,GAAG,IAAI,CAAC;wBAC7B,MAAM;oBACR,CAAC;gBACH,CAAC;gBAED,+FAA+F;gBAC/F,uCAAuC;gBACvC,IAAI,CAAC,qBAAqB,IAAI,IAAI,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;oBAC5C,OAAO;gBACT,CAAC;YACH,CAAC;YAED,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;YACnD,OAAO,CAAC,MAAM,CAAC;gBACb,MAAM,EAAE,4BAA4B;gBACpC,QAAQ,EAAE,OAAO;gBACjB,QAAQ,EAAE,SAAS;gBACnB,UAAU,EAAE,MAAM;gBAClB,OAAO,EAAE,iBAAiB,UAAU,+DAA+D;gBACnG,IAAI,EAAE,UAAU,CAAC,QAAQ;gBACzB,KAAK;gBACL,QAAQ,EAAE;oBACR;wBACE,OAAO,EAAE,iGAAiG;wBAC1G,KAAK;qBACN;iBACF;gBACD,eAAe,EACb,gHAAgH;gBAClH,gBAAgB,EAAE,KAAK;aACxB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"missingEffectCleanup.d.ts","sourceRoot":"","sources":["../../../src/rules/react/missingEffectCleanup.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAe,MAAM,qBAAqB,CAAC;AAE7D,eAAO,MAAM,wBAAwB,EAAE,IA8FtC,CAAC"}
|