oxlint-plugin-react-doctor 0.7.9-dev.9aa98b9 → 0.7.9-dev.c7f61cd

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 CHANGED
@@ -579,6 +579,22 @@ const EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS = new Set([
579
579
  "ResizeObserver",
580
580
  "PerformanceObserver"
581
581
  ]);
582
+ const EXTERNAL_SYNC_DOM_MEMBER_METHOD_NAMES = new Set([
583
+ "blur",
584
+ "focus",
585
+ "getBoundingClientRect",
586
+ "getClientRects",
587
+ "measure",
588
+ "measureInWindow",
589
+ "measureLayout",
590
+ "scroll",
591
+ "scrollBy",
592
+ "scrollIntoView",
593
+ "scrollTo",
594
+ "select",
595
+ "setRangeText",
596
+ "setSelectionRange"
597
+ ]);
582
598
  const STORAGE_OBJECTS = new Set(["localStorage", "sessionStorage"]);
583
599
  //#endregion
584
600
  //#region src/plugin/constants/react.ts
@@ -1819,8 +1835,70 @@ const flattenJsxName$1 = (node) => {
1819
1835
  return null;
1820
1836
  };
1821
1837
  //#endregion
1822
- //#region src/plugin/utils/is-member-property.ts
1823
- const isMemberProperty = (node, propertyName) => Boolean(node && isNodeOfType(node, "MemberExpression") && isNodeOfType(node.property, "Identifier") && node.property.name === propertyName);
1838
+ //#region src/plugin/utils/get-static-property-name.ts
1839
+ const getStaticPropertyName = (memberExpression) => {
1840
+ const property = memberExpression.property;
1841
+ if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
1842
+ if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
1843
+ if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
1844
+ return null;
1845
+ };
1846
+ //#endregion
1847
+ //#region src/plugin/utils/is-generated-image-renderer-call.ts
1848
+ const GENERATED_IMAGE_RENDERER_MODULES = [
1849
+ "next/og",
1850
+ "@vercel/og",
1851
+ "satori"
1852
+ ];
1853
+ const IMAGE_RESPONSE_MODULES = new Set(["next/og", "@vercel/og"]);
1854
+ const getImportDeclaration$1 = (node) => {
1855
+ let current = node.parent;
1856
+ while (current) {
1857
+ if (isNodeOfType(current, "ImportDeclaration")) return current;
1858
+ if (isNodeOfType(current, "Program")) return null;
1859
+ current = current.parent;
1860
+ }
1861
+ return null;
1862
+ };
1863
+ const getImportSource = (declaration) => typeof declaration.source.value === "string" ? declaration.source.value : null;
1864
+ const isNamedImport = (symbol, importedName, moduleSources) => {
1865
+ if (symbol.kind !== "import") return false;
1866
+ const declaration = symbol.declarationNode;
1867
+ if (!isNodeOfType(declaration, "ImportSpecifier")) return false;
1868
+ const importDeclaration = getImportDeclaration$1(declaration);
1869
+ const source = importDeclaration ? getImportSource(importDeclaration) : null;
1870
+ if (!source || !moduleSources.has(source)) return false;
1871
+ const imported = declaration.imported;
1872
+ return isNodeOfType(imported, "Identifier") && imported.name === importedName || isNodeOfType(imported, "Literal") && imported.value === importedName;
1873
+ };
1874
+ const isSatoriImport = (symbol) => {
1875
+ if (symbol.kind !== "import") return false;
1876
+ const declaration = symbol.declarationNode;
1877
+ const importDeclaration = getImportDeclaration$1(declaration);
1878
+ if (!importDeclaration || getImportSource(importDeclaration) !== "satori") return false;
1879
+ if (isNodeOfType(declaration, "ImportDefaultSpecifier")) return true;
1880
+ if (!isNodeOfType(declaration, "ImportSpecifier")) return false;
1881
+ const imported = declaration.imported;
1882
+ return isNodeOfType(imported, "Identifier") && imported.name === "satori" || isNodeOfType(imported, "Literal") && imported.value === "satori";
1883
+ };
1884
+ const isGeneratedImageRendererCall = (node, scopes) => {
1885
+ if (!isNodeOfType(node, "CallExpression") && !isNodeOfType(node, "NewExpression")) return false;
1886
+ const callee = stripParenExpression(node.callee);
1887
+ if (isNodeOfType(callee, "Identifier")) {
1888
+ const symbol = scopes.referenceFor(callee)?.resolvedSymbol ?? null;
1889
+ return Boolean(symbol && (isNamedImport(symbol, "ImageResponse", IMAGE_RESPONSE_MODULES) || isSatoriImport(symbol)));
1890
+ }
1891
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
1892
+ if (getStaticPropertyName(callee) !== "ImageResponse") return false;
1893
+ if (!isNodeOfType(callee.object, "Identifier")) return false;
1894
+ const symbol = scopes.referenceFor(callee.object)?.resolvedSymbol ?? null;
1895
+ if (!symbol || symbol.kind !== "import") return false;
1896
+ const declaration = symbol.declarationNode;
1897
+ if (!isNodeOfType(declaration, "ImportNamespaceSpecifier")) return false;
1898
+ const importDeclaration = getImportDeclaration$1(declaration);
1899
+ const source = importDeclaration ? getImportSource(importDeclaration) : null;
1900
+ return Boolean(source && IMAGE_RESPONSE_MODULES.has(source));
1901
+ };
1824
1902
  //#endregion
1825
1903
  //#region src/plugin/constants/nextjs.ts
1826
1904
  const NEXTJS_SOURCE_FILE_EXTENSION_GROUP = "(?:tsx?|jsx?|mts|mjs)";
@@ -1892,42 +1970,22 @@ const isNextjsMetadataImageRouteFilename = (rawFilename) => {
1892
1970
  const normalizeFilename = (filename) => filename.includes("\\") ? filename.replaceAll("\\", "/") : filename;
1893
1971
  //#endregion
1894
1972
  //#region src/plugin/utils/is-generated-image-render-context.ts
1895
- const IMAGE_RESPONSE_MODULES = ["next/og", "@vercel/og"];
1896
- const SATORI_MODULE = "satori";
1897
- const GENERATED_IMAGE_MODULES = [...IMAGE_RESPONSE_MODULES, SATORI_MODULE];
1898
1973
  const generatedImageJsxCache = /* @__PURE__ */ new WeakMap();
1899
1974
  const isGeneratedImageRenderFilename = (rawFilename) => {
1900
1975
  if (!rawFilename) return false;
1901
1976
  return isNextjsMetadataImageRouteFilename(normalizeFilename(rawFilename));
1902
1977
  };
1903
- const isImageResponseCallee = (contextNode, callee) => {
1904
- if (isNodeOfType(callee, "Identifier")) return IMAGE_RESPONSE_MODULES.some((moduleSource) => getImportedNameFromModule(contextNode, callee.name, moduleSource) === "ImageResponse");
1905
- if (!isMemberProperty(callee, "ImageResponse")) return false;
1906
- if (!isNodeOfType(callee.object, "Identifier")) return false;
1907
- const namespaceIdentifierName = callee.object.name;
1908
- return IMAGE_RESPONSE_MODULES.some((moduleSource) => isNamespaceImportFromModule(contextNode, namespaceIdentifierName, moduleSource));
1909
- };
1910
- const isSatoriCallee = (contextNode, callee) => {
1911
- if (!isNodeOfType(callee, "Identifier")) return false;
1912
- if (getImportedNameFromModule(contextNode, callee.name, SATORI_MODULE) === "satori") return true;
1913
- return isDefaultImportFromModule(contextNode, callee.name, SATORI_MODULE);
1914
- };
1915
- const isGeneratedImageRendererCall = (node) => {
1916
- if (!isNodeOfType(node, "CallExpression") && !isNodeOfType(node, "NewExpression")) return false;
1917
- if (!isNodeOfType(node.callee, "Identifier") && !isNodeOfType(node.callee, "MemberExpression")) return false;
1918
- return isImageResponseCallee(node, node.callee) || isSatoriCallee(node, node.callee);
1919
- };
1920
1978
  const isComponentIdentifierName = (name) => {
1921
1979
  const firstCharacter = name[0];
1922
1980
  return Boolean(firstCharacter && firstCharacter === firstCharacter.toUpperCase());
1923
1981
  };
1924
1982
  const isFunctionLike = (node) => Boolean(node && (isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ArrowFunctionExpression")));
1925
- const markFunctionReturnJsx = (functionNode, programRoot, generatedImageJsxNodes, visitedComponentNames) => {
1983
+ const markFunctionReturnJsx = (functionNode, programRoot, scopes, generatedImageJsxNodes, visitedComponentNames) => {
1926
1984
  if (!isFunctionLike(functionNode)) return;
1927
1985
  if (isNodeOfType(functionNode, "ArrowFunctionExpression")) {
1928
1986
  const body = stripParenExpression(functionNode.body);
1929
1987
  if (!isNodeOfType(body, "BlockStatement")) {
1930
- markGeneratedImageExpression(body, programRoot, generatedImageJsxNodes, visitedComponentNames);
1988
+ markGeneratedImageExpression(body, programRoot, scopes, generatedImageJsxNodes, visitedComponentNames);
1931
1989
  return;
1932
1990
  }
1933
1991
  }
@@ -1937,7 +1995,7 @@ const markFunctionReturnJsx = (functionNode, programRoot, generatedImageJsxNodes
1937
1995
  if (descendantNode !== body && isFunctionLike(descendantNode)) return false;
1938
1996
  if (!isNodeOfType(descendantNode, "ReturnStatement")) return;
1939
1997
  if (!descendantNode.argument) return;
1940
- markGeneratedImageExpression(stripParenExpression(descendantNode.argument), programRoot, generatedImageJsxNodes, visitedComponentNames);
1998
+ markGeneratedImageExpression(stripParenExpression(descendantNode.argument), programRoot, scopes, generatedImageJsxNodes, visitedComponentNames);
1941
1999
  });
1942
2000
  };
1943
2001
  const hasNormalJsxUsage = (programRoot, componentName, generatedImageJsxNodes) => {
@@ -1952,7 +2010,7 @@ const hasNormalJsxUsage = (programRoot, componentName, generatedImageJsxNodes) =
1952
2010
  });
1953
2011
  return hasNormalUsage;
1954
2012
  };
1955
- const markComponentRenderJsx = (programRoot, openingElement, generatedImageJsxNodes, visitedComponentNames) => {
2013
+ const markComponentRenderJsx = (programRoot, scopes, openingElement, generatedImageJsxNodes, visitedComponentNames) => {
1956
2014
  const tagName = flattenJsxName$1(openingElement.name);
1957
2015
  if (!tagName || tagName.includes(".") || !isComponentIdentifierName(tagName)) return;
1958
2016
  if (visitedComponentNames.has(tagName)) return;
@@ -1960,70 +2018,70 @@ const markComponentRenderJsx = (programRoot, openingElement, generatedImageJsxNo
1960
2018
  const binding = findVariableInitializer(openingElement, tagName);
1961
2019
  if (!binding?.initializer) return;
1962
2020
  visitedComponentNames.add(tagName);
1963
- markGeneratedImageExpression(stripParenExpression(binding.initializer), programRoot, generatedImageJsxNodes, visitedComponentNames);
2021
+ markGeneratedImageExpression(stripParenExpression(binding.initializer), programRoot, scopes, generatedImageJsxNodes, visitedComponentNames);
1964
2022
  };
1965
- const isInsideGeneratedImageRendererArgument = (node) => {
2023
+ const isInsideGeneratedImageRendererArgument$1 = (node, scopes) => {
1966
2024
  let cursor = node.parent;
1967
2025
  while (cursor) {
1968
- if (isGeneratedImageRendererCall(cursor)) return true;
2026
+ if (isGeneratedImageRendererCall(cursor, scopes)) return true;
1969
2027
  cursor = cursor.parent ?? null;
1970
2028
  }
1971
2029
  return false;
1972
2030
  };
1973
- const hasNormalFunctionCallUsage = (programRoot, functionName) => {
2031
+ const hasNormalFunctionCallUsage = (programRoot, functionName, scopes) => {
1974
2032
  let hasNormalUsage = false;
1975
2033
  walkAst(programRoot, (descendantNode) => {
1976
2034
  if (hasNormalUsage) return false;
1977
2035
  if (!isNodeOfType(descendantNode, "CallExpression")) return;
1978
2036
  if (!isNodeOfType(descendantNode.callee, "Identifier")) return;
1979
2037
  if (descendantNode.callee.name !== functionName) return;
1980
- if (isInsideGeneratedImageRendererArgument(descendantNode)) return;
2038
+ if (isInsideGeneratedImageRendererArgument$1(descendantNode, scopes)) return;
1981
2039
  hasNormalUsage = true;
1982
2040
  return false;
1983
2041
  });
1984
2042
  return hasNormalUsage;
1985
2043
  };
1986
- const markJsxSubtree = (node, programRoot, generatedImageJsxNodes, visitedComponentNames) => {
2044
+ const markJsxSubtree = (node, programRoot, scopes, generatedImageJsxNodes, visitedComponentNames) => {
1987
2045
  walkAst(node, (descendantNode) => {
1988
2046
  if (!isNodeOfType(descendantNode, "JSXOpeningElement")) return;
1989
2047
  generatedImageJsxNodes.add(descendantNode);
1990
- markComponentRenderJsx(programRoot, descendantNode, generatedImageJsxNodes, visitedComponentNames);
2048
+ markComponentRenderJsx(programRoot, scopes, descendantNode, generatedImageJsxNodes, visitedComponentNames);
1991
2049
  });
1992
2050
  };
1993
- const markGeneratedImageExpression = (expression, programRoot, generatedImageJsxNodes, visitedComponentNames) => {
2051
+ const markGeneratedImageExpression = (expression, programRoot, scopes, generatedImageJsxNodes, visitedComponentNames) => {
1994
2052
  const unwrappedExpression = stripParenExpression(expression);
1995
2053
  if (isNodeOfType(unwrappedExpression, "JSXElement") || isNodeOfType(unwrappedExpression, "JSXFragment")) {
1996
- markJsxSubtree(unwrappedExpression, programRoot, generatedImageJsxNodes, visitedComponentNames);
2054
+ markJsxSubtree(unwrappedExpression, programRoot, scopes, generatedImageJsxNodes, visitedComponentNames);
1997
2055
  return;
1998
2056
  }
1999
2057
  if (isFunctionLike(unwrappedExpression)) {
2000
- markFunctionReturnJsx(unwrappedExpression, programRoot, generatedImageJsxNodes, visitedComponentNames);
2058
+ markFunctionReturnJsx(unwrappedExpression, programRoot, scopes, generatedImageJsxNodes, visitedComponentNames);
2001
2059
  return;
2002
2060
  }
2003
2061
  if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
2004
- markGeneratedImageExpression(unwrappedExpression.consequent, programRoot, generatedImageJsxNodes, visitedComponentNames);
2005
- markGeneratedImageExpression(unwrappedExpression.alternate, programRoot, generatedImageJsxNodes, visitedComponentNames);
2062
+ markGeneratedImageExpression(unwrappedExpression.consequent, programRoot, scopes, generatedImageJsxNodes, visitedComponentNames);
2063
+ markGeneratedImageExpression(unwrappedExpression.alternate, programRoot, scopes, generatedImageJsxNodes, visitedComponentNames);
2006
2064
  return;
2007
2065
  }
2008
2066
  if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
2009
- markGeneratedImageExpression(unwrappedExpression.left, programRoot, generatedImageJsxNodes, visitedComponentNames);
2010
- markGeneratedImageExpression(unwrappedExpression.right, programRoot, generatedImageJsxNodes, visitedComponentNames);
2067
+ markGeneratedImageExpression(unwrappedExpression.left, programRoot, scopes, generatedImageJsxNodes, visitedComponentNames);
2068
+ markGeneratedImageExpression(unwrappedExpression.right, programRoot, scopes, generatedImageJsxNodes, visitedComponentNames);
2011
2069
  return;
2012
2070
  }
2013
2071
  if (isNodeOfType(unwrappedExpression, "CallExpression")) {
2014
2072
  const callee = unwrappedExpression.callee;
2015
2073
  if (isFunctionLike(callee)) {
2016
- markFunctionReturnJsx(callee, programRoot, generatedImageJsxNodes, visitedComponentNames);
2074
+ markFunctionReturnJsx(callee, programRoot, scopes, generatedImageJsxNodes, visitedComponentNames);
2017
2075
  return;
2018
2076
  }
2019
2077
  if (!isNodeOfType(callee, "Identifier")) return;
2020
2078
  if (visitedComponentNames.has(callee.name)) return;
2021
2079
  if (hasNormalJsxUsage(programRoot, callee.name, generatedImageJsxNodes)) return;
2022
- if (hasNormalFunctionCallUsage(programRoot, callee.name)) return;
2080
+ if (hasNormalFunctionCallUsage(programRoot, callee.name, scopes)) return;
2023
2081
  const binding = findVariableInitializer(callee, callee.name);
2024
2082
  if (!binding?.initializer || !isFunctionLike(stripParenExpression(binding.initializer))) return;
2025
2083
  visitedComponentNames.add(callee.name);
2026
- markFunctionReturnJsx(stripParenExpression(binding.initializer), programRoot, generatedImageJsxNodes, visitedComponentNames);
2084
+ markFunctionReturnJsx(stripParenExpression(binding.initializer), programRoot, scopes, generatedImageJsxNodes, visitedComponentNames);
2027
2085
  return;
2028
2086
  }
2029
2087
  if (isNodeOfType(unwrappedExpression, "Identifier")) {
@@ -2031,16 +2089,17 @@ const markGeneratedImageExpression = (expression, programRoot, generatedImageJsx
2031
2089
  visitedComponentNames.add(unwrappedExpression.name);
2032
2090
  const binding = findVariableInitializer(unwrappedExpression, unwrappedExpression.name);
2033
2091
  if (!binding?.initializer) return;
2034
- markGeneratedImageExpression(stripParenExpression(binding.initializer), programRoot, generatedImageJsxNodes, visitedComponentNames);
2092
+ markGeneratedImageExpression(stripParenExpression(binding.initializer), programRoot, scopes, generatedImageJsxNodes, visitedComponentNames);
2035
2093
  }
2036
2094
  };
2037
- const collectGeneratedImageJsxNodes = (programRoot) => {
2095
+ const collectGeneratedImageJsxNodes = (programRoot, scopes) => {
2038
2096
  const cached = generatedImageJsxCache.get(programRoot);
2039
2097
  if (cached) return cached;
2040
2098
  const generatedImageJsxNodes = /* @__PURE__ */ new WeakSet();
2041
- if (hasImportFromModules(programRoot, GENERATED_IMAGE_MODULES)) walkAst(programRoot, (descendantNode) => {
2042
- if (!isGeneratedImageRendererCall(descendantNode)) return;
2043
- for (const argument of descendantNode.arguments) markGeneratedImageExpression(argument, programRoot, generatedImageJsxNodes, /* @__PURE__ */ new Set());
2099
+ if (hasImportFromModules(programRoot, GENERATED_IMAGE_RENDERER_MODULES)) walkAst(programRoot, (descendantNode) => {
2100
+ if (!isNodeOfType(descendantNode, "CallExpression") && !isNodeOfType(descendantNode, "NewExpression")) return;
2101
+ if (!isGeneratedImageRendererCall(descendantNode, scopes)) return;
2102
+ for (const argument of descendantNode.arguments) markGeneratedImageExpression(argument, programRoot, scopes, generatedImageJsxNodes, /* @__PURE__ */ new Set());
2044
2103
  });
2045
2104
  generatedImageJsxCache.set(programRoot, generatedImageJsxNodes);
2046
2105
  return generatedImageJsxNodes;
@@ -2050,7 +2109,7 @@ const isGeneratedImageRenderContext = (context, node) => {
2050
2109
  if (!node) return false;
2051
2110
  const programRoot = findProgramRoot(node);
2052
2111
  if (!programRoot) return false;
2053
- const generatedImageJsxNodes = collectGeneratedImageJsxNodes(programRoot);
2112
+ const generatedImageJsxNodes = collectGeneratedImageJsxNodes(programRoot, context.scopes);
2054
2113
  if (generatedImageJsxNodes.has(node)) return true;
2055
2114
  if (isNodeOfType(node, "JSXElement")) return generatedImageJsxNodes.has(node.openingElement);
2056
2115
  return false;
@@ -4496,15 +4555,6 @@ const getDestructuredBindingPropertyName = (bindingIdentifier) => {
4496
4555
  return getStaticPropertyKeyName(property, { allowComputedString: true });
4497
4556
  };
4498
4557
  //#endregion
4499
- //#region src/plugin/utils/get-static-property-name.ts
4500
- const getStaticPropertyName = (memberExpression) => {
4501
- const property = memberExpression.property;
4502
- if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
4503
- if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
4504
- if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
4505
- return null;
4506
- };
4507
- //#endregion
4508
4558
  //#region src/plugin/utils/has-static-property-write-before.ts
4509
4559
  const equivalentSymbolsByAnalysis = /* @__PURE__ */ new WeakMap();
4510
4560
  const potentiallyAliasedSymbolsByAnalysis = /* @__PURE__ */ new WeakMap();
@@ -6205,6 +6255,23 @@ const asyncDeferAwait = defineRule({
6205
6255
  }
6206
6256
  });
6207
6257
  //#endregion
6258
+ //#region src/plugin/utils/expression-reads-pattern-binding.ts
6259
+ const expressionReadsPatternBinding = (expression, patterns, scopes) => {
6260
+ const bindingSymbolIds = /* @__PURE__ */ new Set();
6261
+ for (const pattern of patterns) walkAst(pattern, (child) => {
6262
+ if (!isNodeOfType(child, "Identifier")) return;
6263
+ const symbol = scopes.symbolFor(child);
6264
+ if (symbol?.bindingIdentifier === child) bindingSymbolIds.add(symbol.id);
6265
+ });
6266
+ let didReadBinding = false;
6267
+ walkAst(expression, (child) => {
6268
+ if (didReadBinding || !isNodeOfType(child, "Identifier")) return;
6269
+ const reference = scopes.referenceFor(child);
6270
+ if (reference?.flag !== "write" && reference?.resolvedSymbol && bindingSymbolIds.has(reference.resolvedSymbol.id)) didReadBinding = true;
6271
+ });
6272
+ return didReadBinding;
6273
+ };
6274
+ //#endregion
6208
6275
  //#region src/plugin/utils/get-callee-identifier-trail.ts
6209
6276
  const getCalleeIdentifierTrail = (call) => {
6210
6277
  let entry = call;
@@ -6310,16 +6377,12 @@ const isInsideTransactionCallback = (node) => {
6310
6377
  return false;
6311
6378
  };
6312
6379
  const reportIfIndependent = (statements, context) => {
6313
- const declaredNames = /* @__PURE__ */ new Set();
6380
+ const declaredPatterns = [];
6314
6381
  for (const statement of statements) {
6315
6382
  const awaitArgument = getAwaitedCall(statement);
6316
6383
  if (!awaitArgument) continue;
6317
- let referencesEarlierResult = false;
6318
- walkAst(awaitArgument, (child) => {
6319
- if (isNodeOfType(child, "Identifier") && declaredNames.has(child.name)) referencesEarlierResult = true;
6320
- });
6321
- if (referencesEarlierResult) return;
6322
- if (isNodeOfType(statement, "VariableDeclaration") && statement.declarations[0]?.id) collectPatternNames(statement.declarations[0].id, declaredNames);
6384
+ if (expressionReadsPatternBinding(awaitArgument, declaredPatterns, context.scopes)) return;
6385
+ if (isNodeOfType(statement, "VariableDeclaration") && statement.declarations[0]?.id) declaredPatterns.push(statement.declarations[0].id);
6323
6386
  }
6324
6387
  context.report({
6325
6388
  node: statements[0],
@@ -8187,6 +8250,9 @@ const createMethodMutationAnalysis = (context) => {
8187
8250
  };
8188
8251
  };
8189
8252
  //#endregion
8253
+ //#region src/plugin/utils/is-member-property.ts
8254
+ const isMemberProperty = (node, propertyName) => Boolean(node && isNodeOfType(node, "MemberExpression") && isNodeOfType(node.property, "Identifier") && node.property.name === propertyName);
8255
+ //#endregion
8190
8256
  //#region src/plugin/utils/collect-function-return-statements.ts
8191
8257
  const collectFunctionReturnStatements = (functionNode) => {
8192
8258
  if (!isFunctionLike$1(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return [];
@@ -8463,10 +8529,21 @@ const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) =>
8463
8529
  };
8464
8530
  const isReactApiCall = (node, apiNames, scopes, options = {}) => {
8465
8531
  if (!isNodeOfType(node, "CallExpression")) return false;
8466
- const callee = stripParenExpression(node.callee);
8532
+ return isReactApiCallee(node.callee, apiNames, scopes, options, /* @__PURE__ */ new Set());
8533
+ };
8534
+ const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds) => {
8535
+ const callee = stripParenExpression(rawCallee);
8536
+ if (options.resolveConditionalAliases && isNodeOfType(callee, "ConditionalExpression")) return isReactApiCallee(callee.consequent, apiNames, scopes, options, new Set(visitedSymbolIds)) && isReactApiCallee(callee.alternate, apiNames, scopes, options, new Set(visitedSymbolIds));
8467
8537
  if (isNodeOfType(callee, "Identifier")) {
8468
8538
  if (isNamedReactApiImport(callee, apiNames, scopes, Boolean(options.resolveNamedAliases))) return true;
8469
8539
  if (options.resolveNamedAliases && isDestructuredReactApiBinding(callee, apiNames, scopes, options)) return true;
8540
+ if (options.resolveConditionalAliases) {
8541
+ const symbol = scopes.symbolFor(callee);
8542
+ if (symbol?.kind === "const" && symbol.initializer && !visitedSymbolIds.has(symbol.id)) {
8543
+ visitedSymbolIds.add(symbol.id);
8544
+ return isReactApiCallee(symbol.initializer, apiNames, scopes, options, visitedSymbolIds);
8545
+ }
8546
+ }
8470
8547
  return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
8471
8548
  }
8472
8549
  if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
@@ -12323,6 +12400,10 @@ const isConstAliasOfGlobalArrayFrom = (node, scopes) => {
12323
12400
  };
12324
12401
  const executesDuringRender = (functionNode, scopes) => {
12325
12402
  const parent = functionNode.parent;
12403
+ if (isNodeOfType(parent, "NewExpression")) {
12404
+ const callee = stripParenExpression(parent.callee);
12405
+ return Boolean(scopes && parent.arguments?.[0] === functionNode && isNodeOfType(callee, "Identifier") && callee.name === "Promise" && scopes.isGlobalReference(callee));
12406
+ }
12326
12407
  if (!isNodeOfType(parent, "CallExpression")) return false;
12327
12408
  if (parent.callee === functionNode) return true;
12328
12409
  if ((scopes ? isReactApiCall(parent, REACT_RENDER_PHASE_HOOK_NAMES, scopes, { allowGlobalReactNamespace: true }) : isHookCall$2(parent, REACT_RENDER_PHASE_HOOK_NAMES)) && parent.arguments?.[0] === functionNode) return true;
@@ -14336,14 +14417,14 @@ const DEPENDENCY_HOOK_NAMES = new Set([
14336
14417
  "useImperativeHandle",
14337
14418
  "useInsertionEffect"
14338
14419
  ]);
14339
- const crossFileScopes = /* @__PURE__ */ new WeakMap();
14420
+ const crossFileScopes$1 = /* @__PURE__ */ new WeakMap();
14340
14421
  const crossFileControlFlow = /* @__PURE__ */ new WeakMap();
14341
14422
  const forwardedFreshDependencyCache = /* @__PURE__ */ new WeakMap();
14342
- const getCrossFileScopes = (resolved) => {
14343
- const cached = crossFileScopes.get(resolved.programNode);
14423
+ const getCrossFileScopes$1 = (resolved) => {
14424
+ const cached = crossFileScopes$1.get(resolved.programNode);
14344
14425
  if (cached) return cached;
14345
14426
  const scopes = analyzeScopes(resolved.programNode);
14346
- crossFileScopes.set(resolved.programNode, scopes);
14427
+ crossFileScopes$1.set(resolved.programNode, scopes);
14347
14428
  return scopes;
14348
14429
  };
14349
14430
  const getCrossFileControlFlow = (resolved) => {
@@ -14378,7 +14459,7 @@ const isCustomHookFunction = (functionNode, fallbackName) => {
14378
14459
  const displayName = componentOrHookDisplayNameForFunction(functionNode) ?? fallbackName ?? "";
14379
14460
  return /^use[A-Z0-9]/.test(displayName);
14380
14461
  };
14381
- const getImportedHookBinding = (callee, scopes) => {
14462
+ const getImportedHookBinding$1 = (callee, scopes) => {
14382
14463
  if (!isNodeOfType(callee, "Identifier")) return null;
14383
14464
  const importedSymbol = resolveConstIdentifierAlias(callee, scopes);
14384
14465
  if (importedSymbol?.kind !== "import" || !importedSymbol.initializer) return null;
@@ -14394,7 +14475,7 @@ const getImportedHookBinding = (callee, scopes) => {
14394
14475
  };
14395
14476
  const resolveImportedHookFunction = (callee, scopes, currentFilename) => {
14396
14477
  if (!currentFilename) return null;
14397
- const importedBinding = getImportedHookBinding(callee, scopes);
14478
+ const importedBinding = getImportedHookBinding$1(callee, scopes);
14398
14479
  if (!importedBinding) return null;
14399
14480
  const resolved = resolveCrossFileFunctionExportWithFilePath(currentFilename, importedBinding.source, importedBinding.exportedName);
14400
14481
  if (!resolved || !isCustomHookFunction(resolved.functionNode, importedBinding.exportedName)) return null;
@@ -14403,7 +14484,7 @@ const resolveImportedHookFunction = (callee, scopes, currentFilename) => {
14403
14484
  filePath: resolved.filePath,
14404
14485
  functionNode: resolved.functionNode,
14405
14486
  programNode: resolved.programNode,
14406
- scopes: getCrossFileScopes(resolved)
14487
+ scopes: getCrossFileScopes$1(resolved)
14407
14488
  };
14408
14489
  };
14409
14490
  const dependencyIndexForReactHookReference = (expression, scopes, dependencyHookNames, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
@@ -14434,11 +14515,11 @@ const dependencyIndexForReactHookReference = (expression, scopes, dependencyHook
14434
14515
  };
14435
14516
  const getImportedReactDependencyIndex = (callExpression, scopes, currentFilename, dependencyHookNames) => {
14436
14517
  if (!currentFilename) return null;
14437
- const importedBinding = getImportedHookBinding(stripParenExpression(callExpression.callee), scopes);
14518
+ const importedBinding = getImportedHookBinding$1(stripParenExpression(callExpression.callee), scopes);
14438
14519
  if (!importedBinding) return null;
14439
14520
  const resolved = resolveCrossFileValueExportWithFilePath(currentFilename, importedBinding.source, importedBinding.exportedName);
14440
14521
  if (!resolved) return null;
14441
- return dependencyIndexForReactHookReference(resolved.exportedNode, getCrossFileScopes(resolved), dependencyHookNames);
14522
+ return dependencyIndexForReactHookReference(resolved.exportedNode, getCrossFileScopes$1(resolved), dependencyHookNames);
14442
14523
  };
14443
14524
  const resolveHookFunction = (callExpression, scopes, cfg, currentFilename) => {
14444
14525
  const callee = stripParenExpression(callExpression.callee);
@@ -19153,10 +19234,10 @@ const isUncacheableOptionsMergeUtility = (node) => {
19153
19234
  return (argument.properties ?? []).some((property) => isNodeOfType(property, "SpreadElement") && isNodeOfType(property.argument, "Identifier") && parameterNames.has(property.argument.name));
19154
19235
  });
19155
19236
  };
19156
- const isIntlNewExpression = (node) => {
19237
+ const isIntlNewExpression = (node, context) => {
19157
19238
  if (!isNodeOfType(node, "NewExpression")) return false;
19158
19239
  const callee = node.callee;
19159
- if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "Intl" && isNodeOfType(callee.property, "Identifier") && INTL_CLASSES.has(callee.property.name)) return true;
19240
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "Intl" && context.scopes.isGlobalReference(callee.object) && isNodeOfType(callee.property, "Identifier") && INTL_CLASSES.has(callee.property.name)) return true;
19160
19241
  return false;
19161
19242
  };
19162
19243
  const jsHoistIntl = defineRule({
@@ -19166,7 +19247,7 @@ const jsHoistIntl = defineRule({
19166
19247
  severity: "warn",
19167
19248
  recommendation: "Move `new Intl.NumberFormat(...)` to the top of the file or wrap it in `useMemo`. Building one is slow, so don't redo it on every call",
19168
19249
  create: (context) => ({ NewExpression(node) {
19169
- if (!isIntlNewExpression(node)) return;
19250
+ if (!isIntlNewExpression(node, context)) return;
19170
19251
  let cursor = node.parent ?? null;
19171
19252
  let inFunctionBody = false;
19172
19253
  while (cursor) {
@@ -19931,6 +20012,28 @@ const jsLengthCheckFirst = defineRule({
19931
20012
  } })
19932
20013
  });
19933
20014
  //#endregion
20015
+ //#region src/plugin/utils/is-proven-global-namespace-reference.ts
20016
+ const isProvenGlobalObjectReference = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
20017
+ const strippedExpression = stripParenExpression(expression);
20018
+ if (!isNodeOfType(strippedExpression, "Identifier")) return false;
20019
+ if ((strippedExpression.name === "globalThis" || strippedExpression.name === "window" || strippedExpression.name === "self" || strippedExpression.name === "global") && scopes.isGlobalReference(strippedExpression)) return true;
20020
+ const symbol = scopes.symbolFor(strippedExpression);
20021
+ if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
20022
+ visitedSymbolIds.add(symbol.id);
20023
+ return isProvenGlobalObjectReference(symbol.initializer, scopes, visitedSymbolIds);
20024
+ };
20025
+ const isProvenGlobalNamespaceReference = (expression, namespaceName, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
20026
+ const strippedExpression = stripParenExpression(expression);
20027
+ if (isNodeOfType(strippedExpression, "Identifier")) {
20028
+ if (strippedExpression.name === namespaceName && scopes.isGlobalReference(strippedExpression)) return true;
20029
+ const symbol = scopes.symbolFor(strippedExpression);
20030
+ if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
20031
+ visitedSymbolIds.add(symbol.id);
20032
+ return isProvenGlobalNamespaceReference(symbol.initializer, namespaceName, scopes, visitedSymbolIds);
20033
+ }
20034
+ return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isProvenGlobalObjectReference(strippedExpression.object, scopes);
20035
+ };
20036
+ //#endregion
19934
20037
  //#region src/plugin/rules/js-performance/js-min-max-loop.ts
19935
20038
  const builtinMutationByProgram = /* @__PURE__ */ new WeakMap();
19936
20039
  const RUNTIMELESS_SYMBOL_KINDS = new Set(["ts-interface", "ts-type-alias"]);
@@ -19987,26 +20090,6 @@ const isSafeFreshNumericArray = (arrayExpression) => {
19987
20090
  }
19988
20091
  return !(didFindPositiveZero && didFindNegativeZero);
19989
20092
  };
19990
- const isGlobalObjectReference = (expression, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
19991
- const strippedExpression = stripParenExpression(expression);
19992
- if (!isNodeOfType(strippedExpression, "Identifier")) return false;
19993
- if ((strippedExpression.name === "globalThis" || strippedExpression.name === "window" || strippedExpression.name === "self" || strippedExpression.name === "global") && scopes.isGlobalReference(strippedExpression)) return true;
19994
- const symbol = scopes.symbolFor(strippedExpression);
19995
- if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbols.has(symbol.id)) return false;
19996
- visitedSymbols.add(symbol.id);
19997
- return isGlobalObjectReference(symbol.initializer, scopes, visitedSymbols);
19998
- };
19999
- const resolvesToGlobalNamespace = (expression, namespaceName, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
20000
- const strippedExpression = stripParenExpression(expression);
20001
- if (isNodeOfType(strippedExpression, "Identifier")) {
20002
- if (strippedExpression.name === namespaceName && scopes.isGlobalReference(strippedExpression)) return true;
20003
- const symbol = scopes.symbolFor(strippedExpression);
20004
- if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbols.has(symbol.id)) return false;
20005
- visitedSymbols.add(symbol.id);
20006
- return resolvesToGlobalNamespace(symbol.initializer, namespaceName, scopes, visitedSymbols);
20007
- }
20008
- return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isGlobalObjectReference(strippedExpression.object, scopes);
20009
- };
20010
20093
  const resolvesToGlobalMethod = (expression, namespaceName, methodNames, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
20011
20094
  const strippedExpression = stripParenExpression(expression);
20012
20095
  if (isNodeOfType(strippedExpression, "Identifier")) {
@@ -20015,7 +20098,7 @@ const resolvesToGlobalMethod = (expression, namespaceName, methodNames, scopes,
20015
20098
  visitedSymbols.add(symbol.id);
20016
20099
  return resolvesToGlobalMethod(symbol.initializer, namespaceName, methodNames, scopes, visitedSymbols);
20017
20100
  }
20018
- return isNodeOfType(strippedExpression, "MemberExpression") && methodNames.has(getStaticPropertyName(strippedExpression) ?? "") && resolvesToGlobalNamespace(strippedExpression.object, namespaceName, scopes);
20101
+ return isNodeOfType(strippedExpression, "MemberExpression") && methodNames.has(getStaticPropertyName(strippedExpression) ?? "") && isProvenGlobalNamespaceReference(strippedExpression.object, namespaceName, scopes);
20019
20102
  };
20020
20103
  const resolvesToNativeArrayPrototype = (expression, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
20021
20104
  const strippedExpression = stripParenExpression(expression);
@@ -20027,7 +20110,7 @@ const resolvesToNativeArrayPrototype = (expression, scopes, visitedSymbols = /*
20027
20110
  }
20028
20111
  if (isNodeOfType(strippedExpression, "MemberExpression")) {
20029
20112
  const propertyName = getStaticPropertyName(strippedExpression);
20030
- if (propertyName === "prototype") return resolvesToGlobalNamespace(strippedExpression.object, "Array", scopes);
20113
+ if (propertyName === "prototype") return isProvenGlobalNamespaceReference(strippedExpression.object, "Array", scopes);
20031
20114
  return propertyName === "__proto__" && isNodeOfType(stripParenExpression(strippedExpression.object), "ArrayExpression");
20032
20115
  }
20033
20116
  if (!isNodeOfType(strippedExpression, "CallExpression")) return false;
@@ -20038,23 +20121,23 @@ const resolvesToNativeArrayPrototype = (expression, scopes, visitedSymbols = /*
20038
20121
  const isGlobalNamespaceReplacementTarget = (target, namespaceName, scopes) => {
20039
20122
  const strippedTarget = stripParenExpression(target);
20040
20123
  if (isNodeOfType(strippedTarget, "Identifier")) return strippedTarget.name === namespaceName && scopes.isGlobalReference(strippedTarget);
20041
- return isNodeOfType(strippedTarget, "MemberExpression") && getStaticPropertyName(strippedTarget) === namespaceName && isGlobalObjectReference(strippedTarget.object, scopes);
20124
+ return isNodeOfType(strippedTarget, "MemberExpression") && getStaticPropertyName(strippedTarget) === namespaceName && isProvenGlobalObjectReference(strippedTarget.object, scopes);
20042
20125
  };
20043
20126
  const isUnsafeBuiltinMemberTarget = (target, targetFunction, scopes) => {
20044
20127
  const strippedTarget = stripParenExpression(target);
20045
20128
  if (!isNodeOfType(strippedTarget, "MemberExpression")) return false;
20046
20129
  const propertyName = getStaticPropertyName(strippedTarget);
20047
20130
  if (resolvesToNativeArrayPrototype(strippedTarget.object, scopes)) return propertyName === null || propertyName === "sort";
20048
- if (resolvesToGlobalNamespace(strippedTarget.object, "Math", scopes)) return propertyName === null || propertyName === targetFunction;
20049
- return isGlobalObjectReference(strippedTarget.object, scopes) && (propertyName === null || propertyName === "Math");
20131
+ if (isProvenGlobalNamespaceReference(strippedTarget.object, "Math", scopes)) return propertyName === null || propertyName === targetFunction;
20132
+ return isProvenGlobalObjectReference(strippedTarget.object, scopes) && (propertyName === null || propertyName === "Math");
20050
20133
  };
20051
20134
  const isUnsafeBuiltinMutationApiCall = (callExpression, targetFunction, scopes) => {
20052
20135
  const target = callExpression.arguments[0];
20053
20136
  if (!target) return false;
20054
20137
  let propertyName = null;
20055
20138
  if (resolvesToNativeArrayPrototype(target, scopes)) propertyName = "sort";
20056
- else if (resolvesToGlobalNamespace(target, "Math", scopes)) propertyName = targetFunction;
20057
- else if (isGlobalObjectReference(target, scopes)) propertyName = "Math";
20139
+ else if (isProvenGlobalNamespaceReference(target, "Math", scopes)) propertyName = targetFunction;
20140
+ else if (isProvenGlobalObjectReference(target, scopes)) propertyName = "Math";
20058
20141
  if (!propertyName) return false;
20059
20142
  const canObjectExpressionSetProperty = (properties) => {
20060
20143
  if (!isNodeOfType(properties, "ObjectExpression")) return true;
@@ -20105,7 +20188,7 @@ const hasUnsafeMathBinding = (node, scopes) => {
20105
20188
  let scope = scopes.scopeFor(node);
20106
20189
  while (scope) {
20107
20190
  const symbol = scope.symbolsByName.get("Math");
20108
- if (symbol && !RUNTIMELESS_SYMBOL_KINDS.has(symbol.kind)) return !(symbol.kind === "const" && symbol.initializer && resolvesToGlobalNamespace(symbol.initializer, "Math", scopes));
20191
+ if (symbol && !RUNTIMELESS_SYMBOL_KINDS.has(symbol.kind)) return !(symbol.kind === "const" && symbol.initializer && isProvenGlobalNamespaceReference(symbol.initializer, "Math", scopes));
20109
20192
  scope = scope.parent;
20110
20193
  }
20111
20194
  return false;
@@ -26458,6 +26541,489 @@ const hasEmailTemplateImport = (programRoot) => {
26458
26541
  return found;
26459
26542
  };
26460
26543
  //#endregion
26544
+ //#region src/plugin/utils/build-generated-image-project-index.ts
26545
+ const GENERATED_IMAGE_SOURCE_FILE_PATTERN = /\.[cm]?[jt]sx?$/i;
26546
+ const GENERATED_IMAGE_DECLARATION_FILE_PATTERN = /\.d\.[cm]?[jt]s$/i;
26547
+ const GENERATED_IMAGE_MDX_FILE_PATTERN = /\.mdx$/i;
26548
+ const GENERATED_IMAGE_IGNORED_DIRECTORY_NAMES = new Set([
26549
+ ".angular",
26550
+ ".astro",
26551
+ ".cache",
26552
+ ".contentlayer",
26553
+ ".docusaurus",
26554
+ ".expo",
26555
+ ".git",
26556
+ ".next",
26557
+ ".nuxt",
26558
+ ".output",
26559
+ ".svelte-kit",
26560
+ ".turbo",
26561
+ ".vercel",
26562
+ "build",
26563
+ "coverage",
26564
+ "dist",
26565
+ "node_modules",
26566
+ "out",
26567
+ "storybook-static"
26568
+ ]);
26569
+ const generatedImageScopeCache = /* @__PURE__ */ new WeakMap();
26570
+ const getGeneratedImageModuleScopes = (programNode) => {
26571
+ const cachedScopes = generatedImageScopeCache.get(programNode);
26572
+ if (cachedScopes) return cachedScopes;
26573
+ const scopes = analyzeScopes(programNode);
26574
+ generatedImageScopeCache.set(programNode, scopes);
26575
+ return scopes;
26576
+ };
26577
+ const listProductionSourceFiles = (rootDirectory) => {
26578
+ const sourceFilePaths = [];
26579
+ const pendingDirectories = [rootDirectory];
26580
+ let hasOpaqueMdxConsumerSurface = false;
26581
+ while (pendingDirectories.length > 0) {
26582
+ const currentDirectory = pendingDirectories.pop();
26583
+ if (!currentDirectory) continue;
26584
+ let entries;
26585
+ try {
26586
+ entries = fs.readdirSync(currentDirectory, { withFileTypes: true });
26587
+ } catch {
26588
+ return null;
26589
+ }
26590
+ for (const entry of entries) {
26591
+ const absolutePath = path.join(currentDirectory, entry.name);
26592
+ const isIgnoredDirectoryName = GENERATED_IMAGE_IGNORED_DIRECTORY_NAMES.has(entry.name) || entry.name.startsWith(".") && entry.name !== ".dumi" && entry.name !== ".storybook";
26593
+ if (entry.isSymbolicLink() && isIgnoredDirectoryName) continue;
26594
+ if (entry.isSymbolicLink()) return null;
26595
+ if (entry.isDirectory()) {
26596
+ if (isIgnoredDirectoryName) continue;
26597
+ pendingDirectories.push(absolutePath);
26598
+ continue;
26599
+ }
26600
+ if (!entry.isFile() || isTestlikeFilename(absolutePath)) continue;
26601
+ if (GENERATED_IMAGE_MDX_FILE_PATTERN.test(entry.name)) {
26602
+ hasOpaqueMdxConsumerSurface = true;
26603
+ continue;
26604
+ }
26605
+ if (!GENERATED_IMAGE_SOURCE_FILE_PATTERN.test(entry.name)) continue;
26606
+ if (GENERATED_IMAGE_DECLARATION_FILE_PATTERN.test(entry.name)) continue;
26607
+ sourceFilePaths.push(normalizeFilename(absolutePath));
26608
+ }
26609
+ }
26610
+ return {
26611
+ sourceFilePaths,
26612
+ hasOpaqueMdxConsumerSurface
26613
+ };
26614
+ };
26615
+ const getRuntimeModuleSource = (node) => {
26616
+ if (isNodeOfType(node, "ImportDeclaration")) {
26617
+ if (isTypeOnlyImport(node)) return null;
26618
+ return typeof node.source.value === "string" ? node.source.value : null;
26619
+ }
26620
+ if (isNodeOfType(node, "ExportNamedDeclaration")) {
26621
+ if (node.exportKind === "type" || isEverySpecifierInlineType(node.specifiers, "ExportSpecifier", "exportKind")) return null;
26622
+ return node.source && typeof node.source.value === "string" ? node.source.value : null;
26623
+ }
26624
+ if (isNodeOfType(node, "ExportAllDeclaration")) {
26625
+ if (node.exportKind === "type") return null;
26626
+ return typeof node.source.value === "string" ? node.source.value : null;
26627
+ }
26628
+ if (isNodeOfType(node, "ImportExpression")) return isNodeOfType(node.source, "Literal") && typeof node.source.value === "string" ? node.source.value : null;
26629
+ if (isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "require" && node.arguments.length === 1) {
26630
+ const source = node.arguments[0];
26631
+ return source && isNodeOfType(source, "Literal") && typeof source.value === "string" ? source.value : null;
26632
+ }
26633
+ return null;
26634
+ };
26635
+ const indexModuleSources = (module, consumerModulesByFilePath, resolvedSourcePathByNode, unresolvedRuntimeSources) => {
26636
+ walkAst(module.programNode, (node) => {
26637
+ const source = getRuntimeModuleSource(node);
26638
+ if (!source) return;
26639
+ const resolvedSourcePath = resolveModulePath(module.filePath, source);
26640
+ if (!resolvedSourcePath) {
26641
+ unresolvedRuntimeSources.add(source);
26642
+ return;
26643
+ }
26644
+ const normalizedSourcePath = normalizeFilename(resolvedSourcePath);
26645
+ resolvedSourcePathByNode.set(node, normalizedSourcePath);
26646
+ const consumerModules = consumerModulesByFilePath.get(normalizedSourcePath) ?? /* @__PURE__ */ new Set();
26647
+ consumerModules.add(module);
26648
+ consumerModulesByFilePath.set(normalizedSourcePath, consumerModules);
26649
+ });
26650
+ };
26651
+ const buildGeneratedImageProjectIndex = (rootDirectory, currentFilePath, currentProgramNode, currentScopes) => {
26652
+ const productionSourceFiles = listProductionSourceFiles(rootDirectory);
26653
+ if (!productionSourceFiles) return null;
26654
+ const modulesByFilePath = /* @__PURE__ */ new Map();
26655
+ for (const filePath of productionSourceFiles.sourceFilePaths) {
26656
+ if (filePath === currentFilePath) {
26657
+ modulesByFilePath.set(filePath, {
26658
+ filePath,
26659
+ programNode: currentProgramNode,
26660
+ scopes: currentScopes
26661
+ });
26662
+ continue;
26663
+ }
26664
+ const parsedProgram = parseSourceFile(filePath);
26665
+ if (!parsedProgram || !isNodeOfType(parsedProgram, "Program")) return null;
26666
+ modulesByFilePath.set(filePath, {
26667
+ filePath,
26668
+ programNode: parsedProgram,
26669
+ scopes: getGeneratedImageModuleScopes(parsedProgram)
26670
+ });
26671
+ }
26672
+ const consumerModulesByFilePath = /* @__PURE__ */ new Map();
26673
+ const resolvedSourcePathByNode = /* @__PURE__ */ new WeakMap();
26674
+ const unresolvedRuntimeSources = /* @__PURE__ */ new Set();
26675
+ for (const module of modulesByFilePath.values()) indexModuleSources(module, consumerModulesByFilePath, resolvedSourcePathByNode, unresolvedRuntimeSources);
26676
+ return {
26677
+ modulesByFilePath,
26678
+ consumerModulesByFilePath,
26679
+ resolvedSourcePathByNode,
26680
+ unresolvedRuntimeSources,
26681
+ hasOpaqueMdxConsumerSurface: productionSourceFiles.hasOpaqueMdxConsumerSurface
26682
+ };
26683
+ };
26684
+ //#endregion
26685
+ //#region src/plugin/utils/read-nearest-package-manifest.ts
26686
+ const cachedPackageDirectoryByFilename = /* @__PURE__ */ new Map();
26687
+ const cachedManifestByPackageDirectory = /* @__PURE__ */ new Map();
26688
+ const resetManifestCaches = () => {
26689
+ cachedPackageDirectoryByFilename.clear();
26690
+ cachedManifestByPackageDirectory.clear();
26691
+ };
26692
+ const findNearestPackageDirectory = (filename) => {
26693
+ if (!filename) return null;
26694
+ const fromCache = cachedPackageDirectoryByFilename.get(filename);
26695
+ if (fromCache !== void 0) {
26696
+ if (isProbeRecorderActive()) {
26697
+ let probedDirectory = path.dirname(filename);
26698
+ while (true) {
26699
+ recordExistenceProbe(path.join(probedDirectory, "package.json"));
26700
+ if (probedDirectory === fromCache) break;
26701
+ const parentDirectory = path.dirname(probedDirectory);
26702
+ if (parentDirectory === probedDirectory) break;
26703
+ probedDirectory = parentDirectory;
26704
+ }
26705
+ }
26706
+ return fromCache;
26707
+ }
26708
+ let currentDirectory = path.dirname(filename);
26709
+ while (true) {
26710
+ const candidatePackageJsonPath = path.join(currentDirectory, "package.json");
26711
+ recordExistenceProbe(candidatePackageJsonPath);
26712
+ let hasPackageJson = false;
26713
+ try {
26714
+ hasPackageJson = fs.statSync(candidatePackageJsonPath).isFile();
26715
+ } catch {
26716
+ hasPackageJson = false;
26717
+ }
26718
+ if (hasPackageJson) {
26719
+ cachedPackageDirectoryByFilename.set(filename, currentDirectory);
26720
+ return currentDirectory;
26721
+ }
26722
+ const parentDirectory = path.dirname(currentDirectory);
26723
+ if (parentDirectory === currentDirectory) {
26724
+ cachedPackageDirectoryByFilename.set(filename, null);
26725
+ return null;
26726
+ }
26727
+ currentDirectory = parentDirectory;
26728
+ }
26729
+ };
26730
+ const readNearestPackageManifest = (filename) => {
26731
+ const packageDirectory = findNearestPackageDirectory(filename);
26732
+ if (!packageDirectory) return null;
26733
+ return readPackageManifest(packageDirectory);
26734
+ };
26735
+ const readPackageManifest = (packageDirectory) => {
26736
+ const packageJsonPath = path.join(packageDirectory, "package.json");
26737
+ recordContentProbe(packageJsonPath);
26738
+ const cached = cachedManifestByPackageDirectory.get(packageDirectory);
26739
+ if (cached !== void 0) return cached;
26740
+ let manifest = null;
26741
+ try {
26742
+ const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
26743
+ if (typeof parsed === "object" && parsed !== null) manifest = parsed;
26744
+ } catch {
26745
+ manifest = null;
26746
+ }
26747
+ cachedManifestByPackageDirectory.set(packageDirectory, manifest);
26748
+ return manifest;
26749
+ };
26750
+ //#endregion
26751
+ //#region src/plugin/utils/is-exported-jsx-owned-by-generated-image-renderers.ts
26752
+ const getExportedSpecifierName = (specifier) => {
26753
+ const exported = specifier.exported;
26754
+ if (isNodeOfType(exported, "Identifier")) return exported.name;
26755
+ return isNodeOfType(exported, "Literal") && typeof exported.value === "string" ? exported.value : null;
26756
+ };
26757
+ const getImportedSpecifierName = (specifier) => {
26758
+ const local = specifier.local;
26759
+ if (isNodeOfType(local, "Identifier")) return local.name;
26760
+ return isNodeOfType(local, "Literal") && typeof local.value === "string" ? local.value : null;
26761
+ };
26762
+ const getImportSpecifierName = (specifier) => {
26763
+ if (isNodeOfType(specifier, "ImportDefaultSpecifier")) return "default";
26764
+ if (!isNodeOfType(specifier, "ImportSpecifier")) return null;
26765
+ const imported = specifier.imported;
26766
+ if (isNodeOfType(imported, "Identifier")) return imported.name;
26767
+ return isNodeOfType(imported, "Literal") && typeof imported.value === "string" ? imported.value : null;
26768
+ };
26769
+ const getDirectFunctionBindingIdentifier = (functionNode) => {
26770
+ if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
26771
+ const functionValueRoot = findTransparentExpressionRoot(functionNode);
26772
+ const parent = functionValueRoot.parent;
26773
+ return isNodeOfType(parent, "VariableDeclarator") && parent.init === functionValueRoot && isNodeOfType(parent.id, "Identifier") ? parent.id : null;
26774
+ };
26775
+ const getExportNamesForFunction = (programNode, functionNode) => {
26776
+ const functionValueRoot = findTransparentExpressionRoot(functionNode);
26777
+ const bindingName = getDirectFunctionBindingIdentifier(functionNode)?.name ?? null;
26778
+ const exportedNames = /* @__PURE__ */ new Set();
26779
+ for (const statement of programNode.body) {
26780
+ if (isNodeOfType(statement, "ExportDefaultDeclaration")) {
26781
+ if (statement.declaration === functionValueRoot || bindingName && isNodeOfType(statement.declaration, "Identifier") && statement.declaration.name === bindingName) exportedNames.add("default");
26782
+ continue;
26783
+ }
26784
+ if (!isNodeOfType(statement, "ExportNamedDeclaration")) continue;
26785
+ const declaration = statement.declaration;
26786
+ if (declaration === functionValueRoot && bindingName) exportedNames.add(bindingName);
26787
+ if (declaration && isNodeOfType(declaration, "VariableDeclaration")) {
26788
+ for (const declarator of declaration.declarations) if (declarator.init === functionValueRoot && isNodeOfType(declarator.id, "Identifier")) exportedNames.add(declarator.id.name);
26789
+ }
26790
+ if (!bindingName || statement.source) continue;
26791
+ for (const specifier of statement.specifiers) {
26792
+ if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
26793
+ if (getImportedSpecifierName(specifier) !== bindingName) continue;
26794
+ const exportedName = getExportedSpecifierName(specifier);
26795
+ if (exportedName) exportedNames.add(exportedName);
26796
+ }
26797
+ }
26798
+ return [...exportedNames];
26799
+ };
26800
+ const isTransparentGeneratedImageValueFlow = (expression, target) => {
26801
+ let current = findTransparentExpressionRoot(expression);
26802
+ while (current !== target) {
26803
+ const parent = current.parent;
26804
+ if (!parent) return false;
26805
+ if (!((isNodeOfType(parent, "JSXExpressionContainer") || isNodeOfType(parent, "JSXSpreadChild")) && parent.expression === current || (isNodeOfType(parent, "JSXElement") || isNodeOfType(parent, "JSXFragment")) && parent.children.some((child) => child === current) || isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === current || parent.alternate === current) || isNodeOfType(parent, "LogicalExpression") && (parent.left === current || parent.right === current) || isNodeOfType(parent, "ArrayExpression") && parent.elements.some((element) => element === current) || isNodeOfType(parent, "SequenceExpression") && parent.expressions.at(-1) === current || isNodeOfType(parent, "AwaitExpression") && parent.argument === current)) return false;
26806
+ current = findTransparentExpressionRoot(parent);
26807
+ }
26808
+ return true;
26809
+ };
26810
+ const isInsideGeneratedImageRendererArgument = (expression, scopes) => {
26811
+ let cursor = expression;
26812
+ while (cursor?.parent) {
26813
+ const parent = cursor.parent;
26814
+ if (isFunctionLike$1(parent)) return false;
26815
+ if (isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "NewExpression")) {
26816
+ if (parent.arguments[0] && isTransparentGeneratedImageValueFlow(expression, parent.arguments[0]) && isGeneratedImageRendererCall(parent, scopes)) return true;
26817
+ }
26818
+ cursor = parent;
26819
+ }
26820
+ return false;
26821
+ };
26822
+ const getInvokedExpression = (identifier) => {
26823
+ const referenceExpression = findTransparentExpressionRoot(identifier);
26824
+ const parent = referenceExpression.parent;
26825
+ if (isNodeOfType(parent, "CallExpression") && parent.callee === referenceExpression) return parent;
26826
+ if (isNodeOfType(parent, "TaggedTemplateExpression") && parent.tag === referenceExpression) return parent;
26827
+ if ((isNodeOfType(parent, "JSXOpeningElement") || isNodeOfType(parent, "JSXClosingElement")) && parent.name === identifier) {
26828
+ const element = parent.parent;
26829
+ return isNodeOfType(element, "JSXElement") ? element : null;
26830
+ }
26831
+ return null;
26832
+ };
26833
+ const getForwardingFunction = (expression) => {
26834
+ const enclosingFunction = findEnclosingFunction$1(expression);
26835
+ if (!enclosingFunction) return null;
26836
+ if (isNodeOfType(enclosingFunction, "ArrowFunctionExpression") && !isNodeOfType(enclosingFunction.body, "BlockStatement") && isTransparentGeneratedImageValueFlow(expression, enclosingFunction.body)) return enclosingFunction;
26837
+ let cursor = expression.parent;
26838
+ while (cursor && cursor !== enclosingFunction) {
26839
+ if (isFunctionLike$1(cursor)) return null;
26840
+ if (isNodeOfType(cursor, "ReturnStatement") && cursor.argument && isTransparentGeneratedImageValueFlow(expression, cursor.argument)) return enclosingFunction;
26841
+ cursor = cursor.parent;
26842
+ }
26843
+ return null;
26844
+ };
26845
+ const enqueueExport = (state, filePath, exportedName) => {
26846
+ state.pendingExports.push({
26847
+ filePath: normalizeFilename(filePath),
26848
+ exportedName
26849
+ });
26850
+ };
26851
+ const classifyInvokedExpression = (module, expression, state) => {
26852
+ if (isInsideGeneratedImageRendererArgument(expression, module.scopes)) {
26853
+ state.didReachRenderer = true;
26854
+ return true;
26855
+ }
26856
+ const forwardingFunction = getForwardingFunction(expression);
26857
+ if (!forwardingFunction) return false;
26858
+ const exportedNames = getExportNamesForFunction(module.programNode, forwardingFunction);
26859
+ if (exportedNames.length === 0) return false;
26860
+ for (const exportedName of exportedNames) enqueueExport(state, module.filePath, exportedName);
26861
+ return true;
26862
+ };
26863
+ const classifySymbolReferences = (module, symbol, state, visitedSymbolIds) => {
26864
+ if (visitedSymbolIds.has(symbol.id)) return true;
26865
+ visitedSymbolIds.add(symbol.id);
26866
+ for (const reference of symbol.references) {
26867
+ if (reference.flag !== "read") return false;
26868
+ state.currentExportWasUsed = true;
26869
+ const identifier = reference.identifier;
26870
+ const invokedExpression = getInvokedExpression(identifier);
26871
+ if (invokedExpression) {
26872
+ if (!classifyInvokedExpression(module, invokedExpression, state)) return false;
26873
+ continue;
26874
+ }
26875
+ const parent = identifier.parent;
26876
+ if (isNodeOfType(parent, "ExportSpecifier") && parent.local === identifier) {
26877
+ const exportedName = getExportedSpecifierName(parent);
26878
+ if (!exportedName) return false;
26879
+ enqueueExport(state, module.filePath, exportedName);
26880
+ continue;
26881
+ }
26882
+ if (isNodeOfType(parent, "ExportDefaultDeclaration")) {
26883
+ enqueueExport(state, module.filePath, "default");
26884
+ continue;
26885
+ }
26886
+ if (isNodeOfType(parent, "VariableDeclarator") && parent.init === identifier && isNodeOfType(parent.id, "Identifier") && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const") {
26887
+ const aliasSymbol = module.scopes.symbolFor(parent.id);
26888
+ if (!aliasSymbol || !classifySymbolReferences(module, aliasSymbol, state, visitedSymbolIds)) return false;
26889
+ continue;
26890
+ }
26891
+ return false;
26892
+ }
26893
+ return true;
26894
+ };
26895
+ const classifyNamespaceImportReferences = (module, symbol, exportedName, state) => {
26896
+ for (const reference of symbol.references) {
26897
+ if (reference.flag !== "read") return false;
26898
+ const identifier = reference.identifier;
26899
+ const parent = identifier.parent;
26900
+ if (!isNodeOfType(parent, "MemberExpression") || parent.object !== identifier) return false;
26901
+ const propertyName = getStaticPropertyName(parent);
26902
+ if (propertyName === null) return false;
26903
+ if (propertyName !== exportedName) continue;
26904
+ state.currentExportWasUsed = true;
26905
+ const invokedExpression = getInvokedExpression(parent);
26906
+ if (!invokedExpression || !classifyInvokedExpression(module, invokedExpression, state)) return false;
26907
+ }
26908
+ return true;
26909
+ };
26910
+ const classifyImportsFromExport = (module, exportIdentity, state) => {
26911
+ for (const statement of module.programNode.body) {
26912
+ if (isNodeOfType(statement, "ImportDeclaration")) {
26913
+ if (state.projectIndex.resolvedSourcePathByNode.get(statement) !== exportIdentity.filePath) continue;
26914
+ if (statement.importKind === "type") continue;
26915
+ for (const specifier of statement.specifiers) {
26916
+ if (isNodeOfType(specifier, "ImportSpecifier") && specifier.importKind === "type") continue;
26917
+ if (isNodeOfType(specifier, "ImportNamespaceSpecifier")) {
26918
+ const namespaceSymbol = module.scopes.symbolFor(specifier.local);
26919
+ if (!namespaceSymbol || !classifyNamespaceImportReferences(module, namespaceSymbol, exportIdentity.exportedName, state)) return false;
26920
+ continue;
26921
+ }
26922
+ if (getImportSpecifierName(specifier) !== exportIdentity.exportedName) continue;
26923
+ const symbol = module.scopes.symbolFor(specifier.local);
26924
+ if (!symbol || !classifySymbolReferences(module, symbol, state, /* @__PURE__ */ new Set())) return false;
26925
+ }
26926
+ continue;
26927
+ }
26928
+ if ((isNodeOfType(statement, "ExportNamedDeclaration") || isNodeOfType(statement, "ExportAllDeclaration")) && statement.source && state.projectIndex.resolvedSourcePathByNode.get(statement) === exportIdentity.filePath) {
26929
+ if (isNodeOfType(statement, "ExportAllDeclaration")) {
26930
+ if (statement.exported) return false;
26931
+ state.currentExportWasUsed = true;
26932
+ enqueueExport(state, module.filePath, exportIdentity.exportedName);
26933
+ continue;
26934
+ }
26935
+ for (const specifier of statement.specifiers) {
26936
+ if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
26937
+ if (getImportedSpecifierName(specifier) !== exportIdentity.exportedName) continue;
26938
+ const exportedName = getExportedSpecifierName(specifier);
26939
+ if (!exportedName) return false;
26940
+ state.currentExportWasUsed = true;
26941
+ enqueueExport(state, module.filePath, exportedName);
26942
+ }
26943
+ }
26944
+ }
26945
+ return true;
26946
+ };
26947
+ const hasOpaqueDynamicImportOfExport = (module, exportIdentity, projectIndex) => {
26948
+ let isOpaque = false;
26949
+ walkAst(module.programNode, (node) => {
26950
+ if (isOpaque) return false;
26951
+ if (isNodeOfType(node, "ImportExpression")) {
26952
+ if (projectIndex.resolvedSourcePathByNode.get(node) === exportIdentity.filePath) {
26953
+ isOpaque = true;
26954
+ return false;
26955
+ }
26956
+ }
26957
+ if (isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "require" && node.arguments.length === 1) {
26958
+ if (projectIndex.resolvedSourcePathByNode.get(node) === exportIdentity.filePath) {
26959
+ isOpaque = true;
26960
+ return false;
26961
+ }
26962
+ }
26963
+ });
26964
+ return isOpaque;
26965
+ };
26966
+ const classifyLocalExportReferences = (module, exportIdentity, state) => {
26967
+ const exportedValue = findExportedValue(module.programNode, exportIdentity.exportedName);
26968
+ if (!exportedValue || !isFunctionLike$1(exportedValue)) return true;
26969
+ const bindingIdentifier = getDirectFunctionBindingIdentifier(exportedValue);
26970
+ if (!bindingIdentifier) return true;
26971
+ const symbol = module.scopes.symbolFor(bindingIdentifier);
26972
+ return symbol ? classifySymbolReferences(module, symbol, state, /* @__PURE__ */ new Set()) : false;
26973
+ };
26974
+ const hasOpaqueWorkspacePackageConsumer = (projectIndex, exportIdentity) => {
26975
+ const packageName = readNearestPackageManifest(exportIdentity.filePath)?.name;
26976
+ if (typeof packageName !== "string" || packageName.length === 0) return false;
26977
+ for (const unresolvedSource of projectIndex.unresolvedRuntimeSources) if (unresolvedSource === packageName || unresolvedSource.startsWith(`${packageName}/`)) return true;
26978
+ return false;
26979
+ };
26980
+ const createExportedJsxGeneratedImageOwnershipAnalyzer = (context) => {
26981
+ const filename = context.filename ? normalizeFilename(context.filename) : "";
26982
+ const rootDirectorySetting = getReactDoctorStringSetting(context.settings, "rootDirectory");
26983
+ const rootDirectory = rootDirectorySetting ? normalizeFilename(rootDirectorySetting).replace(/\/$/, "") : "";
26984
+ const isFileInsideRoot = Boolean(filename && rootDirectory) && (filename === rootDirectory || filename.startsWith(`${rootDirectory}/`));
26985
+ let projectIndex;
26986
+ return (jsxNode) => {
26987
+ if (!isFileInsideRoot) return false;
26988
+ const programNode = findProgramRoot(jsxNode);
26989
+ const enclosingFunction = findEnclosingFunction$1(jsxNode);
26990
+ if (!programNode || !enclosingFunction) return false;
26991
+ const initialExportNames = getExportNamesForFunction(programNode, enclosingFunction);
26992
+ if (initialExportNames.length === 0) return false;
26993
+ if (projectIndex === void 0) projectIndex = buildGeneratedImageProjectIndex(rootDirectory, filename, programNode, context.scopes);
26994
+ if (!projectIndex || projectIndex.hasOpaqueMdxConsumerSurface) return false;
26995
+ const state = {
26996
+ projectIndex,
26997
+ pendingExports: initialExportNames.map((exportedName) => ({
26998
+ filePath: filename,
26999
+ exportedName
27000
+ })),
27001
+ visitedExportKeys: /* @__PURE__ */ new Set(),
27002
+ currentExportWasUsed: false,
27003
+ didReachRenderer: false
27004
+ };
27005
+ while (state.pendingExports.length > 0) {
27006
+ const exportIdentity = state.pendingExports.pop();
27007
+ if (!exportIdentity) continue;
27008
+ const exportKey = `${exportIdentity.filePath}\0${exportIdentity.exportedName}`;
27009
+ if (state.visitedExportKeys.has(exportKey)) continue;
27010
+ state.visitedExportKeys.add(exportKey);
27011
+ state.currentExportWasUsed = false;
27012
+ if (hasOpaqueWorkspacePackageConsumer(projectIndex, exportIdentity)) return false;
27013
+ const ownerModule = projectIndex.modulesByFilePath.get(exportIdentity.filePath);
27014
+ if (!ownerModule || !classifyLocalExportReferences(ownerModule, exportIdentity, state)) return false;
27015
+ const consumerModules = projectIndex.consumerModulesByFilePath.get(exportIdentity.filePath) ?? [];
27016
+ for (const module of consumerModules) {
27017
+ if (module.filePath === exportIdentity.filePath) continue;
27018
+ if (hasOpaqueDynamicImportOfExport(module, exportIdentity, projectIndex)) return false;
27019
+ if (!classifyImportsFromExport(module, exportIdentity, state)) return false;
27020
+ }
27021
+ if (!state.currentExportWasUsed) return false;
27022
+ }
27023
+ return state.didReachRenderer;
27024
+ };
27025
+ };
27026
+ //#endregion
26461
27027
  //#region src/plugin/rules/nextjs/nextjs-no-img-element.ts
26462
27028
  const NON_OPTIMIZABLE_SRC_PREFIX_PATTERN = /^\s*(data:|blob:)/i;
26463
27029
  const GENERATED_URL_NAME_PATTERN = /(data|object|blob)_?url/i;
@@ -26560,9 +27126,20 @@ const nextjsNoImgElement = defineRule({
26560
27126
  recommendation: "Use `next/image` so users get optimized formats, responsive srcsets, and lazy loading instead of oversized image downloads.",
26561
27127
  create: (context) => {
26562
27128
  if (isGeneratedImageRenderContext(context)) return {};
27129
+ const generatedImageOwnershipByFunction = /* @__PURE__ */ new WeakMap();
27130
+ const isExportedJsxGeneratedImageOwned = createExportedJsxGeneratedImageOwnershipAnalyzer(context);
26563
27131
  return { JSXOpeningElement(node) {
26564
27132
  if (resolveJsxElementType(node) !== "img") return;
26565
27133
  if (isGeneratedImageRenderContext(context, node)) return;
27134
+ const enclosingFunction = findEnclosingFunction$1(node);
27135
+ if (enclosingFunction) {
27136
+ let isGeneratedImageOwned = generatedImageOwnershipByFunction.get(enclosingFunction);
27137
+ if (isGeneratedImageOwned === void 0) {
27138
+ isGeneratedImageOwned = isExportedJsxGeneratedImageOwned(node);
27139
+ generatedImageOwnershipByFunction.set(enclosingFunction, isGeneratedImageOwned);
27140
+ }
27141
+ if (isGeneratedImageOwned) return;
27142
+ }
26566
27143
  const programRoot = findProgramRoot(node);
26567
27144
  if (programRoot && hasEmailTemplateImport(programRoot)) return;
26568
27145
  const srcAttribute = findJsxAttribute(node.attributes, "src");
@@ -27908,7 +28485,11 @@ const isProvenNativeReadMethod = (ref, methodName) => Boolean(ref.resolved?.defs
27908
28485
  }));
27909
28486
  //#endregion
27910
28487
  //#region src/plugin/rules/state-and-effects/utils/effect/react.ts
27911
- const KNOWN_PURE_HOC_NAMES = new Set(["memo", "forwardRef"]);
28488
+ const KNOWN_COMPONENT_WRAPPER_NAMES = new Set([
28489
+ "memo",
28490
+ "forwardRef",
28491
+ "observer"
28492
+ ]);
27912
28493
  const startsWithUppercase = (name) => Boolean(name && name.length > 0 && name[0] >= "A" && name[0] <= "Z");
27913
28494
  const isReactFunctionalComponent = (node) => {
27914
28495
  if (!node) return false;
@@ -27930,7 +28511,7 @@ const isReactFunctionalHOC = (analysis, node) => {
27930
28511
  const isWrappedInline = () => {
27931
28512
  if (!isNodeOfType(init, "CallExpression")) return false;
27932
28513
  if (!isNodeOfType(init.callee, "Identifier")) return false;
27933
- if (KNOWN_PURE_HOC_NAMES.has(init.callee.name)) return false;
28514
+ if (KNOWN_COMPONENT_WRAPPER_NAMES.has(init.callee.name)) return false;
27934
28515
  const firstArg = init.arguments?.[0];
27935
28516
  if (!firstArg) return false;
27936
28517
  return isNodeOfType(firstArg, "ArrowFunctionExpression") || isNodeOfType(firstArg, "FunctionExpression");
@@ -27950,7 +28531,7 @@ const isReactFunctionalHOC = (analysis, node) => {
27950
28531
  if (!args.includes(refId)) continue;
27951
28532
  const callee = parent.callee;
27952
28533
  const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : isNodeOfType(callee, "CallExpression") && isNodeOfType(callee.callee, "Identifier") ? callee.callee.name : null;
27953
- if (calleeName != null && !KNOWN_PURE_HOC_NAMES.has(calleeName)) return true;
28534
+ if (calleeName != null && !KNOWN_COMPONENT_WRAPPER_NAMES.has(calleeName)) return true;
27954
28535
  }
27955
28536
  return false;
27956
28537
  };
@@ -29812,18 +30393,143 @@ const ARITHMETIC_KEY_OPERATORS = new Set([
29812
30393
  "%"
29813
30394
  ]);
29814
30395
  const bindingMutationResultsByProgram = /* @__PURE__ */ new WeakMap();
29815
- const extractCandidateIdentifiers = (expression) => {
30396
+ const UNKNOWN_STATIC_KEY_BRANCH_VALUE = {
30397
+ isNullish: null,
30398
+ isTruthy: null
30399
+ };
30400
+ const mergeStaticKeyBranchValues = (firstValue, secondValue) => ({
30401
+ isNullish: firstValue.isNullish === secondValue.isNullish ? firstValue.isNullish : null,
30402
+ isTruthy: firstValue.isTruthy === secondValue.isTruthy ? firstValue.isTruthy : null
30403
+ });
30404
+ const readStaticKeyBranchValue = (expression, depth) => {
30405
+ if (depth > TYPE_RESOLUTION_DEPTH_LIMIT) return null;
30406
+ const node = stripParenExpression(expression);
30407
+ if (isNodeOfType(node, "Literal")) return {
30408
+ isNullish: node.value === null,
30409
+ isTruthy: Boolean(node.value)
30410
+ };
30411
+ if (isNodeOfType(node, "ArrayExpression") || isNodeOfType(node, "ObjectExpression") || isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ClassExpression") || isNodeOfType(node, "NewExpression")) return {
30412
+ isNullish: false,
30413
+ isTruthy: true
30414
+ };
30415
+ if (isNodeOfType(node, "TemplateLiteral")) return {
30416
+ isNullish: false,
30417
+ isTruthy: (node.quasis ?? []).some((quasi) => typeof quasi.value?.cooked === "string" && quasi.value.cooked.length > 0) ? true : null
30418
+ };
30419
+ if (isNodeOfType(node, "BinaryExpression")) return {
30420
+ isNullish: false,
30421
+ isTruthy: null
30422
+ };
30423
+ if (isNodeOfType(node, "Identifier")) {
30424
+ const binding = findVariableInitializer(node, node.name);
30425
+ if (!binding) {
30426
+ if (node.name === "undefined") return {
30427
+ isNullish: true,
30428
+ isTruthy: false
30429
+ };
30430
+ if (node.name === "NaN") return {
30431
+ isNullish: false,
30432
+ isTruthy: false
30433
+ };
30434
+ if (node.name === "Infinity") return {
30435
+ isNullish: false,
30436
+ isTruthy: true
30437
+ };
30438
+ return null;
30439
+ }
30440
+ if (!isConstDeclaredBinding(binding) || !binding.initializer) return null;
30441
+ const declarator = binding.bindingIdentifier.parent;
30442
+ if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || declarator.id !== binding.bindingIdentifier || declarator.init !== binding.initializer) return null;
30443
+ return readStaticKeyBranchValue(binding.initializer, depth + 1);
30444
+ }
30445
+ if (isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier")) {
30446
+ if (!Boolean(findVariableInitializer(node.callee, node.callee.name)) && STRING_COERCION_FUNCTIONS.has(node.callee.name)) return {
30447
+ isNullish: false,
30448
+ isTruthy: null
30449
+ };
30450
+ }
30451
+ if (isNodeOfType(node, "UnaryExpression")) {
30452
+ if (node.operator === "void") return {
30453
+ isNullish: true,
30454
+ isTruthy: false
30455
+ };
30456
+ if (node.operator === "typeof") return {
30457
+ isNullish: false,
30458
+ isTruthy: true
30459
+ };
30460
+ if (node.operator === "+" || node.operator === "-" || node.operator === "~") return {
30461
+ isNullish: false,
30462
+ isTruthy: null
30463
+ };
30464
+ if (node.operator !== "!") return null;
30465
+ const argumentValue = readStaticKeyBranchValue(node.argument, depth + 1);
30466
+ if (!argumentValue || argumentValue.isTruthy === null) return null;
30467
+ return {
30468
+ isNullish: false,
30469
+ isTruthy: !argumentValue.isTruthy
30470
+ };
30471
+ }
30472
+ if (isNodeOfType(node, "SequenceExpression")) {
30473
+ const finalExpression = node.expressions.at(-1);
30474
+ return finalExpression ? readStaticKeyBranchValue(finalExpression, depth + 1) : null;
30475
+ }
30476
+ if (isNodeOfType(node, "LogicalExpression")) {
30477
+ const leftValue = readStaticKeyBranchValue(node.left, depth + 1) ?? UNKNOWN_STATIC_KEY_BRANCH_VALUE;
30478
+ if (node.operator === "&&" && leftValue.isTruthy !== null) return leftValue.isTruthy ? readStaticKeyBranchValue(node.right, depth + 1) : leftValue;
30479
+ if (node.operator === "||" && leftValue.isTruthy !== null) return leftValue.isTruthy ? leftValue : readStaticKeyBranchValue(node.right, depth + 1);
30480
+ if (node.operator === "??" && leftValue.isNullish !== null) return leftValue.isNullish ? readStaticKeyBranchValue(node.right, depth + 1) : leftValue;
30481
+ const rightValue = readStaticKeyBranchValue(node.right, depth + 1) ?? UNKNOWN_STATIC_KEY_BRANCH_VALUE;
30482
+ if (node.operator === "||") return {
30483
+ isNullish: rightValue.isNullish === false ? false : null,
30484
+ isTruthy: rightValue.isTruthy === true ? true : null
30485
+ };
30486
+ if (node.operator === "&&") return {
30487
+ isNullish: leftValue.isNullish === false && rightValue.isNullish === false ? false : null,
30488
+ isTruthy: rightValue.isTruthy === false ? false : null
30489
+ };
30490
+ return {
30491
+ isNullish: rightValue.isNullish === false ? false : null,
30492
+ isTruthy: leftValue.isTruthy !== null && leftValue.isTruthy === rightValue.isTruthy ? leftValue.isTruthy : null
30493
+ };
30494
+ }
30495
+ if (isNodeOfType(node, "ConditionalExpression")) {
30496
+ const testValue = readStaticKeyBranchValue(node.test, depth + 1);
30497
+ if (testValue?.isTruthy !== null && testValue?.isTruthy !== void 0) return readStaticKeyBranchValue(testValue.isTruthy ? node.consequent : node.alternate, depth + 1);
30498
+ return mergeStaticKeyBranchValues(readStaticKeyBranchValue(node.consequent, depth + 1) ?? UNKNOWN_STATIC_KEY_BRANCH_VALUE, readStaticKeyBranchValue(node.alternate, depth + 1) ?? UNKNOWN_STATIC_KEY_BRANCH_VALUE);
30499
+ }
30500
+ return null;
30501
+ };
30502
+ const extractCandidateIdentifiers = (expression, depth = 0) => {
30503
+ if (depth > TYPE_RESOLUTION_DEPTH_LIMIT) return [];
29816
30504
  const node = stripParenExpression(expression);
29817
30505
  if (isNodeOfType(node, "Identifier")) return [node];
29818
- if (isNodeOfType(node, "UnaryExpression") && (node.operator === "-" || node.operator === "+" || node.operator === "~") && isNodeOfType(node.argument, "Identifier")) return [node.argument];
30506
+ if (isNodeOfType(node, "UnaryExpression") && (node.operator === "-" || node.operator === "+" || node.operator === "~") && node.argument) return extractCandidateIdentifiers(node.argument, depth + 1);
29819
30507
  if (isNodeOfType(node, "TemplateLiteral")) {
29820
30508
  const identifiers = [];
29821
- for (const templateExpression of node.expressions ?? []) if (isNodeOfType(templateExpression, "Identifier")) identifiers.push(templateExpression);
30509
+ for (const templateExpression of node.expressions ?? []) identifiers.push(...extractCandidateIdentifiers(templateExpression, depth + 1));
29822
30510
  return identifiers;
29823
30511
  }
30512
+ if (isNodeOfType(node, "LogicalExpression")) {
30513
+ const leftValue = readStaticKeyBranchValue(node.left, 0);
30514
+ if (leftValue) {
30515
+ if (node.operator === "&&" && leftValue.isTruthy !== null) return extractCandidateIdentifiers(leftValue.isTruthy ? node.right : node.left, depth + 1);
30516
+ if (node.operator === "||" && leftValue.isTruthy !== null) return extractCandidateIdentifiers(leftValue.isTruthy ? node.left : node.right, depth + 1);
30517
+ if (node.operator === "??" && leftValue.isNullish !== null) return extractCandidateIdentifiers(leftValue.isNullish ? node.right : node.left, depth + 1);
30518
+ }
30519
+ return [...extractCandidateIdentifiers(node.left, depth + 1), ...extractCandidateIdentifiers(node.right, depth + 1)];
30520
+ }
30521
+ if (isNodeOfType(node, "ConditionalExpression")) {
30522
+ const testValue = readStaticKeyBranchValue(node.test, 0);
30523
+ if (testValue && testValue.isTruthy !== null) return extractCandidateIdentifiers(testValue.isTruthy ? node.consequent : node.alternate, depth + 1);
30524
+ return [...extractCandidateIdentifiers(node.consequent, depth + 1), ...extractCandidateIdentifiers(node.alternate, depth + 1)];
30525
+ }
30526
+ if (isNodeOfType(node, "SequenceExpression")) {
30527
+ const finalExpression = node.expressions.at(-1);
30528
+ return finalExpression ? extractCandidateIdentifiers(finalExpression, depth + 1) : [];
30529
+ }
29824
30530
  if (isNodeOfType(node, "CallExpression")) {
29825
- if (isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.object, "Identifier") && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "toString") return [node.callee.object];
29826
- if (isNodeOfType(node.callee, "Identifier") && STRING_COERCION_FUNCTIONS.has(node.callee.name) && isNodeOfType(node.arguments?.[0], "Identifier")) return [node.arguments[0]];
30531
+ if (isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "toString") return extractCandidateIdentifiers(node.callee.object, depth + 1);
30532
+ if (isNodeOfType(node.callee, "Identifier") && STRING_COERCION_FUNCTIONS.has(node.callee.name) && !findVariableInitializer(node.callee, node.callee.name) && node.arguments?.[0]) return extractCandidateIdentifiers(node.arguments[0], depth + 1);
29827
30533
  return [];
29828
30534
  }
29829
30535
  if (isNodeOfType(node, "BinaryExpression")) {
@@ -30877,72 +31583,6 @@ const isReactNativeDependencyName = (dependencyName) => {
30877
31583
  return false;
30878
31584
  };
30879
31585
  //#endregion
30880
- //#region src/plugin/utils/read-nearest-package-manifest.ts
30881
- const cachedPackageDirectoryByFilename = /* @__PURE__ */ new Map();
30882
- const cachedManifestByPackageDirectory = /* @__PURE__ */ new Map();
30883
- const resetManifestCaches = () => {
30884
- cachedPackageDirectoryByFilename.clear();
30885
- cachedManifestByPackageDirectory.clear();
30886
- };
30887
- const findNearestPackageDirectory = (filename) => {
30888
- if (!filename) return null;
30889
- const fromCache = cachedPackageDirectoryByFilename.get(filename);
30890
- if (fromCache !== void 0) {
30891
- if (isProbeRecorderActive()) {
30892
- let probedDirectory = path.dirname(filename);
30893
- while (true) {
30894
- recordExistenceProbe(path.join(probedDirectory, "package.json"));
30895
- if (probedDirectory === fromCache) break;
30896
- const parentDirectory = path.dirname(probedDirectory);
30897
- if (parentDirectory === probedDirectory) break;
30898
- probedDirectory = parentDirectory;
30899
- }
30900
- }
30901
- return fromCache;
30902
- }
30903
- let currentDirectory = path.dirname(filename);
30904
- while (true) {
30905
- const candidatePackageJsonPath = path.join(currentDirectory, "package.json");
30906
- recordExistenceProbe(candidatePackageJsonPath);
30907
- let hasPackageJson = false;
30908
- try {
30909
- hasPackageJson = fs.statSync(candidatePackageJsonPath).isFile();
30910
- } catch {
30911
- hasPackageJson = false;
30912
- }
30913
- if (hasPackageJson) {
30914
- cachedPackageDirectoryByFilename.set(filename, currentDirectory);
30915
- return currentDirectory;
30916
- }
30917
- const parentDirectory = path.dirname(currentDirectory);
30918
- if (parentDirectory === currentDirectory) {
30919
- cachedPackageDirectoryByFilename.set(filename, null);
30920
- return null;
30921
- }
30922
- currentDirectory = parentDirectory;
30923
- }
30924
- };
30925
- const readNearestPackageManifest = (filename) => {
30926
- const packageDirectory = findNearestPackageDirectory(filename);
30927
- if (!packageDirectory) return null;
30928
- return readPackageManifest(packageDirectory);
30929
- };
30930
- const readPackageManifest = (packageDirectory) => {
30931
- const packageJsonPath = path.join(packageDirectory, "package.json");
30932
- recordContentProbe(packageJsonPath);
30933
- const cached = cachedManifestByPackageDirectory.get(packageDirectory);
30934
- if (cached !== void 0) return cached;
30935
- let manifest = null;
30936
- try {
30937
- const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
30938
- if (typeof parsed === "object" && parsed !== null) manifest = parsed;
30939
- } catch {
30940
- manifest = null;
30941
- }
30942
- cachedManifestByPackageDirectory.set(packageDirectory, manifest);
30943
- return manifest;
30944
- };
30945
- //#endregion
30946
31586
  //#region src/plugin/utils/classify-package-platform.ts
30947
31587
  const WEB_FRAMEWORK_DEPENDENCY_NAMES = new Set([
30948
31588
  "next",
@@ -31976,6 +32616,7 @@ const createStateTriggerReachability = ({ analysis, context, effectFunction }) =
31976
32616
  };
31977
32617
  //#endregion
31978
32618
  //#region src/plugin/rules/state-and-effects/no-chain-state-updates.ts
32619
+ const APPLICABLE_EFFECT_HOOK_NAMES = new Set(["useEffect", "useLayoutEffect"]);
31979
32620
  const getUseStateDeclarator = (ref) => (ref.resolved?.defs ?? []).map((def) => def.node).find((node) => isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "ArrayPattern")) ?? null;
31980
32621
  const isDeclaredWithin = (node, container) => {
31981
32622
  let walker = node;
@@ -31994,6 +32635,24 @@ const isBuiltinNamespaceCallee = (callee) => {
31994
32635
  }
31995
32636
  return false;
31996
32637
  };
32638
+ const getReactUseCallbackSource = (reference, context) => {
32639
+ const identifier = reference.identifier;
32640
+ const symbol = resolveConstIdentifierAlias(identifier, context.scopes);
32641
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer) return null;
32642
+ const initializer = stripParenExpression(symbol.initializer);
32643
+ if (isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, "useCallback", context.scopes, {
32644
+ allowGlobalReactNamespace: true,
32645
+ resolveNamedAliases: true
32646
+ })) return initializer;
32647
+ return null;
32648
+ };
32649
+ const getDependencyStateRefs = (analysis, context, dependencyReference) => {
32650
+ const useCallbackCall = getReactUseCallbackSource(dependencyReference, context);
32651
+ if (!useCallbackCall) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
32652
+ const dependencyList = useCallbackCall.arguments?.[1];
32653
+ if (!dependencyList || !isNodeOfType(dependencyList, "ArrayExpression")) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
32654
+ return getDownstreamRefs(analysis, dependencyList).flatMap((reference) => getUpstreamRefs(analysis, reference)).filter((reference) => isState(analysis, reference));
32655
+ };
31997
32656
  const isSimpleExpression$1 = (analysis, expression, effectFn, visitedDeclarators) => {
31998
32657
  let isSimple = true;
31999
32658
  walkAst(expression, (child) => {
@@ -32027,7 +32686,12 @@ const noChainStateUpdates = defineRule({
32027
32686
  tags: ["test-noise"],
32028
32687
  recommendation: "Set all the related state together in the event handler that starts it, instead of having one useEffect react to a state change and set more state. See https://react.dev/learn/you-might-not-need-an-effect#chains-of-computations",
32029
32688
  create: (context) => ({ CallExpression(node) {
32030
- if (!isUseEffect(node)) return;
32689
+ if (!isReactApiCall(node, APPLICABLE_EFFECT_HOOK_NAMES, context.scopes, {
32690
+ allowGlobalReactNamespace: true,
32691
+ allowUnboundBareCalls: true,
32692
+ resolveConditionalAliases: true,
32693
+ resolveNamedAliases: true
32694
+ })) return;
32031
32695
  const analysis = getProgramAnalysis(node);
32032
32696
  if (!analysis) return;
32033
32697
  if (hasCleanup(analysis, node)) return;
@@ -32036,7 +32700,7 @@ const noChainStateUpdates = defineRule({
32036
32700
  if (!effectFnRefs || !depsRefs) return;
32037
32701
  const effectFn = getEffectFn(analysis, node);
32038
32702
  if (!effectFn) return;
32039
- const stateDeps = depsRefs.flatMap((ref) => getUpstreamRefs(analysis, ref)).filter((ref) => isState(analysis, ref));
32703
+ const stateDeps = depsRefs.flatMap((reference) => getDependencyStateRefs(analysis, context, reference));
32040
32704
  if (stateDeps.length === 0) return;
32041
32705
  if (stateDeps.every((ref) => isExternallyDrivenState(analysis, ref))) return;
32042
32706
  const stateDepDeclarators = new Set(stateDeps.map((ref) => getUseStateDeclarator(ref)).filter((declarator) => declarator !== null));
@@ -32295,16 +32959,7 @@ const isProvenIntrinsicJsxElement = (openingElement, scopes) => {
32295
32959
  return isIntrinsicValue(openingElement.name);
32296
32960
  };
32297
32961
  //#endregion
32298
- //#region src/plugin/rules/react-builtins/is-safe-create-ref-callback-current-write.ts
32299
- const pathStartsWith$1 = (propertyPath, prefix) => prefix.every((propertyName, index) => propertyPath[index] === propertyName);
32300
- const collectMemberExpression = (identifier) => {
32301
- let expression = findTransparentExpressionRoot(identifier);
32302
- while (expression.parent && isNodeOfType(expression.parent, "MemberExpression") && expression.parent.object === expression) {
32303
- if (!getStaticPropertyName(expression.parent)) return null;
32304
- expression = findTransparentExpressionRoot(expression.parent);
32305
- }
32306
- return expression;
32307
- };
32962
+ //#region src/plugin/utils/is-inline-intrinsic-ref-callback.ts
32308
32963
  const isInlineIntrinsicRefCallback = (functionNode, scopes) => {
32309
32964
  const functionExpression = findTransparentExpressionRoot(functionNode);
32310
32965
  if (!isFunctionLike$1(functionExpression) || functionExpression.async || functionExpression.generator) return false;
@@ -32315,6 +32970,17 @@ const isInlineIntrinsicRefCallback = (functionNode, scopes) => {
32315
32970
  const openingElement = attribute.parent;
32316
32971
  return Boolean(openingElement && isNodeOfType(openingElement, "JSXOpeningElement") && isProvenIntrinsicJsxElement(openingElement, scopes));
32317
32972
  };
32973
+ //#endregion
32974
+ //#region src/plugin/rules/react-builtins/is-safe-create-ref-callback-current-write.ts
32975
+ const pathStartsWith$1 = (propertyPath, prefix) => prefix.every((propertyName, index) => propertyPath[index] === propertyName);
32976
+ const collectMemberExpression = (identifier) => {
32977
+ let expression = findTransparentExpressionRoot(identifier);
32978
+ while (expression.parent && isNodeOfType(expression.parent, "MemberExpression") && expression.parent.object === expression) {
32979
+ if (!getStaticPropertyName(expression.parent)) return null;
32980
+ expression = findTransparentExpressionRoot(expression.parent);
32981
+ }
32982
+ return expression;
32983
+ };
32318
32984
  const isSafeCreateRefCallbackCurrentWrite = (referenceNode, accessedPropertyPath, targetPropertyPath, scopes) => {
32319
32985
  if (accessedPropertyPath.length !== targetPropertyPath.length + 1 || !pathStartsWith$1(accessedPropertyPath, targetPropertyPath) || accessedPropertyPath[targetPropertyPath.length] !== "current") return false;
32320
32986
  const memberExpression = collectMemberExpression(referenceNode);
@@ -35258,17 +35924,178 @@ const visitSynchronousFunctionBodies = (analysisFunctions, visitor) => {
35258
35924
  walkInsideStatementBlocks(analysisFunction.body, visitor);
35259
35925
  }
35260
35926
  };
35261
- const collectWrittenStateNamesInEffect = (analysisFunctions, setterToStateName) => {
35262
- const writtenStateNames = /* @__PURE__ */ new Set();
35927
+ const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
35928
+ const unwrappedExpression = stripParenExpression(expression);
35929
+ if (isNodeOfType(unwrappedExpression, "Literal")) {
35930
+ const literalValue = unwrappedExpression.value;
35931
+ if (literalValue === null || typeof literalValue === "boolean" || typeof literalValue === "number" || typeof literalValue === "string") return { value: literalValue };
35932
+ return null;
35933
+ }
35934
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
35935
+ if (scopes.symbolFor(unwrappedExpression)?.id === stateSymbolId) return stateValue;
35936
+ if (unwrappedExpression.name === "undefined" && scopes.isGlobalReference(unwrappedExpression)) return { value: void 0 };
35937
+ const immutableSymbol = scopes.symbolFor(unwrappedExpression);
35938
+ if (immutableSymbol?.kind !== "const" || !immutableSymbol.initializer || !isNodeOfType(immutableSymbol.declarationNode, "VariableDeclarator") || immutableSymbol.declarationNode.id !== immutableSymbol.bindingIdentifier || immutableSymbol.declarationNode.init !== immutableSymbol.initializer || immutableSymbol.references.some((reference) => reference.flag !== "read") || visitedSymbolIds.has(immutableSymbol.id)) return null;
35939
+ return readStaticEffectValue(immutableSymbol.initializer, scopes, stateSymbolId, stateValue, new Set(visitedSymbolIds).add(immutableSymbol.id));
35940
+ }
35941
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression")) {
35942
+ if (unwrappedExpression.operator === "void") return { value: void 0 };
35943
+ if (unwrappedExpression.operator !== "!") return null;
35944
+ const argumentValue = readStaticEffectValue(unwrappedExpression.argument, scopes, stateSymbolId, stateValue, visitedSymbolIds);
35945
+ return argumentValue ? { value: !argumentValue.value } : null;
35946
+ }
35947
+ if (isNodeOfType(unwrappedExpression, "CallExpression")) {
35948
+ if (isNodeOfType(unwrappedExpression.callee, "Identifier") && unwrappedExpression.callee.name === "Boolean" && scopes.isGlobalReference(unwrappedExpression.callee) && unwrappedExpression.arguments.length === 1 && unwrappedExpression.arguments[0] && !isNodeOfType(unwrappedExpression.arguments[0], "SpreadElement")) {
35949
+ const argumentValue = readStaticEffectValue(unwrappedExpression.arguments[0], scopes, stateSymbolId, stateValue, visitedSymbolIds);
35950
+ return argumentValue ? { value: Boolean(argumentValue.value) } : null;
35951
+ }
35952
+ return null;
35953
+ }
35954
+ if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
35955
+ const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
35956
+ if (!leftValue) return null;
35957
+ if (unwrappedExpression.operator === "&&" && !leftValue.value) return leftValue;
35958
+ if (unwrappedExpression.operator === "||" && leftValue.value) return leftValue;
35959
+ if (unwrappedExpression.operator === "??" && leftValue.value !== null && leftValue.value !== void 0) return leftValue;
35960
+ return readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
35961
+ }
35962
+ if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
35963
+ const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds);
35964
+ if (!testValue) return null;
35965
+ return readStaticEffectValue(testValue.value ? unwrappedExpression.consequent : unwrappedExpression.alternate, scopes, stateSymbolId, stateValue, visitedSymbolIds);
35966
+ }
35967
+ if (isNodeOfType(unwrappedExpression, "MemberExpression") && unwrappedExpression.optional) {
35968
+ const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds);
35969
+ if (objectValue?.value === null || objectValue?.value === void 0) return { value: void 0 };
35970
+ return null;
35971
+ }
35972
+ if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
35973
+ const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
35974
+ const rightValue = readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
35975
+ if (!leftValue || !rightValue) return null;
35976
+ if (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==") {
35977
+ const areEqual = leftValue.value === rightValue.value;
35978
+ return { value: unwrappedExpression.operator === "===" ? areEqual : !areEqual };
35979
+ }
35980
+ if (unwrappedExpression.operator === "==" || unwrappedExpression.operator === "!=") {
35981
+ const isLeftNullish = leftValue.value === null || leftValue.value === void 0;
35982
+ const isRightNullish = rightValue.value === null || rightValue.value === void 0;
35983
+ if (!isLeftNullish && !isRightNullish && typeof leftValue.value !== typeof rightValue.value) return null;
35984
+ const areEqual = isLeftNullish || isRightNullish ? isLeftNullish && isRightNullish : leftValue.value === rightValue.value;
35985
+ return { value: unwrappedExpression.operator === "==" ? areEqual : !areEqual };
35986
+ }
35987
+ }
35988
+ return null;
35989
+ };
35990
+ const readStaticUpdaterReturnValue = (updater, scopes) => {
35991
+ if (!isFunctionLike$1(updater) || updater.async || updater.generator) return null;
35992
+ if (!isNodeOfType(updater.body, "BlockStatement")) return readStaticEffectValue(updater.body, scopes, null, null);
35993
+ if (updater.body.body.length === 0) return { value: void 0 };
35994
+ if (updater.body.body.length !== 1) return null;
35995
+ const returnStatement = updater.body.body[0];
35996
+ if (!isNodeOfType(returnStatement, "ReturnStatement")) return null;
35997
+ if (!returnStatement.argument) return { value: void 0 };
35998
+ return readStaticEffectValue(returnStatement.argument, scopes, null, null);
35999
+ };
36000
+ const readStaticSetterValue = (setterCall, scopes) => {
36001
+ const argument = setterCall.arguments[0];
36002
+ if (!argument) return { value: void 0 };
36003
+ if (isNodeOfType(argument, "SpreadElement")) return null;
36004
+ const updater = resolveExactLocalFunction(argument, scopes);
36005
+ if (updater) return readStaticUpdaterReturnValue(updater, scopes);
36006
+ return readStaticEffectValue(argument, scopes, null, null);
36007
+ };
36008
+ const collectStateWritesInEffect = (analysisFunctions, setterToStateName, scopes) => {
36009
+ const stateWrites = /* @__PURE__ */ new Map();
35263
36010
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
35264
36011
  if (!isNodeOfType(child, "CallExpression")) return;
35265
36012
  if (!isNodeOfType(child.callee, "Identifier")) return;
35266
36013
  const stateName = setterToStateName.get(child.callee.name);
35267
- if (stateName) writtenStateNames.add(stateName);
36014
+ if (!stateName) return;
36015
+ const writeInfo = stateWrites.get(stateName) ?? {
36016
+ values: /* @__PURE__ */ new Set(),
36017
+ hasUnknownValue: false
36018
+ };
36019
+ const staticValue = readStaticSetterValue(child, scopes);
36020
+ if (staticValue) writeInfo.values.add(staticValue.value);
36021
+ else writeInfo.hasUnknownValue = true;
36022
+ stateWrites.set(stateName, writeInfo);
35268
36023
  });
35269
- return writtenStateNames;
36024
+ return stateWrites;
36025
+ };
36026
+ const isGlobalBooleanCall = (node, scopes) => {
36027
+ return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "Boolean" && scopes.isGlobalReference(node.callee);
36028
+ };
36029
+ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes) => {
36030
+ let currentNode = workNode;
36031
+ while (currentNode.parent) {
36032
+ const parentNode = currentNode.parent;
36033
+ if (isFunctionLike$1(parentNode)) break;
36034
+ if (isNodeOfType(parentNode, "IfStatement")) {
36035
+ const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
36036
+ if (testValue) {
36037
+ if (currentNode === parentNode.consequent && !testValue.value) return false;
36038
+ if (currentNode === parentNode.alternate && testValue.value) return false;
36039
+ }
36040
+ }
36041
+ if (isNodeOfType(parentNode, "ConditionalExpression")) {
36042
+ const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
36043
+ if (testValue) {
36044
+ if (currentNode === parentNode.consequent && !testValue.value) return false;
36045
+ if (currentNode === parentNode.alternate && testValue.value) return false;
36046
+ }
36047
+ }
36048
+ if (isNodeOfType(parentNode, "LogicalExpression") && currentNode === parentNode.right) {
36049
+ const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue);
36050
+ if (leftValue) {
36051
+ if (parentNode.operator === "&&" && !leftValue.value) return false;
36052
+ if (parentNode.operator === "||" && leftValue.value) return false;
36053
+ if (parentNode.operator === "??" && leftValue.value !== null && leftValue.value !== void 0) return false;
36054
+ }
36055
+ }
36056
+ if (isNodeOfType(parentNode, "BlockStatement")) {
36057
+ const statementIndex = parentNode.body.findIndex((statement) => statement === currentNode);
36058
+ if (statementIndex >= 0) for (let index = 0; index < statementIndex; index += 1) {
36059
+ const earlierStatement = parentNode.body[index];
36060
+ if (!isNodeOfType(earlierStatement, "IfStatement") || earlierStatement.alternate || !statementAlwaysExits(earlierStatement.consequent)) continue;
36061
+ if (readStaticEffectValue(earlierStatement.test, scopes, stateSymbolId, stateValue)?.value) return false;
36062
+ }
36063
+ }
36064
+ currentNode = parentNode;
36065
+ }
36066
+ return true;
36067
+ };
36068
+ const isReaderWorkNode = (node, analysisFunctions, scopes) => {
36069
+ if (isNodeOfType(node, "CallExpression")) {
36070
+ if (isGlobalBooleanCall(node, scopes)) return false;
36071
+ const invokedFunction = resolveExactLocalFunction(node.callee, scopes);
36072
+ return !invokedFunction || !analysisFunctions.has(invokedFunction);
36073
+ }
36074
+ return isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "TaggedTemplateExpression") || isNodeOfType(node, "ThrowStatement") || isNodeOfType(node, "UnaryExpression") && node.operator === "delete";
36075
+ };
36076
+ const canStateWriteReachReaderWork = (writeInfo, readerEffect, stateSymbolId, scopes) => {
36077
+ if (writeInfo.hasUnknownValue || stateSymbolId === null) return true;
36078
+ for (const writtenValue of writeInfo.values) {
36079
+ const stateValue = { value: writtenValue };
36080
+ let didFindReachableWork = false;
36081
+ visitSynchronousFunctionBodies(readerEffect.analysisFunctions, (child) => {
36082
+ if (didFindReachableWork || !isReaderWorkNode(child, readerEffect.analysisFunctions, scopes)) return;
36083
+ if (isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes)) didFindReachableWork = true;
36084
+ });
36085
+ if (didFindReachableWork) return true;
36086
+ }
36087
+ return false;
35270
36088
  };
35271
36089
  const EMPTY_CLEANUP_NAME_SET = /* @__PURE__ */ new Set();
36090
+ const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
36091
+ "clear",
36092
+ "delete",
36093
+ "entries",
36094
+ "get",
36095
+ "has",
36096
+ "keys",
36097
+ "values"
36098
+ ]);
35272
36099
  const isFunctionShapedReturn = (returnedValue, setterToStateName, isExplicitReturnStatement) => {
35273
36100
  if (isNodeOfType(returnedValue, "ArrowFunctionExpression") || isNodeOfType(returnedValue, "FunctionExpression")) return true;
35274
36101
  if (isNodeOfType(returnedValue, "CallExpression")) {
@@ -35319,27 +36146,135 @@ const callsOpaqueExternalSetter = (analysisFunctions, setterToStateName) => {
35319
36146
  });
35320
36147
  return didFindOpaqueSetterCall;
35321
36148
  };
36149
+ const isReactRefCall = (expression, scopes) => isNodeOfType(expression, "CallExpression") && (isReactApiCall(expression, "useRef", scopes, {
36150
+ allowGlobalReactNamespace: true,
36151
+ allowUnboundBareCalls: true,
36152
+ resolveNamedAliases: true
36153
+ }) || isReactApiCall(expression, "createRef", scopes, {
36154
+ allowGlobalReactNamespace: true,
36155
+ allowUnboundBareCalls: true,
36156
+ resolveNamedAliases: true
36157
+ }));
36158
+ const getDirectReactRefSymbol = (rawExpression, scopes) => {
36159
+ const expression = stripParenExpression(rawExpression);
36160
+ if (!isNodeOfType(expression, "Identifier")) return null;
36161
+ const symbol = scopes.symbolFor(expression);
36162
+ if (!symbol) return null;
36163
+ const initializer = getDirectUnreassignedInitializer(symbol);
36164
+ return initializer && isReactRefCall(stripParenExpression(initializer), scopes) ? symbol : null;
36165
+ };
36166
+ const isReactNativeJsxElement = (openingElement, scopes) => {
36167
+ if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
36168
+ const symbol = scopes.symbolFor(openingElement.name);
36169
+ const importDeclaration = symbol?.declarationNode.parent;
36170
+ return Boolean(symbol?.kind === "import" && importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "react-native");
36171
+ };
36172
+ const isDirectHostJsxRef = (symbol, scopes) => {
36173
+ let hostRefCount = 0;
36174
+ for (const reference of symbol.references) {
36175
+ const expression = findTransparentExpressionRoot(reference.identifier);
36176
+ const container = expression.parent;
36177
+ if (isNodeOfType(container, "MemberExpression") && container.object === expression && getStaticPropertyName(container) === "current") continue;
36178
+ if (!container || !isNodeOfType(container, "JSXExpressionContainer") || container.expression !== expression) return false;
36179
+ const attribute = container.parent;
36180
+ if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
36181
+ const openingElement = attribute.parent;
36182
+ if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement") || !isProvenIntrinsicJsxElement(openingElement, scopes) && !isReactNativeJsxElement(openingElement, scopes)) return false;
36183
+ hostRefCount += 1;
36184
+ }
36185
+ return hostRefCount > 0;
36186
+ };
36187
+ const isIntrinsicRefCallbackParameter = (expression, scopes) => {
36188
+ const identifier = stripParenExpression(expression);
36189
+ if (!isNodeOfType(identifier, "Identifier")) return false;
36190
+ const callback = findEnclosingFunction$1(identifier);
36191
+ if (!callback || !isFunctionLike$1(callback) || !isInlineIntrinsicRefCallback(callback, scopes)) return false;
36192
+ const rawFirstParameter = callback.params?.[0];
36193
+ const firstParameter = isNodeOfType(rawFirstParameter, "AssignmentPattern") ? rawFirstParameter.left : rawFirstParameter;
36194
+ const symbol = scopes.symbolFor(identifier);
36195
+ return Boolean(firstParameter && symbol?.bindingIdentifier === firstParameter);
36196
+ };
36197
+ const getDirectReactRefCall = (symbol, scopes) => {
36198
+ const initializer = getDirectUnreassignedInitializer(symbol);
36199
+ if (!initializer) return null;
36200
+ const expression = stripParenExpression(initializer);
36201
+ return isNodeOfType(expression, "CallExpression") && isReactRefCall(expression, scopes) ? expression : null;
36202
+ };
36203
+ const storesOnlyIntrinsicRefCallbackValues = (symbol, scopes) => {
36204
+ const initialValue = getDirectReactRefCall(symbol, scopes)?.arguments?.[0];
36205
+ if (!initialValue || !isNodeOfType(initialValue, "NewExpression") || !isNodeOfType(initialValue.callee, "Identifier") || initialValue.callee.name !== "Map" || !scopes.isGlobalReference(initialValue.callee) || initialValue.arguments.length !== 0) return false;
36206
+ let intrinsicValueWriteCount = 0;
36207
+ for (const reference of symbol.references) {
36208
+ const identifier = findTransparentExpressionRoot(reference.identifier);
36209
+ const currentMember = identifier.parent;
36210
+ if (!isNodeOfType(currentMember, "MemberExpression") || currentMember.object !== identifier || getStaticPropertyName(currentMember) !== "current") return false;
36211
+ const currentExpression = findTransparentExpressionRoot(currentMember);
36212
+ const methodMember = currentExpression.parent;
36213
+ if (!isNodeOfType(methodMember, "MemberExpression") || methodMember.object !== currentExpression) return false;
36214
+ const methodName = getStaticPropertyName(methodMember);
36215
+ if (methodName === "size") continue;
36216
+ const call = methodMember.parent;
36217
+ if (!isNodeOfType(call, "CallExpression") || call.callee !== methodMember) return false;
36218
+ if (methodName && NON_CONTAMINATING_MAP_METHOD_NAMES.has(methodName)) continue;
36219
+ if (methodName !== "set") return false;
36220
+ const storedValue = call.arguments[1];
36221
+ if (!storedValue || isNodeOfType(storedValue, "SpreadElement") || !isIntrinsicRefCallbackParameter(storedValue, scopes)) return false;
36222
+ intrinsicValueWriteCount += 1;
36223
+ }
36224
+ return intrinsicValueWriteCount > 0;
36225
+ };
36226
+ const isDerivedFromProvenDomRefCurrent = (rawExpression, scopes, didReadCollectionValue = false, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
36227
+ const expression = stripParenExpression(rawExpression);
36228
+ if (isNodeOfType(expression, "Identifier")) {
36229
+ const symbol = scopes.symbolFor(expression);
36230
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
36231
+ const initializer = getDirectUnreassignedInitializer(symbol);
36232
+ if (!initializer) return false;
36233
+ visitedSymbolIds.add(symbol.id);
36234
+ return isDerivedFromProvenDomRefCurrent(initializer, scopes, didReadCollectionValue, visitedSymbolIds);
36235
+ }
36236
+ if (isNodeOfType(expression, "MemberExpression")) {
36237
+ if (getStaticPropertyName(expression) === "current") {
36238
+ const symbol = getDirectReactRefSymbol(expression.object, scopes);
36239
+ return Boolean(symbol && (isDirectHostJsxRef(symbol, scopes) || didReadCollectionValue && storesOnlyIntrinsicRefCallbackValues(symbol, scopes)));
36240
+ }
36241
+ return isDerivedFromProvenDomRefCurrent(expression.object, scopes, didReadCollectionValue, visitedSymbolIds);
36242
+ }
36243
+ if (!isNodeOfType(expression, "CallExpression")) return false;
36244
+ const callee = stripParenExpression(expression.callee);
36245
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
36246
+ return isDerivedFromProvenDomRefCurrent(callee.object, scopes, didReadCollectionValue || getStaticPropertyName(callee) === "get", visitedSymbolIds);
36247
+ };
36248
+ const isCommittedDomSyncNode = (node, scopes) => {
36249
+ if (!isNodeOfType(node, "CallExpression")) return false;
36250
+ const callee = stripParenExpression(node.callee);
36251
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
36252
+ const propertyName = getStaticPropertyName(callee);
36253
+ if (propertyName === null || !EXTERNAL_SYNC_DOM_MEMBER_METHOD_NAMES.has(propertyName)) return false;
36254
+ return isDerivedFromProvenDomRefCurrent(callee.object, scopes) || isProvenBrowserApiReceiver(callee.object, "dom-event-target", scopes);
36255
+ };
35322
36256
  const isExternalSyncNode = (node) => {
35323
36257
  if (isNodeOfType(node, "NewExpression")) return isNodeOfType(node.callee, "Identifier") && EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS.has(node.callee.name);
35324
36258
  if (isNodeOfType(node, "AssignmentExpression")) return isNodeOfType(node.left, "MemberExpression") && isNodeOfType(node.left.property, "Identifier") && node.left.property.name === "current";
35325
36259
  if (!isNodeOfType(node, "CallExpression")) return false;
35326
36260
  if (isNodeOfType(node.callee, "Identifier")) return EXTERNAL_SYNC_DIRECT_CALLEE_NAMES.has(node.callee.name);
35327
- if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier")) return false;
35328
- const propertyName = node.callee.property.name;
36261
+ if (!isNodeOfType(node.callee, "MemberExpression")) return false;
36262
+ const propertyName = getStaticPropertyName(node.callee);
36263
+ if (propertyName === null) return false;
35329
36264
  if (EXTERNAL_SYNC_MEMBER_METHOD_NAMES.has(propertyName)) return true;
35330
36265
  if (isBrowserStorageReceiver(node.callee.object)) return true;
35331
36266
  if (!EXTERNAL_SYNC_AMBIGUOUS_HTTP_METHOD_NAMES.has(propertyName)) return false;
35332
36267
  const receiverRootName = getRootIdentifierName(node.callee.object);
35333
36268
  return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
35334
36269
  };
35335
- const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName) => {
36270
+ const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, scopes, allowCommittedDomSync) => {
35336
36271
  if (!isFunctionLike$1(effectCallback)) return false;
35337
36272
  if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
35338
36273
  if (isFunctionShapedReturn(effectCallback.body, setterToStateName, false)) return true;
35339
36274
  } else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, true)) return true;
35340
36275
  let didFindExternalCall = false;
35341
36276
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
35342
- if (isExternalSyncNode(child)) didFindExternalCall = true;
36277
+ if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
35343
36278
  });
35344
36279
  return didFindExternalCall;
35345
36280
  };
@@ -35355,32 +36290,45 @@ const noEffectChain = defineRule({
35355
36290
  const useStateBindings = collectUseStateBindings(componentBody);
35356
36291
  if (useStateBindings.length === 0) return;
35357
36292
  const setterToStateName = /* @__PURE__ */ new Map();
35358
- for (const binding of useStateBindings) setterToStateName.set(binding.setterName, binding.valueName);
36293
+ const stateSymbolIds = /* @__PURE__ */ new Map();
36294
+ for (const binding of useStateBindings) {
36295
+ setterToStateName.set(binding.setterName, binding.valueName);
36296
+ if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
36297
+ const stateIdentifier = binding.declarator.id.elements[0];
36298
+ if (isNodeOfType(stateIdentifier, "Identifier")) {
36299
+ const stateSymbol = context.scopes.symbolFor(stateIdentifier);
36300
+ if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
36301
+ }
36302
+ }
35359
36303
  const storageSetterNames = collectStorageHookSetterNames(componentBody);
35360
36304
  const effectInfos = [];
35361
36305
  for (const effectCall of findTopLevelEffectCalls(componentBody)) {
35362
36306
  const callback = getEffectCallback(effectCall, context.scopes);
35363
36307
  if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
35364
36308
  const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
35365
- const writtenStateNames = collectWrittenStateNamesInEffect(analysisFunctions, setterToStateName);
36309
+ const stateWrites = collectStateWritesInEffect(analysisFunctions, setterToStateName, context.scopes);
36310
+ const writtenStateNames = new Set(stateWrites.keys());
35366
36311
  effectInfos.push({
35367
36312
  node: effectCall,
35368
36313
  depNames: collectDepIdentifierNames(effectCall),
35369
- writtenStateNames,
35370
- isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
36314
+ stateWrites,
36315
+ analysisFunctions,
36316
+ isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName, context.scopes, writtenStateNames.size === 0) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
35371
36317
  });
35372
36318
  }
35373
36319
  if (effectInfos.length < 2) return;
35374
36320
  const reportedNodes = /* @__PURE__ */ new Set();
35375
36321
  for (const writerEffect of effectInfos) {
35376
36322
  if (writerEffect.isExternalSync) continue;
35377
- if (writerEffect.writtenStateNames.size === 0) continue;
36323
+ if (writerEffect.stateWrites.size === 0) continue;
35378
36324
  for (const readerEffect of effectInfos) {
35379
36325
  if (readerEffect === writerEffect) continue;
35380
36326
  if (readerEffect.isExternalSync) continue;
35381
36327
  if (readerEffect.depNames.size === 0) continue;
35382
36328
  let chainedStateName = null;
35383
- for (const writtenName of writerEffect.writtenStateNames) if (readerEffect.depNames.has(writtenName)) {
36329
+ for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
36330
+ if (!readerEffect.depNames.has(writtenName)) continue;
36331
+ if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
35384
36332
  chainedStateName = writtenName;
35385
36333
  break;
35386
36334
  }
@@ -39047,6 +39995,18 @@ const functionContainsProvenReactHookCall = (functionNode, scopes) => {
39047
39995
  return containsReactHookCall;
39048
39996
  };
39049
39997
  //#endregion
39998
+ //#region src/plugin/utils/function-returns-only-null.ts
39999
+ const isNullExpression = (expression) => {
40000
+ const candidate = stripParenExpression(expression);
40001
+ return isNodeOfType(candidate, "Literal") && candidate.value === null;
40002
+ };
40003
+ const functionReturnsOnlyNull = (functionNode) => {
40004
+ if (!isFunctionLike$1(functionNode)) return false;
40005
+ if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
40006
+ const returnStatements = collectFunctionReturnStatements(functionNode);
40007
+ return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
40008
+ };
40009
+ //#endregion
39050
40010
  //#region src/plugin/utils/function-returns-props-children.ts
39051
40011
  const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
39052
40012
  if (!isFunctionLike$1(functionNode) || functionNode.params.length === 0) return false;
@@ -39079,17 +40039,8 @@ const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
39079
40039
  }, controlFlow);
39080
40040
  };
39081
40041
  //#endregion
39082
- //#region src/plugin/utils/function-returns-only-null.ts
39083
- const isNullExpression = (expression) => {
39084
- const candidate = stripParenExpression(expression);
39085
- return isNodeOfType(candidate, "Literal") && candidate.value === null;
39086
- };
39087
- const functionReturnsOnlyNull = (functionNode) => {
39088
- if (!isFunctionLike$1(functionNode)) return false;
39089
- if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
39090
- const returnStatements = collectFunctionReturnStatements(functionNode);
39091
- return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
39092
- };
40042
+ //#region src/plugin/utils/function-has-react-component-evidence.ts
40043
+ const functionHasReactComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
39093
40044
  //#endregion
39094
40045
  //#region src/plugin/utils/is-proven-styled-component-expression.ts
39095
40046
  const findFactoryRoot = (node) => {
@@ -39122,17 +40073,16 @@ const isProvenStyledComponentExpression = (expression, scopes) => {
39122
40073
  //#region src/plugin/utils/is-proven-react-component-symbol.ts
39123
40074
  const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
39124
40075
  const LEGACY_REACT_COMPONENT_FACTORY_NAMES = new Set(["createClass", "createReactClass"]);
39125
- const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
39126
40076
  const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
39127
40077
  const candidate = stripParenExpression(expression);
39128
- if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
40078
+ if (isInlineFunctionExpression(candidate)) return functionHasReactComponentEvidence(candidate, scopes, controlFlow);
39129
40079
  if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
39130
40080
  if (isProvenStyledComponentExpression(candidate, scopes)) return true;
39131
40081
  if (isNodeOfType(candidate, "Identifier")) {
39132
40082
  const symbol = scopes.symbolFor(candidate);
39133
40083
  if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
39134
40084
  visitedSymbolIds.add(symbol.id);
39135
- if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
40085
+ if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasReactComponentEvidence(symbol.declarationNode, scopes, controlFlow);
39136
40086
  if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
39137
40087
  return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
39138
40088
  }
@@ -39159,7 +40109,7 @@ const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentRefe
39159
40109
  for (const candidateSymbol of candidateSymbols) {
39160
40110
  if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
39161
40111
  if (isComponentDeclaration(candidateSymbol.declarationNode)) {
39162
- if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
40112
+ if (functionHasReactComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
39163
40113
  continue;
39164
40114
  }
39165
40115
  const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
@@ -42005,7 +42955,7 @@ const getRefBindingProvenance = (analysis, receiver, isReactUseRefCall) => {
42005
42955
  variables
42006
42956
  };
42007
42957
  };
42008
- const getCallbackRefProvenance = (analysis, effectCall, callExpression, isReactUseRefCall) => {
42958
+ const getCallbackRefProvenance = (analysis, effectCall, callExpression, isReactUseRefCall, isReactUseEffectCall) => {
42009
42959
  const callee = stripParenExpression(callExpression.callee);
42010
42960
  if (!isNodeOfType(callee, "MemberExpression") || getStaticMemberPropertyName(callee) !== "current") return null;
42011
42961
  const bindingProvenance = getRefBindingProvenance(analysis, stripParenExpression(callee.object), isReactUseRefCall);
@@ -42013,7 +42963,9 @@ const getCallbackRefProvenance = (analysis, effectCall, callExpression, isReactU
42013
42963
  const { refCall, variables } = bindingProvenance;
42014
42964
  const componentFunction = findEnclosingFunction$1(effectCall);
42015
42965
  if (!componentFunction || !isFunctionLike$1(componentFunction) || !isComponentFunction$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
42016
- if (!getDirectComponentBodyStatement(effectCall, componentFunction.body)) return null;
42966
+ const notificationEffectStatement = getDirectComponentBodyStatement(effectCall, componentFunction.body);
42967
+ const notificationEffectRoot = findTransparentExpressionRoot(effectCall);
42968
+ if (!notificationEffectStatement || !isNodeOfType(notificationEffectStatement, "ExpressionStatement") || notificationEffectRoot.parent !== notificationEffectStatement || !isReactUseEffectCall(effectCall)) return null;
42017
42969
  const callbackPropNames = /* @__PURE__ */ new Set();
42018
42970
  const initializer = refCall.arguments?.[0];
42019
42971
  const initializerCallbackName = initializer ? getParentCallbackPropName(analysis, initializer) : null;
@@ -42031,9 +42983,20 @@ const getCallbackRefProvenance = (analysis, effectCall, callExpression, isReactU
42031
42983
  const assignment = getRefMemberAssignment(identifier);
42032
42984
  if (!assignment) continue;
42033
42985
  const assignmentStatement = findTransparentExpressionRoot(assignment).parent;
42034
- if (assignment.operator !== "=" || !assignmentStatement || !isNodeOfType(assignmentStatement, "ExpressionStatement") || assignmentStatement.parent !== componentFunction.body) return null;
42986
+ if (assignment.operator !== "=" || !assignmentStatement || !isNodeOfType(assignmentStatement, "ExpressionStatement")) return null;
42035
42987
  const assignedCallbackName = getParentCallbackPropName(analysis, assignment.right);
42036
42988
  if (!assignedCallbackName) return null;
42989
+ if (assignmentStatement.parent !== componentFunction.body) {
42990
+ if (!initializerCallbackName || assignedCallbackName !== initializerCallbackName) return null;
42991
+ const assignmentEffectBody = assignmentStatement.parent;
42992
+ if (!assignmentEffectBody || !isNodeOfType(assignmentEffectBody, "BlockStatement")) return null;
42993
+ const assignmentEffectFunction = assignmentEffectBody.parent;
42994
+ if (!assignmentEffectFunction || !isFunctionLike$1(assignmentEffectFunction) || assignmentEffectFunction.body !== assignmentEffectBody) return null;
42995
+ const assignmentEffectCall = findTransparentExpressionRoot(assignmentEffectFunction).parent;
42996
+ if (!assignmentEffectCall || !isNodeOfType(assignmentEffectCall, "CallExpression") || !isReactUseEffectCall(assignmentEffectCall) || getEffectFn(analysis, assignmentEffectCall) !== assignmentEffectFunction) return null;
42997
+ const assignmentEffectStatement = getDirectComponentBodyStatement(assignmentEffectCall, componentFunction.body);
42998
+ if (!assignmentEffectStatement || !isNodeOfType(assignmentEffectStatement, "ExpressionStatement") || assignmentEffectCall.parent !== assignmentEffectStatement || assignmentEffectStatement.range[0] >= notificationEffectStatement.range[0]) return null;
42999
+ }
42037
43000
  callbackPropNames.add(assignedCallbackName);
42038
43001
  }
42039
43002
  return callbackPropNames.size > 0 ? { callbackPropNames } : null;
@@ -42246,6 +43209,10 @@ const noPassDataToParent = defineRule({
42246
43209
  allowGlobalReactNamespace: true,
42247
43210
  allowUnboundBareCalls: true
42248
43211
  });
43212
+ const isReactUseEffectCall = (node) => isReactApiCall(node, "useEffect", context.scopes, {
43213
+ allowGlobalReactNamespace: true,
43214
+ allowUnboundBareCalls: true
43215
+ });
42249
43216
  return { CallExpression(node) {
42250
43217
  if (!isUseEffect(node)) return;
42251
43218
  const analysis = getProgramAnalysis(node);
@@ -42258,7 +43225,7 @@ const noPassDataToParent = defineRule({
42258
43225
  for (const ref of effectFnRefs) {
42259
43226
  const callExpr = getCallExpr(ref);
42260
43227
  if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) continue;
42261
- const callbackRefProvenance = getCallbackRefProvenance(analysis, node, callExpr, isReactUseRefCall);
43228
+ const callbackRefProvenance = getCallbackRefProvenance(analysis, node, callExpr, isReactUseRefCall, isReactUseEffectCall);
42262
43229
  if (isRefCall(analysis, ref) && !callbackRefProvenance) continue;
42263
43230
  if (!isSynchronous(ref.identifier, effectFn)) continue;
42264
43231
  const calleeNode = unwrapChainExpression(callExpr.callee);
@@ -42928,6 +43895,53 @@ const noPropCallbackInEffect = defineRule({
42928
43895
  });
42929
43896
  //#endregion
42930
43897
  //#region src/plugin/rules/state-and-effects/no-prop-callback-in-render.ts
43898
+ const functionBindingSymbols = (functionNode, scopes) => {
43899
+ let bindingIdentifier = null;
43900
+ if (isNodeOfType(functionNode, "FunctionDeclaration") && functionNode.id) bindingIdentifier = functionNode.id;
43901
+ else {
43902
+ let bindingExpression = findTransparentExpressionRoot(functionNode);
43903
+ let parent = bindingExpression.parent;
43904
+ while (isNodeOfType(parent, "CallExpression") && parent.arguments[0] === bindingExpression) {
43905
+ const callee = parent.callee;
43906
+ const wrapperName = isNodeOfType(callee, "Identifier") ? callee.name : isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") ? callee.property.name : null;
43907
+ if (!isReactApiCall(parent, REACT_HOC_NAMES, scopes, {
43908
+ allowGlobalReactNamespace: true,
43909
+ resolveNamedAliases: true
43910
+ }) && (!wrapperName || REACT_HOC_NAMES.has(wrapperName) || !COMPONENT_HOC_WRAPPER_NAMES.has(wrapperName))) break;
43911
+ bindingExpression = findTransparentExpressionRoot(parent);
43912
+ parent = bindingExpression.parent;
43913
+ }
43914
+ if (isNodeOfType(parent, "VariableDeclarator") && parent.init === bindingExpression && isNodeOfType(parent.id, "Identifier")) bindingIdentifier = parent.id;
43915
+ }
43916
+ if (!bindingIdentifier) return [];
43917
+ let scope = scopes.scopeFor(functionNode);
43918
+ while (scope) {
43919
+ const symbols = scope.symbols.filter((symbol) => symbol.bindingIdentifier === bindingIdentifier);
43920
+ if (symbols.length > 0) return symbols;
43921
+ scope = scope.parent;
43922
+ }
43923
+ return [];
43924
+ };
43925
+ const symbolHasReactComponentUse = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
43926
+ if (visitedSymbolIds.has(symbol.id)) return false;
43927
+ visitedSymbolIds.add(symbol.id);
43928
+ for (const reference of symbol.references) {
43929
+ const identifier = reference.identifier;
43930
+ if (hasSymbolWriteBefore(symbol, identifier, scopes)) continue;
43931
+ const parent = identifier.parent;
43932
+ if (isNodeOfType(parent, "JSXOpeningElement") && isNodeOfType(parent.name, "JSXIdentifier") && parent.name === identifier) return true;
43933
+ const expression = findTransparentExpressionRoot(identifier);
43934
+ const expressionParent = expression.parent;
43935
+ if (isNodeOfType(expressionParent, "CallExpression") && expressionParent.arguments[0] === expression && isReactApiCall(expressionParent, "createElement", scopes, { resolveNamedAliases: true })) return true;
43936
+ if (!isNodeOfType(expressionParent, "VariableDeclarator") || expressionParent.init !== expression || !isNodeOfType(expressionParent.id, "Identifier") || !isNodeOfType(expressionParent.parent, "VariableDeclaration") || expressionParent.parent.kind !== "const") continue;
43937
+ const aliasSymbol = scopes.symbolFor(expressionParent.id);
43938
+ if (aliasSymbol && symbolHasReactComponentUse(aliasSymbol, scopes, visitedSymbolIds)) return true;
43939
+ }
43940
+ return false;
43941
+ };
43942
+ const functionHasReactComponentUse = (functionNode, scopes) => {
43943
+ return functionBindingSymbols(functionNode, scopes).some((symbol) => symbolHasReactComponentUse(symbol, scopes));
43944
+ };
42931
43945
  const isPreservedThroughConciseArrow = (callExpression, scopes) => {
42932
43946
  let node = callExpression;
42933
43947
  let parent = node.parent;
@@ -42974,7 +43988,10 @@ const noPropCallbackInRender = defineRule({
42974
43988
  create: (context) => ({ CallExpression(node) {
42975
43989
  if (!isResultDiscardedCall(node)) return;
42976
43990
  if (isPreservedThroughConciseArrow(node, context.scopes)) return;
42977
- if (!findRenderPhaseComponentOrHook(node, context.scopes)) return;
43991
+ const renderPhaseOwner = findRenderPhaseComponentOrHook(node, context.scopes);
43992
+ if (!renderPhaseOwner) return;
43993
+ const renderPhaseOwnerName = componentOrHookDisplayNameForFunction(renderPhaseOwner);
43994
+ if (!renderPhaseOwnerName || !isReactHookName(renderPhaseOwnerName) && !functionHasReactComponentEvidence(renderPhaseOwner, context.scopes, context.cfg) && !functionHasReactComponentUse(renderPhaseOwner, context.scopes)) return;
42978
43995
  const analysis = getProgramAnalysis(node);
42979
43996
  if (!analysis) return;
42980
43997
  const callee = stripParenExpression(node.callee);
@@ -43710,11 +44727,110 @@ const isSameRefCurrentMember = (node, refSymbol, scopes) => {
43710
44727
  return isNodeOfType(receiver, "Identifier") && resolveConstIdentifierAlias(receiver, scopes)?.id === refSymbol.id;
43711
44728
  };
43712
44729
  const isSameRefCurrentAlias = (node, refSymbol, scopes) => {
43713
- if (isSameRefCurrentMember(node, refSymbol, scopes)) return true;
43714
- if (!isNodeOfType(node, "Identifier")) return false;
43715
- const aliasSymbol = scopes.symbolFor(node);
44730
+ const expression = stripParenExpression(node);
44731
+ if (isSameRefCurrentMember(expression, refSymbol, scopes)) return true;
44732
+ if (!isNodeOfType(expression, "Identifier")) return false;
44733
+ const aliasSymbol = scopes.symbolFor(expression);
43716
44734
  return aliasSymbol?.kind === "const" && aliasSymbol.initializer !== null && isSameRefCurrentMember(stripParenExpression(aliasSymbol.initializer), refSymbol, scopes);
43717
44735
  };
44736
+ const resolveImmutableInitializationValue = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
44737
+ const expression = stripParenExpression(node);
44738
+ if (!isNodeOfType(expression, "Identifier")) return expression;
44739
+ const symbol = scopes.symbolFor(expression);
44740
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || visitedSymbolIds.has(symbol.id)) return null;
44741
+ visitedSymbolIds.add(symbol.id);
44742
+ return resolveImmutableInitializationValue(symbol.initializer, scopes, visitedSymbolIds);
44743
+ };
44744
+ const isProvablyTruthyInitializationValue = (node, scopes) => {
44745
+ const expression = resolveImmutableInitializationValue(node, scopes);
44746
+ return Boolean(expression && (isNodeOfType(expression, "NewExpression") || isNodeOfType(expression, "ObjectExpression") || isNodeOfType(expression, "ArrayExpression") || isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression") || isNodeOfType(expression, "ClassExpression")));
44747
+ };
44748
+ const getInitializationConstructorName = (node, scopes) => {
44749
+ const expression = resolveImmutableInitializationValue(node, scopes);
44750
+ if (!expression) return null;
44751
+ if (isNodeOfType(expression, "NewExpression")) {
44752
+ const callee = stripParenExpression(expression.callee);
44753
+ return isNodeOfType(callee, "Identifier") ? callee.name : null;
44754
+ }
44755
+ return null;
44756
+ };
44757
+ const isClosedTruthyTypeDomain = (typeNode, initializationValue, scopes) => {
44758
+ const initializationExpression = stripParenExpression(initializationValue);
44759
+ if (isNodeOfType(typeNode, "TSTypeLiteral")) return isNodeOfType(initializationExpression, "ObjectExpression");
44760
+ if (isNodeOfType(typeNode, "TSArrayType") || isNodeOfType(typeNode, "TSTupleType")) return isNodeOfType(initializationExpression, "ArrayExpression");
44761
+ if (isNodeOfType(typeNode, "TSFunctionType") || isNodeOfType(typeNode, "TSConstructorType")) return isNodeOfType(initializationExpression, "ArrowFunctionExpression") || isNodeOfType(initializationExpression, "FunctionExpression") || isNodeOfType(initializationExpression, "ClassExpression");
44762
+ if (isNodeOfType(typeNode, "TSObjectKeyword")) return true;
44763
+ if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
44764
+ const typeName = typeNode.typeName;
44765
+ return isNodeOfType(typeName, "Identifier") && typeName.name === getInitializationConstructorName(initializationExpression, scopes);
44766
+ };
44767
+ const refHasClosedFalsySentinelDomain = (refSymbol, initializationValue, scopes) => {
44768
+ const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
44769
+ if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
44770
+ const [initialValue] = initializer.arguments ?? [];
44771
+ if (!initialValue || isNodeOfType(initialValue, "SpreadElement") || !isEmptySentinel(initialValue, scopes)) return false;
44772
+ const [declaredType] = initializer.typeArguments?.params ?? [];
44773
+ if (!declaredType || !isNodeOfType(declaredType, "TSUnionType")) return false;
44774
+ let hasEmptySentinel = false;
44775
+ let hasTruthyDomain = false;
44776
+ for (const memberType of declaredType.types ?? []) {
44777
+ if (isNodeOfType(memberType, "TSNullKeyword") || isNodeOfType(memberType, "TSUndefinedKeyword")) {
44778
+ hasEmptySentinel = true;
44779
+ continue;
44780
+ }
44781
+ if (!isClosedTruthyTypeDomain(memberType, initializationValue, scopes)) return false;
44782
+ hasTruthyDomain = true;
44783
+ }
44784
+ return hasEmptySentinel && hasTruthyDomain;
44785
+ };
44786
+ const isSafeRefIdentifierUse = (identifier) => {
44787
+ const expressionRoot = findTransparentExpressionRoot(identifier);
44788
+ const parent = expressionRoot.parent;
44789
+ if (parent && isNodeOfType(parent, "VariableDeclarator") && parent.id === expressionRoot && parent.parent !== null && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const") return true;
44790
+ if (parent && isNodeOfType(parent, "MemberExpression") && parent.object === expressionRoot && getStaticPropertyName(parent) === "current") return true;
44791
+ if (!parent || !isNodeOfType(parent, "VariableDeclarator") || parent.init !== expressionRoot) return false;
44792
+ return isNodeOfType(parent.id, "Identifier") && parent.parent !== null && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const";
44793
+ };
44794
+ const refDoesNotEscape = (branchRoot, refSymbol, scopes) => {
44795
+ let didEscape = false;
44796
+ walkAst(branchRoot, (child) => {
44797
+ if (didEscape) return false;
44798
+ if (!isNodeOfType(child, "Identifier")) return;
44799
+ if (resolveConstIdentifierAlias(child, scopes)?.id !== refSymbol.id) return;
44800
+ if (child === refSymbol.bindingIdentifier || isSafeRefIdentifierUse(child)) return;
44801
+ didEscape = true;
44802
+ return false;
44803
+ });
44804
+ return !didEscape;
44805
+ };
44806
+ const expressionContainsRefCurrent = (expression, refSymbol, scopes) => {
44807
+ let didFindRefCurrent = false;
44808
+ walkAst(expression, (child) => {
44809
+ if (didFindRefCurrent) return false;
44810
+ if (resolveReactRefSymbol(child, scopes)?.id !== refSymbol.id) return;
44811
+ didFindRefCurrent = true;
44812
+ return false;
44813
+ });
44814
+ return didFindRefCurrent;
44815
+ };
44816
+ const hasNoCompetingRefCurrentWrite = (branchRoot, assignmentExpression, refSymbol, scopes) => {
44817
+ let writeCount = 0;
44818
+ walkAst(branchRoot, (child) => {
44819
+ if (writeCount > 1) return false;
44820
+ if (isNodeOfType(child, "AssignmentExpression")) {
44821
+ if (expressionContainsRefCurrent(child.left, refSymbol, scopes)) writeCount++;
44822
+ return;
44823
+ }
44824
+ if (isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete") {
44825
+ if (expressionContainsRefCurrent(child.argument, refSymbol, scopes)) writeCount++;
44826
+ return;
44827
+ }
44828
+ if (isNodeOfType(child, "ForInStatement") || isNodeOfType(child, "ForOfStatement")) {
44829
+ if (expressionContainsRefCurrent(child.left, refSymbol, scopes)) writeCount++;
44830
+ }
44831
+ });
44832
+ return writeCount === 1 && expressionContainsRefCurrent(assignmentExpression.left, refSymbol, scopes);
44833
+ };
43718
44834
  const isEmptySentinel = (node, scopes) => isNodeOfType(node, "Literal") && node.value === null || isNodeOfType(node, "Identifier") && node.name === "undefined" && scopes.isGlobalReference(node);
43719
44835
  const hasRepeatedExecutionAncestor = (node, stop) => {
43720
44836
  let ancestor = node.parent;
@@ -43763,18 +44879,22 @@ const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol
43763
44879
  const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes) => {
43764
44880
  if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return true;
43765
44881
  if (assignmentExpression.operator !== "=") return false;
44882
+ const renderOwner = findRenderPhaseComponentOrHook(assignmentExpression, scopes);
44883
+ if (!renderOwner) return false;
43766
44884
  let descendant = assignmentExpression;
43767
44885
  let ancestor = descendant.parent;
43768
44886
  while (ancestor) {
43769
- if (isNodeOfType(ancestor, "IfStatement") && isNodeOfType(ancestor.test, "BinaryExpression") && [
44887
+ const test = isNodeOfType(ancestor, "IfStatement") ? stripParenExpression(ancestor.test) : null;
44888
+ if (isNodeOfType(ancestor, "IfStatement") && test && isNodeOfType(test, "UnaryExpression") && test.operator === "!" && isSameRefCurrentAlias(test.argument, refSymbol, scopes) && ancestor.consequent === descendant && isProvablyTruthyInitializationValue(assignmentExpression.right, scopes) && refHasClosedFalsySentinelDomain(refSymbol, assignmentExpression.right, scopes) && !hasRepeatedExecutionAncestor(assignmentExpression, ancestor.consequent) && !hasRepeatedExecutionAncestor(ancestor, renderOwner) && hasNoPriorCoExecutableWrite(assignmentExpression, ancestor.consequent, refSymbol, scopes) && hasNoCompetingRefCurrentWrite(renderOwner, assignmentExpression, refSymbol, scopes) && refDoesNotEscape(renderOwner, refSymbol, scopes)) return true;
44889
+ if (isNodeOfType(ancestor, "IfStatement") && isNodeOfType(test, "BinaryExpression") && [
43770
44890
  "===",
43771
44891
  "==",
43772
44892
  "!==",
43773
44893
  "!="
43774
- ].includes(ancestor.test.operator)) {
43775
- const { left, right } = ancestor.test;
44894
+ ].includes(test.operator)) {
44895
+ const { left, right } = test;
43776
44896
  const comparesEmptySentinel = isSameRefCurrentAlias(left, refSymbol, scopes) && isEmptySentinel(right, scopes) || isSameRefCurrentAlias(right, refSymbol, scopes) && isEmptySentinel(left, scopes);
43777
- const guardedBranch = ancestor.test.operator === "===" || ancestor.test.operator === "==" ? ancestor.consequent : ancestor.alternate;
44897
+ const guardedBranch = test.operator === "===" || test.operator === "==" ? ancestor.consequent : ancestor.alternate;
43778
44898
  if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch && !hasRepeatedExecutionAncestor(assignmentExpression, guardedBranch) && hasNoPriorCoExecutableWrite(assignmentExpression, guardedBranch, refSymbol, scopes)) return true;
43779
44899
  }
43780
44900
  descendant = ancestor;
@@ -43820,9 +44940,9 @@ const isInsideComponentContext = (node) => {
43820
44940
  }
43821
44941
  return false;
43822
44942
  };
43823
- const functionBodyOf = (node) => {
43824
- if (isFunctionLike$1(node)) return node.body ?? null;
43825
- if (isNodeOfType(node, "VariableDeclarator") && node.init && isFunctionLike$1(node.init)) return node.init.body ?? null;
44943
+ const getFunctionFromDeclaration = (node) => {
44944
+ if (isFunctionLike$1(node)) return node;
44945
+ if (isNodeOfType(node, "VariableDeclarator") && node.init && isFunctionLike$1(node.init)) return node.init;
43826
44946
  return null;
43827
44947
  };
43828
44948
  const isHookCallee = (callee) => {
@@ -43830,23 +44950,43 @@ const isHookCallee = (callee) => {
43830
44950
  if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.object, "Identifier") && isUppercaseName(callee.object.name) && isNodeOfType(callee.property, "Identifier")) return isReactHookName(callee.property.name);
43831
44951
  return false;
43832
44952
  };
43833
- const containsHookCall = (body) => {
43834
- let found = false;
43835
- walkAst(body, (child) => {
43836
- if (found) return false;
43837
- if (child !== body && isFunctionLike$1(child) && isComponentFunction$1(child)) return false;
43838
- if (!isNodeOfType(child, "CallExpression")) return;
43839
- if (isHookCallee(child.callee)) found = true;
44953
+ const containsReachableHookCall = (functionNode, rootFunction, context, visitedFunctions) => {
44954
+ if (!isFunctionLike$1(functionNode) || visitedFunctions.has(functionNode)) return false;
44955
+ visitedFunctions.add(functionNode);
44956
+ let didFindReachableHook = false;
44957
+ walkAst(functionNode.body, (child) => {
44958
+ if (didFindReachableHook) return false;
44959
+ if (isFunctionLike$1(child) && !executesDuringRender(child, context.scopes)) return false;
44960
+ if (!isNodeOfType(child, "CallExpression") && !isNodeOfType(child, "NewExpression")) return;
44961
+ if (isNodeOfType(child, "CallExpression")) {
44962
+ if (isHookCallee(child.callee)) {
44963
+ didFindReachableHook = true;
44964
+ return false;
44965
+ }
44966
+ const calledFunction = resolveExactLocalFunction(child.callee, context.scopes);
44967
+ if (calledFunction && isAstDescendant(calledFunction, rootFunction) && containsReachableHookCall(calledFunction, rootFunction, context, visitedFunctions)) {
44968
+ didFindReachableHook = true;
44969
+ return false;
44970
+ }
44971
+ }
44972
+ for (const callArgument of child.arguments ?? []) {
44973
+ if (!executesDuringRender(callArgument, context.scopes)) continue;
44974
+ const callbackFunction = resolveExactLocalFunction(callArgument, context.scopes);
44975
+ if (callbackFunction && isAstDescendant(callbackFunction, rootFunction) && containsReachableHookCall(callbackFunction, rootFunction, context, visitedFunctions)) {
44976
+ didFindReachableHook = true;
44977
+ return false;
44978
+ }
44979
+ }
43840
44980
  });
43841
- return found;
44981
+ return didFindReachableHook;
43842
44982
  };
43843
- const isHookCallingRenderHelper = (symbol) => {
44983
+ const isHookCallingRenderHelper = (symbol, context) => {
43844
44984
  if (!symbol) return false;
43845
44985
  const declaration = symbol.declarationNode;
43846
44986
  if (!isNodeOfType(declaration, "FunctionDeclaration") && !isNodeOfType(declaration, "VariableDeclarator")) return false;
43847
- const body = functionBodyOf(declaration);
43848
- if (!body) return false;
43849
- return containsHookCall(body);
44987
+ const functionNode = getFunctionFromDeclaration(declaration);
44988
+ if (!functionNode) return false;
44989
+ return containsReachableHookCall(functionNode, functionNode, context, /* @__PURE__ */ new Set());
43850
44990
  };
43851
44991
  const noRenderInRender = defineRule({
43852
44992
  id: "no-render-in-render",
@@ -43861,7 +45001,7 @@ const noRenderInRender = defineRule({
43861
45001
  const calleeName = expression.callee.name;
43862
45002
  if (!RENDER_FUNCTION_PATTERN.test(calleeName)) return;
43863
45003
  if (!isInsideComponentContext(node)) return;
43864
- if (!isHookCallingRenderHelper(context.scopes.symbolFor(expression.callee))) return;
45004
+ if (!isHookCallingRenderHelper(context.scopes.symbolFor(expression.callee), context)) return;
43865
45005
  context.report({
43866
45006
  node: expression,
43867
45007
  message: `"${calleeName}()" hides a component behind an inline call, so pull it into its own component and render it as JSX so React can track it.`
@@ -46389,6 +47529,185 @@ const noUnescapedEntities = defineRule({
46389
47529
  } })
46390
47530
  });
46391
47531
  //#endregion
47532
+ //#region src/plugin/utils/read-server-snapshot-boolean.ts
47533
+ const crossFileScopes = /* @__PURE__ */ new WeakMap();
47534
+ const getCrossFileScopes = (programNode) => {
47535
+ const cachedScopes = crossFileScopes.get(programNode);
47536
+ if (cachedScopes) return cachedScopes;
47537
+ const scopes = analyzeScopes(programNode);
47538
+ crossFileScopes.set(programNode, scopes);
47539
+ return scopes;
47540
+ };
47541
+ const symbolIsImmutable = (symbol) => (symbol.kind === "const" || symbol.kind === "function") && symbol.references.every((reference) => reference.flag === "read");
47542
+ const identifierAliasChainIsImmutable = (identifier, scopes, allowImportTerminal = false) => {
47543
+ if (!isNodeOfType(identifier, "Identifier")) return false;
47544
+ const visitedSymbolIds = /* @__PURE__ */ new Set();
47545
+ let symbol = scopes.symbolFor(identifier);
47546
+ while (symbol) {
47547
+ if (symbol.kind === "import") return allowImportTerminal;
47548
+ if (visitedSymbolIds.has(symbol.id) || !symbolIsImmutable(symbol)) return false;
47549
+ visitedSymbolIds.add(symbol.id);
47550
+ if (symbol.kind !== "const" || !symbol.initializer) return symbol.kind === "function";
47551
+ const initializer = stripParenExpression(symbol.initializer);
47552
+ if (!isNodeOfType(initializer, "Identifier")) return true;
47553
+ symbol = scopes.symbolFor(initializer);
47554
+ }
47555
+ return false;
47556
+ };
47557
+ const getImportedHookBinding = (callee, scopes) => {
47558
+ if (!isNodeOfType(callee, "Identifier") || !identifierAliasChainIsImmutable(callee, scopes, true)) return null;
47559
+ const importedSymbol = resolveConstIdentifierAlias(callee, scopes);
47560
+ if (importedSymbol?.kind !== "import") return null;
47561
+ const importDeclaration = importedSymbol.declarationNode.parent;
47562
+ if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration")) return null;
47563
+ const source = importDeclaration.source.value;
47564
+ if (typeof source !== "string") return null;
47565
+ const exportedName = resolveImportedExportName(importedSymbol.declarationNode);
47566
+ return exportedName ? {
47567
+ exportedName,
47568
+ source
47569
+ } : null;
47570
+ };
47571
+ const functionBindingIsImmutable = (functionNode, scopes) => {
47572
+ if (isNodeOfType(functionNode, "FunctionDeclaration") && functionNode.id) {
47573
+ const symbol = scopes.symbolFor(functionNode.id);
47574
+ return Boolean(symbol && symbolIsImmutable(symbol));
47575
+ }
47576
+ const expressionRoot = findTransparentExpressionRoot(functionNode);
47577
+ const parentNode = expressionRoot.parent;
47578
+ if (isNodeOfType(parentNode, "ExportDefaultDeclaration")) return true;
47579
+ if (!isNodeOfType(parentNode, "VariableDeclarator") || parentNode.init !== expressionRoot || !isNodeOfType(parentNode.id, "Identifier")) return false;
47580
+ const symbol = scopes.symbolFor(parentNode.id);
47581
+ return Boolean(symbol && symbolIsImmutable(symbol));
47582
+ };
47583
+ const getExactFunctionResultExpression = (functionNode) => {
47584
+ if (!isFunctionLike$1(functionNode) || functionNode.async) return null;
47585
+ if (isNodeOfType(functionNode, "FunctionDeclaration") && functionNode.generator) return null;
47586
+ if (isNodeOfType(functionNode, "FunctionExpression") && functionNode.generator) return null;
47587
+ if (!isNodeOfType(functionNode.body, "BlockStatement")) return functionNode.body;
47588
+ const [returnStatement, additionalReturnStatement] = collectFunctionReturnStatements(functionNode);
47589
+ if (!returnStatement || additionalReturnStatement || !returnStatement.argument) return null;
47590
+ if ([...functionNode.body.body].pop() !== returnStatement) return null;
47591
+ return returnStatement.argument;
47592
+ };
47593
+ const functionHasNoParameters = (functionNode) => isFunctionLike$1(functionNode) && (functionNode.params?.length ?? 0) === 0;
47594
+ const readImmutableBooleanLiteral = (expression, scopes, visitedSymbolIds) => {
47595
+ const unwrappedExpression = stripParenExpression(expression);
47596
+ if (isNodeOfType(unwrappedExpression, "Literal") && typeof unwrappedExpression.value === "boolean") return unwrappedExpression.value;
47597
+ if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
47598
+ const symbol = scopes.symbolFor(unwrappedExpression);
47599
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || !symbolIsImmutable(symbol) || visitedSymbolIds.has(symbol.id)) return null;
47600
+ visitedSymbolIds.add(symbol.id);
47601
+ return readImmutableBooleanLiteral(symbol.initializer, scopes, visitedSymbolIds);
47602
+ };
47603
+ const readFunctionLiteralBoolean = (expression, scopes) => {
47604
+ const unwrappedExpression = stripParenExpression(expression);
47605
+ if (isNodeOfType(unwrappedExpression, "Identifier") && !identifierAliasChainIsImmutable(unwrappedExpression, scopes)) return null;
47606
+ const functionNode = resolveExactLocalFunction(unwrappedExpression, scopes);
47607
+ if (!functionNode) return null;
47608
+ const resultExpression = getExactFunctionResultExpression(functionNode);
47609
+ return resultExpression ? readImmutableBooleanLiteral(resultExpression, scopes, /* @__PURE__ */ new Set()) : null;
47610
+ };
47611
+ const readServerSnapshotBooleanInternal = (expression, scopes, visitedSymbolIds, visitedFunctionNodes, currentFilename) => {
47612
+ const unwrappedExpression = stripParenExpression(expression);
47613
+ if (isNodeOfType(unwrappedExpression, "Literal") && typeof unwrappedExpression.value === "boolean") return {
47614
+ hasUseSyncExternalStoreOrigin: false,
47615
+ value: unwrappedExpression.value
47616
+ };
47617
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
47618
+ const symbol = scopes.symbolFor(unwrappedExpression);
47619
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || !symbolIsImmutable(symbol) || visitedSymbolIds.has(symbol.id)) return null;
47620
+ visitedSymbolIds.add(symbol.id);
47621
+ return readServerSnapshotBooleanInternal(symbol.initializer, scopes, visitedSymbolIds, visitedFunctionNodes, currentFilename);
47622
+ }
47623
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
47624
+ const argumentResult = readServerSnapshotBooleanInternal(unwrappedExpression.argument, scopes, visitedSymbolIds, visitedFunctionNodes, currentFilename);
47625
+ return argumentResult ? {
47626
+ hasUseSyncExternalStoreOrigin: argumentResult.hasUseSyncExternalStoreOrigin,
47627
+ value: !argumentResult.value
47628
+ } : null;
47629
+ }
47630
+ if (isNodeOfType(unwrappedExpression, "LogicalExpression") && (unwrappedExpression.operator === "&&" || unwrappedExpression.operator === "||")) {
47631
+ const leftResult = readServerSnapshotBooleanInternal(unwrappedExpression.left, scopes, new Set(visitedSymbolIds), new Set(visitedFunctionNodes), currentFilename);
47632
+ if (leftResult && (unwrappedExpression.operator === "&&" && !leftResult.value || unwrappedExpression.operator === "||" && leftResult.value)) return leftResult;
47633
+ const rightResult = readServerSnapshotBooleanInternal(unwrappedExpression.right, scopes, new Set(visitedSymbolIds), new Set(visitedFunctionNodes), currentFilename);
47634
+ if (rightResult && (unwrappedExpression.operator === "&&" && !rightResult.value || unwrappedExpression.operator === "||" && rightResult.value)) return rightResult;
47635
+ return leftResult && rightResult ? {
47636
+ hasUseSyncExternalStoreOrigin: leftResult.hasUseSyncExternalStoreOrigin || rightResult.hasUseSyncExternalStoreOrigin,
47637
+ value: rightResult.value
47638
+ } : null;
47639
+ }
47640
+ if (!isNodeOfType(unwrappedExpression, "CallExpression")) return null;
47641
+ if (isReactApiCall(unwrappedExpression, "useSyncExternalStore", scopes, { resolveNamedAliases: true })) {
47642
+ const [, , serverSnapshotArgument] = unwrappedExpression.arguments ?? [];
47643
+ if (!serverSnapshotArgument || isNodeOfType(serverSnapshotArgument, "SpreadElement")) return null;
47644
+ const serverSnapshotValue = readFunctionLiteralBoolean(serverSnapshotArgument, scopes);
47645
+ return serverSnapshotValue === null ? null : {
47646
+ hasUseSyncExternalStoreOrigin: true,
47647
+ value: serverSnapshotValue
47648
+ };
47649
+ }
47650
+ if ((unwrappedExpression.arguments?.length ?? 0) !== 0) return null;
47651
+ const callee = stripParenExpression(unwrappedExpression.callee);
47652
+ const importedHookBinding = getImportedHookBinding(callee, scopes);
47653
+ if (importedHookBinding && currentFilename) {
47654
+ const resolvedHook = resolveCrossFileFunctionExportWithFilePath(path.resolve(currentFilename), importedHookBinding.source, importedHookBinding.exportedName);
47655
+ if (resolvedHook && !resolvedHook.filePath.split(path.sep).includes("node_modules")) {
47656
+ const resolvedScopes = getCrossFileScopes(resolvedHook.programNode);
47657
+ if (isReactHookName(componentOrHookDisplayNameForFunction(resolvedHook.functionNode) ?? (importedHookBinding.exportedName === "default" && isNodeOfType(callee, "Identifier") ? callee.name : importedHookBinding.exportedName)) && functionHasNoParameters(resolvedHook.functionNode) && functionBindingIsImmutable(resolvedHook.functionNode, resolvedScopes) && !visitedFunctionNodes.has(resolvedHook.functionNode)) {
47658
+ visitedFunctionNodes.add(resolvedHook.functionNode);
47659
+ const resultExpression = getExactFunctionResultExpression(resolvedHook.functionNode);
47660
+ if (resultExpression) return readServerSnapshotBooleanInternal(resultExpression, resolvedScopes, /* @__PURE__ */ new Set(), visitedFunctionNodes, resolvedHook.filePath);
47661
+ }
47662
+ }
47663
+ return null;
47664
+ }
47665
+ if (!isNodeOfType(callee, "Identifier") || !isReactHookName(callee.name) || !identifierAliasChainIsImmutable(callee, scopes)) return null;
47666
+ const hookFunction = resolveExactLocalFunction(callee, scopes);
47667
+ if (!hookFunction || !functionHasNoParameters(hookFunction) || visitedFunctionNodes.has(hookFunction)) return null;
47668
+ visitedFunctionNodes.add(hookFunction);
47669
+ const resultExpression = getExactFunctionResultExpression(hookFunction);
47670
+ return resultExpression ? readServerSnapshotBooleanInternal(resultExpression, scopes, visitedSymbolIds, visitedFunctionNodes, currentFilename) : null;
47671
+ };
47672
+ const readServerSnapshotBoolean = (expression, scopes, currentFilename) => {
47673
+ const result = readServerSnapshotBooleanInternal(expression, scopes, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set(), currentFilename);
47674
+ return result?.hasUseSyncExternalStoreOrigin ? result.value : null;
47675
+ };
47676
+ //#endregion
47677
+ //#region src/plugin/utils/is-after-falsy-server-snapshot-early-return.ts
47678
+ const isAfterFalsyServerSnapshotEarlyReturn = (node, componentOrHookNode, scopes, filename) => {
47679
+ const enclosingFunction = findEnclosingFunction$1(node);
47680
+ if (!enclosingFunction || enclosingFunction !== componentOrHookNode && !executesDuringRender(enclosingFunction, scopes) || !isFunctionLike$1(enclosingFunction) || !isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
47681
+ let currentNode = node;
47682
+ while (currentNode !== enclosingFunction) {
47683
+ const parentNode = currentNode.parent;
47684
+ if (!parentNode) return false;
47685
+ if (isNodeOfType(parentNode, "BlockStatement")) for (const statement of parentNode.body) {
47686
+ if (statement === currentNode) break;
47687
+ if (!isNodeOfType(statement, "IfStatement")) continue;
47688
+ const serverResult = readServerSnapshotBoolean(statement.test, scopes, filename);
47689
+ if (serverResult === true && statementAlwaysExits(statement.consequent)) return true;
47690
+ if (serverResult === false && statement.alternate && statementAlwaysExits(statement.alternate)) return true;
47691
+ }
47692
+ currentNode = parentNode;
47693
+ }
47694
+ return false;
47695
+ };
47696
+ //#endregion
47697
+ //#region src/plugin/utils/is-gated-by-falsy-server-snapshot.ts
47698
+ const isGatedByFalsyServerSnapshot = (node, scopes, filename) => {
47699
+ let currentNode = node;
47700
+ let parentNode = node.parent;
47701
+ while (parentNode) {
47702
+ if (isNodeOfType(parentNode, "LogicalExpression") && parentNode.right === currentNode && (parentNode.operator === "&&" && readServerSnapshotBoolean(parentNode.left, scopes, filename) === false || parentNode.operator === "||" && readServerSnapshotBoolean(parentNode.left, scopes, filename) === true)) return true;
47703
+ if (isNodeOfType(parentNode, "ConditionalExpression") && (parentNode.consequent === currentNode && readServerSnapshotBoolean(parentNode.test, scopes, filename) === false || parentNode.alternate === currentNode && readServerSnapshotBoolean(parentNode.test, scopes, filename) === true)) return true;
47704
+ if (isNodeOfType(parentNode, "IfStatement") && (parentNode.consequent === currentNode && readServerSnapshotBoolean(parentNode.test, scopes, filename) === false || parentNode.alternate === currentNode && readServerSnapshotBoolean(parentNode.test, scopes, filename) === true)) return true;
47705
+ currentNode = parentNode;
47706
+ parentNode = parentNode.parent ?? null;
47707
+ }
47708
+ return false;
47709
+ };
47710
+ //#endregion
46392
47711
  //#region src/plugin/rules/performance/no-unguarded-browser-global-in-render-or-hook-init.ts
46393
47712
  const BROWSER_GLOBAL_NAMES = new Set([
46394
47713
  "window",
@@ -46495,7 +47814,9 @@ const noUnguardedBrowserGlobalInRenderOrHookInit = defineRule({
46495
47814
  if (fileIsEmailTemplate) return;
46496
47815
  if (isGeneratedImageRenderContext(context, findEnclosingJsxOpeningElement(node) ?? node)) return;
46497
47816
  if (isGatedByFalsyInitialState(node, context.scopes)) return;
47817
+ if (isGatedByFalsyServerSnapshot(node, context.scopes, context.filename)) return;
46498
47818
  if (isAfterClientOnlyEarlyReturn(node, componentOrHookNode, context.scopes)) return;
47819
+ if (isAfterFalsyServerSnapshotEarlyReturn(node, componentOrHookNode, context.scopes, context.filename)) return;
46499
47820
  if (isInsideAvailabilityGuard(node, browserGlobalName, context)) return;
46500
47821
  if (isAfterAvailabilityEarlyExit(node, componentOrHookNode, browserGlobalName, context)) return;
46501
47822
  reportedNodes.add(node);
@@ -50698,6 +52019,31 @@ const preferModuleScopePureFunction = defineRule({
50698
52019
  }
50699
52020
  });
50700
52021
  //#endregion
52022
+ //#region src/plugin/utils/get-require-call-source.ts
52023
+ const getRequireCallSource = (expression) => {
52024
+ const unwrappedExpression = stripParenExpression(expression);
52025
+ if (isNodeOfType(unwrappedExpression, "MemberExpression")) return getRequireCallSource(unwrappedExpression.object);
52026
+ if (!isNodeOfType(unwrappedExpression, "CallExpression")) return null;
52027
+ if (!isNodeOfType(unwrappedExpression.callee, "Identifier") || unwrappedExpression.callee.name !== "require") return null;
52028
+ const [firstArgument] = unwrappedExpression.arguments ?? [];
52029
+ if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
52030
+ return typeof firstArgument.value === "string" ? firstArgument.value : null;
52031
+ };
52032
+ //#endregion
52033
+ //#region src/plugin/utils/is-proven-node-crypto-namespace-reference.ts
52034
+ const NODE_CRYPTO_MODULE_SOURCES = new Set(["crypto", "node:crypto"]);
52035
+ const isProvenNodeCryptoNamespaceReference = (expression, scopes) => {
52036
+ const identifier = stripParenExpression(expression);
52037
+ if (!isNodeOfType(identifier, "Identifier")) return false;
52038
+ const symbol = resolveConstIdentifierAlias(identifier, scopes);
52039
+ if (!symbol) return false;
52040
+ if (symbol.kind === "import") {
52041
+ const importBinding = getImportBindingForName(identifier, symbol.name);
52042
+ return Boolean(importBinding && NODE_CRYPTO_MODULE_SOURCES.has(importBinding.source));
52043
+ }
52044
+ return Boolean(symbol.kind === "const" && symbol.initializer && NODE_CRYPTO_MODULE_SOURCES.has(getRequireCallSource(symbol.initializer) ?? ""));
52045
+ };
52046
+ //#endregion
50701
52047
  //#region src/plugin/rules/architecture/prefer-module-scope-static-value.ts
50702
52048
  const MUTATING_RECEIVER_METHOD_NAMES = new Set([...MUTATING_ARRAY_METHODS, ...MUTATING_COLLECTION_METHODS]);
50703
52049
  const isMutationContext = (referenceIdentifier) => {
@@ -50811,9 +52157,9 @@ const isImpureCall = (node, scopes) => {
50811
52157
  const callee = node.callee;
50812
52158
  if (isNodeOfType(callee, "Identifier")) return isImpureBareCallee(callee, scopes);
50813
52159
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return false;
50814
- if (!isNodeOfType(callee.object, "Identifier")) return false;
50815
52160
  if (!isNodeOfType(callee.property, "Identifier")) return false;
50816
- return Boolean(IMPURE_MEMBER_RECEIVERS.get(callee.object.name)?.has(callee.property.name));
52161
+ for (const [receiverName, receiverMethodNames] of IMPURE_MEMBER_RECEIVERS) if (receiverMethodNames.has(callee.property.name) && (isProvenGlobalNamespaceReference(callee.object, receiverName, scopes) || receiverName === "crypto" && isProvenNodeCryptoNamespaceReference(callee.object, scopes))) return true;
52162
+ return false;
50817
52163
  };
50818
52164
  const containsImpureExpression = (expression, scopes) => {
50819
52165
  let foundImpure = false;
@@ -55582,16 +56928,6 @@ const rnListCallbackPerRow = defineRule({
55582
56928
  }
55583
56929
  });
55584
56930
  //#endregion
55585
- //#region src/plugin/utils/get-require-call-source.ts
55586
- const getRequireCallSource = (expression) => {
55587
- if (isNodeOfType(expression, "MemberExpression")) return getRequireCallSource(expression.object);
55588
- if (!isNodeOfType(expression, "CallExpression")) return null;
55589
- if (!isNodeOfType(expression.callee, "Identifier") || expression.callee.name !== "require") return null;
55590
- const [firstArgument] = expression.arguments ?? [];
55591
- if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
55592
- return typeof firstArgument.value === "string" ? firstArgument.value : null;
55593
- };
55594
- //#endregion
55595
56931
  //#region src/plugin/utils/get-initializer-module-source.ts
55596
56932
  const getInitializerModuleSource = (contextNode, initializer) => {
55597
56933
  const requireSource = getRequireCallSource(initializer);
@@ -62370,9 +63706,35 @@ const serverDedupProps = defineRule({
62370
63706
  });
62371
63707
  //#endregion
62372
63708
  //#region src/plugin/rules/server/server-fetch-without-revalidate.ts
62373
- const isFetchCall = (node) => {
63709
+ const isGlobalThisFetchMember = (node, context) => {
63710
+ const memberExpression = stripParenExpression(node);
63711
+ if (!isNodeOfType(memberExpression, "MemberExpression")) return false;
63712
+ const receiver = stripParenExpression(memberExpression.object);
63713
+ return getStaticPropertyName(memberExpression) === "fetch" && isNodeOfType(receiver, "Identifier") && receiver.name === "globalThis" && context.scopes.isGlobalReference(receiver);
63714
+ };
63715
+ const isGlobalThisIdentifier = (node, context) => {
63716
+ const expression = stripParenExpression(node);
63717
+ return isNodeOfType(expression, "Identifier") && expression.name === "globalThis" && context.scopes.isGlobalReference(expression);
63718
+ };
63719
+ const isGlobalFetchDestructuringBinding = (symbolBinding, declaration, context) => isNodeOfType(declaration.id, "ObjectPattern") && Boolean(declaration.id.properties.some((property) => isNodeOfType(property, "Property") && property.value === symbolBinding && getStaticPropertyKeyName(property, { allowComputedString: true }) === "fetch") && declaration.init && isGlobalThisIdentifier(declaration.init, context));
63720
+ const isExactGlobalFetchValue = (node, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
63721
+ const expression = stripParenExpression(node);
63722
+ if (isGlobalThisFetchMember(expression, context)) return true;
63723
+ if (!isNodeOfType(expression, "Identifier")) return false;
63724
+ if (expression.name === "fetch" && context.scopes.isGlobalReference(expression)) return true;
63725
+ const symbol = context.scopes.symbolFor(expression);
63726
+ if (!symbol || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
63727
+ if (!isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
63728
+ if (isGlobalFetchDestructuringBinding(symbol.bindingIdentifier, symbol.declarationNode, context)) return true;
63729
+ if (symbol.declarationNode.id !== symbol.bindingIdentifier || !symbol.initializer) return false;
63730
+ visitedSymbolIds.add(symbol.id);
63731
+ return isExactGlobalFetchValue(symbol.initializer, context, visitedSymbolIds);
63732
+ };
63733
+ const isFetchCall = (node, context) => {
62374
63734
  if (!isNodeOfType(node, "CallExpression")) return false;
62375
- return isNodeOfType(node.callee, "Identifier") && node.callee.name === "fetch";
63735
+ const callee = stripParenExpression(node.callee);
63736
+ if (!isNodeOfType(callee, "Identifier") || callee.name !== "fetch") return false;
63737
+ return isExactGlobalFetchValue(callee, context);
62376
63738
  };
62377
63739
  const getPropertyKeyName$1 = (property) => {
62378
63740
  if (!isNodeOfType(property, "Property")) return null;
@@ -62418,7 +63780,7 @@ const serverFetchWithoutRevalidate = defineRule({
62418
63780
  },
62419
63781
  CallExpression(node) {
62420
63782
  if (!isServerSideFile) return;
62421
- if (!isFetchCall(node)) return;
63783
+ if (!isFetchCall(node, context)) return;
62422
63784
  if (isMutatingFetchCall(node)) return;
62423
63785
  const optionsArg = node.arguments?.[1];
62424
63786
  if (optionsArg) {
@@ -62844,29 +64206,19 @@ const serverNoMutableModuleState = defineRule({
62844
64206
  });
62845
64207
  //#endregion
62846
64208
  //#region src/plugin/rules/server/server-sequential-independent-await.ts
62847
- const collectDeclaredNames = (declaration) => {
62848
- const names = /* @__PURE__ */ new Set();
62849
- if (!isNodeOfType(declaration, "VariableDeclaration")) return names;
62850
- for (const declarator of declaration.declarations ?? []) collectPatternNames(declarator.id, names);
62851
- return names;
62852
- };
62853
64209
  const declarationStartsWithAwait = (declaration) => {
62854
64210
  if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
62855
64211
  for (const declarator of declaration.declarations ?? []) if (isNodeOfType(declarator.init, "AwaitExpression")) return true;
62856
64212
  return false;
62857
64213
  };
62858
- const declarationReadsAnyName = (declaration, names) => {
62859
- if (names.size === 0) return false;
64214
+ const declarationReadsAnyPatternBinding = (declaration, patterns, context) => {
64215
+ if (patterns.length === 0) return false;
62860
64216
  if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
62861
- let didRead = false;
62862
64217
  for (const declarator of declaration.declarations ?? []) {
62863
64218
  if (!declarator.init) continue;
62864
- walkAst(declarator.init, (child) => {
62865
- if (didRead) return;
62866
- if (isNodeOfType(child, "Identifier") && names.has(child.name)) didRead = true;
62867
- });
64219
+ if (expressionReadsPatternBinding(declarator.init, patterns, context.scopes)) return true;
62868
64220
  }
62869
- return didRead;
64221
+ return false;
62870
64222
  };
62871
64223
  const GATE_LEADING_VERBS = new Set([
62872
64224
  "require",
@@ -62940,11 +64292,11 @@ const serverSequentialIndependentAwait = defineRule({
62940
64292
  const currentStatement = statements[statementIndex];
62941
64293
  if (!isNodeOfType(currentStatement, "VariableDeclaration")) continue;
62942
64294
  if (!declarationStartsWithAwait(currentStatement)) continue;
62943
- const declaredNames = collectDeclaredNames(currentStatement);
64295
+ const declaredPatterns = currentStatement.declarations.map((declarator) => declarator.id);
62944
64296
  const nextStatement = statements[statementIndex + 1];
62945
64297
  if (!isNodeOfType(nextStatement, "VariableDeclaration")) continue;
62946
64298
  if (!declarationStartsWithAwait(nextStatement)) continue;
62947
- if (declarationReadsAnyName(nextStatement, declaredNames)) continue;
64299
+ if (declarationReadsAnyPatternBinding(nextStatement, declaredPatterns, context)) continue;
62948
64300
  if (declarationAwaitsExistingPromise(nextStatement)) continue;
62949
64301
  if (declarationAwaitsRequestScopedCall(currentStatement) || declarationAwaitsRequestScopedCall(nextStatement)) continue;
62950
64302
  if (declarationAwaitsGate(currentStatement, context)) continue;
@@ -69910,6 +71262,7 @@ const CROSS_FILE_RULE_IDS = new Set([
69910
71262
  "exhaustive-deps",
69911
71263
  "no-barrel-import",
69912
71264
  "nextjs-missing-metadata",
71265
+ "nextjs-no-img-element",
69913
71266
  "nextjs-no-use-search-params-without-suspense",
69914
71267
  "no-dynamic-import-path",
69915
71268
  "no-full-lodash-import",
@@ -70141,12 +71494,12 @@ const CROSS_FILE_DEPENDENCY_COLLECTORS = new Map([
70141
71494
  ]);
70142
71495
  /**
70143
71496
  * Cross-file rules whose dependency set CANNOT be soundly bounded — they are
70144
- * excluded from fingerprinting and re-lint every file on every scan. Empty
70145
- * today; a new cross-file rule must be added either here or to
71497
+ * excluded from fingerprinting and re-lint every file on every scan. A new
71498
+ * cross-file rule must be added either here or to
70146
71499
  * `CROSS_FILE_DEPENDENCY_COLLECTORS` (the core guard test enforces the
70147
71500
  * partition), forcing a conscious classification.
70148
71501
  */
70149
- const UNBOUNDED_CROSS_FILE_RULE_IDS = new Set(["only-export-components"]);
71502
+ const UNBOUNDED_CROSS_FILE_RULE_IDS = new Set(["nextjs-no-img-element", "only-export-components"]);
70150
71503
  /**
70151
71504
  * Runs the collectors for `ruleIds` over one file and returns every
70152
71505
  * filesystem probe they made — the file's cross-file dependency set.