oxlint-plugin-react-doctor 0.7.6-dev.1f14fa1 → 0.7.6-dev.47beb25

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.
Files changed (2) hide show
  1. package/dist/index.js +298 -25
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5964,7 +5964,7 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
5964
5964
  //#endregion
5965
5965
  //#region src/plugin/utils/get-static-property-key-name.ts
5966
5966
  const getStaticPropertyKeyName = (node, options = {}) => {
5967
- if (!isNodeOfType(node, "Property")) return null;
5967
+ if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition")) return null;
5968
5968
  if (node.computed) {
5969
5969
  if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
5970
5970
  return null;
@@ -21401,6 +21401,7 @@ const nextjsNoNativeScript = defineRule({
21401
21401
  });
21402
21402
  //#endregion
21403
21403
  //#region src/plugin/rules/nextjs/nextjs-no-polyfill-script.ts
21404
+ const ABSOLUTE_URL_SCHEME_PATTERN = /^[a-z][a-z\d+.-]*:/i;
21404
21405
  const nextjsNoPolyfillScript = defineRule({
21405
21406
  id: "nextjs-no-polyfill-script",
21406
21407
  title: "Redundant polyfill script",
@@ -21414,7 +21415,9 @@ const nextjsNoPolyfillScript = defineRule({
21414
21415
  const srcAttribute = findJsxAttribute(node.attributes ?? [], "src");
21415
21416
  if (!srcAttribute?.value) return;
21416
21417
  const srcValue = isNodeOfType(srcAttribute.value, "Literal") ? srcAttribute.value.value : null;
21417
- if (typeof srcValue === "string" && POLYFILL_SCRIPT_PATTERN.test(srcValue)) context.report({
21418
+ const requestUrl = typeof srcValue === "string" ? srcValue.split("#", 1)[0] : null;
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({
21418
21421
  node,
21419
21422
  message: "This polyfill CDN script makes your users download polyfills Next.js already includes."
21420
21423
  });
@@ -33003,6 +33006,18 @@ const INTL_FORMAT_METHOD_NAMES = new Set([
33003
33006
  "formatToParts",
33004
33007
  "formatRange"
33005
33008
  ]);
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
+ ]);
33006
33021
  const isProvableDateExpression = (expression) => {
33007
33022
  if (!expression) return false;
33008
33023
  const unwrapped = stripParenExpression(expression);
@@ -33017,32 +33032,290 @@ const receiverNameLooksDateFlavored = (expression) => {
33017
33032
  if (isNodeOfType(unwrapped, "CallExpression")) return receiverNameLooksDateFlavored(unwrapped.callee);
33018
33033
  return false;
33019
33034
  };
33020
- const objectLiteralHasProperty = (objectExpression, propertyName) => {
33021
- if (!objectExpression) return false;
33022
- const unwrapped = stripParenExpression(objectExpression);
33023
- if (!isNodeOfType(unwrapped, "ObjectExpression")) return false;
33024
- for (const property of unwrapped.properties ?? []) {
33025
- if (!isNodeOfType(property, "Property")) continue;
33026
- if (property.computed) continue;
33027
- if (isNodeOfType(property.key, "Identifier") && property.key.name === propertyName) return true;
33028
- if (isNodeOfType(property.key, "Literal") && property.key.value === propertyName) return true;
33035
+ const isStaticUndefined = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
33036
+ const expression = stripParenExpression(node);
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);
33029
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;
33030
33100
  return false;
33031
33101
  };
33032
- const hasExplicitLocaleArgument = (argument) => {
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;
33272
+ const unwrapped = stripParenExpression(objectExpression);
33273
+ if (isNodeOfType(unwrapped, "Literal") || isStaticUndefined(unwrapped, scopes)) return ABSENT_PROPERTY_PROOF;
33274
+ if (isNodeOfType(unwrapped, "Identifier")) {
33275
+ const symbol = scopes.symbolFor(unwrapped);
33276
+ const nextReadNodesBySymbolId = new Map(readNodesBySymbolId);
33277
+ if (symbol) nextReadNodesBySymbolId.set(symbol.id, unwrapped);
33278
+ const usageBoundary = inheritedUsageBoundary === void 0 ? getMutationUsageBoundary(usageNode, unwrapped) : inheritedUsageBoundary;
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;
33301
+ }
33302
+ return ABSENT_PROPERTY_PROOF;
33303
+ };
33304
+ const objectHasExplicitProperty = (objectExpression, propertyName, scopes, usageNode) => getObjectPropertyProof(objectExpression, propertyName, scopes, usageNode).status === "present";
33305
+ const hasExplicitLocaleArgument = (argument, scopes) => {
33033
33306
  if (!argument) return false;
33034
33307
  const unwrapped = stripParenExpression(argument);
33035
- if (isNodeOfType(unwrapped, "Identifier") && unwrapped.name === "undefined") return false;
33308
+ if (isNodeOfType(unwrapped, "Identifier") && unwrapped.name === "undefined" && scopes.isGlobalReference(unwrapped) || isNodeOfType(unwrapped, "UnaryExpression") && unwrapped.operator === "void") return false;
33036
33309
  return true;
33037
33310
  };
33038
- const isDeterministicLocaleMethodCall = (call, methodName, receiverIsProvablyDate) => {
33311
+ const isDeterministicLocaleMethodCall = (call, methodName, receiverIsProvablyDate, scopes) => {
33039
33312
  const localeArgument = call.arguments?.[0];
33040
- if (!hasExplicitLocaleArgument(localeArgument)) return false;
33313
+ if (!hasExplicitLocaleArgument(localeArgument, scopes)) return false;
33041
33314
  const optionsArgument = call.arguments?.[1];
33042
- if (objectLiteralHasProperty(optionsArgument, "timeZone")) return true;
33315
+ if (objectHasExplicitProperty(optionsArgument, "timeZone", scopes, call)) return true;
33043
33316
  return !DATE_ONLY_LOCALE_METHOD_NAMES.has(methodName) && !receiverIsProvablyDate;
33044
33317
  };
33045
- const matchLocaleMethodCall = (call) => {
33318
+ const matchLocaleMethodCall = (call, scopes) => {
33046
33319
  const callee = call.callee;
33047
33320
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return null;
33048
33321
  if (!isNodeOfType(callee.property, "Identifier")) return null;
@@ -33050,7 +33323,7 @@ const matchLocaleMethodCall = (call) => {
33050
33323
  if (!LOCALE_FORMAT_METHOD_NAMES.has(methodName)) return null;
33051
33324
  const receiverIsProvablyDate = isProvableDateExpression(callee.object);
33052
33325
  if (methodName === "toLocaleString" && !receiverIsProvablyDate && !receiverNameLooksDateFlavored(callee.object)) return null;
33053
- if (isDeterministicLocaleMethodCall(call, methodName, receiverIsProvablyDate)) return null;
33326
+ if (isDeterministicLocaleMethodCall(call, methodName, receiverIsProvablyDate, scopes)) return null;
33054
33327
  return {
33055
33328
  node: call,
33056
33329
  display: `${methodName}()`
@@ -33066,13 +33339,13 @@ const getIntlFormatterName = (expression) => {
33066
33339
  if (!isNodeOfType(callee.property, "Identifier")) return null;
33067
33340
  return INTL_FORMATTER_NAMES.has(callee.property.name) ? callee.property.name : null;
33068
33341
  };
33069
- const isDeterministicIntlConstruction = (construction, formatterName) => {
33342
+ const isDeterministicIntlConstruction = (construction, formatterName, scopes) => {
33070
33343
  if (!isNodeOfType(construction, "CallExpression") && !isNodeOfType(construction, "NewExpression")) return false;
33071
- if (!hasExplicitLocaleArgument(construction.arguments?.[0])) return false;
33344
+ if (!hasExplicitLocaleArgument(construction.arguments?.[0], scopes)) return false;
33072
33345
  if (formatterName !== "DateTimeFormat") return true;
33073
- return objectLiteralHasProperty(construction.arguments?.[1], "timeZone");
33346
+ return objectHasExplicitProperty(construction.arguments?.[1], "timeZone", scopes, construction);
33074
33347
  };
33075
- const matchIntlFormatCall = (call) => {
33348
+ const matchIntlFormatCall = (call, scopes) => {
33076
33349
  const callee = call.callee;
33077
33350
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return null;
33078
33351
  if (!isNodeOfType(callee.property, "Identifier")) return null;
@@ -33085,7 +33358,7 @@ const matchIntlFormatCall = (call) => {
33085
33358
  if (!construction) return null;
33086
33359
  const formatterName = getIntlFormatterName(construction);
33087
33360
  if (!formatterName) return null;
33088
- if (isDeterministicIntlConstruction(construction, formatterName)) return null;
33361
+ if (isDeterministicIntlConstruction(construction, formatterName, scopes)) return null;
33089
33362
  return {
33090
33363
  node: call,
33091
33364
  display: `Intl.${formatterName}().${callee.property.name}()`
@@ -33154,7 +33427,7 @@ const noLocaleFormatInRender = defineRule({
33154
33427
  fileIsEmailTemplate = hasEmailTemplateImport(node);
33155
33428
  },
33156
33429
  CallExpression(node) {
33157
- const match = matchLocaleMethodCall(node) ?? matchIntlFormatCall(node) ?? matchDateDefaultStringification(node);
33430
+ const match = matchLocaleMethodCall(node, context.scopes) ?? matchIntlFormatCall(node, context.scopes) ?? matchDateDefaultStringification(node);
33158
33431
  if (match) reportIfRenderPhase(match);
33159
33432
  },
33160
33433
  TemplateLiteral(node) {
@@ -33174,7 +33447,7 @@ const noLocaleFormatInRender = defineRule({
33174
33447
  walkAst(helperNode.body ?? helperNode, (child) => {
33175
33448
  if (isFunctionLike$1(child)) return false;
33176
33449
  if (!isNodeOfType(child, "CallExpression")) return;
33177
- const match = matchLocaleMethodCall(child) ?? matchIntlFormatCall(child);
33450
+ const match = matchLocaleMethodCall(child, context.scopes) ?? matchIntlFormatCall(child, context.scopes);
33178
33451
  if (!match || reportedNodes.has(match.node)) return;
33179
33452
  if (fileIsEmailTemplate) return;
33180
33453
  if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.6-dev.1f14fa1",
3
+ "version": "0.7.6-dev.47beb25",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",