oxlint-plugin-react-doctor 0.7.5-dev.654849 → 0.7.5-dev.76cd6be

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 +88 -402
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -397,7 +397,6 @@ const isProductionFilePath = (relativePath, sourceFilePattern) => {
397
397
  //#endregion
398
398
  //#region src/plugin/rules/security-scan/utils/is-production-source-path.ts
399
399
  const isProductionSourcePath = (relativePath) => {
400
- if (/\.d\.[cm]?[jt]s$/i.test(relativePath)) return false;
401
400
  return isProductionFilePath(relativePath, SOURCE_FILE_PATTERN);
402
401
  };
403
402
  //#endregion
@@ -5179,10 +5178,8 @@ const STORAGE_GLOBALS = new Set([
5179
5178
  const SENSITIVE_KEY_PATTERN = /token|jwt|secret|password|passwd|credential|api[-_]?key|bearer|private[-_]?key/i;
5180
5179
  const NON_AUTH_TOKEN_PATTERN = /csrf|xsrf|device|fcm|apns|push|design|tokeniz|syntax|css|theme|color/i;
5181
5180
  const STRONG_AUTH_KEY_PATTERN = /jwt|secret|password|passwd|credential|private[-_]?key|api[-_]?key|bearer|access[-_]?token|refresh[-_]?token|auth[-_]?token|id[-_]?token|session/i;
5182
- const PRODUCT_API_KEY_RECORDS_PATTERN = /(?:^|[._:-])(?:created|saved|integration|mailing)[-_]?api[-_]?keys$/i;
5183
5181
  const PRODUCT_API_KEY_COLLECTION_PATTERN = /[._:-](?:created|generated|saved)[-_]?api[-_]?keys$/i;
5184
5182
  const isAuthCredentialKey = (key) => {
5185
- if (PRODUCT_API_KEY_RECORDS_PATTERN.test(key)) return false;
5186
5183
  if (!SENSITIVE_KEY_PATTERN.test(key)) return false;
5187
5184
  if (PRODUCT_API_KEY_COLLECTION_PATTERN.test(key)) return false;
5188
5185
  if (NON_AUTH_TOKEN_PATTERN.test(key) && !STRONG_AUTH_KEY_PATTERN.test(key)) return false;
@@ -6871,10 +6868,11 @@ const getTemplateInterpolations = (valueTail) => {
6871
6868
  const interpolations = valueTail.slice(1, closingBacktickIndex).match(/\$\{[^}]*\}/g);
6872
6869
  return interpolations === null ? "" : interpolations.join(" ");
6873
6870
  };
6874
- const getIdentifierDeclarationInitializer = (identifier, sinkIndex, fileContent) => {
6875
- const declaration = findVisibleIdentifierDeclaration(identifier, sinkIndex, fileContent);
6876
- if (declaration === null) return null;
6877
- return fileContent.slice(declaration.initializerStartIndex, declaration.initializerStartIndex + STATIC_TEMPLATE_MAX_CHARS);
6871
+ const getIdentifierDeclarationInitializer = (identifier, fileContent) => {
6872
+ const declarationMatch = new RegExp(`(?:const|let|var)\\s+${escapeRegExp(identifier)}\\s*(?::[^=\\n]{0,120})?=\\s*`).exec(fileContent);
6873
+ if (declarationMatch === null) return null;
6874
+ const initializerStartIndex = declarationMatch.index + declarationMatch[0].length;
6875
+ return fileContent.slice(initializerStartIndex, initializerStartIndex + STATIC_TEMPLATE_MAX_CHARS);
6878
6876
  };
6879
6877
  const isPureStringLiteralConcat = (initializerText) => {
6880
6878
  if (!/^["']/.test(initializerText)) return false;
@@ -6944,153 +6942,6 @@ const isAllOperandsDomContentConcat = (valueExpression) => {
6944
6942
  return !HTML_TAINT_PATTERN.test(withoutCast.replace(DOM_CONTENT_SOURCE_VALUE_PATTERN, ""));
6945
6943
  });
6946
6944
  };
6947
- const findMatchingBraceIndex = (fileContent, openingBraceIndex) => {
6948
- let depth = 0;
6949
- let quote = null;
6950
- for (let index = openingBraceIndex; index < fileContent.length; index += 1) {
6951
- const character = fileContent[index];
6952
- if (quote !== null) {
6953
- if (character === quote && fileContent[index - 1] !== "\\") quote = null;
6954
- continue;
6955
- }
6956
- if (character === "\"" || character === "'" || character === "`") {
6957
- quote = character;
6958
- continue;
6959
- }
6960
- if (character === "{") depth += 1;
6961
- if (character === "}") {
6962
- depth -= 1;
6963
- if (depth === 0) return index;
6964
- }
6965
- }
6966
- return fileContent.length;
6967
- };
6968
- const findContainingBlockEndIndex = (fileContent, targetIndex) => {
6969
- const openingBraceIndexes = [];
6970
- let quote = null;
6971
- let isLineComment = false;
6972
- let isBlockComment = false;
6973
- for (let index = 0; index < targetIndex; index += 1) {
6974
- const character = fileContent[index];
6975
- const nextCharacter = fileContent[index + 1];
6976
- if (isLineComment) {
6977
- if (character === "\n") isLineComment = false;
6978
- continue;
6979
- }
6980
- if (isBlockComment) {
6981
- if (character === "*" && nextCharacter === "/") {
6982
- isBlockComment = false;
6983
- index += 1;
6984
- }
6985
- continue;
6986
- }
6987
- if (quote !== null) {
6988
- if (character === quote && fileContent[index - 1] !== "\\") quote = null;
6989
- continue;
6990
- }
6991
- if (character === "/" && nextCharacter === "/") {
6992
- isLineComment = true;
6993
- index += 1;
6994
- continue;
6995
- }
6996
- if (character === "/" && nextCharacter === "*") {
6997
- isBlockComment = true;
6998
- index += 1;
6999
- continue;
7000
- }
7001
- if (character === "\"" || character === "'" || character === "`") {
7002
- quote = character;
7003
- continue;
7004
- }
7005
- if (character === "{") openingBraceIndexes.push(index);
7006
- if (character === "}") openingBraceIndexes.pop();
7007
- }
7008
- const openingBraceIndex = openingBraceIndexes.at(-1);
7009
- return openingBraceIndex === void 0 ? fileContent.length : findMatchingBraceIndex(fileContent, openingBraceIndex);
7010
- };
7011
- const findVisibleIdentifierDeclaration = (identifier, sinkIndex, fileContent) => {
7012
- const initializerPattern = new RegExp(`(?:const|let|var)\\s+${escapeRegExp(identifier)}\\s*(?::[^=\\n]*)?=\\s*([^;\\n]+)`, "g");
7013
- let nearestDeclaration = null;
7014
- let nearestDeclarationIndex = -1;
7015
- for (const match of fileContent.matchAll(initializerPattern)) {
7016
- const declarationIndex = match.index;
7017
- if (declarationIndex === void 0 || declarationIndex >= sinkIndex || declarationIndex <= nearestDeclarationIndex || findContainingBlockEndIndex(fileContent, declarationIndex) < sinkIndex) continue;
7018
- nearestDeclarationIndex = declarationIndex;
7019
- const initializer = match[1];
7020
- if (initializer === void 0) continue;
7021
- nearestDeclaration = {
7022
- initializer,
7023
- initializerStartIndex: declarationIndex + match[0].length - initializer.length
7024
- };
7025
- }
7026
- return nearestDeclaration;
7027
- };
7028
- const findContainingFunctionParameterSource = (identifier, sinkIndex, fileContent) => {
7029
- const patterns = [/function\s+([\w$]+)\s*\(([^)]*)\)\s*\{/g, /(?:const|let|var)\s+([\w$]+)\s*=\s*(?:async\s*)?\(([^)]*)\)\s*=>\s*\{/g];
7030
- let closestSource = null;
7031
- let closestStartIndex = -1;
7032
- for (const pattern of patterns) for (const match of fileContent.matchAll(pattern)) {
7033
- const matchIndex = match.index;
7034
- if (matchIndex === void 0 || matchIndex >= sinkIndex || matchIndex < closestStartIndex) continue;
7035
- if (findMatchingBraceIndex(fileContent, matchIndex + match[0].lastIndexOf("{")) < sinkIndex) continue;
7036
- const parameterIndex = (match[2] ?? "").split(",").findIndex((parameter) => parameter.trim().match(/^(?:\.\.\.)?([\w$]+)/)?.[1] === identifier);
7037
- if (parameterIndex < 0) continue;
7038
- closestStartIndex = matchIndex;
7039
- closestSource = {
7040
- functionName: match[1] ?? "",
7041
- parameterIndex
7042
- };
7043
- }
7044
- return closestSource;
7045
- };
7046
- const splitTopLevelArguments = (argumentText) => {
7047
- const argumentsList = [];
7048
- let startIndex = 0;
7049
- let depth = 0;
7050
- let quote = null;
7051
- for (let index = 0; index < argumentText.length; index += 1) {
7052
- const character = argumentText[index];
7053
- if (quote !== null) {
7054
- if (character === quote && argumentText[index - 1] !== "\\") quote = null;
7055
- continue;
7056
- }
7057
- if (character === "\"" || character === "'" || character === "`") {
7058
- quote = character;
7059
- continue;
7060
- }
7061
- if (character === "(" || character === "[" || character === "{") depth += 1;
7062
- if (character === ")" || character === "]" || character === "}") depth -= 1;
7063
- if (character === "," && depth === 0) {
7064
- argumentsList.push(argumentText.slice(startIndex, index).trim());
7065
- startIndex = index + 1;
7066
- }
7067
- }
7068
- argumentsList.push(argumentText.slice(startIndex).trim());
7069
- return argumentsList;
7070
- };
7071
- const isHtmlTainted = (expression, fileContent, sinkIndex, visitedIdentifiers) => {
7072
- const trimmedExpression = expression.trim();
7073
- if (STRING_LITERAL_VALUE_PATTERN.test(`${trimmedExpression};`)) return false;
7074
- if (MODULE_CONSTANT_VALUE_PATTERN.test(`${trimmedExpression};`)) return false;
7075
- if (SANITIZER_PATTERN.test(trimmedExpression)) return false;
7076
- if (ENV_CONFIG_VALUE_PATTERN.test(trimmedExpression)) return false;
7077
- if (I18N_VALUE_PATTERN.test(trimmedExpression)) return false;
7078
- if (ESCAPING_SERIALIZER_CALL_PATTERN.test(trimmedExpression)) return false;
7079
- if (HTML_TAINT_PATTERN.test(trimmedExpression)) return true;
7080
- const identifier = trimmedExpression.match(/^([\w$]+)\s*(?:[;,})\n]|$)/)?.[1];
7081
- if (identifier === void 0 || visitedIdentifiers.has(identifier)) return false;
7082
- visitedIdentifiers.add(identifier);
7083
- const declaration = findVisibleIdentifierDeclaration(identifier, sinkIndex, fileContent);
7084
- if (declaration !== null && isHtmlTainted(declaration.initializer, fileContent, sinkIndex, visitedIdentifiers)) return true;
7085
- const parameterSource = findContainingFunctionParameterSource(identifier, sinkIndex, fileContent);
7086
- if (parameterSource === null || parameterSource.functionName.length === 0) return false;
7087
- const callPattern = new RegExp(`\\b${escapeRegExp(parameterSource.functionName)}\\s*\\(([^)]*)\\)`, "g");
7088
- for (const callMatch of fileContent.matchAll(callPattern)) {
7089
- const argument = splitTopLevelArguments(callMatch[1] ?? "")[parameterSource.parameterIndex];
7090
- if (argument !== void 0 && isHtmlTainted(argument, fileContent, sinkIndex, new Set(visitedIdentifiers))) return true;
7091
- }
7092
- return false;
7093
- };
7094
6945
  const dangerousHtmlSink = defineRule({
7095
6946
  id: "dangerous-html-sink",
7096
6947
  title: "HTML injection sink with dynamic content",
@@ -7117,7 +6968,6 @@ const dangerousHtmlSink = defineRule({
7117
6968
  const valueTail = fullValueTail.slice(0, VALUE_EXPRESSION_MAX_CHARS);
7118
6969
  const terminatorIndex = valueTail.search(/[;}]/);
7119
6970
  const valueExpression = terminatorIndex >= 0 ? valueTail.slice(0, terminatorIndex + 1) : valueTail;
7120
- const sinkIndex = lines.slice(0, lineIndex).join("\n").length + (lineIndex > 0 ? 1 : 0) + line.search(DANGEROUS_HTML_PATTERN);
7121
6971
  if (STRING_LITERAL_VALUE_PATTERN.test(valueExpression)) continue;
7122
6972
  if (MODULE_CONSTANT_VALUE_PATTERN.test(valueExpression)) continue;
7123
6973
  if (DOM_CONTENT_SOURCE_VALUE_PATTERN.test(valueExpression) && !valueExpression.includes("+")) {
@@ -7129,7 +6979,7 @@ const dangerousHtmlSink = defineRule({
7129
6979
  let templateInterpolations = getTemplateInterpolations(longValueTail ?? fullValueTail);
7130
6980
  const valueIdentifier = BARE_IDENTIFIER_VALUE_PATTERN.test(valueExpression) || MEMBER_OR_INDEX_ACCESS_VALUE_PATTERN.test(valueExpression) || IDENTIFIER_WITH_LITERAL_FALLBACK_PATTERN.test(valueExpression) ? valueExpression.match(/^[\w$]+/)?.[0] : void 0;
7131
6981
  if (templateInterpolations === null && valueIdentifier !== void 0 && BARE_IDENTIFIER_VALUE_PATTERN.test(valueExpression)) {
7132
- const declarationInitializer = getIdentifierDeclarationInitializer(valueIdentifier, sinkIndex, file.content);
6982
+ const declarationInitializer = getIdentifierDeclarationInitializer(valueIdentifier, file.content);
7133
6983
  if (declarationInitializer !== null) {
7134
6984
  if (isPureStringLiteralConcat(declarationInitializer)) continue;
7135
6985
  templateInterpolations = getTemplateInterpolations(declarationInitializer);
@@ -7140,7 +6990,7 @@ const dangerousHtmlSink = defineRule({
7140
6990
  if (SANITIZER_PATTERN.test(judgedExpression)) continue;
7141
6991
  if (ENV_CONFIG_VALUE_PATTERN.test(judgedExpression)) continue;
7142
6992
  if (I18N_VALUE_PATTERN.test(judgedExpression)) continue;
7143
- if (!isHtmlTainted(judgedExpression, file.content, sinkIndex, /* @__PURE__ */ new Set())) continue;
6993
+ if (!HTML_TAINT_PATTERN.test(judgedExpression)) continue;
7144
6994
  if (ESCAPING_SERIALIZER_CALL_PATTERN.test(valueExpression)) continue;
7145
6995
  if (/highlighted/i.test(valueExpression)) continue;
7146
6996
  if (/highlight/i.test(valueExpression) && HIGHLIGHTER_LIBRARY_PATTERN.test(file.content)) continue;
@@ -7587,7 +7437,7 @@ const containsJsx$1 = (root) => {
7587
7437
  containsJsxCache.set(root, found);
7588
7438
  return found;
7589
7439
  };
7590
- const getStaticMemberName$2 = (node) => {
7440
+ const getStaticMemberName$1 = (node) => {
7591
7441
  if (!isNodeOfType(node, "MemberExpression")) return null;
7592
7442
  if (!node.computed && isNodeOfType(node.property, "Identifier")) return node.property.name;
7593
7443
  if (node.computed && isNodeOfType(node.property, "Literal") && typeof node.property.value === "string") return node.property.value;
@@ -7601,7 +7451,7 @@ const getAssignedName = (node) => {
7601
7451
  if (isNodeOfType(parent, "AssignmentExpression")) {
7602
7452
  const left = parent.left;
7603
7453
  if (isNodeOfType(left, "Identifier")) return left.name;
7604
- if (isNodeOfType(left, "MemberExpression")) return getStaticMemberName$2(left);
7454
+ if (isNodeOfType(left, "MemberExpression")) return getStaticMemberName$1(left);
7605
7455
  }
7606
7456
  return null;
7607
7457
  };
@@ -7609,20 +7459,20 @@ const isModuleExportsAssignment = (node) => {
7609
7459
  const parent = node.parent;
7610
7460
  if (!parent || !isNodeOfType(parent, "AssignmentExpression")) return false;
7611
7461
  const left = parent.left;
7612
- return isNodeOfType(left, "MemberExpression") && isNodeOfType(left.object, "Identifier") && left.object.name === "module" && getStaticMemberName$2(left) === "exports";
7462
+ return isNodeOfType(left, "MemberExpression") && isNodeOfType(left.object, "Identifier") && left.object.name === "module" && getStaticMemberName$1(left) === "exports";
7613
7463
  };
7614
7464
  const isCreateClassLikeCall = (node) => {
7615
7465
  if (!isNodeOfType(node, "CallExpression")) return false;
7616
7466
  if (isEs5Component(node)) return true;
7617
7467
  const callee = node.callee;
7618
- if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$2(callee) === "createClass";
7468
+ if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$1(callee) === "createClass";
7619
7469
  return false;
7620
7470
  };
7621
- const isCreateContextCall$1 = (node) => {
7471
+ const isCreateContextCall = (node) => {
7622
7472
  if (!isNodeOfType(node, "CallExpression")) return false;
7623
7473
  const callee = node.callee;
7624
7474
  if (isNodeOfType(callee, "Identifier")) return callee.name === "createContext";
7625
- return isNodeOfType(callee, "MemberExpression") && getStaticMemberName$2(callee) === "createContext";
7475
+ return isNodeOfType(callee, "MemberExpression") && getStaticMemberName$1(callee) === "createContext";
7626
7476
  };
7627
7477
  const isNamedFunctionLike = (node) => (isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) && Boolean(node.id?.name);
7628
7478
  const firstCallArgument = (node) => {
@@ -7680,7 +7530,7 @@ const memberExpressionPath = (node) => {
7680
7530
  if (isNodeOfType(node, "Identifier")) return [node.name];
7681
7531
  if (!isNodeOfType(node, "MemberExpression")) return [];
7682
7532
  const objectPath = memberExpressionPath(node.object);
7683
- const propertyName = getStaticMemberName$2(node);
7533
+ const propertyName = getStaticMemberName$1(node);
7684
7534
  return propertyName ? [...objectPath, propertyName] : objectPath;
7685
7535
  };
7686
7536
  const displayNameAssignmentIndexCache = /* @__PURE__ */ new WeakMap();
@@ -7690,7 +7540,7 @@ const getDisplayNameAssignmentIndex = (programRoot) => {
7690
7540
  const identifierTargets = /* @__PURE__ */ new Set();
7691
7541
  const memberPathSegments = /* @__PURE__ */ new Set();
7692
7542
  const visit = (node) => {
7693
- if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && getStaticMemberName$2(node.left) === "displayName") {
7543
+ if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && getStaticMemberName$1(node.left) === "displayName") {
7694
7544
  if (isNodeOfType(node.left.object, "Identifier")) identifierTargets.add(node.left.object.name);
7695
7545
  for (const segment of memberExpressionPath(node.left.object)) memberPathSegments.add(segment);
7696
7546
  }
@@ -7795,7 +7645,7 @@ const displayName = defineRule({
7795
7645
  }
7796
7646
  },
7797
7647
  CallExpression(node) {
7798
- if (settings.checkContextObjects && isReactVersionAtLeast$1(settings.reactVersion, 16, 3) && isCreateContextCall$1(node)) {
7648
+ if (settings.checkContextObjects && isReactVersionAtLeast$1(settings.reactVersion, 16, 3) && isCreateContextCall(node)) {
7799
7649
  const assignedName = getAssignedName(node);
7800
7650
  if (assignedName) {
7801
7651
  const programRoot = findProgramRoot(node);
@@ -7887,9 +7737,9 @@ const isImportedFromReact = (symbol) => {
7887
7737
  const importDeclaration = symbol.declarationNode.parent;
7888
7738
  return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && typeof importDeclaration.source.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(importDeclaration.source.value));
7889
7739
  };
7890
- const isNamedReactApiImport = (identifier, apiNames, scopes, resolveAliases) => {
7740
+ const isNamedReactApiImport = (identifier, apiNames, scopes) => {
7891
7741
  if (!isNodeOfType(identifier, "Identifier")) return false;
7892
- const symbol = resolveAliases ? resolveConstIdentifierAlias(identifier, scopes) : scopes.symbolFor(identifier);
7742
+ const symbol = scopes.symbolFor(identifier);
7893
7743
  if (!symbol || !isImportedFromReact(symbol)) return false;
7894
7744
  const importedName = getImportedName(symbol.declarationNode);
7895
7745
  return Boolean(importedName && includesApiName(apiNames, importedName));
@@ -7903,7 +7753,7 @@ const isReactApiCall = (node, apiNames, scopes, options = {}) => {
7903
7753
  if (!isNodeOfType(node, "CallExpression")) return false;
7904
7754
  const callee = stripParenExpression(node.callee);
7905
7755
  if (isNodeOfType(callee, "Identifier")) {
7906
- if (isNamedReactApiImport(callee, apiNames, scopes, Boolean(options.resolveNamedAliases))) return true;
7756
+ if (isNamedReactApiImport(callee, apiNames, scopes)) return true;
7907
7757
  return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
7908
7758
  }
7909
7759
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || !includesApiName(apiNames, callee.property.name)) return false;
@@ -11112,11 +10962,6 @@ If the missing value is recreated every render, move it inside the hook or stabi
11112
10962
  return { CallExpression(node) {
11113
10963
  const hookName = getHookName(node.callee, context.scopes);
11114
10964
  if (!hookName || !isHookOfInterest(hookName, node.callee)) return;
11115
- if (HOOKS_REQUIRING_DEPS_MATCH.has(hookName) && !isReactApiCall(node, HOOKS_REQUIRING_DEPS_MATCH, context.scopes, {
11116
- allowGlobalReactNamespace: true,
11117
- allowUnboundBareCalls: true,
11118
- resolveNamedAliases: true
11119
- })) return;
11120
10965
  const callbackArgumentIndex = getCallbackArgumentIndex(hookName);
11121
10966
  const depsArgumentIndex = getDepsArgumentIndex(hookName);
11122
10967
  const callbackArgument = node.arguments[callbackArgumentIndex];
@@ -14411,11 +14256,7 @@ const jsIndexMaps = defineRule({
14411
14256
  //#region src/plugin/utils/are-expressions-structurally-equal.ts
14412
14257
  const areExpressionsStructurallyEqual = (a, b) => {
14413
14258
  if (!a || !b) return a === b;
14414
- const unwrappedA = stripParenExpression(a);
14415
- const unwrappedB = stripParenExpression(b);
14416
- if (unwrappedA !== a || unwrappedB !== b) return areExpressionsStructurallyEqual(unwrappedA, unwrappedB);
14417
14259
  if (a.type !== b.type) return false;
14418
- if (isNodeOfType(a, "ThisExpression")) return true;
14419
14260
  if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
14420
14261
  if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
14421
14262
  if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
@@ -20958,8 +20799,8 @@ const catchClauseRethrowsCaught = (handler) => {
20958
20799
  return didRethrow;
20959
20800
  };
20960
20801
  //#endregion
20961
- //#region src/plugin/utils/is-immediately-invoked-function.ts
20962
- const isImmediatelyInvokedFunction = (functionNode) => {
20802
+ //#region src/plugin/utils/find-guarding-try-statement.ts
20803
+ const isImmediatelyInvokedFunctionCallee = (functionNode) => {
20963
20804
  let wrappedCallee = functionNode;
20964
20805
  let enclosing = functionNode.parent;
20965
20806
  while (enclosing && stripParenExpression(enclosing) === functionNode) {
@@ -20968,13 +20809,11 @@ const isImmediatelyInvokedFunction = (functionNode) => {
20968
20809
  }
20969
20810
  return Boolean(enclosing && isNodeOfType(enclosing, "CallExpression") && enclosing.callee === wrappedCallee);
20970
20811
  };
20971
- //#endregion
20972
- //#region src/plugin/utils/find-guarding-try-statement.ts
20973
20812
  const findGuardingTryStatement = (node) => {
20974
20813
  let child = node;
20975
20814
  let ancestor = node.parent;
20976
20815
  while (ancestor) {
20977
- if (isFunctionLike$1(ancestor) && !isImmediatelyInvokedFunction(ancestor)) return null;
20816
+ if (isFunctionLike$1(ancestor) && !isImmediatelyInvokedFunctionCallee(ancestor)) return null;
20978
20817
  if (isNodeOfType(ancestor, "TryStatement") && ancestor.block === child && ancestor.handler && !catchClauseRethrowsCaught(ancestor.handler)) return ancestor;
20979
20818
  child = ancestor;
20980
20819
  ancestor = ancestor.parent ?? null;
@@ -22107,8 +21946,6 @@ const DOM_QUERY_MEMBER_NAMES = new Set([
22107
21946
  ]);
22108
21947
  const LAYOUT_MEASUREMENT_MEMBER_NAMES = new Set([
22109
21948
  "current",
22110
- "textContent",
22111
- "innerText",
22112
21949
  "scrollWidth",
22113
21950
  "clientWidth",
22114
21951
  "offsetWidth",
@@ -22130,7 +21967,7 @@ const POST_MOUNT_GLOBAL_NAMES = new Set([
22130
21967
  "navigator"
22131
21968
  ]);
22132
21969
  const REF_FACTORY_CALLEE_NAMES = new Set(["useRef", "createRef"]);
22133
- const hasRefLikeName = (name) => name === "ref" || name.endsWith("Ref") || name.endsWith("ref") || name.endsWith("Node") || name.endsWith("node") || name.endsWith("Element") || name.endsWith("element");
21970
+ const hasRefLikeName = (name) => name === "ref" || name.endsWith("Ref") || name.endsWith("ref");
22134
21971
  const isRefFactoryInitializer = (init) => {
22135
21972
  if (!init || !isNodeOfType(init, "CallExpression")) return false;
22136
21973
  const callee = init.callee;
@@ -23107,7 +22944,7 @@ const STORAGE_GLOBAL_NAMES$1 = new Set([
23107
22944
  "localStorage",
23108
22945
  "sessionStorage"
23109
22946
  ]);
23110
- const getStaticMemberName$1 = (node) => {
22947
+ const getStaticMemberName = (node) => {
23111
22948
  if (!isNodeOfType(node, "MemberExpression")) return null;
23112
22949
  if (!node.computed && isNodeOfType(node.property, "Identifier")) return node.property.name;
23113
22950
  if (node.computed && isNodeOfType(node.property, "Literal")) return typeof node.property.value === "string" ? node.property.value : null;
@@ -23122,7 +22959,7 @@ const getCallCalleeName$1 = (callExpression) => {
23122
22959
  if (!isNodeOfType(callExpression, "CallExpression")) return null;
23123
22960
  const callee = stripParenExpression(callExpression.callee);
23124
22961
  if (isNodeOfType(callee, "Identifier")) return callee.name;
23125
- return getStaticMemberName$1(callee);
22962
+ return getStaticMemberName(callee);
23126
22963
  };
23127
22964
  const getFunctionParameters = (functionNode) => isFunctionLike$1(functionNode) ? functionNode.params ?? [] : [];
23128
22965
  const getIdentifierBindingIdentity = (analysis, identifier) => {
@@ -23238,14 +23075,14 @@ const localUseEventPreservesCallback = (analysis, callee) => {
23238
23075
  if (!isNodeOfType(child, "CallExpression")) return;
23239
23076
  const forwardedCallee = stripParenExpression(child.callee);
23240
23077
  if (!isNodeOfType(forwardedCallee, "MemberExpression")) return;
23241
- if (getStaticMemberName$1(forwardedCallee) !== "current") return;
23078
+ if (getStaticMemberName(forwardedCallee) !== "current") return;
23242
23079
  if (!isNodeOfType(forwardedCallee.object, "Identifier")) return;
23243
23080
  const refReference = getRef(analysis, forwardedCallee.object);
23244
23081
  if (!callbackRefDeclarators.find((declarator) => refReference?.resolved?.defs.some((definition) => definition.node === declarator)) || !refReference?.resolved) return;
23245
23082
  if (!refReference.resolved.references.some((candidateReference) => {
23246
23083
  const memberExpression = candidateReference.identifier.parent;
23247
23084
  const assignmentExpression = memberExpression?.parent;
23248
- if (!memberExpression || !isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName$1(memberExpression) !== "current" || !assignmentExpression || !isNodeOfType(assignmentExpression, "AssignmentExpression") || assignmentExpression.left !== memberExpression) return false;
23085
+ if (!memberExpression || !isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName(memberExpression) !== "current" || !assignmentExpression || !isNodeOfType(assignmentExpression, "AssignmentExpression") || assignmentExpression.left !== memberExpression) return false;
23249
23086
  return getIdentifierBindingIdentity(analysis, assignmentExpression.right) !== callbackBinding;
23250
23087
  })) forwardsCallback = true;
23251
23088
  });
@@ -23270,7 +23107,7 @@ const resolveWrappedCallable = (analysis, node) => {
23270
23107
  return callback && isFunctionLike$1(callback) ? callback : null;
23271
23108
  }
23272
23109
  if (!isNodeOfType(candidate, "MemberExpression")) return null;
23273
- if (getStaticMemberName$1(candidate) !== "current") return null;
23110
+ if (getStaticMemberName(candidate) !== "current") return null;
23274
23111
  if (!isNodeOfType(candidate.object, "Identifier")) return null;
23275
23112
  const reference = getRef(analysis, candidate.object);
23276
23113
  const declarator = reference?.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
@@ -23282,7 +23119,7 @@ const resolveWrappedCallable = (analysis, node) => {
23282
23119
  return Boolean(reference?.resolved?.references.some((candidateReference) => {
23283
23120
  const member = candidateReference.identifier.parent;
23284
23121
  const assignment = member?.parent;
23285
- return Boolean(member && isNodeOfType(member, "MemberExpression") && getStaticMemberName$1(member) === "current" && assignment && isNodeOfType(assignment, "AssignmentExpression") && assignment.left === member);
23122
+ return Boolean(member && isNodeOfType(member, "MemberExpression") && getStaticMemberName(member) === "current" && assignment && isNodeOfType(assignment, "AssignmentExpression") && assignment.left === member);
23286
23123
  })) ? null : initializer;
23287
23124
  };
23288
23125
  const functionInvokesItself = (analysis, functionNode) => {
@@ -23321,7 +23158,7 @@ const collectBoundedEffectExecutionFrames = (analysis, effectNode, currentFilena
23321
23158
  if (!isNodeOfType(child, "CallExpression")) return;
23322
23159
  const callee = stripParenExpression(child.callee);
23323
23160
  const calleeName = getCallCalleeName$1(child);
23324
- const memberName = getStaticMemberName$1(callee);
23161
+ const memberName = getStaticMemberName(callee);
23325
23162
  const isDeferringCall = calleeName !== null && DEFERRING_CALLEE_NAMES.has(calleeName) || memberName !== null && DEFERRING_MEMBER_NAMES.has(memberName);
23326
23163
  const isIteratorCall = memberName !== null && SYNCHRONOUS_ITERATOR_METHOD_NAMES$1.has(memberName) && isNodeOfType(callee, "MemberExpression");
23327
23164
  const enqueueFrame = (callableNode, argumentsForCallable, isDeferred, introducedBindings, allowWithoutInvocationEvidence = false) => {
@@ -23485,9 +23322,9 @@ const analyzeHelperExpression = (expression, environment, usedParameterIndices)
23485
23322
  const calleeRoot = getMemberRoot(callee);
23486
23323
  const isPureGlobalCall = isNodeOfType(callee, "Identifier") && PURE_GLOBAL_CALLEE_NAMES.has(callee.name) && !environment.parameterIndices.has(callee.name) && !environment.recursiveNames.has(callee.name) && !environment.shadowedGlobalNames.has(callee.name);
23487
23324
  const namespaceName = isNodeOfType(calleeRoot, "Identifier") && !environment.parameterIndices.has(calleeRoot.name) && !environment.recursiveNames.has(calleeRoot.name) && !environment.shadowedGlobalNames.has(calleeRoot.name) ? calleeRoot.name : null;
23488
- const namespaceMemberName = getStaticMemberName$1(callee);
23325
+ const namespaceMemberName = getStaticMemberName(callee);
23489
23326
  const isPureNamespaceCall = isNodeOfType(callee, "MemberExpression") && namespaceName !== null && namespaceMemberName !== null && PURE_HELPER_NAMESPACE_MEMBER_NAMES.get(namespaceName)?.has(namespaceMemberName) === true;
23490
- const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName$1(callee) ?? "");
23327
+ const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName(callee) ?? "");
23491
23328
  if (!isPureGlobalCall && !isPureNamespaceCall && !isPureMemberTransform) return false;
23492
23329
  if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression") && !analyzeHelperExpression(callee.object, environment, usedParameterIndices)) return false;
23493
23330
  return (node.arguments ?? []).every((argument) => analyzeHelperExpression(argument, environment, usedParameterIndices));
@@ -23561,7 +23398,7 @@ const isLocallyConstructedObjectMember = (reference, memberExpression) => isNode
23561
23398
  return isNodeOfType(definitionNode, "VariableDeclarator") && Boolean(definitionNode.init) && (isNodeOfType(definitionNode.init, "ObjectExpression") || isNodeOfType(definitionNode.init, "ArrayExpression"));
23562
23399
  }) === true;
23563
23400
  const getUseRefDeclarator = (analysis, memberExpression) => {
23564
- if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName$1(memberExpression) !== "current" || !isNodeOfType(memberExpression.object, "Identifier")) return null;
23401
+ if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName(memberExpression) !== "current" || !isNodeOfType(memberExpression.object, "Identifier")) return null;
23565
23402
  return getRef(analysis, memberExpression.object)?.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator") && isNodeOfType(definitionNode.init, "CallExpression") && getCallCalleeName$1(definitionNode.init) === "useRef") ?? null;
23566
23403
  };
23567
23404
  const collectValueEvidence = (analysis, expression, frame, remainingCallFrames, visitedBindings = /* @__PURE__ */ new Set()) => {
@@ -23630,7 +23467,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23630
23467
  return collectValueEvidence(analysis, initializer.init, frame, remainingCallFrames, visitedBindings);
23631
23468
  }
23632
23469
  if (isNodeOfType(node, "MemberExpression")) {
23633
- if (getStaticMemberName$1(node) === "current" && isNodeOfType(node.object, "Identifier")) {
23470
+ if (getStaticMemberName(node) === "current" && isNodeOfType(node.object, "Identifier")) {
23634
23471
  const refBinding = getRef(analysis, node.object)?.resolved;
23635
23472
  const refDeclarator = useRefDeclarator;
23636
23473
  if (refBinding && refDeclarator && isNodeOfType(refDeclarator, "VariableDeclarator") && isNodeOfType(refDeclarator.init, "CallExpression")) {
@@ -23646,7 +23483,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23646
23483
  if (candidateReference.init) continue;
23647
23484
  const identifier = candidateReference.identifier;
23648
23485
  const member = identifier.parent;
23649
- if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName$1(member) !== "current") {
23486
+ if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName(member) !== "current") {
23650
23487
  evidence.hasUnknownSource = true;
23651
23488
  continue;
23652
23489
  }
@@ -23683,7 +23520,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23683
23520
  const callee = stripParenExpression(node.callee);
23684
23521
  const calleeRoot = getMemberRoot(callee);
23685
23522
  const isPureGlobalCall = isNodeOfType(callee, "Identifier") && PURE_GLOBAL_CALLEE_NAMES.has(callee.name) && getIdentifierBindingIdentity(analysis, callee) === null || isNodeOfType(calleeRoot, "Identifier") && PURE_GLOBAL_NAMESPACE_NAMES.has(calleeRoot.name) && getIdentifierBindingIdentity(analysis, calleeRoot) === null;
23686
- const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName$1(callee) ?? "");
23523
+ const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName(callee) ?? "");
23687
23524
  if (isPureGlobalCall || isPureMemberTransform) {
23688
23525
  if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression")) mergeEvidence(evidence, collectValueEvidence(analysis, callee.object, frame, remainingCallFrames, new Set(visitedBindings)));
23689
23526
  for (const argument of node.arguments ?? []) {
@@ -24153,26 +23990,6 @@ const STATEFUL_HTML_DESCENDANT_TAGS = new Set([
24153
23990
  "dialog",
24154
23991
  "canvas"
24155
23992
  ]);
24156
- const STATEFUL_HTML_ATTRIBUTE_NAMES = new Set([
24157
- "autofocus",
24158
- "contenteditable",
24159
- "draggable",
24160
- "tabindex"
24161
- ]);
24162
- const isStaticallyFalseAttributeValue = (attribute) => {
24163
- if (!isNodeOfType(attribute, "JSXAttribute") || !attribute.value) return false;
24164
- const value = isNodeOfType(attribute.value, "JSXExpressionContainer") ? attribute.value.expression : attribute.value;
24165
- return isNodeOfType(value, "Literal") && (value.value === false || value.value === "false");
24166
- };
24167
- const hasStatefulHtmlAttribute = (openingElement) => {
24168
- if (!isNodeOfType(openingElement, "JSXOpeningElement")) return false;
24169
- return openingElement.attributes.some((attribute) => {
24170
- if (!isNodeOfType(attribute, "JSXAttribute") || !isNodeOfType(attribute.name, "JSXIdentifier")) return false;
24171
- const attributeName = attribute.name.name.toLowerCase();
24172
- if (!STATEFUL_HTML_ATTRIBUTE_NAMES.has(attributeName)) return false;
24173
- return attributeName === "tabindex" || !isStaticallyFalseAttributeValue(attribute);
24174
- });
24175
- };
24176
23993
  const STATEFUL_DESCENDANT_SCAN_BUDGET = 200;
24177
23994
  const rootIdentifierNameOf = (expression) => {
24178
23995
  let object = expression;
@@ -24190,8 +24007,7 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
24190
24007
  budget -= 1;
24191
24008
  const node = stack.pop();
24192
24009
  if (isNodeOfType(node, "JSXElement")) {
24193
- const opening = node.openingElement;
24194
- const name = opening.name;
24010
+ const name = node.openingElement.name;
24195
24011
  if (name && isNodeOfType(name, "JSXIdentifier")) {
24196
24012
  const tagName = name.name;
24197
24013
  const firstChar = tagName.charCodeAt(0);
@@ -24199,7 +24015,6 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
24199
24015
  if (STATEFUL_HTML_DESCENDANT_TAGS.has(tagName)) return true;
24200
24016
  }
24201
24017
  if (name && isNodeOfType(name, "JSXMemberExpression")) return true;
24202
- if (hasStatefulHtmlAttribute(opening)) return true;
24203
24018
  const children = node.children ?? [];
24204
24019
  for (const child of children) stack.push(child);
24205
24020
  continue;
@@ -24759,14 +24574,6 @@ const templateHasOuterMemberIdentity = (template, bindingFunction) => {
24759
24574
  }
24760
24575
  return false;
24761
24576
  };
24762
- const findBareItemNamesReferencedByTemplate = (template, itemNames) => {
24763
- const referencedItemNames = /* @__PURE__ */ new Set();
24764
- for (const expression of template.expressions ?? []) {
24765
- const unwrappedExpression = stripParenExpression(expression);
24766
- if (isNodeOfType(unwrappedExpression, "Identifier") && itemNames.has(unwrappedExpression.name)) referencedItemNames.add(unwrappedExpression.name);
24767
- }
24768
- return referencedItemNames;
24769
- };
24770
24577
  const forLoopTestReadsDataLength = (test) => {
24771
24578
  let didFindLengthRead = false;
24772
24579
  walkAst(test, (child) => {
@@ -24903,11 +24710,9 @@ const noArrayIndexAsKey = defineRule({
24903
24710
  } else if (STATELESS_HTML_LEAF_TAGS.has(elementName.name)) {
24904
24711
  const jsxElement = openingElement.parent;
24905
24712
  if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
24906
- const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
24907
- const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET;
24908
24713
  if (!containsStatefulDescendant(jsxElement, {
24909
- memberRootNames: isInlineTextRun ? itemNames : EMPTY_NAME_SET,
24910
- bareIdentifierNames: primitiveItemNames.size > 0 ? new Set([...derivedNames, ...primitiveItemNames]) : derivedNames
24714
+ memberRootNames: INLINE_TEXT_LEAF_TAGS.has(elementName.name) ? itemNames : EMPTY_NAME_SET,
24715
+ bareIdentifierNames: derivedNames
24911
24716
  })) return;
24912
24717
  }
24913
24718
  }
@@ -25075,11 +24880,7 @@ const noAsyncEffectCallback = defineRule({
25075
24880
  severity: "warn",
25076
24881
  recommendation: "Don't make the effect callback `async`. Define an async function inside the effect and call it, then return a real cleanup function if you need one.",
25077
24882
  create: (context) => ({ CallExpression(node) {
25078
- if (!isReactApiCall(node, EFFECT_HOOK_NAMES$1, context.scopes, {
25079
- allowGlobalReactNamespace: true,
25080
- allowUnboundBareCalls: true,
25081
- resolveNamedAliases: true
25082
- })) return;
24883
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
25083
24884
  const callback = getEffectCallback(node);
25084
24885
  if (!callback) return;
25085
24886
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
@@ -26277,19 +26078,6 @@ const noCloneElement = defineRule({
26277
26078
  } })
26278
26079
  });
26279
26080
  //#endregion
26280
- //#region src/plugin/utils/get-call-method-name.ts
26281
- /**
26282
- * Returns the static method name of a call's callee when it's a
26283
- * non-computed MemberExpression (`obj.method` → `"method"`), or
26284
- * `null` otherwise. Used by `no-pass-data-to-parent` and
26285
- * `no-pass-live-state-to-parent` to match the receiver-bound
26286
- * method-call shape against an allow/block list.
26287
- */
26288
- const getCallMethodName = (callee) => {
26289
- if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
26290
- return null;
26291
- };
26292
- //#endregion
26293
26081
  //#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
26294
26082
  const MESSAGE$31 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
26295
26083
  const CONTEXT_MODULES = [
@@ -26297,35 +26085,23 @@ const CONTEXT_MODULES = [
26297
26085
  "use-context-selector",
26298
26086
  "react-tracked"
26299
26087
  ];
26300
- const getSupportedContextImportSource = (symbol) => {
26301
- if (symbol?.kind !== "import") return null;
26302
- const importDeclaration = symbol.declarationNode.parent;
26303
- if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration") || typeof importDeclaration.source.value !== "string" || !CONTEXT_MODULES.includes(importDeclaration.source.value)) return null;
26304
- return importDeclaration.source.value;
26305
- };
26306
- const isSupportedNamespaceSymbol = (symbol) => getSupportedContextImportSource(symbol) !== null && Boolean(symbol && (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")));
26307
- const isDestructuredCreateContextBinding = (identifier, scopes) => {
26308
- if (!isNodeOfType(identifier, "Identifier")) return false;
26309
- const symbol = scopes.symbolFor(identifier);
26310
- if (symbol?.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "ObjectPattern")) return false;
26311
- const property = symbol.bindingIdentifier.parent;
26312
- if (!property || !isNodeOfType(property, "Property") || getStaticPropertyKeyName(property, { allowComputedString: true }) !== "createContext") return false;
26313
- const initializer = stripParenExpression(symbol.initializer);
26314
- return isNodeOfType(initializer, "Identifier") && isSupportedNamespaceSymbol(resolveConstIdentifierAlias(initializer, scopes));
26315
- };
26316
- const isCreateContextCall = (node, scopes) => {
26317
- const callee = stripParenExpression(node.callee);
26088
+ const isCreateContextCallee = (callee) => {
26318
26089
  if (isNodeOfType(callee, "Identifier")) {
26319
- if (isDestructuredCreateContextBinding(callee, scopes)) return true;
26320
- const symbol = resolveConstIdentifierAlias(callee, scopes);
26321
- return getSupportedContextImportSource(symbol) !== null && Boolean(symbol && getImportedName(symbol.declarationNode) === "createContext");
26090
+ const binding = getImportBindingForName(callee, callee.name);
26091
+ return binding !== null && binding.exportedName === "createContext" && CONTEXT_MODULES.includes(binding.source);
26322
26092
  }
26323
- if (!isNodeOfType(callee, "MemberExpression")) return false;
26324
- if ((getCallMethodName(callee) ?? (callee.computed && isNodeOfType(callee.property, "Literal") && typeof callee.property.value === "string" ? callee.property.value : null)) !== "createContext") return false;
26325
- const receiver = stripParenExpression(callee.object);
26326
- if (!isNodeOfType(receiver, "Identifier")) return false;
26327
- if (receiver.name === "React" && scopes.isGlobalReference(receiver)) return true;
26328
- return isSupportedNamespaceSymbol(resolveConstIdentifierAlias(receiver, scopes));
26093
+ if (isNodeOfType(callee, "MemberExpression") && !callee.computed) {
26094
+ const namespaceIdentifier = callee.object;
26095
+ const propertyIdentifier = callee.property;
26096
+ if (!isNodeOfType(namespaceIdentifier, "Identifier")) return false;
26097
+ if (!isNodeOfType(propertyIdentifier, "Identifier")) return false;
26098
+ if (propertyIdentifier.name !== "createContext") return false;
26099
+ const namespaceName = namespaceIdentifier.name;
26100
+ if (isCanonicalReactNamespaceName(namespaceName)) return true;
26101
+ const importSource = getImportSourceForName(namespaceIdentifier, namespaceName);
26102
+ return importSource !== null && CONTEXT_MODULES.includes(importSource);
26103
+ }
26104
+ return false;
26329
26105
  };
26330
26106
  const noCreateContextInRender = defineRule({
26331
26107
  id: "no-create-context-in-render",
@@ -26334,7 +26110,7 @@ const noCreateContextInRender = defineRule({
26334
26110
  category: "Correctness",
26335
26111
  recommendation: "Move `createContext(...)` outside the component, to the top level of the file, so it stays the same on every render.",
26336
26112
  create: (context) => ({ CallExpression(node) {
26337
- if (!isCreateContextCall(node, context.scopes)) return;
26113
+ if (!isCreateContextCallee(node.callee)) return;
26338
26114
  const componentOrHookName = enclosingComponentOrHookName(node);
26339
26115
  if (!componentOrHookName) return;
26340
26116
  context.report({
@@ -27569,7 +27345,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
27569
27345
  let ancestor = setStateCall.parent;
27570
27346
  while (ancestor) {
27571
27347
  if (!lifecycleMember) {
27572
- if (FUNCTION_NODE_TYPES$1.has(ancestor.type) && (!isImmediatelyInvokedFunction(ancestor) || !("async" in ancestor) || ancestor.async === true)) nestedFunctionCount += 1;
27348
+ if (FUNCTION_NODE_TYPES$1.has(ancestor.type)) nestedFunctionCount += 1;
27573
27349
  if (isLifecycleMember(ancestor, lifecycleNames)) lifecycleMember = ancestor;
27574
27350
  } else if (isEs5Component(ancestor) || isEs6Component(ancestor)) {
27575
27351
  if (nestedFunctionCount > 1 && !options.disallowInNestedFunctions) return false;
@@ -27771,7 +27547,7 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
27771
27547
  const body = lifecycleFunction.body;
27772
27548
  if (!body) return derivedNames;
27773
27549
  walkAst(body, (node) => {
27774
- if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
27550
+ if (FUNCTION_NODE_TYPES.has(node.type)) return false;
27775
27551
  if (!isNodeOfType(node, "VariableDeclarator")) return;
27776
27552
  const init = node.init;
27777
27553
  if (!init) return;
@@ -27781,67 +27557,6 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
27781
27557
  return derivedNames;
27782
27558
  };
27783
27559
  const isStatefulOperand = (node, paramNames, derivedNames) => referencesAnyName(node, paramNames) || referencesAnyName(node, derivedNames) || containsThisStateOrProps(node);
27784
- const getStaticMemberName = (node) => {
27785
- if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
27786
- return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
27787
- };
27788
- const getThisStateFieldName = (node) => {
27789
- const unwrappedNode = stripParenExpression(node);
27790
- if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
27791
- const object = stripParenExpression(unwrappedNode.object);
27792
- if (!isNodeOfType(object, "MemberExpression") || !isNodeOfType(stripParenExpression(object.object), "ThisExpression") || getStaticMemberName(object) !== "state") return null;
27793
- return getStaticMemberName(unwrappedNode);
27794
- };
27795
- const collectLocalInitializers = (lifecycleFunction) => {
27796
- const initializers = /* @__PURE__ */ new Map();
27797
- const body = lifecycleFunction.body;
27798
- if (!body) return initializers;
27799
- walkAst(body, (node) => {
27800
- if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
27801
- if (isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && node.init) initializers.set(node.id.name, node.init);
27802
- });
27803
- return initializers;
27804
- };
27805
- const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
27806
- if (readsPostMountValue(node)) return true;
27807
- const referencedNames = /* @__PURE__ */ new Set();
27808
- collectReferenceIdentifierNames(node, referencedNames);
27809
- for (const referencedName of referencedNames) {
27810
- if (visitedNames.has(referencedName)) continue;
27811
- const initializer = localInitializers.get(referencedName);
27812
- if (!initializer) continue;
27813
- if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
27814
- }
27815
- return false;
27816
- };
27817
- const getSetStateFieldValue = (setStateCall, fieldName) => {
27818
- if (!isNodeOfType(setStateCall, "CallExpression")) return null;
27819
- const argument = setStateCall.arguments?.[0];
27820
- if (!argument || !isNodeOfType(argument, "ObjectExpression")) return null;
27821
- for (const property of argument.properties ?? []) {
27822
- if (!isNodeOfType(property, "Property") || property.computed === true) continue;
27823
- if ((isNodeOfType(property.key, "Identifier") && property.key.name || isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" && property.key.value || null) === fieldName) return property.value;
27824
- }
27825
- return null;
27826
- };
27827
- const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
27828
- let qualifies = false;
27829
- walkAst(test, (node) => {
27830
- if (qualifies) return false;
27831
- if (!isNodeOfType(node, "BinaryExpression") || !EQUALITY_OPERATORS.has(node.operator)) return;
27832
- const leftFieldName = getThisStateFieldName(node.left);
27833
- const rightFieldName = getThisStateFieldName(node.right);
27834
- const fieldName = leftFieldName ?? rightFieldName;
27835
- const comparedValue = leftFieldName ? node.right : node.left;
27836
- if (!fieldName || !leftFieldName && !rightFieldName) return;
27837
- const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
27838
- if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return;
27839
- if (!derivesFromPostMountValue(comparedValue, localInitializers)) return;
27840
- qualifies = true;
27841
- return false;
27842
- });
27843
- return qualifies;
27844
- };
27845
27560
  const isDiffGuardTest = (test, paramNames, derivedNames) => {
27846
27561
  if (referencesAnyName(test, paramNames)) return true;
27847
27562
  let qualifies = false;
@@ -27849,7 +27564,7 @@ const isDiffGuardTest = (test, paramNames, derivedNames) => {
27849
27564
  if (qualifies) return false;
27850
27565
  if (!isNodeOfType(node, "BinaryExpression")) return;
27851
27566
  if (!EQUALITY_OPERATORS.has(node.operator)) return;
27852
- if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames) && (referencesAnyName(node.left, derivedNames) || referencesAnyName(node.right, derivedNames))) {
27567
+ if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames)) {
27853
27568
  qualifies = true;
27854
27569
  return false;
27855
27570
  }
@@ -27862,12 +27577,11 @@ const isInsideDiffGuard = (setStateCall) => {
27862
27577
  const paramNames = /* @__PURE__ */ new Set();
27863
27578
  for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
27864
27579
  const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
27865
- const localInitializers = collectLocalInitializers(lifecycleFunction);
27866
27580
  let child = setStateCall;
27867
27581
  let ancestor = setStateCall.parent;
27868
27582
  while (ancestor && ancestor !== lifecycleFunction) {
27869
27583
  const guardTest = isNodeOfType(ancestor, "IfStatement") && child !== ancestor.test && ancestor.test || isNodeOfType(ancestor, "ConditionalExpression") && child !== ancestor.test && ancestor.test || isNodeOfType(ancestor, "LogicalExpression") && ancestor.operator === "&&" && child === ancestor.right && ancestor.left || null;
27870
- if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers))) return true;
27584
+ if (guardTest && isDiffGuardTest(guardTest, paramNames, derivedNames)) return true;
27871
27585
  child = ancestor;
27872
27586
  ancestor = ancestor.parent ?? null;
27873
27587
  }
@@ -30385,11 +30099,7 @@ const noFetchInEffect = defineRule({
30385
30099
  severity: "warn",
30386
30100
  recommendation: "Use a data-fetching layer or Server Component so fetches do not race, double-fire, or leak from `useEffect`.",
30387
30101
  create: (context) => ({ CallExpression(node) {
30388
- if (!isReactApiCall(node, EFFECT_HOOK_NAMES$1, context.scopes, {
30389
- allowGlobalReactNamespace: true,
30390
- allowUnboundBareCalls: true,
30391
- resolveNamedAliases: true
30392
- })) return;
30102
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
30393
30103
  const callback = getEffectCallback(node);
30394
30104
  if (!callback) return;
30395
30105
  const analysisFunctions = collectEffectAnalysisFunctions(callback, context);
@@ -34599,6 +34309,19 @@ const DATA_SINK_METHOD_NAMES = new Set([
34599
34309
  "deserialize"
34600
34310
  ]);
34601
34311
  //#endregion
34312
+ //#region src/plugin/utils/get-call-method-name.ts
34313
+ /**
34314
+ * Returns the static method name of a call's callee when it's a
34315
+ * non-computed MemberExpression (`obj.method` → `"method"`), or
34316
+ * `null` otherwise. Used by `no-pass-data-to-parent` and
34317
+ * `no-pass-live-state-to-parent` to match the receiver-bound
34318
+ * method-call shape against an allow/block list.
34319
+ */
34320
+ const getCallMethodName = (callee) => {
34321
+ if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
34322
+ return null;
34323
+ };
34324
+ //#endregion
34602
34325
  //#region src/plugin/rules/state-and-effects/no-pass-data-to-parent.ts
34603
34326
  const isUseStateIdentifier = (identifier) => {
34604
34327
  if (!isNodeOfType(identifier, "Identifier")) return false;
@@ -35224,30 +34947,6 @@ const guardsRenderShape = (comparison) => {
35224
34947
  }
35225
34948
  return false;
35226
34949
  };
35227
- const isPropsChildrenLength = (node, scopes) => {
35228
- const unwrappedNode = stripParenExpression(node);
35229
- return isNodeOfType(unwrappedNode, "MemberExpression") && !unwrappedNode.computed && isNodeOfType(unwrappedNode.property, "Identifier") && unwrappedNode.property.name === "length" && resolvesToPropsChildren(stripParenExpression(unwrappedNode.object), scopes);
35230
- };
35231
- const isLargeTextLengthComparison = (node, scopes) => {
35232
- const unwrappedNode = stripParenExpression(node);
35233
- if (!isNodeOfType(unwrappedNode, "BinaryExpression")) return false;
35234
- const leftIsLength = isPropsChildrenLength(unwrappedNode.left, scopes);
35235
- const rightIsLength = isPropsChildrenLength(unwrappedNode.right, scopes);
35236
- const thresholdNode = leftIsLength ? unwrappedNode.right : unwrappedNode.left;
35237
- if (!leftIsLength && !rightIsLength || !isNodeOfType(thresholdNode, "Literal")) return false;
35238
- if (typeof thresholdNode.value !== "number" || thresholdNode.value < 1e3) return false;
35239
- return leftIsLength ? unwrappedNode.operator === ">" || unwrappedNode.operator === ">=" : unwrappedNode.operator === "<" || unwrappedNode.operator === "<=";
35240
- };
35241
- const isLargeStringOptimizationGuard = (comparison, scopes) => {
35242
- let current = findTransparentExpressionRoot(comparison);
35243
- while (current.parent) {
35244
- const parent = current.parent;
35245
- if (!isNodeOfType(parent, "LogicalExpression") || parent.operator !== "&&") return false;
35246
- if (isLargeTextLengthComparison(parent.left === current ? parent.right : parent.left, scopes)) return true;
35247
- current = findTransparentExpressionRoot(parent);
35248
- }
35249
- return false;
35250
- };
35251
34950
  const noPolymorphicChildren = defineRule({
35252
34951
  id: "no-polymorphic-children",
35253
34952
  title: "Children type checked at runtime",
@@ -35261,7 +34960,6 @@ const noPolymorphicChildren = defineRule({
35261
34960
  const isStringLiteral = (operand) => isNodeOfType(operand, "Literal") && operand.value === "string";
35262
34961
  if (!isStringLiteral(node.left) && !isStringLiteral(node.right)) return;
35263
34962
  if (!guardsRenderShape(node)) return;
35264
- if (isLargeStringOptimizationGuard(node, context.scopes)) return;
35265
34963
  context.report({
35266
34964
  node,
35267
34965
  message: "Your users hit inconsistent behavior because `typeof children === \"string\"` makes this component switch on what callers pass, so add clear subcomponents like `<Button.Text>` instead."
@@ -35479,27 +35177,6 @@ const hasPreviousValueDep = (effectNode, depElements) => {
35479
35177
  }
35480
35178
  return false;
35481
35179
  };
35482
- const isStateLikeDependency = (analysis, element, isPropName) => {
35483
- if (!isNodeOfType(element, "Identifier") || isPropName(element.name)) return false;
35484
- if (!analysis) return true;
35485
- const reference = getRef(analysis, element);
35486
- if (!reference) return true;
35487
- const upstreamReferences = getUpstreamRefs(analysis, reference);
35488
- if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
35489
- return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
35490
- };
35491
- const getRefHeldPropCallbackName = (callExpression, isPropName) => {
35492
- const callee = stripParenExpression(callExpression.callee);
35493
- if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "current") return null;
35494
- const receiver = stripParenExpression(callee.object);
35495
- if (!isNodeOfType(receiver, "Identifier")) return null;
35496
- const binding = findVariableInitializer(callExpression, receiver.name);
35497
- if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
35498
- if (getCalleeName$2(binding.initializer) !== "useRef") return null;
35499
- const callbackArgument = binding.initializer.arguments?.[0];
35500
- if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
35501
- return isPropName(callbackArgument.name) ? callbackArgument.name : null;
35502
- };
35503
35180
  const noPropCallbackInEffect = defineRule({
35504
35181
  id: "no-prop-callback-in-effect",
35505
35182
  title: "Parent kept in sync with a callback effect",
@@ -35516,11 +35193,11 @@ const noPropCallbackInEffect = defineRule({
35516
35193
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
35517
35194
  const depsNode = node.arguments[1];
35518
35195
  if (!isNodeOfType(depsNode, "ArrayExpression") || !depsNode.elements?.length) return;
35519
- const analysis = getProgramAnalysis(node);
35520
- const stateLikeDeps = (depsNode.elements ?? []).filter((element) => element && isStateLikeDependency(analysis, element, propStackTracker.isPropName));
35196
+ const stateLikeDeps = (depsNode.elements ?? []).filter((element) => isNodeOfType(element, "Identifier") && !propStackTracker.isPropName(element.name));
35521
35197
  if (stateLikeDeps.length === 0) return;
35522
35198
  if (isRefLatchGuardedEffect(callback.body)) return;
35523
35199
  if (hasPreviousValueDep(node, depsNode.elements ?? [])) return;
35200
+ const analysis = getProgramAnalysis(node);
35524
35201
  if (analysis) {
35525
35202
  const stateLikeDepRefs = [];
35526
35203
  for (const element of stateLikeDeps) {
@@ -35532,9 +35209,9 @@ const noPropCallbackInEffect = defineRule({
35532
35209
  const reportedNodes = /* @__PURE__ */ new Set();
35533
35210
  walkInsideStatementBlocks(callback.body, (child) => {
35534
35211
  if (!isNodeOfType(child, "CallExpression")) return;
35535
- const directCallee = stripParenExpression(child.callee);
35536
- const calleeName = isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName);
35537
- if (!calleeName) return;
35212
+ if (!isNodeOfType(child.callee, "Identifier")) return;
35213
+ const calleeName = child.callee.name;
35214
+ if (!propStackTracker.isPropName(calleeName)) return;
35538
35215
  if (!isResultDiscardedCall(child)) return;
35539
35216
  if (reportedNodes.has(child)) return;
35540
35217
  reportedNodes.add(child);
@@ -43375,6 +43052,15 @@ const isStableHookWrapperArgument = (node) => {
43375
43052
  const parent = node.parent;
43376
43053
  return isNodeOfType(parent, "CallExpression") && isNodeOfType(parent.callee, "Identifier") && STABLE_HOOK_WRAPPERS.has(parent.callee.name) && Boolean(parent.arguments?.some((argument) => argument === node));
43377
43054
  };
43055
+ const isImmediatelyInvokedFunction = (functionNode) => {
43056
+ let wrappedCallee = functionNode;
43057
+ let enclosing = functionNode.parent;
43058
+ while (enclosing && stripParenExpression(enclosing) === functionNode) {
43059
+ wrappedCallee = enclosing;
43060
+ enclosing = enclosing.parent ?? null;
43061
+ }
43062
+ return Boolean(enclosing && isNodeOfType(enclosing, "CallExpression") && enclosing.callee === wrappedCallee);
43063
+ };
43378
43064
  const queryStableQueryClient = defineRule({
43379
43065
  id: "query-stable-query-client",
43380
43066
  title: "Unstable QueryClient in component",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.5-dev.0654849",
3
+ "version": "0.7.5-dev.76cd6be",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",