oxlint-plugin-react-doctor 0.7.6-dev.47beb25 → 0.7.6-dev.4fb3694
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 +109 -728
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1180,8 +1180,8 @@ const stripParenExpression = (node) => {
|
|
|
1180
1180
|
return current;
|
|
1181
1181
|
};
|
|
1182
1182
|
//#endregion
|
|
1183
|
-
//#region src/plugin/utils/get-root-identifier.ts
|
|
1184
|
-
const
|
|
1183
|
+
//#region src/plugin/utils/get-root-identifier-name.ts
|
|
1184
|
+
const getRootIdentifierName = (node, options) => {
|
|
1185
1185
|
if (!node) return null;
|
|
1186
1186
|
const followCallChains = options?.followCallChains === true;
|
|
1187
1187
|
let cursor = node;
|
|
@@ -1192,18 +1192,16 @@ const getRootIdentifier$1 = (node, options) => {
|
|
|
1192
1192
|
continue;
|
|
1193
1193
|
}
|
|
1194
1194
|
if (followCallChains && isNodeOfType(cursor, "CallExpression")) {
|
|
1195
|
-
|
|
1196
|
-
|
|
1195
|
+
const callee = cursor.callee;
|
|
1196
|
+
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
1197
|
+
cursor = callee.object;
|
|
1197
1198
|
continue;
|
|
1198
1199
|
}
|
|
1199
1200
|
break;
|
|
1200
1201
|
}
|
|
1201
|
-
return isNodeOfType(cursor, "Identifier") ? cursor : null;
|
|
1202
|
+
return isNodeOfType(cursor, "Identifier") ? cursor.name : null;
|
|
1202
1203
|
};
|
|
1203
1204
|
//#endregion
|
|
1204
|
-
//#region src/plugin/utils/get-root-identifier-name.ts
|
|
1205
|
-
const getRootIdentifierName = (node, options) => getRootIdentifier$1(node, options)?.name ?? null;
|
|
1206
|
-
//#endregion
|
|
1207
1205
|
//#region src/plugin/rules/state-and-effects/advanced-event-handler-refs.ts
|
|
1208
1206
|
const STABLE_HANDLER_HOOK_NAMES = new Set([
|
|
1209
1207
|
"useCallback",
|
|
@@ -5964,7 +5962,7 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
5964
5962
|
//#endregion
|
|
5965
5963
|
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
5966
5964
|
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
5967
|
-
if (!isNodeOfType(node, "Property")
|
|
5965
|
+
if (!isNodeOfType(node, "Property")) return null;
|
|
5968
5966
|
if (node.computed) {
|
|
5969
5967
|
if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
|
|
5970
5968
|
return null;
|
|
@@ -6023,7 +6021,7 @@ const isPureEventBlockerHandler = (attribute) => {
|
|
|
6023
6021
|
//#endregion
|
|
6024
6022
|
//#region src/plugin/utils/resolve-const-identifier-alias.ts
|
|
6025
6023
|
const resolveConstIdentifierAlias = (identifier, scopes) => {
|
|
6026
|
-
if (!isNodeOfType(identifier, "Identifier")
|
|
6024
|
+
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
6027
6025
|
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
6028
6026
|
let symbol = scopes.symbolFor(identifier);
|
|
6029
6027
|
while (symbol?.kind === "const") {
|
|
@@ -6298,131 +6296,6 @@ const isGlobalMethodCall = (node, objectName, methodName) => {
|
|
|
6298
6296
|
return isNodeOfType(receiver, "Identifier") && receiver.name === objectName && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName;
|
|
6299
6297
|
};
|
|
6300
6298
|
//#endregion
|
|
6301
|
-
//#region src/plugin/utils/collect-safely-validated-local-storage-keys.ts
|
|
6302
|
-
const isLocalStorageCall = (node, methodName) => {
|
|
6303
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
6304
|
-
const callee = stripParenExpression(node.callee);
|
|
6305
|
-
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
6306
|
-
const receiver = stripParenExpression(callee.object);
|
|
6307
|
-
return isNodeOfType(receiver, "Identifier") && receiver.name === "localStorage" && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName;
|
|
6308
|
-
};
|
|
6309
|
-
const catchReturnsFallback = (tryStatement) => {
|
|
6310
|
-
const handler = tryStatement.handler;
|
|
6311
|
-
if (!handler) return false;
|
|
6312
|
-
let hasReturn = false;
|
|
6313
|
-
let hasThrow = false;
|
|
6314
|
-
walkAst(handler.body, (child) => {
|
|
6315
|
-
if (isFunctionLike$1(child)) return false;
|
|
6316
|
-
if (isNodeOfType(child, "ReturnStatement")) hasReturn = true;
|
|
6317
|
-
if (isNodeOfType(child, "ThrowStatement")) hasThrow = true;
|
|
6318
|
-
});
|
|
6319
|
-
return hasReturn && !hasThrow;
|
|
6320
|
-
};
|
|
6321
|
-
const resolveValidatorFunction = (callee, scopes) => {
|
|
6322
|
-
const unwrappedCallee = stripParenExpression(callee);
|
|
6323
|
-
if (!isNodeOfType(unwrappedCallee, "Identifier")) return null;
|
|
6324
|
-
const symbol = scopes.symbolFor(unwrappedCallee);
|
|
6325
|
-
if (!symbol) return null;
|
|
6326
|
-
const candidate = symbol.kind === "function" ? symbol.declarationNode : symbol.initializer;
|
|
6327
|
-
return isFunctionLike$1(candidate) ? candidate : null;
|
|
6328
|
-
};
|
|
6329
|
-
const validatorChecksPayloadProperties = (validatorFunction, scopes) => {
|
|
6330
|
-
if (!isFunctionLike$1(validatorFunction)) return false;
|
|
6331
|
-
const firstParameter = validatorFunction.params?.[0];
|
|
6332
|
-
if (!firstParameter || !isNodeOfType(firstParameter, "Identifier")) return false;
|
|
6333
|
-
const parameterSymbol = scopes.symbolFor(firstParameter);
|
|
6334
|
-
if (!parameterSymbol) return false;
|
|
6335
|
-
const payloadSymbolIds = new Set([parameterSymbol.id]);
|
|
6336
|
-
let hasPropertyTypeCheck = false;
|
|
6337
|
-
walkAst(validatorFunction.body, (child) => {
|
|
6338
|
-
if (isFunctionLike$1(child)) return false;
|
|
6339
|
-
if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier") && child.init) {
|
|
6340
|
-
const initializer = stripParenExpression(child.init);
|
|
6341
|
-
if (isNodeOfType(initializer, "Identifier")) {
|
|
6342
|
-
const initializerSymbol = scopes.symbolFor(initializer);
|
|
6343
|
-
if (initializerSymbol && payloadSymbolIds.has(initializerSymbol.id)) {
|
|
6344
|
-
const aliasSymbol = scopes.symbolFor(child.id);
|
|
6345
|
-
if (aliasSymbol) payloadSymbolIds.add(aliasSymbol.id);
|
|
6346
|
-
}
|
|
6347
|
-
}
|
|
6348
|
-
}
|
|
6349
|
-
if (!isNodeOfType(child, "UnaryExpression") || child.operator !== "typeof") return;
|
|
6350
|
-
const checkedValue = stripParenExpression(child.argument);
|
|
6351
|
-
if (!isNodeOfType(checkedValue, "MemberExpression")) return;
|
|
6352
|
-
const receiver = stripParenExpression(checkedValue.object);
|
|
6353
|
-
if (!isNodeOfType(receiver, "Identifier")) return;
|
|
6354
|
-
const receiverSymbol = scopes.symbolFor(receiver);
|
|
6355
|
-
if (receiverSymbol && payloadSymbolIds.has(receiverSymbol.id)) hasPropertyTypeCheck = true;
|
|
6356
|
-
});
|
|
6357
|
-
return hasPropertyTypeCheck;
|
|
6358
|
-
};
|
|
6359
|
-
const incrementCount = (counts, keyValue) => {
|
|
6360
|
-
counts.set(keyValue, (counts.get(keyValue) ?? 0) + 1);
|
|
6361
|
-
};
|
|
6362
|
-
const isReturnedExpression = (expression) => {
|
|
6363
|
-
const parent = expression.parent;
|
|
6364
|
-
return Boolean(parent && isNodeOfType(parent, "ReturnStatement") && parent.argument === expression);
|
|
6365
|
-
};
|
|
6366
|
-
const collectSafelyValidatedLocalStorageKeys = ({ programNode, scopes, resolveKey }) => {
|
|
6367
|
-
const readCountsByKey = /* @__PURE__ */ new Map();
|
|
6368
|
-
const safeReadCountsByKey = /* @__PURE__ */ new Map();
|
|
6369
|
-
const safeReadSymbolIds = /* @__PURE__ */ new Set();
|
|
6370
|
-
walkAst(programNode, (child) => {
|
|
6371
|
-
if (!isNodeOfType(child, "CallExpression") || !isLocalStorageCall(child, "getItem")) return;
|
|
6372
|
-
const keyArgument = child.arguments?.[0];
|
|
6373
|
-
if (!keyArgument) return;
|
|
6374
|
-
const keyValue = resolveKey(keyArgument);
|
|
6375
|
-
if (keyValue !== null) incrementCount(readCountsByKey, keyValue);
|
|
6376
|
-
});
|
|
6377
|
-
walkAst(programNode, (child) => {
|
|
6378
|
-
if (!isNodeOfType(child, "TryStatement") || !catchReturnsFallback(child)) return;
|
|
6379
|
-
const rawValueKeys = /* @__PURE__ */ new Map();
|
|
6380
|
-
const parsedValueSources = /* @__PURE__ */ new Map();
|
|
6381
|
-
walkAst(child.block, (tryChild) => {
|
|
6382
|
-
if (isFunctionLike$1(tryChild)) return false;
|
|
6383
|
-
if (isNodeOfType(tryChild, "VariableDeclarator") && isNodeOfType(tryChild.id, "Identifier") && tryChild.init) {
|
|
6384
|
-
const initializer = stripParenExpression(tryChild.init);
|
|
6385
|
-
if (isNodeOfType(initializer, "CallExpression") && isLocalStorageCall(initializer, "getItem")) {
|
|
6386
|
-
const keyArgument = initializer.arguments?.[0];
|
|
6387
|
-
const bindingSymbol = scopes.symbolFor(tryChild.id);
|
|
6388
|
-
if (keyArgument && bindingSymbol) {
|
|
6389
|
-
const keyValue = resolveKey(keyArgument);
|
|
6390
|
-
if (keyValue !== null) rawValueKeys.set(bindingSymbol.id, keyValue);
|
|
6391
|
-
}
|
|
6392
|
-
}
|
|
6393
|
-
if (isNodeOfType(initializer, "CallExpression") && isGlobalMethodCall(initializer, "JSON", "parse")) {
|
|
6394
|
-
const rawValueArgument = initializer.arguments?.[0];
|
|
6395
|
-
const parsedValueSymbol = scopes.symbolFor(tryChild.id);
|
|
6396
|
-
if (rawValueArgument && isNodeOfType(rawValueArgument, "Identifier") && parsedValueSymbol) {
|
|
6397
|
-
const rawValueSymbol = scopes.symbolFor(rawValueArgument);
|
|
6398
|
-
if (rawValueSymbol && rawValueKeys.has(rawValueSymbol.id)) parsedValueSources.set(parsedValueSymbol.id, rawValueSymbol.id);
|
|
6399
|
-
}
|
|
6400
|
-
}
|
|
6401
|
-
}
|
|
6402
|
-
if (!isNodeOfType(tryChild, "ConditionalExpression") || !isReturnedExpression(tryChild)) return;
|
|
6403
|
-
const test = stripParenExpression(tryChild.test);
|
|
6404
|
-
if (!isNodeOfType(test, "CallExpression")) return;
|
|
6405
|
-
const testedValue = test.arguments?.[0];
|
|
6406
|
-
if (!testedValue || !isNodeOfType(testedValue, "Identifier")) return;
|
|
6407
|
-
const parsedValueSymbol = scopes.symbolFor(testedValue);
|
|
6408
|
-
if (!parsedValueSymbol) return;
|
|
6409
|
-
const rawValueSymbolId = parsedValueSources.get(parsedValueSymbol.id);
|
|
6410
|
-
if (rawValueSymbolId === void 0) return;
|
|
6411
|
-
const returnedValue = stripParenExpression(tryChild.consequent);
|
|
6412
|
-
if (!isNodeOfType(returnedValue, "Identifier") || scopes.symbolFor(returnedValue)?.id !== parsedValueSymbol.id) return;
|
|
6413
|
-
const validatorFunction = resolveValidatorFunction(test.callee, scopes);
|
|
6414
|
-
if (!validatorFunction || !validatorChecksPayloadProperties(validatorFunction, scopes)) return;
|
|
6415
|
-
const keyValue = rawValueKeys.get(rawValueSymbolId);
|
|
6416
|
-
if (keyValue === void 0 || safeReadSymbolIds.has(rawValueSymbolId)) return;
|
|
6417
|
-
safeReadSymbolIds.add(rawValueSymbolId);
|
|
6418
|
-
incrementCount(safeReadCountsByKey, keyValue);
|
|
6419
|
-
});
|
|
6420
|
-
});
|
|
6421
|
-
const safeKeys = /* @__PURE__ */ new Set();
|
|
6422
|
-
for (const [keyValue, readCount] of readCountsByKey) if (safeReadCountsByKey.get(keyValue) === readCount) safeKeys.add(keyValue);
|
|
6423
|
-
return safeKeys;
|
|
6424
|
-
};
|
|
6425
|
-
//#endregion
|
|
6426
6299
|
//#region src/plugin/rules/client/client-localstorage-no-version.ts
|
|
6427
6300
|
const VERSIONED_KEY_PATTERN = /(?:[._:-]v\d+|@\d+|\bv\d+\b)/i;
|
|
6428
6301
|
const CAMEL_CASE_VERSIONED_KEY_PATTERN = /[a-z]V\d+/;
|
|
@@ -6444,39 +6317,26 @@ const clientLocalstorageNoVersion = defineRule({
|
|
|
6444
6317
|
severity: "warn",
|
|
6445
6318
|
category: "Correctness",
|
|
6446
6319
|
recommendation: "Put a version in the storage key (e.g. \"myKey:v1\"). If you change the data shape later, old saved data can be ignored instead of crashing the app.",
|
|
6447
|
-
create: (context) => {
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
if (keyValue === null) return;
|
|
6468
|
-
if (isVersionedKey(keyValue)) return;
|
|
6469
|
-
if (safelyValidatedStorageKeys.has(keyValue)) return;
|
|
6470
|
-
const valueArg = node.arguments?.[1];
|
|
6471
|
-
if (!valueArg) return;
|
|
6472
|
-
if (!isJsonStringifyCall(valueArg)) return;
|
|
6473
|
-
context.report({
|
|
6474
|
-
node: keyArg,
|
|
6475
|
-
message: `${receiver.name}.setItem("${keyValue}", JSON.stringify(...)) has no version, so changing the data shape later crashes your users' saved sessions. Add one to the key (e.g. "${keyValue}:v1").`
|
|
6476
|
-
});
|
|
6477
|
-
}
|
|
6478
|
-
};
|
|
6479
|
-
}
|
|
6320
|
+
create: (context) => ({ CallExpression(node) {
|
|
6321
|
+
if (!isNodeOfType(node.callee, "MemberExpression")) return;
|
|
6322
|
+
const receiver = stripParenExpression(node.callee.object);
|
|
6323
|
+
if (!isNodeOfType(receiver, "Identifier")) return;
|
|
6324
|
+
if (receiver.name !== "localStorage") return;
|
|
6325
|
+
if (!isNodeOfType(node.callee.property, "Identifier")) return;
|
|
6326
|
+
if (node.callee.property.name !== "setItem") return;
|
|
6327
|
+
const keyArg = node.arguments?.[0];
|
|
6328
|
+
if (!keyArg) return;
|
|
6329
|
+
const keyValue = resolveStringKey(keyArg, context);
|
|
6330
|
+
if (keyValue === null) return;
|
|
6331
|
+
if (isVersionedKey(keyValue)) return;
|
|
6332
|
+
const valueArg = node.arguments?.[1];
|
|
6333
|
+
if (!valueArg) return;
|
|
6334
|
+
if (!isJsonStringifyCall(valueArg)) return;
|
|
6335
|
+
context.report({
|
|
6336
|
+
node: keyArg,
|
|
6337
|
+
message: `${receiver.name}.setItem("${keyValue}", JSON.stringify(...)) has no version, so changing the data shape later crashes your users' saved sessions. Add one to the key (e.g. "${keyValue}:v1").`
|
|
6338
|
+
});
|
|
6339
|
+
} })
|
|
6480
6340
|
});
|
|
6481
6341
|
//#endregion
|
|
6482
6342
|
//#region src/plugin/rules/client/client-passive-event-listeners.ts
|
|
@@ -16112,21 +15972,14 @@ const jsxFilenameExtension = defineRule({
|
|
|
16112
15972
|
});
|
|
16113
15973
|
//#endregion
|
|
16114
15974
|
//#region src/plugin/utils/is-jsx-fragment-element.ts
|
|
16115
|
-
const isJsxFragmentElement = (node
|
|
15975
|
+
const isJsxFragmentElement = (node) => {
|
|
16116
15976
|
if (!isNodeOfType(node, "JSXOpeningElement")) return false;
|
|
16117
15977
|
const elementName = node.name;
|
|
16118
|
-
if (isNodeOfType(elementName, "JSXIdentifier"))
|
|
16119
|
-
if (!scopes) return elementName.name === "Fragment";
|
|
16120
|
-
const symbol = resolveConstIdentifierAlias(elementName, scopes);
|
|
16121
|
-
if (!symbol) return elementName.name === "Fragment" && scopes.isGlobalReference(elementName);
|
|
16122
|
-
return isImportedFromReact(symbol) && getImportedName(symbol.declarationNode) === "Fragment";
|
|
16123
|
-
}
|
|
15978
|
+
if (isNodeOfType(elementName, "JSXIdentifier")) return elementName.name === "Fragment";
|
|
16124
15979
|
if (isNodeOfType(elementName, "JSXMemberExpression")) {
|
|
16125
15980
|
if (!isNodeOfType(elementName.object, "JSXIdentifier")) return false;
|
|
16126
|
-
if (elementName.
|
|
16127
|
-
|
|
16128
|
-
if (isReactNamespaceImport(elementName.object, scopes)) return true;
|
|
16129
|
-
return elementName.object.name === "React" && scopes.isGlobalReference(elementName.object);
|
|
15981
|
+
if (elementName.object.name !== "React") return false;
|
|
15982
|
+
return elementName.property.name === "Fragment";
|
|
16130
15983
|
}
|
|
16131
15984
|
return false;
|
|
16132
15985
|
};
|
|
@@ -16152,7 +16005,7 @@ const jsxFragments = defineRule({
|
|
|
16152
16005
|
if (mode !== "syntax") return;
|
|
16153
16006
|
if (!node.closingElement) return;
|
|
16154
16007
|
const openingElement = node.openingElement;
|
|
16155
|
-
if (!isJsxFragmentElement(openingElement
|
|
16008
|
+
if (!isJsxFragmentElement(openingElement)) return;
|
|
16156
16009
|
if (openingElement.attributes.length > 0) return;
|
|
16157
16010
|
context.report({
|
|
16158
16011
|
node: openingElement,
|
|
@@ -19293,7 +19146,7 @@ const jsxNoUselessFragment = defineRule({
|
|
|
19293
19146
|
return {
|
|
19294
19147
|
JSXElement(node) {
|
|
19295
19148
|
const openingElement = node.openingElement;
|
|
19296
|
-
if (!isJsxFragmentElement(openingElement
|
|
19149
|
+
if (!isJsxFragmentElement(openingElement)) return;
|
|
19297
19150
|
if (hasJsxKeyAttribute(openingElement)) return;
|
|
19298
19151
|
if (checkChildren(node, openingElement, node.children)) return;
|
|
19299
19152
|
if (isChildOfHtmlElement(node)) context.report({
|
|
@@ -20535,24 +20388,8 @@ const hasDirective = (programNode, directive) => {
|
|
|
20535
20388
|
return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
|
|
20536
20389
|
};
|
|
20537
20390
|
//#endregion
|
|
20538
|
-
//#region src/plugin/utils/
|
|
20539
|
-
const
|
|
20540
|
-
"freeze",
|
|
20541
|
-
"seal",
|
|
20542
|
-
"preventExtensions"
|
|
20543
|
-
]);
|
|
20544
|
-
const OBJECT_FREEZE_OR_SEAL_METHOD_NAMES = new Set(["freeze", "seal"]);
|
|
20545
|
-
const unwrapObjectIntegrityExpression = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
|
|
20546
|
-
let expression = stripParenExpression(node);
|
|
20547
|
-
while (isNodeOfType(expression, "CallExpression")) {
|
|
20548
|
-
const callee = stripParenExpression(expression.callee);
|
|
20549
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || callee.object.name !== "Object" || !scopes.isGlobalReference(callee.object) || !isNodeOfType(callee.property, "Identifier") || !methodNames.has(callee.property.name)) break;
|
|
20550
|
-
const wrappedExpression = expression.arguments[0];
|
|
20551
|
-
if (!wrappedExpression || isNodeOfType(wrappedExpression, "SpreadElement")) break;
|
|
20552
|
-
expression = stripParenExpression(wrappedExpression);
|
|
20553
|
-
}
|
|
20554
|
-
return expression;
|
|
20555
|
-
};
|
|
20391
|
+
//#region src/plugin/utils/is-component-assignment.ts
|
|
20392
|
+
const isComponentAssignment = (node) => isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && isUppercaseName(node.id.name) && Boolean(node.init) && (isNodeOfType(node.init, "ArrowFunctionExpression") || isNodeOfType(node.init, "FunctionExpression"));
|
|
20556
20393
|
//#endregion
|
|
20557
20394
|
//#region src/plugin/rules/nextjs/nextjs-async-client-component.ts
|
|
20558
20395
|
const nextjsAsyncClientComponent = defineRule({
|
|
@@ -20578,10 +20415,10 @@ const nextjsAsyncClientComponent = defineRule({
|
|
|
20578
20415
|
},
|
|
20579
20416
|
VariableDeclarator(node) {
|
|
20580
20417
|
if (!fileHasUseClient) return;
|
|
20418
|
+
if (!isComponentAssignment(node)) return;
|
|
20419
|
+
if (!isInlineFunctionExpression(node.init)) return;
|
|
20420
|
+
if (!node.init.async) return;
|
|
20581
20421
|
if (!isNodeOfType(node.id, "Identifier")) return;
|
|
20582
|
-
if (!isUppercaseName(node.id.name) || !node.init) return;
|
|
20583
|
-
const componentFunction = unwrapObjectIntegrityExpression(node.init, context.scopes, OBJECT_FREEZE_OR_SEAL_METHOD_NAMES);
|
|
20584
|
-
if (!isInlineFunctionExpression(componentFunction) || !componentFunction.async) return;
|
|
20585
20422
|
context.report({
|
|
20586
20423
|
node,
|
|
20587
20424
|
message: `Async client component "${node.id.name}" fails to render because client components can't be async.`
|
|
@@ -21401,7 +21238,6 @@ const nextjsNoNativeScript = defineRule({
|
|
|
21401
21238
|
});
|
|
21402
21239
|
//#endregion
|
|
21403
21240
|
//#region src/plugin/rules/nextjs/nextjs-no-polyfill-script.ts
|
|
21404
|
-
const ABSOLUTE_URL_SCHEME_PATTERN = /^[a-z][a-z\d+.-]*:/i;
|
|
21405
21241
|
const nextjsNoPolyfillScript = defineRule({
|
|
21406
21242
|
id: "nextjs-no-polyfill-script",
|
|
21407
21243
|
title: "Redundant polyfill script",
|
|
@@ -21415,9 +21251,7 @@ const nextjsNoPolyfillScript = defineRule({
|
|
|
21415
21251
|
const srcAttribute = findJsxAttribute(node.attributes ?? [], "src");
|
|
21416
21252
|
if (!srcAttribute?.value) return;
|
|
21417
21253
|
const srcValue = isNodeOfType(srcAttribute.value, "Literal") ? srcAttribute.value.value : null;
|
|
21418
|
-
|
|
21419
|
-
const requestScheme = requestUrl?.trimStart().match(ABSOLUTE_URL_SCHEME_PATTERN)?.[0].toLowerCase();
|
|
21420
|
-
if (requestUrl && (!requestScheme || requestScheme === "http:" || requestScheme === "https:") && POLYFILL_SCRIPT_PATTERN.test(requestUrl)) context.report({
|
|
21254
|
+
if (typeof srcValue === "string" && POLYFILL_SCRIPT_PATTERN.test(srcValue)) context.report({
|
|
21421
21255
|
node,
|
|
21422
21256
|
message: "This polyfill CDN script makes your users download polyfills Next.js already includes."
|
|
21423
21257
|
});
|
|
@@ -26320,9 +26154,6 @@ const functionContainsReactRenderOutput = (functionNode, scopes) => {
|
|
|
26320
26154
|
return hasRenderOutput;
|
|
26321
26155
|
};
|
|
26322
26156
|
//#endregion
|
|
26323
|
-
//#region src/plugin/utils/is-component-assignment.ts
|
|
26324
|
-
const isComponentAssignment = (node) => isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && isUppercaseName(node.id.name) && Boolean(node.init) && (isNodeOfType(node.init, "ArrowFunctionExpression") || isNodeOfType(node.init, "FunctionExpression"));
|
|
26325
|
-
//#endregion
|
|
26326
26157
|
//#region src/plugin/utils/is-component-declaration.ts
|
|
26327
26158
|
const isComponentDeclaration = (node) => isNodeOfType(node, "FunctionDeclaration") && node.id !== null && Boolean(node.id?.name) && isUppercaseName(node.id.name);
|
|
26328
26159
|
//#endregion
|
|
@@ -31964,26 +31795,6 @@ const noImgLazyWithHighFetchpriority = defineRule({
|
|
|
31964
31795
|
} })
|
|
31965
31796
|
});
|
|
31966
31797
|
//#endregion
|
|
31967
|
-
//#region src/plugin/utils/react-ref-origin.ts
|
|
31968
|
-
const resolveReactRefSymbol = (memberExpression, scopes) => {
|
|
31969
|
-
const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
|
|
31970
|
-
if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticPropertyName(memberExpression) !== "current" || !isNodeOfType(receiver, "Identifier")) return null;
|
|
31971
|
-
const symbol = resolveConstIdentifierAlias(receiver, scopes);
|
|
31972
|
-
if (!symbol?.initializer) return null;
|
|
31973
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
31974
|
-
if (!isNodeOfType(initializer, "CallExpression")) return null;
|
|
31975
|
-
return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
|
|
31976
|
-
};
|
|
31977
|
-
const hasReactRefCurrentOrigin = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
31978
|
-
const expression = stripParenExpression(node);
|
|
31979
|
-
if (resolveReactRefSymbol(expression, scopes)) return true;
|
|
31980
|
-
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
31981
|
-
const symbol = resolveConstIdentifierAlias(expression, scopes);
|
|
31982
|
-
if (!symbol?.initializer || visitedSymbolIds.has(symbol.id)) return false;
|
|
31983
|
-
visitedSymbolIds.add(symbol.id);
|
|
31984
|
-
return hasReactRefCurrentOrigin(symbol.initializer, scopes, visitedSymbolIds);
|
|
31985
|
-
};
|
|
31986
|
-
//#endregion
|
|
31987
31798
|
//#region src/plugin/rules/state-and-effects/no-impure-state-updater.ts
|
|
31988
31799
|
const TIMER_FUNCTION_NAMES = new Set([
|
|
31989
31800
|
"cancelAnimationFrame",
|
|
@@ -32015,33 +31826,6 @@ const NOTIFICATION_METHOD_NAMES = new Set([
|
|
|
32015
31826
|
"success",
|
|
32016
31827
|
"warning"
|
|
32017
31828
|
]);
|
|
32018
|
-
const NOTIFICATION_MODULE_SOURCES = new Set([
|
|
32019
|
-
"@chakra-ui/react",
|
|
32020
|
-
"@heroui/react",
|
|
32021
|
-
"@mantine/notifications",
|
|
32022
|
-
"antd",
|
|
32023
|
-
"react-hot-toast",
|
|
32024
|
-
"react-toastify",
|
|
32025
|
-
"sonner"
|
|
32026
|
-
]);
|
|
32027
|
-
const SYNCHRONOUS_ARRAY_METHOD_NAMES = new Set([
|
|
32028
|
-
"every",
|
|
32029
|
-
"filter",
|
|
32030
|
-
"find",
|
|
32031
|
-
"findIndex",
|
|
32032
|
-
"flatMap",
|
|
32033
|
-
"forEach",
|
|
32034
|
-
"map",
|
|
32035
|
-
"reduce",
|
|
32036
|
-
"reduceRight",
|
|
32037
|
-
"some",
|
|
32038
|
-
"sort"
|
|
32039
|
-
]);
|
|
32040
|
-
const isNotificationModuleSource = (source) => {
|
|
32041
|
-
if (!source) return false;
|
|
32042
|
-
for (const moduleSource of NOTIFICATION_MODULE_SOURCES) if (source === moduleSource || source.startsWith(`${moduleSource}/`)) return true;
|
|
32043
|
-
return /(?:^|[/_.-])(?:notification|toast)s?(?:$|[/_.-])/i.test(source);
|
|
32044
|
-
};
|
|
32045
31829
|
const getMemberCall = (node) => {
|
|
32046
31830
|
if (!isNodeOfType(node, "CallExpression")) return null;
|
|
32047
31831
|
if (!isNodeOfType(node.callee, "MemberExpression") || node.callee.computed) return null;
|
|
@@ -32053,37 +31837,27 @@ const getMemberCall = (node) => {
|
|
|
32053
31837
|
};
|
|
32054
31838
|
const isNotificationReceiver = (receiver, scopes) => {
|
|
32055
31839
|
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
32056
|
-
|
|
32057
|
-
if (importBinding) return isNotificationModuleSource(importBinding.source) && (importBinding.exportedName === "default" || importBinding.exportedName !== null && NOTIFICATION_RECEIVER_NAMES.has(importBinding.exportedName));
|
|
31840
|
+
if (!NOTIFICATION_RECEIVER_NAMES.has(receiver.name)) return false;
|
|
32058
31841
|
const symbol = scopes.symbolFor(receiver);
|
|
31842
|
+
if (symbol?.kind === "import") return true;
|
|
32059
31843
|
if (!isNodeOfType(symbol?.initializer, "CallExpression")) return false;
|
|
32060
31844
|
const callee = symbol.initializer.callee;
|
|
32061
|
-
|
|
32062
|
-
const hookImport = getImportBindingForName(callee, callee.name);
|
|
32063
|
-
return Boolean(hookImport && isNotificationModuleSource(hookImport.source) && hookImport.exportedName !== null && /^use(?:Message|Notification|Toast)$/.test(hookImport.exportedName));
|
|
32064
|
-
};
|
|
32065
|
-
const isKnownGlobalObject = (node, expectedName, scopes) => isNodeOfType(node, "Identifier") && node.name === expectedName && scopes.isGlobalReference(node);
|
|
32066
|
-
const isGlobalMember = (node, expectedPropertyName, scopes) => isNodeOfType(node, "MemberExpression") && !node.computed && isNodeOfType(node.property, "Identifier") && node.property.name === expectedPropertyName && (isKnownGlobalObject(node.object, "window", scopes) || isKnownGlobalObject(node.object, "globalThis", scopes));
|
|
32067
|
-
const isStorageReceiver = (node, scopes) => {
|
|
32068
|
-
if (isNodeOfType(node, "Identifier") && STORAGE_RECEIVER_NAMES.has(node.name) && scopes.isGlobalReference(node)) return true;
|
|
32069
|
-
for (const receiverName of STORAGE_RECEIVER_NAMES) if (isGlobalMember(node, receiverName, scopes)) return true;
|
|
32070
|
-
return false;
|
|
31845
|
+
return isNodeOfType(callee, "Identifier") && /^use(?:Message|Notification|Toast)$/.test(callee.name);
|
|
32071
31846
|
};
|
|
32072
31847
|
const getKnownImpureCall = (callExpression, scopes) => {
|
|
32073
31848
|
if (isNodeOfType(callExpression.callee, "Identifier") && TIMER_FUNCTION_NAMES.has(callExpression.callee.name) && scopes.isGlobalReference(callExpression.callee)) return `${callExpression.callee.name}()`;
|
|
32074
|
-
if (isNodeOfType(callExpression.callee, "MemberExpression") && !callExpression.callee.computed && isNodeOfType(callExpression.callee.property, "Identifier") && TIMER_FUNCTION_NAMES.has(callExpression.callee.property.name) && (isKnownGlobalObject(callExpression.callee.object, "window", scopes) || isKnownGlobalObject(callExpression.callee.object, "globalThis", scopes))) return `${callExpression.callee.property.name}()`;
|
|
32075
31849
|
const memberCall = getMemberCall(callExpression);
|
|
32076
31850
|
if (!memberCall) return null;
|
|
32077
31851
|
const { methodName, receiver } = memberCall;
|
|
32078
|
-
if (STORAGE_MUTATION_METHOD_NAMES.has(methodName) &&
|
|
32079
|
-
if (EXTERNAL_READ_METHOD_NAMES.has(methodName)
|
|
32080
|
-
|
|
32081
|
-
if (EXTERNAL_READ_METHOD_NAMES.has(methodName) && receiverRoot !== null && isKnownGlobalObject(receiverRoot, "document", scopes)) return `.${methodName}()`;
|
|
32082
|
-
if (NOTIFICATION_METHOD_NAMES.has(methodName) && isNotificationReceiver(receiver, scopes)) return `${methodName}()`;
|
|
31852
|
+
if (STORAGE_MUTATION_METHOD_NAMES.has(methodName) && isNodeOfType(receiver, "Identifier") && STORAGE_RECEIVER_NAMES.has(receiver.name) && scopes.isGlobalReference(receiver)) return `${receiver.name}.${methodName}()`;
|
|
31853
|
+
if (EXTERNAL_READ_METHOD_NAMES.has(methodName)) return `.${methodName}()`;
|
|
31854
|
+
if (NOTIFICATION_METHOD_NAMES.has(methodName) && isNodeOfType(receiver, "Identifier") && isNotificationReceiver(receiver, scopes)) return `${receiver.name}.${methodName}()`;
|
|
32083
31855
|
return null;
|
|
32084
31856
|
};
|
|
32085
31857
|
const getExternalAssignmentDescription = (assignmentTarget, updater, scopes) => {
|
|
32086
|
-
|
|
31858
|
+
let rootIdentifier = null;
|
|
31859
|
+
if (isNodeOfType(assignmentTarget, "Identifier")) rootIdentifier = assignmentTarget;
|
|
31860
|
+
else if (isNodeOfType(assignmentTarget, "MemberExpression") && isNodeOfType(assignmentTarget.object, "Identifier")) rootIdentifier = assignmentTarget.object;
|
|
32087
31861
|
if (!rootIdentifier) return null;
|
|
32088
31862
|
const updaterScope = scopes.ownScopeFor(updater);
|
|
32089
31863
|
if (!updaterScope) return null;
|
|
@@ -32092,26 +31866,12 @@ const getExternalAssignmentDescription = (assignmentTarget, updater, scopes) =>
|
|
|
32092
31866
|
if (symbol.kind === "parameter" && symbol.scope === updaterScope) return `the updater argument "${rootIdentifier.name}"`;
|
|
32093
31867
|
return isDescendantScope(symbol.scope, updaterScope) ? null : `the captured value "${rootIdentifier.name}"`;
|
|
32094
31868
|
};
|
|
32095
|
-
const isArrayValue = (node, scopes) => {
|
|
32096
|
-
const expression = stripParenExpression(node);
|
|
32097
|
-
if (isNodeOfType(expression, "ArrayExpression")) return true;
|
|
32098
|
-
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
32099
|
-
return isNodeOfType(resolveConstIdentifierAlias(expression, scopes)?.initializer, "ArrayExpression");
|
|
32100
|
-
};
|
|
32101
|
-
const isDefinitelySynchronousCallback = (callback, scopes) => {
|
|
32102
|
-
const parent = callback.parent;
|
|
32103
|
-
if (!parent) return false;
|
|
32104
|
-
if (isNodeOfType(parent, "CallExpression") && parent.callee === callback) return true;
|
|
32105
|
-
if (!isNodeOfType(parent, "CallExpression") || !parent.arguments?.some((argument) => argument === callback)) return false;
|
|
32106
|
-
if (isNodeOfType(parent.callee, "MemberExpression") && !parent.callee.computed && isNodeOfType(parent.callee.property, "Identifier") && SYNCHRONOUS_ARRAY_METHOD_NAMES.has(parent.callee.property.name) && isArrayValue(parent.callee.object, scopes)) return true;
|
|
32107
|
-
return isNodeOfType(parent.callee, "MemberExpression") && !parent.callee.computed && isNodeOfType(parent.callee.object, "Identifier") && parent.callee.object.name === "Array" && scopes.isGlobalReference(parent.callee.object) && isNodeOfType(parent.callee.property, "Identifier") && parent.callee.property.name === "from" && parent.arguments[1] === callback;
|
|
32108
|
-
};
|
|
32109
31869
|
const findImpureUpdaterOperation = (updater, scopes) => {
|
|
32110
31870
|
const analysis = getProgramAnalysis(updater);
|
|
32111
31871
|
let operation = null;
|
|
32112
31872
|
walkAst(updater, (child) => {
|
|
32113
31873
|
if (operation) return false;
|
|
32114
|
-
if (child !== updater && isFunctionLike$1(child) && !
|
|
31874
|
+
if (child !== updater && isFunctionLike$1(child) && !executesDuringRender(child, scopes)) return false;
|
|
32115
31875
|
if (isNodeOfType(child, "CallExpression")) {
|
|
32116
31876
|
if (isNodeOfType(child.callee, "Identifier") && analysis) {
|
|
32117
31877
|
const calleeReference = getRef(analysis, child.callee);
|
|
@@ -32143,25 +31903,18 @@ const noImpureStateUpdater = defineRule({
|
|
|
32143
31903
|
severity: "error",
|
|
32144
31904
|
recommendation: "Keep state updater callbacks pure and return only the next state. Move notifications, storage, timers, ref writes, and other external work into the event or effect that queues the update.",
|
|
32145
31905
|
create: (context) => ({ CallExpression(node) {
|
|
32146
|
-
const
|
|
32147
|
-
if (!
|
|
31906
|
+
const updater = node.arguments?.[0];
|
|
31907
|
+
if (!updater || !isFunctionLike$1(updater)) return;
|
|
32148
31908
|
const analysis = getProgramAnalysis(node);
|
|
32149
31909
|
if (!analysis || !isNodeOfType(node.callee, "Identifier")) return;
|
|
32150
31910
|
const calleeReference = getRef(analysis, node.callee);
|
|
32151
31911
|
if (!calleeReference || !isStateSetterCall(analysis, calleeReference)) return;
|
|
32152
31912
|
const stateDeclarator = getUseStateDecl(analysis, calleeReference);
|
|
32153
31913
|
if (!isNodeOfType(stateDeclarator, "VariableDeclarator") || !isNodeOfType(stateDeclarator.init, "CallExpression") || !isReactApiCall(stateDeclarator.init, "useState", context.scopes, { allowGlobalReactNamespace: true })) return;
|
|
32154
|
-
let updater = null;
|
|
32155
|
-
if (isFunctionLike$1(updaterArgument)) updater = updaterArgument;
|
|
32156
|
-
else if (isNodeOfType(updaterArgument, "Identifier")) {
|
|
32157
|
-
const updaterReference = getRef(analysis, updaterArgument);
|
|
32158
|
-
if (updaterReference) updater = resolveToFunction(updaterReference);
|
|
32159
|
-
}
|
|
32160
|
-
if (!updater) return;
|
|
32161
31914
|
const operation = findImpureUpdaterOperation(updater, context.scopes);
|
|
32162
31915
|
if (!operation) return;
|
|
32163
31916
|
context.report({
|
|
32164
|
-
node:
|
|
31917
|
+
node: updater,
|
|
32165
31918
|
message: `This state updater performs ${operation}. React may run updater functions more than once, so side effects here can repeat or observe inconsistent external state.`
|
|
32166
31919
|
});
|
|
32167
31920
|
} })
|
|
@@ -32450,12 +32203,11 @@ const isMemoCall = (node) => {
|
|
|
32450
32203
|
};
|
|
32451
32204
|
const isDefaultEquivalentComparator = (comparator) => isNodeOfType(comparator, "Identifier") && (comparator.name === "undefined" || comparator.name === "shallowEqual");
|
|
32452
32205
|
const hasCustomComparator = (node) => isNodeOfType(node, "CallExpression") && (node.arguments?.length ?? 0) >= 2 && !isDefaultEquivalentComparator(node.arguments?.[1]);
|
|
32453
|
-
const isInlineReference = (node
|
|
32454
|
-
|
|
32455
|
-
if (isNodeOfType(
|
|
32456
|
-
if (isNodeOfType(
|
|
32457
|
-
if (isNodeOfType(
|
|
32458
|
-
if (isNodeOfType(referenceNode, "JSXElement") || isNodeOfType(referenceNode, "JSXFragment")) return "JSX";
|
|
32206
|
+
const isInlineReference = (node) => {
|
|
32207
|
+
if (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "bind") return "functions";
|
|
32208
|
+
if (isNodeOfType(node, "ObjectExpression")) return "objects";
|
|
32209
|
+
if (isNodeOfType(node, "ArrayExpression")) return "Arrays";
|
|
32210
|
+
if (isNodeOfType(node, "JSXElement") || isNodeOfType(node, "JSXFragment")) return "JSX";
|
|
32459
32211
|
return null;
|
|
32460
32212
|
};
|
|
32461
32213
|
const noInlinePropOnMemoComponent = defineRule({
|
|
@@ -32485,7 +32237,7 @@ const noInlinePropOnMemoComponent = defineRule({
|
|
|
32485
32237
|
let elementName = null;
|
|
32486
32238
|
if (isNodeOfType(openingElement.name, "JSXIdentifier")) elementName = openingElement.name.name;
|
|
32487
32239
|
if (!elementName || !memoizedComponentNames.has(elementName)) return;
|
|
32488
|
-
const propType = isInlineReference(node.value.expression
|
|
32240
|
+
const propType = isInlineReference(node.value.expression);
|
|
32489
32241
|
if (propType) context.report({
|
|
32490
32242
|
node: node.value.expression,
|
|
32491
32243
|
message: `This redraws ${elementName} on every render because the prop is ${propType} built right here, so memo() can't skip it. Move it to a stable value with useMemo, useCallback, or module scope`
|
|
@@ -33006,18 +32758,6 @@ const INTL_FORMAT_METHOD_NAMES = new Set([
|
|
|
33006
32758
|
"formatToParts",
|
|
33007
32759
|
"formatRange"
|
|
33008
32760
|
]);
|
|
33009
|
-
const ABSENT_PROPERTY_PROOF = { status: "absent" };
|
|
33010
|
-
const PRESENT_PROPERTY_PROOF = { status: "present" };
|
|
33011
|
-
const UNDEFINED_PROPERTY_PROOF = { status: "undefined" };
|
|
33012
|
-
const UNKNOWN_PROPERTY_PROOF = { status: "unknown" };
|
|
33013
|
-
const READ_ONLY_OBJECT_METHOD_NAMES = new Set([
|
|
33014
|
-
"hasOwnProperty",
|
|
33015
|
-
"isPrototypeOf",
|
|
33016
|
-
"propertyIsEnumerable",
|
|
33017
|
-
"toLocaleString",
|
|
33018
|
-
"toString",
|
|
33019
|
-
"valueOf"
|
|
33020
|
-
]);
|
|
33021
32761
|
const isProvableDateExpression = (expression) => {
|
|
33022
32762
|
if (!expression) return false;
|
|
33023
32763
|
const unwrapped = stripParenExpression(expression);
|
|
@@ -33032,290 +32772,32 @@ const receiverNameLooksDateFlavored = (expression) => {
|
|
|
33032
32772
|
if (isNodeOfType(unwrapped, "CallExpression")) return receiverNameLooksDateFlavored(unwrapped.callee);
|
|
33033
32773
|
return false;
|
|
33034
32774
|
};
|
|
33035
|
-
const
|
|
33036
|
-
|
|
33037
|
-
if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return true;
|
|
33038
|
-
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
33039
|
-
if (expression.name === "undefined" && scopes.isGlobalReference(expression)) return true;
|
|
33040
|
-
const symbol = scopes.symbolFor(expression);
|
|
33041
|
-
if (symbol?.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || visitedSymbolIds.has(symbol.id)) return false;
|
|
33042
|
-
visitedSymbolIds.add(symbol.id);
|
|
33043
|
-
return isStaticUndefined(symbol.initializer, scopes, visitedSymbolIds);
|
|
33044
|
-
};
|
|
33045
|
-
const isNestedAssignmentTarget = (expression) => {
|
|
33046
|
-
let target = expression;
|
|
33047
|
-
let parent = target.parent;
|
|
33048
|
-
while (parent) {
|
|
33049
|
-
if (isNodeOfType(parent, "AssignmentExpression")) return parent.left === target;
|
|
33050
|
-
if (isNodeOfType(parent, "ForInStatement") || isNodeOfType(parent, "ForOfStatement")) return parent.left === target;
|
|
33051
|
-
if (isNodeOfType(parent, "AssignmentPattern")) {
|
|
33052
|
-
if (parent.left !== target) return false;
|
|
33053
|
-
} else if (isNodeOfType(parent, "RestElement")) {
|
|
33054
|
-
if (parent.argument !== target) return false;
|
|
33055
|
-
} else if (isNodeOfType(parent, "ArrayPattern")) {
|
|
33056
|
-
if (!parent.elements?.some((element) => element === target)) return false;
|
|
33057
|
-
} else if (isNodeOfType(parent, "Property")) {
|
|
33058
|
-
if (parent.value !== target || !isNodeOfType(parent.parent, "ObjectPattern")) return false;
|
|
33059
|
-
} else if (isNodeOfType(parent, "ObjectPattern")) {
|
|
33060
|
-
if (!parent.properties?.some((property) => property === target)) return false;
|
|
33061
|
-
} else return false;
|
|
33062
|
-
target = parent;
|
|
33063
|
-
parent = target.parent;
|
|
33064
|
-
}
|
|
33065
|
-
return false;
|
|
33066
|
-
};
|
|
33067
|
-
const isPotentialMutationReference = (identifier, readNode) => {
|
|
33068
|
-
let expression = identifier;
|
|
33069
|
-
let parent = expression.parent;
|
|
33070
|
-
while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === expression) {
|
|
33071
|
-
expression = parent;
|
|
33072
|
-
parent = expression.parent;
|
|
33073
|
-
}
|
|
33074
|
-
const rootExpression = expression;
|
|
33075
|
-
let memberDepth = 0;
|
|
33076
|
-
while (parent && isNodeOfType(parent, "MemberExpression") && parent.object === expression) {
|
|
33077
|
-
memberDepth += 1;
|
|
33078
|
-
expression = parent;
|
|
33079
|
-
parent = expression.parent;
|
|
33080
|
-
while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === expression) {
|
|
33081
|
-
expression = parent;
|
|
33082
|
-
parent = expression.parent;
|
|
33083
|
-
}
|
|
33084
|
-
}
|
|
33085
|
-
if (!parent || isAstDescendant(identifier, readNode)) return false;
|
|
33086
|
-
if (isNestedAssignmentTarget(expression)) return true;
|
|
33087
|
-
if (isNodeOfType(parent, "AssignmentExpression") && parent.left === expression) return true;
|
|
33088
|
-
if (isNodeOfType(parent, "UpdateExpression") && parent.argument === expression) return true;
|
|
33089
|
-
if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expression) return true;
|
|
33090
|
-
if (isNodeOfType(parent, "CallExpression")) {
|
|
33091
|
-
if (parent.callee === expression) {
|
|
33092
|
-
if (memberDepth === 0) return true;
|
|
33093
|
-
const memberExpression = stripParenExpression(expression);
|
|
33094
|
-
return memberDepth === 1 && isNodeOfType(memberExpression, "MemberExpression") && !READ_ONLY_OBJECT_METHOD_NAMES.has(getStaticPropertyName(memberExpression) ?? "");
|
|
33095
|
-
}
|
|
33096
|
-
return memberDepth === 0 && parent.arguments?.some((argument) => argument === rootExpression);
|
|
33097
|
-
}
|
|
33098
|
-
if (isNodeOfType(parent, "NewExpression")) return memberDepth === 0 && parent.arguments?.some((argument) => argument === rootExpression);
|
|
33099
|
-
if (isNodeOfType(parent, "AssignmentExpression") && parent.right === rootExpression) return true;
|
|
33100
|
-
return false;
|
|
33101
|
-
};
|
|
33102
|
-
const getSimpleAlias = (identifier, scopes) => {
|
|
33103
|
-
let expression = identifier;
|
|
33104
|
-
let parent = expression.parent;
|
|
33105
|
-
while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === expression) {
|
|
33106
|
-
expression = parent;
|
|
33107
|
-
parent = expression.parent;
|
|
33108
|
-
}
|
|
33109
|
-
if (!isNodeOfType(parent, "VariableDeclarator") || parent.init !== expression || !isNodeOfType(parent.id, "Identifier")) return null;
|
|
33110
|
-
const symbol = scopes.symbolFor(parent.id);
|
|
33111
|
-
return symbol ? {
|
|
33112
|
-
symbol,
|
|
33113
|
-
readNode: parent.id
|
|
33114
|
-
} : null;
|
|
33115
|
-
};
|
|
33116
|
-
const getDirectCallForExpression = (expression) => {
|
|
33117
|
-
let callee = expression;
|
|
33118
|
-
let parent = callee.parent;
|
|
33119
|
-
while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === callee) {
|
|
33120
|
-
callee = parent;
|
|
33121
|
-
parent = callee.parent;
|
|
33122
|
-
}
|
|
33123
|
-
return isNodeOfType(parent, "CallExpression") && parent.callee === callee ? parent : null;
|
|
33124
|
-
};
|
|
33125
|
-
const getMethodOwner = (functionNode, scopes) => {
|
|
33126
|
-
const methodNode = functionNode.parent;
|
|
33127
|
-
if (isNodeOfType(methodNode, "Property") && methodNode.value === functionNode && isNodeOfType(methodNode.parent, "ObjectExpression")) {
|
|
33128
|
-
const objectParent = methodNode.parent.parent;
|
|
33129
|
-
if (!isNodeOfType(objectParent, "VariableDeclarator") || objectParent.init !== methodNode.parent || !isNodeOfType(objectParent.id, "Identifier")) return null;
|
|
33130
|
-
const ownerSymbol = scopes.symbolFor(objectParent.id);
|
|
33131
|
-
const methodName = getStaticPropertyKeyName(methodNode, { allowComputedString: true });
|
|
33132
|
-
return ownerSymbol && methodName ? {
|
|
33133
|
-
methodName,
|
|
33134
|
-
ownerKind: "object",
|
|
33135
|
-
ownerSymbol,
|
|
33136
|
-
isStatic: false
|
|
33137
|
-
} : null;
|
|
33138
|
-
}
|
|
33139
|
-
if (!isNodeOfType(methodNode, "MethodDefinition") || methodNode.value !== functionNode || !isNodeOfType(methodNode.parent, "ClassBody")) return null;
|
|
33140
|
-
const classNode = methodNode.parent.parent;
|
|
33141
|
-
if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression")) return null;
|
|
33142
|
-
let bindingIdentifier = isNodeOfType(classNode.id, "Identifier") ? classNode.id : null;
|
|
33143
|
-
if (!bindingIdentifier && isNodeOfType(classNode.parent, "VariableDeclarator")) bindingIdentifier = isNodeOfType(classNode.parent.id, "Identifier") ? classNode.parent.id : null;
|
|
33144
|
-
if (!bindingIdentifier) return null;
|
|
33145
|
-
const ownerSymbol = scopes.symbolFor(bindingIdentifier);
|
|
33146
|
-
const methodName = getStaticPropertyKeyName(methodNode, { allowComputedString: true });
|
|
33147
|
-
return ownerSymbol && methodName ? {
|
|
33148
|
-
methodName,
|
|
33149
|
-
ownerKind: "class",
|
|
33150
|
-
ownerSymbol,
|
|
33151
|
-
isStatic: Boolean(methodNode.static)
|
|
33152
|
-
} : null;
|
|
33153
|
-
};
|
|
33154
|
-
const doesSymbolResolveToOwner = (symbol, ownerSymbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
33155
|
-
if (!symbol) return false;
|
|
33156
|
-
if (symbol.declarationNode === ownerSymbol.declarationNode) return true;
|
|
33157
|
-
if (symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id)) return false;
|
|
33158
|
-
visitedSymbolIds.add(symbol.id);
|
|
33159
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
33160
|
-
return isNodeOfType(initializer, "Identifier") && doesSymbolResolveToOwner(scopes.symbolFor(initializer), ownerSymbol, scopes, visitedSymbolIds);
|
|
33161
|
-
};
|
|
33162
|
-
const isClassInstanceExpression = (expression, ownerSymbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
33163
|
-
const candidate = stripParenExpression(expression);
|
|
33164
|
-
if (isNodeOfType(candidate, "NewExpression") && isNodeOfType(candidate.callee, "Identifier")) return doesSymbolResolveToOwner(scopes.symbolFor(candidate.callee), ownerSymbol, scopes);
|
|
33165
|
-
if (!isNodeOfType(candidate, "Identifier")) return false;
|
|
33166
|
-
const symbol = scopes.symbolFor(candidate);
|
|
33167
|
-
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id)) return false;
|
|
33168
|
-
visitedSymbolIds.add(symbol.id);
|
|
33169
|
-
return isClassInstanceExpression(symbol.initializer, ownerSymbol, scopes, visitedSymbolIds);
|
|
33170
|
-
};
|
|
33171
|
-
const getMethodCalls = (functionNode, scopes) => {
|
|
33172
|
-
const owner = getMethodOwner(functionNode, scopes);
|
|
33173
|
-
if (!owner) return [];
|
|
33174
|
-
const calls = [];
|
|
33175
|
-
walkAst(scopes.rootScope.node, (child) => {
|
|
33176
|
-
if (!isNodeOfType(child, "MemberExpression")) return;
|
|
33177
|
-
if (getStaticPropertyName(child) !== owner.methodName) return;
|
|
33178
|
-
const receiver = stripParenExpression(child.object);
|
|
33179
|
-
let doesReceiverMatch = isNodeOfType(receiver, "Identifier") && doesSymbolResolveToOwner(scopes.symbolFor(receiver), owner.ownerSymbol, scopes);
|
|
33180
|
-
if (owner.ownerKind === "class" && !owner.isStatic) doesReceiverMatch = isClassInstanceExpression(receiver, owner.ownerSymbol, scopes);
|
|
33181
|
-
if (!doesReceiverMatch) return;
|
|
33182
|
-
const call = getDirectCallForExpression(child);
|
|
33183
|
-
if (call) calls.push(call);
|
|
33184
|
-
});
|
|
33185
|
-
return calls;
|
|
33186
|
-
};
|
|
33187
|
-
const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, scopes, visitedSymbolIds, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
33188
|
-
if (visitedFunctionNodes.has(functionNode)) return false;
|
|
33189
|
-
visitedFunctionNodes.add(functionNode);
|
|
33190
|
-
const usageFunction = findEnclosingFunction(usageNode);
|
|
33191
|
-
const immediateCall = getDirectCallForExpression(functionNode);
|
|
33192
|
-
if (immediateCall) {
|
|
33193
|
-
const immediateCallFunction = findEnclosingFunction(immediateCall);
|
|
33194
|
-
if (immediateCallFunction === usageFunction) {
|
|
33195
|
-
const immediateCallStart = getRangeStart(immediateCall);
|
|
33196
|
-
return immediateCallStart === null || immediateCallStart < usageBoundary;
|
|
33197
|
-
}
|
|
33198
|
-
if (!immediateCallFunction) return usageFunction !== null;
|
|
33199
|
-
return isFunctionInvokedBeforeUsage(immediateCallFunction, usageNode, usageBoundary, scopes, visitedSymbolIds, new Set(visitedFunctionNodes));
|
|
33200
|
-
}
|
|
33201
|
-
for (const methodCall of getMethodCalls(functionNode, scopes)) {
|
|
33202
|
-
const methodCallFunction = findEnclosingFunction(methodCall);
|
|
33203
|
-
if (methodCallFunction === usageFunction) {
|
|
33204
|
-
const methodCallStart = getRangeStart(methodCall);
|
|
33205
|
-
if (methodCallStart === null || methodCallStart < usageBoundary) return true;
|
|
33206
|
-
continue;
|
|
33207
|
-
}
|
|
33208
|
-
if (!methodCallFunction) {
|
|
33209
|
-
if (usageFunction) return true;
|
|
33210
|
-
continue;
|
|
33211
|
-
}
|
|
33212
|
-
if (isFunctionInvokedBeforeUsage(methodCallFunction, usageNode, usageBoundary, scopes, new Set(visitedSymbolIds), new Set(visitedFunctionNodes))) return true;
|
|
33213
|
-
}
|
|
33214
|
-
const bindingIdentifier = getFunctionBindingIdentifier(functionNode);
|
|
33215
|
-
if (!bindingIdentifier) return false;
|
|
33216
|
-
const symbol = scopes.symbolFor(bindingIdentifier);
|
|
33217
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
33218
|
-
visitedSymbolIds.add(symbol.id);
|
|
33219
|
-
let wasInvokedBeforeUsage = false;
|
|
33220
|
-
walkAst(scopes.rootScope.node, (child) => {
|
|
33221
|
-
if (wasInvokedBeforeUsage || !isNodeOfType(child, "Identifier")) return;
|
|
33222
|
-
if (scopes.symbolFor(child)?.declarationNode !== symbol.declarationNode) return;
|
|
33223
|
-
const call = getDirectCallForExpression(child);
|
|
33224
|
-
if (!call) return false;
|
|
33225
|
-
const callFunction = findEnclosingFunction(call);
|
|
33226
|
-
if (callFunction === usageFunction) {
|
|
33227
|
-
const callStart = getRangeStart(call);
|
|
33228
|
-
wasInvokedBeforeUsage = callStart === null || callStart < usageBoundary;
|
|
33229
|
-
return;
|
|
33230
|
-
}
|
|
33231
|
-
if (!callFunction) {
|
|
33232
|
-
wasInvokedBeforeUsage = usageFunction !== null;
|
|
33233
|
-
return;
|
|
33234
|
-
}
|
|
33235
|
-
wasInvokedBeforeUsage = isFunctionInvokedBeforeUsage(callFunction, usageNode, usageBoundary, scopes, new Set(visitedSymbolIds), new Set(visitedFunctionNodes));
|
|
33236
|
-
});
|
|
33237
|
-
return wasInvokedBeforeUsage;
|
|
33238
|
-
};
|
|
33239
|
-
const getMutationUsageBoundary = (usageNode, readNode) => {
|
|
33240
|
-
const readStart = getRangeStart(readNode);
|
|
33241
|
-
let readExpression = readNode;
|
|
33242
|
-
let readParent = readExpression.parent;
|
|
33243
|
-
while (readParent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(readParent.type) && "expression" in readParent && readParent.expression === readExpression) {
|
|
33244
|
-
readExpression = readParent;
|
|
33245
|
-
readParent = readExpression.parent;
|
|
33246
|
-
}
|
|
33247
|
-
if ((isNodeOfType(usageNode, "CallExpression") || isNodeOfType(usageNode, "NewExpression")) && usageNode.arguments?.some((argument) => argument === readExpression)) return usageNode.range?.[1] ?? null;
|
|
33248
|
-
if (isAstDescendant(readNode, usageNode)) return readStart;
|
|
33249
|
-
return getRangeStart(usageNode);
|
|
33250
|
-
};
|
|
33251
|
-
const wasMutatedBeforeUsage = (symbol, usageNode, readNode, scopes, visitedMutationSymbolIds = /* @__PURE__ */ new Set(), inheritedUsageBoundary, readNodesBySymbolId = /* @__PURE__ */ new Map()) => {
|
|
33252
|
-
if (visitedMutationSymbolIds.has(symbol.id)) return false;
|
|
33253
|
-
visitedMutationSymbolIds.add(symbol.id);
|
|
33254
|
-
const usageBoundary = inheritedUsageBoundary === void 0 ? getMutationUsageBoundary(usageNode, readNode) : inheritedUsageBoundary;
|
|
33255
|
-
if (typeof usageBoundary !== "number") return true;
|
|
33256
|
-
const usageFunction = findEnclosingFunction(usageNode);
|
|
33257
|
-
return symbol.references.some((reference) => {
|
|
33258
|
-
const referenceStart = getRangeStart(reference.identifier);
|
|
33259
|
-
const simpleAlias = getSimpleAlias(reference.identifier, scopes);
|
|
33260
|
-
if (simpleAlias) return wasMutatedBeforeUsage(simpleAlias.symbol, usageNode, readNodesBySymbolId.get(simpleAlias.symbol.id) ?? simpleAlias.readNode, scopes, new Set(visitedMutationSymbolIds), usageBoundary, readNodesBySymbolId);
|
|
33261
|
-
if (!isPotentialMutationReference(reference.identifier, readNode)) return false;
|
|
33262
|
-
if (referenceStart === null) return true;
|
|
33263
|
-
const mutationFunction = findEnclosingFunction(reference.identifier);
|
|
33264
|
-
if (mutationFunction === usageFunction) return referenceStart < usageBoundary;
|
|
33265
|
-
if (!mutationFunction) return usageFunction !== null;
|
|
33266
|
-
if (usageFunction && isAstDescendant(usageFunction, mutationFunction)) return true;
|
|
33267
|
-
return isFunctionInvokedBeforeUsage(mutationFunction, usageNode, usageBoundary, scopes, /* @__PURE__ */ new Set());
|
|
33268
|
-
});
|
|
33269
|
-
};
|
|
33270
|
-
const getObjectPropertyProof = (objectExpression, propertyName, scopes, usageNode, visitedSymbolIds = /* @__PURE__ */ new Set(), inheritedUsageBoundary, readNodesBySymbolId = /* @__PURE__ */ new Map()) => {
|
|
33271
|
-
if (!objectExpression) return ABSENT_PROPERTY_PROOF;
|
|
32775
|
+
const objectLiteralHasProperty = (objectExpression, propertyName) => {
|
|
32776
|
+
if (!objectExpression) return false;
|
|
33272
32777
|
const unwrapped = stripParenExpression(objectExpression);
|
|
33273
|
-
if (isNodeOfType(unwrapped, "
|
|
33274
|
-
|
|
33275
|
-
|
|
33276
|
-
|
|
33277
|
-
if (
|
|
33278
|
-
|
|
33279
|
-
if (symbol?.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || visitedSymbolIds.has(symbol.id) || wasMutatedBeforeUsage(symbol, usageNode, unwrapped, scopes, /* @__PURE__ */ new Set(), usageBoundary, nextReadNodesBySymbolId)) return UNKNOWN_PROPERTY_PROOF;
|
|
33280
|
-
visitedSymbolIds.add(symbol.id);
|
|
33281
|
-
return getObjectPropertyProof(symbol.initializer, propertyName, scopes, usageNode, visitedSymbolIds, usageBoundary, nextReadNodesBySymbolId);
|
|
33282
|
-
}
|
|
33283
|
-
if (isNodeOfType(unwrapped, "ConditionalExpression")) {
|
|
33284
|
-
const consequent = getObjectPropertyProof(unwrapped.consequent, propertyName, scopes, usageNode, new Set(visitedSymbolIds), inheritedUsageBoundary, new Map(readNodesBySymbolId));
|
|
33285
|
-
const alternate = getObjectPropertyProof(unwrapped.alternate, propertyName, scopes, usageNode, new Set(visitedSymbolIds), inheritedUsageBoundary, new Map(readNodesBySymbolId));
|
|
33286
|
-
return consequent.status === alternate.status ? consequent : UNKNOWN_PROPERTY_PROOF;
|
|
33287
|
-
}
|
|
33288
|
-
if (isNodeOfType(unwrapped, "CallExpression") && isNodeOfType(unwrapped.callee, "MemberExpression") && !unwrapped.callee.computed && isNodeOfType(unwrapped.callee.object, "Identifier") && unwrapped.callee.object.name === "Object" && scopes.isGlobalReference(unwrapped.callee.object) && isNodeOfType(unwrapped.callee.property, "Identifier") && unwrapped.callee.property.name === "freeze") return getObjectPropertyProof(unwrapped.arguments?.[0], propertyName, scopes, usageNode, visitedSymbolIds, inheritedUsageBoundary, readNodesBySymbolId);
|
|
33289
|
-
if (!isNodeOfType(unwrapped, "ObjectExpression")) return UNKNOWN_PROPERTY_PROOF;
|
|
33290
|
-
const properties = unwrapped.properties ?? [];
|
|
33291
|
-
for (let propertyIndex = properties.length - 1; propertyIndex >= 0; propertyIndex -= 1) {
|
|
33292
|
-
const property = properties[propertyIndex];
|
|
33293
|
-
if (!property) continue;
|
|
33294
|
-
if (isNodeOfType(property, "SpreadElement")) {
|
|
33295
|
-
const spreadProof = getObjectPropertyProof(property.argument, propertyName, scopes, unwrapped, new Set(visitedSymbolIds), void 0);
|
|
33296
|
-
if (spreadProof.status !== "absent") return spreadProof;
|
|
33297
|
-
continue;
|
|
33298
|
-
}
|
|
33299
|
-
if (!isNodeOfType(property, "Property") || getStaticPropertyKeyName(property) !== propertyName) continue;
|
|
33300
|
-
return isStaticUndefined(property.value, scopes) ? UNDEFINED_PROPERTY_PROOF : PRESENT_PROPERTY_PROOF;
|
|
32778
|
+
if (!isNodeOfType(unwrapped, "ObjectExpression")) return false;
|
|
32779
|
+
for (const property of unwrapped.properties ?? []) {
|
|
32780
|
+
if (!isNodeOfType(property, "Property")) continue;
|
|
32781
|
+
if (property.computed) continue;
|
|
32782
|
+
if (isNodeOfType(property.key, "Identifier") && property.key.name === propertyName) return true;
|
|
32783
|
+
if (isNodeOfType(property.key, "Literal") && property.key.value === propertyName) return true;
|
|
33301
32784
|
}
|
|
33302
|
-
return
|
|
32785
|
+
return false;
|
|
33303
32786
|
};
|
|
33304
|
-
const
|
|
33305
|
-
const hasExplicitLocaleArgument = (argument, scopes) => {
|
|
32787
|
+
const hasExplicitLocaleArgument = (argument) => {
|
|
33306
32788
|
if (!argument) return false;
|
|
33307
32789
|
const unwrapped = stripParenExpression(argument);
|
|
33308
|
-
if (isNodeOfType(unwrapped, "Identifier") && unwrapped.name === "undefined"
|
|
32790
|
+
if (isNodeOfType(unwrapped, "Identifier") && unwrapped.name === "undefined") return false;
|
|
33309
32791
|
return true;
|
|
33310
32792
|
};
|
|
33311
|
-
const isDeterministicLocaleMethodCall = (call, methodName, receiverIsProvablyDate
|
|
32793
|
+
const isDeterministicLocaleMethodCall = (call, methodName, receiverIsProvablyDate) => {
|
|
33312
32794
|
const localeArgument = call.arguments?.[0];
|
|
33313
|
-
if (!hasExplicitLocaleArgument(localeArgument
|
|
32795
|
+
if (!hasExplicitLocaleArgument(localeArgument)) return false;
|
|
33314
32796
|
const optionsArgument = call.arguments?.[1];
|
|
33315
|
-
if (
|
|
32797
|
+
if (objectLiteralHasProperty(optionsArgument, "timeZone")) return true;
|
|
33316
32798
|
return !DATE_ONLY_LOCALE_METHOD_NAMES.has(methodName) && !receiverIsProvablyDate;
|
|
33317
32799
|
};
|
|
33318
|
-
const matchLocaleMethodCall = (call
|
|
32800
|
+
const matchLocaleMethodCall = (call) => {
|
|
33319
32801
|
const callee = call.callee;
|
|
33320
32802
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return null;
|
|
33321
32803
|
if (!isNodeOfType(callee.property, "Identifier")) return null;
|
|
@@ -33323,7 +32805,7 @@ const matchLocaleMethodCall = (call, scopes) => {
|
|
|
33323
32805
|
if (!LOCALE_FORMAT_METHOD_NAMES.has(methodName)) return null;
|
|
33324
32806
|
const receiverIsProvablyDate = isProvableDateExpression(callee.object);
|
|
33325
32807
|
if (methodName === "toLocaleString" && !receiverIsProvablyDate && !receiverNameLooksDateFlavored(callee.object)) return null;
|
|
33326
|
-
if (isDeterministicLocaleMethodCall(call, methodName, receiverIsProvablyDate
|
|
32808
|
+
if (isDeterministicLocaleMethodCall(call, methodName, receiverIsProvablyDate)) return null;
|
|
33327
32809
|
return {
|
|
33328
32810
|
node: call,
|
|
33329
32811
|
display: `${methodName}()`
|
|
@@ -33339,13 +32821,13 @@ const getIntlFormatterName = (expression) => {
|
|
|
33339
32821
|
if (!isNodeOfType(callee.property, "Identifier")) return null;
|
|
33340
32822
|
return INTL_FORMATTER_NAMES.has(callee.property.name) ? callee.property.name : null;
|
|
33341
32823
|
};
|
|
33342
|
-
const isDeterministicIntlConstruction = (construction, formatterName
|
|
32824
|
+
const isDeterministicIntlConstruction = (construction, formatterName) => {
|
|
33343
32825
|
if (!isNodeOfType(construction, "CallExpression") && !isNodeOfType(construction, "NewExpression")) return false;
|
|
33344
|
-
if (!hasExplicitLocaleArgument(construction.arguments?.[0]
|
|
32826
|
+
if (!hasExplicitLocaleArgument(construction.arguments?.[0])) return false;
|
|
33345
32827
|
if (formatterName !== "DateTimeFormat") return true;
|
|
33346
|
-
return
|
|
32828
|
+
return objectLiteralHasProperty(construction.arguments?.[1], "timeZone");
|
|
33347
32829
|
};
|
|
33348
|
-
const matchIntlFormatCall = (call
|
|
32830
|
+
const matchIntlFormatCall = (call) => {
|
|
33349
32831
|
const callee = call.callee;
|
|
33350
32832
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return null;
|
|
33351
32833
|
if (!isNodeOfType(callee.property, "Identifier")) return null;
|
|
@@ -33358,7 +32840,7 @@ const matchIntlFormatCall = (call, scopes) => {
|
|
|
33358
32840
|
if (!construction) return null;
|
|
33359
32841
|
const formatterName = getIntlFormatterName(construction);
|
|
33360
32842
|
if (!formatterName) return null;
|
|
33361
|
-
if (isDeterministicIntlConstruction(construction, formatterName
|
|
32843
|
+
if (isDeterministicIntlConstruction(construction, formatterName)) return null;
|
|
33362
32844
|
return {
|
|
33363
32845
|
node: call,
|
|
33364
32846
|
display: `Intl.${formatterName}().${callee.property.name}()`
|
|
@@ -33427,7 +32909,7 @@ const noLocaleFormatInRender = defineRule({
|
|
|
33427
32909
|
fileIsEmailTemplate = hasEmailTemplateImport(node);
|
|
33428
32910
|
},
|
|
33429
32911
|
CallExpression(node) {
|
|
33430
|
-
const match = matchLocaleMethodCall(node
|
|
32912
|
+
const match = matchLocaleMethodCall(node) ?? matchIntlFormatCall(node) ?? matchDateDefaultStringification(node);
|
|
33431
32913
|
if (match) reportIfRenderPhase(match);
|
|
33432
32914
|
},
|
|
33433
32915
|
TemplateLiteral(node) {
|
|
@@ -33447,7 +32929,7 @@ const noLocaleFormatInRender = defineRule({
|
|
|
33447
32929
|
walkAst(helperNode.body ?? helperNode, (child) => {
|
|
33448
32930
|
if (isFunctionLike$1(child)) return false;
|
|
33449
32931
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
33450
|
-
const match = matchLocaleMethodCall(child
|
|
32932
|
+
const match = matchLocaleMethodCall(child) ?? matchIntlFormatCall(child);
|
|
33451
32933
|
if (!match || reportedNodes.has(match.node)) return;
|
|
33452
32934
|
if (fileIsEmailTemplate) return;
|
|
33453
32935
|
if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
|
|
@@ -37273,85 +36755,28 @@ const noRedundantShouldComponentUpdate = defineRule({
|
|
|
37273
36755
|
});
|
|
37274
36756
|
//#endregion
|
|
37275
36757
|
//#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
|
|
37276
|
-
const
|
|
37277
|
-
"
|
|
37278
|
-
"
|
|
37279
|
-
|
|
37280
|
-
|
|
37281
|
-
|
|
37282
|
-
|
|
36758
|
+
const resolveReactRefSymbol = (memberExpression, scopes) => {
|
|
36759
|
+
const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
|
|
36760
|
+
if (!isNodeOfType(memberExpression, "MemberExpression") || memberExpression.computed || !isNodeOfType(memberExpression.property, "Identifier") || memberExpression.property.name !== "current" || !isNodeOfType(receiver, "Identifier")) return null;
|
|
36761
|
+
const symbol = resolveConstIdentifierAlias(receiver, scopes);
|
|
36762
|
+
if (!symbol?.initializer) return null;
|
|
36763
|
+
const initializer = stripParenExpression(symbol.initializer);
|
|
36764
|
+
if (!isNodeOfType(initializer, "CallExpression")) return null;
|
|
36765
|
+
return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
|
|
36766
|
+
};
|
|
37283
36767
|
const isSameRefCurrentMember = (node, refSymbol, scopes) => {
|
|
37284
|
-
if (!isNodeOfType(node, "MemberExpression") ||
|
|
36768
|
+
if (!isNodeOfType(node, "MemberExpression") || node.computed || !isNodeOfType(node.property, "Identifier") || node.property.name !== "current") return false;
|
|
37285
36769
|
const receiver = stripParenExpression(node.object);
|
|
37286
36770
|
return isNodeOfType(receiver, "Identifier") && resolveConstIdentifierAlias(receiver, scopes)?.id === refSymbol.id;
|
|
37287
36771
|
};
|
|
37288
|
-
const isSameRefCurrentAlias = (node, refSymbol, scopes) => {
|
|
37289
|
-
if (isSameRefCurrentMember(node, refSymbol, scopes)) return true;
|
|
37290
|
-
if (!isNodeOfType(node, "Identifier")) return false;
|
|
37291
|
-
const aliasSymbol = scopes.symbolFor(node);
|
|
37292
|
-
return aliasSymbol?.kind === "const" && aliasSymbol.initializer !== null && isSameRefCurrentMember(stripParenExpression(aliasSymbol.initializer), refSymbol, scopes);
|
|
37293
|
-
};
|
|
37294
|
-
const isEmptySentinel = (node, scopes) => isNodeOfType(node, "Literal") && node.value === null || isNodeOfType(node, "Identifier") && node.name === "undefined" && scopes.isGlobalReference(node);
|
|
37295
|
-
const hasRepeatedExecutionAncestor = (node, stop) => {
|
|
37296
|
-
let ancestor = node.parent;
|
|
37297
|
-
while (ancestor && ancestor !== stop) {
|
|
37298
|
-
if (isFunctionLike$1(ancestor) || REPEATED_ANCESTOR_TYPES.has(ancestor.type)) return true;
|
|
37299
|
-
ancestor = ancestor.parent;
|
|
37300
|
-
}
|
|
37301
|
-
return ancestor !== stop;
|
|
37302
|
-
};
|
|
37303
|
-
const getBranchConstraints = (node, branchRoot) => {
|
|
37304
|
-
const constraints = /* @__PURE__ */ new Map();
|
|
37305
|
-
let descendant = node;
|
|
37306
|
-
let ancestor = descendant.parent;
|
|
37307
|
-
while (ancestor && descendant !== branchRoot) {
|
|
37308
|
-
if (isNodeOfType(ancestor, "IfStatement")) {
|
|
37309
|
-
if (ancestor.consequent === descendant) constraints.set(ancestor, true);
|
|
37310
|
-
if (ancestor.alternate === descendant) constraints.set(ancestor, false);
|
|
37311
|
-
}
|
|
37312
|
-
descendant = ancestor;
|
|
37313
|
-
ancestor = ancestor.parent;
|
|
37314
|
-
}
|
|
37315
|
-
return constraints;
|
|
37316
|
-
};
|
|
37317
|
-
const canExecuteTogether = (firstConstraints, secondConstraints) => {
|
|
37318
|
-
for (const [statement, branch] of firstConstraints) {
|
|
37319
|
-
const otherBranch = secondConstraints.get(statement);
|
|
37320
|
-
if (otherBranch !== void 0 && otherBranch !== branch) return false;
|
|
37321
|
-
}
|
|
37322
|
-
return true;
|
|
37323
|
-
};
|
|
37324
|
-
const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol, scopes) => {
|
|
37325
|
-
const assignmentConstraints = getBranchConstraints(assignmentExpression, branchRoot);
|
|
37326
|
-
const assignmentStart = getRangeStart(assignmentExpression);
|
|
37327
|
-
let hasCoExecutableWrite = false;
|
|
37328
|
-
walkAst(branchRoot, (child) => {
|
|
37329
|
-
if (hasCoExecutableWrite) return false;
|
|
37330
|
-
const childStart = getRangeStart(child);
|
|
37331
|
-
if (child === assignmentExpression || !isNodeOfType(child, "AssignmentExpression") || assignmentStart === null || childStart === null || childStart >= assignmentStart || resolveReactRefSymbol(child.left, scopes)?.id !== refSymbol.id || hasRepeatedExecutionAncestor(child, branchRoot)) return;
|
|
37332
|
-
if (canExecuteTogether(assignmentConstraints, getBranchConstraints(child, branchRoot))) {
|
|
37333
|
-
hasCoExecutableWrite = true;
|
|
37334
|
-
return false;
|
|
37335
|
-
}
|
|
37336
|
-
});
|
|
37337
|
-
return !hasCoExecutableWrite;
|
|
37338
|
-
};
|
|
37339
36772
|
const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes) => {
|
|
37340
|
-
if (assignmentExpression.operator
|
|
37341
|
-
if (assignmentExpression.operator !== "=") return false;
|
|
36773
|
+
if (assignmentExpression.operator !== "=" || !isNodeOfType(assignmentExpression.right, "NewExpression")) return false;
|
|
37342
36774
|
let descendant = assignmentExpression;
|
|
37343
36775
|
let ancestor = descendant.parent;
|
|
37344
36776
|
while (ancestor) {
|
|
37345
|
-
if (isNodeOfType(ancestor, "IfStatement") && isNodeOfType(ancestor.test, "BinaryExpression") &&
|
|
37346
|
-
"===",
|
|
37347
|
-
"==",
|
|
37348
|
-
"!==",
|
|
37349
|
-
"!="
|
|
37350
|
-
].includes(ancestor.test.operator)) {
|
|
36777
|
+
if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant && isNodeOfType(ancestor.test, "BinaryExpression") && (ancestor.test.operator === "===" || ancestor.test.operator === "==")) {
|
|
37351
36778
|
const { left, right } = ancestor.test;
|
|
37352
|
-
|
|
37353
|
-
const guardedBranch = ancestor.test.operator === "===" || ancestor.test.operator === "==" ? ancestor.consequent : ancestor.alternate;
|
|
37354
|
-
if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch && !hasRepeatedExecutionAncestor(assignmentExpression, guardedBranch) && hasNoPriorCoExecutableWrite(assignmentExpression, guardedBranch, refSymbol, scopes)) return true;
|
|
36779
|
+
if (isSameRefCurrentMember(left, refSymbol, scopes) && isNodeOfType(right, "Literal") && right.value === null || isSameRefCurrentMember(right, refSymbol, scopes) && isNodeOfType(left, "Literal") && left.value === null) return true;
|
|
37355
36780
|
}
|
|
37356
36781
|
descendant = ancestor;
|
|
37357
36782
|
ancestor = descendant.parent;
|
|
@@ -44260,13 +43685,13 @@ const resolveTanstackQueryHookName = (callExpression) => {
|
|
|
44260
43685
|
}
|
|
44261
43686
|
return null;
|
|
44262
43687
|
};
|
|
44263
|
-
const resolveHookNameFromInitializer = (initializer
|
|
43688
|
+
const resolveHookNameFromInitializer = (initializer) => {
|
|
44264
43689
|
if (isNodeOfType(initializer, "CallExpression")) return resolveTanstackQueryHookName(initializer);
|
|
44265
43690
|
if (!isNodeOfType(initializer, "Identifier")) return null;
|
|
44266
|
-
const
|
|
44267
|
-
if (
|
|
44268
|
-
if (!isNodeOfType(
|
|
44269
|
-
return resolveTanstackQueryHookName(
|
|
43691
|
+
const binding = findVariableInitializer(initializer, initializer.name);
|
|
43692
|
+
if (!binding?.initializer || !isConstDeclaredBinding(binding)) return null;
|
|
43693
|
+
if (!isNodeOfType(binding.initializer, "CallExpression")) return null;
|
|
43694
|
+
return resolveTanstackQueryHookName(binding.initializer);
|
|
44270
43695
|
};
|
|
44271
43696
|
const queryNoRestDestructuring = defineRule({
|
|
44272
43697
|
id: "query-no-rest-destructuring",
|
|
@@ -44279,7 +43704,7 @@ const queryNoRestDestructuring = defineRule({
|
|
|
44279
43704
|
if (!isNodeOfType(node.id, "ObjectPattern")) return;
|
|
44280
43705
|
if (!node.init) return;
|
|
44281
43706
|
if (!node.id.properties?.some((property) => isNodeOfType(property, "RestElement"))) return;
|
|
44282
|
-
const hookName = resolveHookNameFromInitializer(node.init
|
|
43707
|
+
const hookName = resolveHookNameFromInitializer(node.init);
|
|
44283
43708
|
if (!hookName) return;
|
|
44284
43709
|
context.report({
|
|
44285
43710
|
node: node.id,
|
|
@@ -45634,7 +45059,7 @@ const DEFERRABLE_HOOK_NAMES = new Set([
|
|
|
45634
45059
|
"useParams",
|
|
45635
45060
|
"usePathname"
|
|
45636
45061
|
]);
|
|
45637
|
-
const findHookCallBindings = (componentBody
|
|
45062
|
+
const findHookCallBindings = (componentBody) => {
|
|
45638
45063
|
const bindings = [];
|
|
45639
45064
|
if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
|
|
45640
45065
|
for (const statement of componentBody.body ?? []) {
|
|
@@ -45645,10 +45070,8 @@ const findHookCallBindings = (componentBody, scopes) => {
|
|
|
45645
45070
|
const callee = declarator.init.callee;
|
|
45646
45071
|
if (!isNodeOfType(callee, "Identifier")) continue;
|
|
45647
45072
|
if (!DEFERRABLE_HOOK_NAMES.has(callee.name)) continue;
|
|
45648
|
-
const valueSymbol = scopes.symbolFor(declarator.id);
|
|
45649
|
-
if (!valueSymbol) continue;
|
|
45650
45073
|
bindings.push({
|
|
45651
|
-
|
|
45074
|
+
valueName: declarator.id.name,
|
|
45652
45075
|
hookName: callee.name,
|
|
45653
45076
|
declarator
|
|
45654
45077
|
});
|
|
@@ -45656,49 +45079,6 @@ const findHookCallBindings = (componentBody, scopes) => {
|
|
|
45656
45079
|
}
|
|
45657
45080
|
return bindings;
|
|
45658
45081
|
};
|
|
45659
|
-
const collectExactAliasSymbols = (componentBody, sourceSymbol, scopes) => {
|
|
45660
|
-
const symbols = [sourceSymbol];
|
|
45661
|
-
const symbolIds = new Set([sourceSymbol.id]);
|
|
45662
|
-
const aliasSourceIdentifiers = /* @__PURE__ */ new Set();
|
|
45663
|
-
if (!isNodeOfType(componentBody, "BlockStatement")) return {
|
|
45664
|
-
symbols,
|
|
45665
|
-
aliasSourceIdentifiers
|
|
45666
|
-
};
|
|
45667
|
-
let didFindAlias = true;
|
|
45668
|
-
while (didFindAlias) {
|
|
45669
|
-
didFindAlias = false;
|
|
45670
|
-
for (const statement of componentBody.body ?? []) {
|
|
45671
|
-
if (!isNodeOfType(statement, "VariableDeclaration") || statement.kind !== "const") continue;
|
|
45672
|
-
for (const declarator of statement.declarations ?? []) {
|
|
45673
|
-
let aliasIdentifier = null;
|
|
45674
|
-
let sourceIdentifier = null;
|
|
45675
|
-
const initializer = declarator.init ? stripParenExpression(declarator.init) : null;
|
|
45676
|
-
const arrayBinding = isNodeOfType(declarator.id, "ArrayPattern") ? declarator.id.elements[0] : null;
|
|
45677
|
-
const arrayValueNode = isNodeOfType(initializer, "ArrayExpression") ? initializer.elements[0] : null;
|
|
45678
|
-
const arrayValue = arrayValueNode && !isNodeOfType(arrayValueNode, "SpreadElement") ? stripParenExpression(arrayValueNode) : null;
|
|
45679
|
-
if (isNodeOfType(declarator.id, "Identifier") && isNodeOfType(initializer, "Identifier")) {
|
|
45680
|
-
aliasIdentifier = declarator.id;
|
|
45681
|
-
sourceIdentifier = initializer;
|
|
45682
|
-
} else if (isNodeOfType(declarator.id, "ArrayPattern") && declarator.id.elements.length === 1 && isNodeOfType(arrayBinding, "Identifier") && isNodeOfType(initializer, "ArrayExpression") && initializer.elements.length === 1 && isNodeOfType(arrayValue, "Identifier")) {
|
|
45683
|
-
aliasIdentifier = arrayBinding;
|
|
45684
|
-
sourceIdentifier = arrayValue;
|
|
45685
|
-
}
|
|
45686
|
-
if (!aliasIdentifier || !sourceIdentifier) continue;
|
|
45687
|
-
const referencedSymbol = scopes.symbolFor(sourceIdentifier);
|
|
45688
|
-
const aliasSymbol = scopes.symbolFor(aliasIdentifier);
|
|
45689
|
-
if (!referencedSymbol || !symbolIds.has(referencedSymbol.id) || !aliasSymbol || aliasSymbol.kind !== "const" || symbolIds.has(aliasSymbol.id)) continue;
|
|
45690
|
-
symbolIds.add(aliasSymbol.id);
|
|
45691
|
-
symbols.push(aliasSymbol);
|
|
45692
|
-
aliasSourceIdentifiers.add(sourceIdentifier);
|
|
45693
|
-
didFindAlias = true;
|
|
45694
|
-
}
|
|
45695
|
-
}
|
|
45696
|
-
}
|
|
45697
|
-
return {
|
|
45698
|
-
symbols,
|
|
45699
|
-
aliasSourceIdentifiers
|
|
45700
|
-
};
|
|
45701
|
-
};
|
|
45702
45082
|
const rerenderDeferReadsHook = defineRule({
|
|
45703
45083
|
id: "rerender-defer-reads-hook",
|
|
45704
45084
|
title: "URL hook value only read in handlers",
|
|
@@ -45709,13 +45089,15 @@ const rerenderDeferReadsHook = defineRule({
|
|
|
45709
45089
|
create: (context) => {
|
|
45710
45090
|
const checkComponent = (componentBody) => {
|
|
45711
45091
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
45712
|
-
const bindings = findHookCallBindings(componentBody
|
|
45092
|
+
const bindings = findHookCallBindings(componentBody);
|
|
45713
45093
|
if (bindings.length === 0) return;
|
|
45714
45094
|
const handlerBindingNames = collectHandlerBindingNames(componentBody);
|
|
45715
45095
|
for (const binding of bindings) {
|
|
45716
|
-
const { symbols, aliasSourceIdentifiers } = collectExactAliasSymbols(componentBody, binding.valueSymbol, context.scopes);
|
|
45717
45096
|
const referenceLocations = [];
|
|
45718
|
-
|
|
45097
|
+
walkAst(componentBody, (child) => {
|
|
45098
|
+
if (isNodeOfType(binding.declarator, "VariableDeclarator") && child === binding.declarator.id) return;
|
|
45099
|
+
if (isNodeOfType(child, "Identifier") && child.name === binding.valueName) referenceLocations.push(child);
|
|
45100
|
+
});
|
|
45719
45101
|
if (referenceLocations.length === 0) continue;
|
|
45720
45102
|
if (!referenceLocations.every((ref) => isInsideEventHandler(ref, handlerBindingNames))) continue;
|
|
45721
45103
|
context.report({
|
|
@@ -53664,8 +53046,7 @@ const serverCacheWithObjectLiteral = defineRule({
|
|
|
53664
53046
|
if (!isNodeOfType(node.callee, "Identifier")) return;
|
|
53665
53047
|
if (!cachedFunctionNames.has(node.callee.name)) return;
|
|
53666
53048
|
const firstArg = node.arguments?.[0];
|
|
53667
|
-
if (!
|
|
53668
|
-
if (!isNodeOfType(unwrapObjectIntegrityExpression(firstArg, context.scopes, OBJECT_FREEZE_OR_SEAL_METHOD_NAMES), "ObjectExpression")) return;
|
|
53049
|
+
if (!isNodeOfType(firstArg, "ObjectExpression")) return;
|
|
53669
53050
|
context.report({
|
|
53670
53051
|
node,
|
|
53671
53052
|
message: `Passing a new object to React.cache() each render misses the cache, so it refetches every request.`
|