eslint-plugin-react-x 5.14.0 → 5.14.2
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/index.js +125 -82
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -5,9 +5,9 @@ import * as core from "@eslint-react/core";
|
|
|
5
5
|
import { merge } from "@eslint-react/eslint";
|
|
6
6
|
import { JsxDetectionHint, findParentAttribute, getElementFullType, hasAttribute } from "@eslint-react/jsx";
|
|
7
7
|
import { AST_NODE_TYPES } from "@typescript-eslint/types";
|
|
8
|
+
import { computeObjectType, isAssignmentTargetEqual, isInitializedFromReact, resolve, resolveEnclosingAssignmentTarget } from "@eslint-react/var";
|
|
8
9
|
import { DefinitionType, ScopeType } from "@typescript-eslint/scope-manager";
|
|
9
10
|
import { findVariable, getStaticValue } from "@typescript-eslint/utils/ast-utils";
|
|
10
|
-
import { computeObjectType, isAssignmentTargetEqual, isInitializedFromReact, resolve, resolveEnclosingAssignmentTarget } from "@eslint-react/var";
|
|
11
11
|
import { P, isMatching, match } from "ts-pattern";
|
|
12
12
|
import { compare } from "compare-versions";
|
|
13
13
|
import { getConstrainedTypeAtLocation } from "@typescript-eslint/type-utils";
|
|
@@ -144,7 +144,7 @@ const rules$6 = {
|
|
|
144
144
|
//#endregion
|
|
145
145
|
//#region package.json
|
|
146
146
|
var name$6 = "eslint-plugin-react-x";
|
|
147
|
-
var version = "5.14.
|
|
147
|
+
var version = "5.14.2";
|
|
148
148
|
|
|
149
149
|
//#endregion
|
|
150
150
|
//#region src/utils/create-rule.ts
|
|
@@ -1157,6 +1157,67 @@ const MUTATING_ARRAY_METHODS = /* @__PURE__ */ new Set([
|
|
|
1157
1157
|
"splice",
|
|
1158
1158
|
"unshift"
|
|
1159
1159
|
]);
|
|
1160
|
+
/**
|
|
1161
|
+
* Return whether an identifier is an unresolved global or is declared in the
|
|
1162
|
+
* global/module scope.
|
|
1163
|
+
*/
|
|
1164
|
+
function isGlobalVariable(context, node) {
|
|
1165
|
+
const variable = findVariable(context.sourceCode.getScope(node), node);
|
|
1166
|
+
if (variable == null || variable.defs.length === 0) return true;
|
|
1167
|
+
return variable.scope.type === ScopeType.global || variable.scope.type === ScopeType.module;
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Resolve an object expression to the global/module binding it aliases.
|
|
1171
|
+
*
|
|
1172
|
+
* This intentionally follows only stable `const alias = value` declarations.
|
|
1173
|
+
* It gives the rule useful Alias effects without pretending to perform full
|
|
1174
|
+
* control-flow analysis for reassigned locals.
|
|
1175
|
+
*/
|
|
1176
|
+
function resolveGlobalOrigin(context, node, seen = /* @__PURE__ */ new Set()) {
|
|
1177
|
+
const expression = Extract.unwrap(node);
|
|
1178
|
+
if (seen.has(expression)) return null;
|
|
1179
|
+
seen.add(expression);
|
|
1180
|
+
if (expression.type === AST_NODE_TYPES.MemberExpression) return resolveGlobalOrigin(context, expression.object, seen);
|
|
1181
|
+
if (expression.type !== AST_NODE_TYPES.Identifier) return null;
|
|
1182
|
+
if (isGlobalVariable(context, expression)) return expression;
|
|
1183
|
+
const variable = findVariable(context.sourceCode.getScope(expression), expression);
|
|
1184
|
+
const definition = variable?.defs.length === 1 ? variable.defs[0] : null;
|
|
1185
|
+
if (definition?.type !== DefinitionType.Variable) return null;
|
|
1186
|
+
if (definition.node.id.type !== AST_NODE_TYPES.Identifier || definition.node.init == null) return null;
|
|
1187
|
+
if (definition.node.parent.kind !== "const") return null;
|
|
1188
|
+
const initializer = Extract.unwrap(definition.node.init);
|
|
1189
|
+
if (initializer.type !== AST_NODE_TYPES.Identifier && initializer.type !== AST_NODE_TYPES.MemberExpression) return null;
|
|
1190
|
+
return resolveGlobalOrigin(context, initializer, seen);
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Collect every write target in an assignment, including destructuring
|
|
1194
|
+
* patterns such as `[local, globalValue] = source`.
|
|
1195
|
+
*/
|
|
1196
|
+
function getAssignmentTargets(node) {
|
|
1197
|
+
const target = Extract.unwrap(node);
|
|
1198
|
+
switch (target.type) {
|
|
1199
|
+
case AST_NODE_TYPES.Identifier:
|
|
1200
|
+
case AST_NODE_TYPES.MemberExpression: return [target];
|
|
1201
|
+
case AST_NODE_TYPES.ArrayPattern: return target.elements.flatMap((element) => element == null ? [] : getAssignmentTargets(element));
|
|
1202
|
+
case AST_NODE_TYPES.AssignmentPattern: return getAssignmentTargets(target.left);
|
|
1203
|
+
case AST_NODE_TYPES.ObjectPattern: return target.properties.flatMap((property) => {
|
|
1204
|
+
if (property.type === AST_NODE_TYPES.RestElement) return getAssignmentTargets(property.argument);
|
|
1205
|
+
return getAssignmentTargets(property.value);
|
|
1206
|
+
});
|
|
1207
|
+
case AST_NODE_TYPES.RestElement: return getAssignmentTargets(target.argument);
|
|
1208
|
+
default: return [];
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
/** Resolve a direct call target, following simple function aliases. */
|
|
1212
|
+
function resolveToFunction(context, node, seen = /* @__PURE__ */ new Set()) {
|
|
1213
|
+
const expression = Extract.unwrap(node);
|
|
1214
|
+
if (Check.isFunction(expression)) return expression;
|
|
1215
|
+
if (expression.type !== AST_NODE_TYPES.Identifier || seen.has(expression)) return null;
|
|
1216
|
+
seen.add(expression);
|
|
1217
|
+
const resolved = resolve(context, expression);
|
|
1218
|
+
if (resolved == null) return null;
|
|
1219
|
+
return resolveToFunction(context, resolved, seen);
|
|
1220
|
+
}
|
|
1160
1221
|
|
|
1161
1222
|
//#endregion
|
|
1162
1223
|
//#region src/rules/globals/globals.ts
|
|
@@ -1179,101 +1240,83 @@ var globals_default = createRule({
|
|
|
1179
1240
|
function create$49(context) {
|
|
1180
1241
|
const hc = core.getHookCollector(context);
|
|
1181
1242
|
const fc = core.getFunctionComponentCollector(context);
|
|
1182
|
-
const
|
|
1183
|
-
|
|
1184
|
-
* Return true when `id` refers to a variable defined in the global or module
|
|
1185
|
-
* scope (i.e. outside any function), or when it has no known definition.
|
|
1186
|
-
*/
|
|
1187
|
-
function isGlobalOrModuleVariable(id) {
|
|
1188
|
-
const variable = findVariable(context.sourceCode.getScope(id), id);
|
|
1189
|
-
if (variable == null) return true;
|
|
1190
|
-
if (variable.defs.length === 0) return true;
|
|
1191
|
-
const scopeType = variable.scope.type;
|
|
1192
|
-
return scopeType === ScopeType.global || scopeType === ScopeType.module;
|
|
1193
|
-
}
|
|
1194
|
-
/**
|
|
1195
|
-
* Return the nearest enclosing function for `node`.
|
|
1196
|
-
*/
|
|
1243
|
+
const directEffects = /* @__PURE__ */ new Map();
|
|
1244
|
+
const callGraph = /* @__PURE__ */ new Map();
|
|
1197
1245
|
function getEnclosingFunction(node) {
|
|
1198
1246
|
return Traverse.findParent(node, Check.isFunction);
|
|
1199
1247
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
if (func == null) return;
|
|
1206
|
-
violations.push({
|
|
1248
|
+
function recordEffect(node, messageId, data) {
|
|
1249
|
+
const enclosing = getEnclosingFunction(node);
|
|
1250
|
+
if (enclosing == null) return;
|
|
1251
|
+
const effects = directEffects.get(enclosing) ?? [];
|
|
1252
|
+
effects.push({
|
|
1207
1253
|
data,
|
|
1208
|
-
func,
|
|
1209
1254
|
messageId,
|
|
1210
1255
|
node
|
|
1211
1256
|
});
|
|
1257
|
+
directEffects.set(enclosing, effects);
|
|
1258
|
+
}
|
|
1259
|
+
function recordWrite(node, target) {
|
|
1260
|
+
if (target.type === AST_NODE_TYPES.Identifier) {
|
|
1261
|
+
if (!isGlobalVariable(context, target)) return;
|
|
1262
|
+
recordEffect(node, "mutatingGlobal", { name: target.name });
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
if (resolveGlobalOrigin(context, target.object) == null) return;
|
|
1266
|
+
recordEffect(node, "mutatingGlobalProperty", { name: context.sourceCode.getText(target) });
|
|
1267
|
+
}
|
|
1268
|
+
function recordCallEdge(node) {
|
|
1269
|
+
const caller = getEnclosingFunction(node);
|
|
1270
|
+
if (caller == null) return;
|
|
1271
|
+
const callee = resolveToFunction(context, node.callee);
|
|
1272
|
+
if (callee == null) return;
|
|
1273
|
+
const callees = callGraph.get(caller) ?? /* @__PURE__ */ new Set();
|
|
1274
|
+
callees.add(callee);
|
|
1275
|
+
callGraph.set(caller, callees);
|
|
1212
1276
|
}
|
|
1213
1277
|
return merge(hc.visitor, fc.visitor, {
|
|
1214
|
-
/**
|
|
1215
|
-
* Detect `renderCount++`, `renderCount--`
|
|
1216
|
-
*/
|
|
1217
|
-
UpdateExpression(node) {
|
|
1218
|
-
const arg = Extract.unwrap(node.argument);
|
|
1219
|
-
if (arg.type === AST_NODE_TYPES.Identifier) {
|
|
1220
|
-
if (!isGlobalOrModuleVariable(arg)) return;
|
|
1221
|
-
recordViolation(node, "mutatingGlobal", { name: arg.name });
|
|
1222
|
-
return;
|
|
1223
|
-
}
|
|
1224
|
-
if (arg.type === AST_NODE_TYPES.MemberExpression) {
|
|
1225
|
-
const rootId = Extract.getRootIdentifier(arg);
|
|
1226
|
-
if (rootId == null) return;
|
|
1227
|
-
if (!isGlobalOrModuleVariable(rootId)) return;
|
|
1228
|
-
recordViolation(node, "mutatingGlobalProperty", { name: context.sourceCode.getText(arg) });
|
|
1229
|
-
}
|
|
1230
|
-
},
|
|
1231
|
-
/**
|
|
1232
|
-
* Detect `renderCount = 1`, `window.currentUser = id`, `cache[id] = value`
|
|
1233
|
-
*/
|
|
1234
1278
|
AssignmentExpression(node) {
|
|
1235
|
-
const
|
|
1236
|
-
if (left.type === AST_NODE_TYPES.Identifier) {
|
|
1237
|
-
if (!isGlobalOrModuleVariable(left)) return;
|
|
1238
|
-
recordViolation(node, "mutatingGlobal", { name: left.name });
|
|
1239
|
-
return;
|
|
1240
|
-
}
|
|
1241
|
-
if (left.type === AST_NODE_TYPES.MemberExpression) {
|
|
1242
|
-
const rootId = Extract.getRootIdentifier(left);
|
|
1243
|
-
if (rootId == null) return;
|
|
1244
|
-
if (!isGlobalOrModuleVariable(rootId)) return;
|
|
1245
|
-
recordViolation(node, "mutatingGlobalProperty", { name: context.sourceCode.getText(left) });
|
|
1246
|
-
}
|
|
1279
|
+
for (const target of getAssignmentTargets(node.left)) recordWrite(node, target);
|
|
1247
1280
|
},
|
|
1248
|
-
/**
|
|
1249
|
-
* Detect `events.push(event)`, `cache.sort()`, etc.
|
|
1250
|
-
*/
|
|
1251
1281
|
CallExpression(node) {
|
|
1282
|
+
recordCallEdge(node);
|
|
1252
1283
|
const callee = Extract.unwrap(node.callee);
|
|
1253
1284
|
if (callee.type !== AST_NODE_TYPES.MemberExpression) return;
|
|
1254
|
-
const
|
|
1255
|
-
if (
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
name: rootId.name,
|
|
1262
|
-
method: property.name
|
|
1285
|
+
const method = Extract.getPropertyName(callee.property);
|
|
1286
|
+
if (method == null || !MUTATING_ARRAY_METHODS.has(method)) return;
|
|
1287
|
+
const origin = resolveGlobalOrigin(context, callee.object);
|
|
1288
|
+
if (origin == null) return;
|
|
1289
|
+
recordEffect(node, "mutatingGlobalArrayMethod", {
|
|
1290
|
+
name: origin.name,
|
|
1291
|
+
method
|
|
1263
1292
|
});
|
|
1264
1293
|
},
|
|
1265
|
-
"Program:exit"(
|
|
1266
|
-
const
|
|
1267
|
-
const
|
|
1268
|
-
const
|
|
1269
|
-
|
|
1270
|
-
if (
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1294
|
+
"Program:exit"(program) {
|
|
1295
|
+
const renderFunctions = [...fc.api.getAllComponents(program), ...hc.api.getAllHooks(program)];
|
|
1296
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1297
|
+
const reported = /* @__PURE__ */ new Set();
|
|
1298
|
+
function applyFunctionEffects(func) {
|
|
1299
|
+
if (visited.has(func)) return;
|
|
1300
|
+
visited.add(func);
|
|
1301
|
+
for (const effect of directEffects.get(func) ?? []) {
|
|
1302
|
+
if (reported.has(effect)) continue;
|
|
1303
|
+
reported.add(effect);
|
|
1304
|
+
context.report(effect);
|
|
1305
|
+
}
|
|
1306
|
+
for (const callee of callGraph.get(func) ?? []) applyFunctionEffects(callee);
|
|
1276
1307
|
}
|
|
1308
|
+
for (const { node } of renderFunctions) applyFunctionEffects(node);
|
|
1309
|
+
},
|
|
1310
|
+
UnaryExpression(node) {
|
|
1311
|
+
if (node.operator !== "delete") return;
|
|
1312
|
+
const argument = Extract.unwrap(node.argument);
|
|
1313
|
+
if (argument.type !== AST_NODE_TYPES.MemberExpression) return;
|
|
1314
|
+
recordWrite(node, argument);
|
|
1315
|
+
},
|
|
1316
|
+
UpdateExpression(node) {
|
|
1317
|
+
const argument = Extract.unwrap(node.argument);
|
|
1318
|
+
if (argument.type !== AST_NODE_TYPES.Identifier && argument.type !== AST_NODE_TYPES.MemberExpression) return;
|
|
1319
|
+
recordWrite(node, argument);
|
|
1277
1320
|
}
|
|
1278
1321
|
});
|
|
1279
1322
|
}
|
|
@@ -7547,9 +7590,9 @@ function create$2(context) {
|
|
|
7547
7590
|
});
|
|
7548
7591
|
},
|
|
7549
7592
|
"Program:exit"(node) {
|
|
7550
|
-
const
|
|
7593
|
+
const comps = fc.api.getAllComponents(node);
|
|
7551
7594
|
const hooks = hc.api.getAllHooks(node);
|
|
7552
|
-
const funcs = [...
|
|
7595
|
+
const funcs = [...comps, ...hooks];
|
|
7553
7596
|
for (const { func, node } of evalCalls) {
|
|
7554
7597
|
if (!funcs.some((f) => f.node === func)) continue;
|
|
7555
7598
|
context.report({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-react-x",
|
|
3
|
-
"version": "5.14.
|
|
3
|
+
"version": "5.14.2",
|
|
4
4
|
"description": "A set of composable ESLint rules for libraries and frameworks that use React as a UI runtime.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -45,12 +45,12 @@
|
|
|
45
45
|
"string-ts": "^2.3.1",
|
|
46
46
|
"ts-api-utils": "^2.5.0",
|
|
47
47
|
"ts-pattern": "^5.9.0",
|
|
48
|
-
"@eslint-react/ast": "5.14.
|
|
49
|
-
"@eslint-react/
|
|
50
|
-
"@eslint-react/
|
|
51
|
-
"@eslint-react/
|
|
52
|
-
"@eslint-react/
|
|
53
|
-
"@eslint-react/var": "5.14.
|
|
48
|
+
"@eslint-react/ast": "5.14.2",
|
|
49
|
+
"@eslint-react/core": "5.14.2",
|
|
50
|
+
"@eslint-react/shared": "5.14.2",
|
|
51
|
+
"@eslint-react/eslint": "5.14.2",
|
|
52
|
+
"@eslint-react/jsx": "5.14.2",
|
|
53
|
+
"@eslint-react/var": "5.14.2"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/react": "^19.2.17",
|