oxlint-plugin-react-doctor 0.7.9-dev.b30fb80 → 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.d.ts +2 -2
- package/dist/index.js +1400 -233
- package/package.json +1 -1
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/
|
|
1823
|
-
const
|
|
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,
|
|
2042
|
-
if (!
|
|
2043
|
-
|
|
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();
|
|
@@ -8200,6 +8250,9 @@ const createMethodMutationAnalysis = (context) => {
|
|
|
8200
8250
|
};
|
|
8201
8251
|
};
|
|
8202
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
|
|
8203
8256
|
//#region src/plugin/utils/collect-function-return-statements.ts
|
|
8204
8257
|
const collectFunctionReturnStatements = (functionNode) => {
|
|
8205
8258
|
if (!isFunctionLike$1(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return [];
|
|
@@ -14364,14 +14417,14 @@ const DEPENDENCY_HOOK_NAMES = new Set([
|
|
|
14364
14417
|
"useImperativeHandle",
|
|
14365
14418
|
"useInsertionEffect"
|
|
14366
14419
|
]);
|
|
14367
|
-
const crossFileScopes = /* @__PURE__ */ new WeakMap();
|
|
14420
|
+
const crossFileScopes$1 = /* @__PURE__ */ new WeakMap();
|
|
14368
14421
|
const crossFileControlFlow = /* @__PURE__ */ new WeakMap();
|
|
14369
14422
|
const forwardedFreshDependencyCache = /* @__PURE__ */ new WeakMap();
|
|
14370
|
-
const getCrossFileScopes = (resolved) => {
|
|
14371
|
-
const cached = crossFileScopes.get(resolved.programNode);
|
|
14423
|
+
const getCrossFileScopes$1 = (resolved) => {
|
|
14424
|
+
const cached = crossFileScopes$1.get(resolved.programNode);
|
|
14372
14425
|
if (cached) return cached;
|
|
14373
14426
|
const scopes = analyzeScopes(resolved.programNode);
|
|
14374
|
-
crossFileScopes.set(resolved.programNode, scopes);
|
|
14427
|
+
crossFileScopes$1.set(resolved.programNode, scopes);
|
|
14375
14428
|
return scopes;
|
|
14376
14429
|
};
|
|
14377
14430
|
const getCrossFileControlFlow = (resolved) => {
|
|
@@ -14406,7 +14459,7 @@ const isCustomHookFunction = (functionNode, fallbackName) => {
|
|
|
14406
14459
|
const displayName = componentOrHookDisplayNameForFunction(functionNode) ?? fallbackName ?? "";
|
|
14407
14460
|
return /^use[A-Z0-9]/.test(displayName);
|
|
14408
14461
|
};
|
|
14409
|
-
const getImportedHookBinding = (callee, scopes) => {
|
|
14462
|
+
const getImportedHookBinding$1 = (callee, scopes) => {
|
|
14410
14463
|
if (!isNodeOfType(callee, "Identifier")) return null;
|
|
14411
14464
|
const importedSymbol = resolveConstIdentifierAlias(callee, scopes);
|
|
14412
14465
|
if (importedSymbol?.kind !== "import" || !importedSymbol.initializer) return null;
|
|
@@ -14422,7 +14475,7 @@ const getImportedHookBinding = (callee, scopes) => {
|
|
|
14422
14475
|
};
|
|
14423
14476
|
const resolveImportedHookFunction = (callee, scopes, currentFilename) => {
|
|
14424
14477
|
if (!currentFilename) return null;
|
|
14425
|
-
const importedBinding = getImportedHookBinding(callee, scopes);
|
|
14478
|
+
const importedBinding = getImportedHookBinding$1(callee, scopes);
|
|
14426
14479
|
if (!importedBinding) return null;
|
|
14427
14480
|
const resolved = resolveCrossFileFunctionExportWithFilePath(currentFilename, importedBinding.source, importedBinding.exportedName);
|
|
14428
14481
|
if (!resolved || !isCustomHookFunction(resolved.functionNode, importedBinding.exportedName)) return null;
|
|
@@ -14431,7 +14484,7 @@ const resolveImportedHookFunction = (callee, scopes, currentFilename) => {
|
|
|
14431
14484
|
filePath: resolved.filePath,
|
|
14432
14485
|
functionNode: resolved.functionNode,
|
|
14433
14486
|
programNode: resolved.programNode,
|
|
14434
|
-
scopes: getCrossFileScopes(resolved)
|
|
14487
|
+
scopes: getCrossFileScopes$1(resolved)
|
|
14435
14488
|
};
|
|
14436
14489
|
};
|
|
14437
14490
|
const dependencyIndexForReactHookReference = (expression, scopes, dependencyHookNames, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
@@ -14462,11 +14515,11 @@ const dependencyIndexForReactHookReference = (expression, scopes, dependencyHook
|
|
|
14462
14515
|
};
|
|
14463
14516
|
const getImportedReactDependencyIndex = (callExpression, scopes, currentFilename, dependencyHookNames) => {
|
|
14464
14517
|
if (!currentFilename) return null;
|
|
14465
|
-
const importedBinding = getImportedHookBinding(stripParenExpression(callExpression.callee), scopes);
|
|
14518
|
+
const importedBinding = getImportedHookBinding$1(stripParenExpression(callExpression.callee), scopes);
|
|
14466
14519
|
if (!importedBinding) return null;
|
|
14467
14520
|
const resolved = resolveCrossFileValueExportWithFilePath(currentFilename, importedBinding.source, importedBinding.exportedName);
|
|
14468
14521
|
if (!resolved) return null;
|
|
14469
|
-
return dependencyIndexForReactHookReference(resolved.exportedNode, getCrossFileScopes(resolved), dependencyHookNames);
|
|
14522
|
+
return dependencyIndexForReactHookReference(resolved.exportedNode, getCrossFileScopes$1(resolved), dependencyHookNames);
|
|
14470
14523
|
};
|
|
14471
14524
|
const resolveHookFunction = (callExpression, scopes, cfg, currentFilename) => {
|
|
14472
14525
|
const callee = stripParenExpression(callExpression.callee);
|
|
@@ -19181,10 +19234,10 @@ const isUncacheableOptionsMergeUtility = (node) => {
|
|
|
19181
19234
|
return (argument.properties ?? []).some((property) => isNodeOfType(property, "SpreadElement") && isNodeOfType(property.argument, "Identifier") && parameterNames.has(property.argument.name));
|
|
19182
19235
|
});
|
|
19183
19236
|
};
|
|
19184
|
-
const isIntlNewExpression = (node) => {
|
|
19237
|
+
const isIntlNewExpression = (node, context) => {
|
|
19185
19238
|
if (!isNodeOfType(node, "NewExpression")) return false;
|
|
19186
19239
|
const callee = node.callee;
|
|
19187
|
-
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;
|
|
19188
19241
|
return false;
|
|
19189
19242
|
};
|
|
19190
19243
|
const jsHoistIntl = defineRule({
|
|
@@ -19194,7 +19247,7 @@ const jsHoistIntl = defineRule({
|
|
|
19194
19247
|
severity: "warn",
|
|
19195
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",
|
|
19196
19249
|
create: (context) => ({ NewExpression(node) {
|
|
19197
|
-
if (!isIntlNewExpression(node)) return;
|
|
19250
|
+
if (!isIntlNewExpression(node, context)) return;
|
|
19198
19251
|
let cursor = node.parent ?? null;
|
|
19199
19252
|
let inFunctionBody = false;
|
|
19200
19253
|
while (cursor) {
|
|
@@ -19959,6 +20012,28 @@ const jsLengthCheckFirst = defineRule({
|
|
|
19959
20012
|
} })
|
|
19960
20013
|
});
|
|
19961
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
|
|
19962
20037
|
//#region src/plugin/rules/js-performance/js-min-max-loop.ts
|
|
19963
20038
|
const builtinMutationByProgram = /* @__PURE__ */ new WeakMap();
|
|
19964
20039
|
const RUNTIMELESS_SYMBOL_KINDS = new Set(["ts-interface", "ts-type-alias"]);
|
|
@@ -20015,26 +20090,6 @@ const isSafeFreshNumericArray = (arrayExpression) => {
|
|
|
20015
20090
|
}
|
|
20016
20091
|
return !(didFindPositiveZero && didFindNegativeZero);
|
|
20017
20092
|
};
|
|
20018
|
-
const isGlobalObjectReference = (expression, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
|
|
20019
|
-
const strippedExpression = stripParenExpression(expression);
|
|
20020
|
-
if (!isNodeOfType(strippedExpression, "Identifier")) return false;
|
|
20021
|
-
if ((strippedExpression.name === "globalThis" || strippedExpression.name === "window" || strippedExpression.name === "self" || strippedExpression.name === "global") && scopes.isGlobalReference(strippedExpression)) return true;
|
|
20022
|
-
const symbol = scopes.symbolFor(strippedExpression);
|
|
20023
|
-
if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbols.has(symbol.id)) return false;
|
|
20024
|
-
visitedSymbols.add(symbol.id);
|
|
20025
|
-
return isGlobalObjectReference(symbol.initializer, scopes, visitedSymbols);
|
|
20026
|
-
};
|
|
20027
|
-
const resolvesToGlobalNamespace = (expression, namespaceName, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
|
|
20028
|
-
const strippedExpression = stripParenExpression(expression);
|
|
20029
|
-
if (isNodeOfType(strippedExpression, "Identifier")) {
|
|
20030
|
-
if (strippedExpression.name === namespaceName && scopes.isGlobalReference(strippedExpression)) return true;
|
|
20031
|
-
const symbol = scopes.symbolFor(strippedExpression);
|
|
20032
|
-
if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbols.has(symbol.id)) return false;
|
|
20033
|
-
visitedSymbols.add(symbol.id);
|
|
20034
|
-
return resolvesToGlobalNamespace(symbol.initializer, namespaceName, scopes, visitedSymbols);
|
|
20035
|
-
}
|
|
20036
|
-
return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isGlobalObjectReference(strippedExpression.object, scopes);
|
|
20037
|
-
};
|
|
20038
20093
|
const resolvesToGlobalMethod = (expression, namespaceName, methodNames, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
|
|
20039
20094
|
const strippedExpression = stripParenExpression(expression);
|
|
20040
20095
|
if (isNodeOfType(strippedExpression, "Identifier")) {
|
|
@@ -20043,7 +20098,7 @@ const resolvesToGlobalMethod = (expression, namespaceName, methodNames, scopes,
|
|
|
20043
20098
|
visitedSymbols.add(symbol.id);
|
|
20044
20099
|
return resolvesToGlobalMethod(symbol.initializer, namespaceName, methodNames, scopes, visitedSymbols);
|
|
20045
20100
|
}
|
|
20046
|
-
return isNodeOfType(strippedExpression, "MemberExpression") && methodNames.has(getStaticPropertyName(strippedExpression) ?? "") &&
|
|
20101
|
+
return isNodeOfType(strippedExpression, "MemberExpression") && methodNames.has(getStaticPropertyName(strippedExpression) ?? "") && isProvenGlobalNamespaceReference(strippedExpression.object, namespaceName, scopes);
|
|
20047
20102
|
};
|
|
20048
20103
|
const resolvesToNativeArrayPrototype = (expression, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
|
|
20049
20104
|
const strippedExpression = stripParenExpression(expression);
|
|
@@ -20055,7 +20110,7 @@ const resolvesToNativeArrayPrototype = (expression, scopes, visitedSymbols = /*
|
|
|
20055
20110
|
}
|
|
20056
20111
|
if (isNodeOfType(strippedExpression, "MemberExpression")) {
|
|
20057
20112
|
const propertyName = getStaticPropertyName(strippedExpression);
|
|
20058
|
-
if (propertyName === "prototype") return
|
|
20113
|
+
if (propertyName === "prototype") return isProvenGlobalNamespaceReference(strippedExpression.object, "Array", scopes);
|
|
20059
20114
|
return propertyName === "__proto__" && isNodeOfType(stripParenExpression(strippedExpression.object), "ArrayExpression");
|
|
20060
20115
|
}
|
|
20061
20116
|
if (!isNodeOfType(strippedExpression, "CallExpression")) return false;
|
|
@@ -20066,23 +20121,23 @@ const resolvesToNativeArrayPrototype = (expression, scopes, visitedSymbols = /*
|
|
|
20066
20121
|
const isGlobalNamespaceReplacementTarget = (target, namespaceName, scopes) => {
|
|
20067
20122
|
const strippedTarget = stripParenExpression(target);
|
|
20068
20123
|
if (isNodeOfType(strippedTarget, "Identifier")) return strippedTarget.name === namespaceName && scopes.isGlobalReference(strippedTarget);
|
|
20069
|
-
return isNodeOfType(strippedTarget, "MemberExpression") && getStaticPropertyName(strippedTarget) === namespaceName &&
|
|
20124
|
+
return isNodeOfType(strippedTarget, "MemberExpression") && getStaticPropertyName(strippedTarget) === namespaceName && isProvenGlobalObjectReference(strippedTarget.object, scopes);
|
|
20070
20125
|
};
|
|
20071
20126
|
const isUnsafeBuiltinMemberTarget = (target, targetFunction, scopes) => {
|
|
20072
20127
|
const strippedTarget = stripParenExpression(target);
|
|
20073
20128
|
if (!isNodeOfType(strippedTarget, "MemberExpression")) return false;
|
|
20074
20129
|
const propertyName = getStaticPropertyName(strippedTarget);
|
|
20075
20130
|
if (resolvesToNativeArrayPrototype(strippedTarget.object, scopes)) return propertyName === null || propertyName === "sort";
|
|
20076
|
-
if (
|
|
20077
|
-
return
|
|
20131
|
+
if (isProvenGlobalNamespaceReference(strippedTarget.object, "Math", scopes)) return propertyName === null || propertyName === targetFunction;
|
|
20132
|
+
return isProvenGlobalObjectReference(strippedTarget.object, scopes) && (propertyName === null || propertyName === "Math");
|
|
20078
20133
|
};
|
|
20079
20134
|
const isUnsafeBuiltinMutationApiCall = (callExpression, targetFunction, scopes) => {
|
|
20080
20135
|
const target = callExpression.arguments[0];
|
|
20081
20136
|
if (!target) return false;
|
|
20082
20137
|
let propertyName = null;
|
|
20083
20138
|
if (resolvesToNativeArrayPrototype(target, scopes)) propertyName = "sort";
|
|
20084
|
-
else if (
|
|
20085
|
-
else if (
|
|
20139
|
+
else if (isProvenGlobalNamespaceReference(target, "Math", scopes)) propertyName = targetFunction;
|
|
20140
|
+
else if (isProvenGlobalObjectReference(target, scopes)) propertyName = "Math";
|
|
20086
20141
|
if (!propertyName) return false;
|
|
20087
20142
|
const canObjectExpressionSetProperty = (properties) => {
|
|
20088
20143
|
if (!isNodeOfType(properties, "ObjectExpression")) return true;
|
|
@@ -20133,7 +20188,7 @@ const hasUnsafeMathBinding = (node, scopes) => {
|
|
|
20133
20188
|
let scope = scopes.scopeFor(node);
|
|
20134
20189
|
while (scope) {
|
|
20135
20190
|
const symbol = scope.symbolsByName.get("Math");
|
|
20136
|
-
if (symbol && !RUNTIMELESS_SYMBOL_KINDS.has(symbol.kind)) return !(symbol.kind === "const" && symbol.initializer &&
|
|
20191
|
+
if (symbol && !RUNTIMELESS_SYMBOL_KINDS.has(symbol.kind)) return !(symbol.kind === "const" && symbol.initializer && isProvenGlobalNamespaceReference(symbol.initializer, "Math", scopes));
|
|
20137
20192
|
scope = scope.parent;
|
|
20138
20193
|
}
|
|
20139
20194
|
return false;
|
|
@@ -26486,6 +26541,489 @@ const hasEmailTemplateImport = (programRoot) => {
|
|
|
26486
26541
|
return found;
|
|
26487
26542
|
};
|
|
26488
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
|
|
26489
27027
|
//#region src/plugin/rules/nextjs/nextjs-no-img-element.ts
|
|
26490
27028
|
const NON_OPTIMIZABLE_SRC_PREFIX_PATTERN = /^\s*(data:|blob:)/i;
|
|
26491
27029
|
const GENERATED_URL_NAME_PATTERN = /(data|object|blob)_?url/i;
|
|
@@ -26588,9 +27126,20 @@ const nextjsNoImgElement = defineRule({
|
|
|
26588
27126
|
recommendation: "Use `next/image` so users get optimized formats, responsive srcsets, and lazy loading instead of oversized image downloads.",
|
|
26589
27127
|
create: (context) => {
|
|
26590
27128
|
if (isGeneratedImageRenderContext(context)) return {};
|
|
27129
|
+
const generatedImageOwnershipByFunction = /* @__PURE__ */ new WeakMap();
|
|
27130
|
+
const isExportedJsxGeneratedImageOwned = createExportedJsxGeneratedImageOwnershipAnalyzer(context);
|
|
26591
27131
|
return { JSXOpeningElement(node) {
|
|
26592
27132
|
if (resolveJsxElementType(node) !== "img") return;
|
|
26593
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
|
+
}
|
|
26594
27143
|
const programRoot = findProgramRoot(node);
|
|
26595
27144
|
if (programRoot && hasEmailTemplateImport(programRoot)) return;
|
|
26596
27145
|
const srcAttribute = findJsxAttribute(node.attributes, "src");
|
|
@@ -27936,7 +28485,11 @@ const isProvenNativeReadMethod = (ref, methodName) => Boolean(ref.resolved?.defs
|
|
|
27936
28485
|
}));
|
|
27937
28486
|
//#endregion
|
|
27938
28487
|
//#region src/plugin/rules/state-and-effects/utils/effect/react.ts
|
|
27939
|
-
const
|
|
28488
|
+
const KNOWN_COMPONENT_WRAPPER_NAMES = new Set([
|
|
28489
|
+
"memo",
|
|
28490
|
+
"forwardRef",
|
|
28491
|
+
"observer"
|
|
28492
|
+
]);
|
|
27940
28493
|
const startsWithUppercase = (name) => Boolean(name && name.length > 0 && name[0] >= "A" && name[0] <= "Z");
|
|
27941
28494
|
const isReactFunctionalComponent = (node) => {
|
|
27942
28495
|
if (!node) return false;
|
|
@@ -27958,7 +28511,7 @@ const isReactFunctionalHOC = (analysis, node) => {
|
|
|
27958
28511
|
const isWrappedInline = () => {
|
|
27959
28512
|
if (!isNodeOfType(init, "CallExpression")) return false;
|
|
27960
28513
|
if (!isNodeOfType(init.callee, "Identifier")) return false;
|
|
27961
|
-
if (
|
|
28514
|
+
if (KNOWN_COMPONENT_WRAPPER_NAMES.has(init.callee.name)) return false;
|
|
27962
28515
|
const firstArg = init.arguments?.[0];
|
|
27963
28516
|
if (!firstArg) return false;
|
|
27964
28517
|
return isNodeOfType(firstArg, "ArrowFunctionExpression") || isNodeOfType(firstArg, "FunctionExpression");
|
|
@@ -27978,7 +28531,7 @@ const isReactFunctionalHOC = (analysis, node) => {
|
|
|
27978
28531
|
if (!args.includes(refId)) continue;
|
|
27979
28532
|
const callee = parent.callee;
|
|
27980
28533
|
const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : isNodeOfType(callee, "CallExpression") && isNodeOfType(callee.callee, "Identifier") ? callee.callee.name : null;
|
|
27981
|
-
if (calleeName != null && !
|
|
28534
|
+
if (calleeName != null && !KNOWN_COMPONENT_WRAPPER_NAMES.has(calleeName)) return true;
|
|
27982
28535
|
}
|
|
27983
28536
|
return false;
|
|
27984
28537
|
};
|
|
@@ -31030,72 +31583,6 @@ const isReactNativeDependencyName = (dependencyName) => {
|
|
|
31030
31583
|
return false;
|
|
31031
31584
|
};
|
|
31032
31585
|
//#endregion
|
|
31033
|
-
//#region src/plugin/utils/read-nearest-package-manifest.ts
|
|
31034
|
-
const cachedPackageDirectoryByFilename = /* @__PURE__ */ new Map();
|
|
31035
|
-
const cachedManifestByPackageDirectory = /* @__PURE__ */ new Map();
|
|
31036
|
-
const resetManifestCaches = () => {
|
|
31037
|
-
cachedPackageDirectoryByFilename.clear();
|
|
31038
|
-
cachedManifestByPackageDirectory.clear();
|
|
31039
|
-
};
|
|
31040
|
-
const findNearestPackageDirectory = (filename) => {
|
|
31041
|
-
if (!filename) return null;
|
|
31042
|
-
const fromCache = cachedPackageDirectoryByFilename.get(filename);
|
|
31043
|
-
if (fromCache !== void 0) {
|
|
31044
|
-
if (isProbeRecorderActive()) {
|
|
31045
|
-
let probedDirectory = path.dirname(filename);
|
|
31046
|
-
while (true) {
|
|
31047
|
-
recordExistenceProbe(path.join(probedDirectory, "package.json"));
|
|
31048
|
-
if (probedDirectory === fromCache) break;
|
|
31049
|
-
const parentDirectory = path.dirname(probedDirectory);
|
|
31050
|
-
if (parentDirectory === probedDirectory) break;
|
|
31051
|
-
probedDirectory = parentDirectory;
|
|
31052
|
-
}
|
|
31053
|
-
}
|
|
31054
|
-
return fromCache;
|
|
31055
|
-
}
|
|
31056
|
-
let currentDirectory = path.dirname(filename);
|
|
31057
|
-
while (true) {
|
|
31058
|
-
const candidatePackageJsonPath = path.join(currentDirectory, "package.json");
|
|
31059
|
-
recordExistenceProbe(candidatePackageJsonPath);
|
|
31060
|
-
let hasPackageJson = false;
|
|
31061
|
-
try {
|
|
31062
|
-
hasPackageJson = fs.statSync(candidatePackageJsonPath).isFile();
|
|
31063
|
-
} catch {
|
|
31064
|
-
hasPackageJson = false;
|
|
31065
|
-
}
|
|
31066
|
-
if (hasPackageJson) {
|
|
31067
|
-
cachedPackageDirectoryByFilename.set(filename, currentDirectory);
|
|
31068
|
-
return currentDirectory;
|
|
31069
|
-
}
|
|
31070
|
-
const parentDirectory = path.dirname(currentDirectory);
|
|
31071
|
-
if (parentDirectory === currentDirectory) {
|
|
31072
|
-
cachedPackageDirectoryByFilename.set(filename, null);
|
|
31073
|
-
return null;
|
|
31074
|
-
}
|
|
31075
|
-
currentDirectory = parentDirectory;
|
|
31076
|
-
}
|
|
31077
|
-
};
|
|
31078
|
-
const readNearestPackageManifest = (filename) => {
|
|
31079
|
-
const packageDirectory = findNearestPackageDirectory(filename);
|
|
31080
|
-
if (!packageDirectory) return null;
|
|
31081
|
-
return readPackageManifest(packageDirectory);
|
|
31082
|
-
};
|
|
31083
|
-
const readPackageManifest = (packageDirectory) => {
|
|
31084
|
-
const packageJsonPath = path.join(packageDirectory, "package.json");
|
|
31085
|
-
recordContentProbe(packageJsonPath);
|
|
31086
|
-
const cached = cachedManifestByPackageDirectory.get(packageDirectory);
|
|
31087
|
-
if (cached !== void 0) return cached;
|
|
31088
|
-
let manifest = null;
|
|
31089
|
-
try {
|
|
31090
|
-
const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
31091
|
-
if (typeof parsed === "object" && parsed !== null) manifest = parsed;
|
|
31092
|
-
} catch {
|
|
31093
|
-
manifest = null;
|
|
31094
|
-
}
|
|
31095
|
-
cachedManifestByPackageDirectory.set(packageDirectory, manifest);
|
|
31096
|
-
return manifest;
|
|
31097
|
-
};
|
|
31098
|
-
//#endregion
|
|
31099
31586
|
//#region src/plugin/utils/classify-package-platform.ts
|
|
31100
31587
|
const WEB_FRAMEWORK_DEPENDENCY_NAMES = new Set([
|
|
31101
31588
|
"next",
|
|
@@ -32148,6 +32635,24 @@ const isBuiltinNamespaceCallee = (callee) => {
|
|
|
32148
32635
|
}
|
|
32149
32636
|
return false;
|
|
32150
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
|
+
};
|
|
32151
32656
|
const isSimpleExpression$1 = (analysis, expression, effectFn, visitedDeclarators) => {
|
|
32152
32657
|
let isSimple = true;
|
|
32153
32658
|
walkAst(expression, (child) => {
|
|
@@ -32195,7 +32700,7 @@ const noChainStateUpdates = defineRule({
|
|
|
32195
32700
|
if (!effectFnRefs || !depsRefs) return;
|
|
32196
32701
|
const effectFn = getEffectFn(analysis, node);
|
|
32197
32702
|
if (!effectFn) return;
|
|
32198
|
-
const stateDeps = depsRefs.flatMap((
|
|
32703
|
+
const stateDeps = depsRefs.flatMap((reference) => getDependencyStateRefs(analysis, context, reference));
|
|
32199
32704
|
if (stateDeps.length === 0) return;
|
|
32200
32705
|
if (stateDeps.every((ref) => isExternallyDrivenState(analysis, ref))) return;
|
|
32201
32706
|
const stateDepDeclarators = new Set(stateDeps.map((ref) => getUseStateDeclarator(ref)).filter((declarator) => declarator !== null));
|
|
@@ -32454,16 +32959,7 @@ const isProvenIntrinsicJsxElement = (openingElement, scopes) => {
|
|
|
32454
32959
|
return isIntrinsicValue(openingElement.name);
|
|
32455
32960
|
};
|
|
32456
32961
|
//#endregion
|
|
32457
|
-
//#region src/plugin/
|
|
32458
|
-
const pathStartsWith$1 = (propertyPath, prefix) => prefix.every((propertyName, index) => propertyPath[index] === propertyName);
|
|
32459
|
-
const collectMemberExpression = (identifier) => {
|
|
32460
|
-
let expression = findTransparentExpressionRoot(identifier);
|
|
32461
|
-
while (expression.parent && isNodeOfType(expression.parent, "MemberExpression") && expression.parent.object === expression) {
|
|
32462
|
-
if (!getStaticPropertyName(expression.parent)) return null;
|
|
32463
|
-
expression = findTransparentExpressionRoot(expression.parent);
|
|
32464
|
-
}
|
|
32465
|
-
return expression;
|
|
32466
|
-
};
|
|
32962
|
+
//#region src/plugin/utils/is-inline-intrinsic-ref-callback.ts
|
|
32467
32963
|
const isInlineIntrinsicRefCallback = (functionNode, scopes) => {
|
|
32468
32964
|
const functionExpression = findTransparentExpressionRoot(functionNode);
|
|
32469
32965
|
if (!isFunctionLike$1(functionExpression) || functionExpression.async || functionExpression.generator) return false;
|
|
@@ -32474,6 +32970,17 @@ const isInlineIntrinsicRefCallback = (functionNode, scopes) => {
|
|
|
32474
32970
|
const openingElement = attribute.parent;
|
|
32475
32971
|
return Boolean(openingElement && isNodeOfType(openingElement, "JSXOpeningElement") && isProvenIntrinsicJsxElement(openingElement, scopes));
|
|
32476
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
|
+
};
|
|
32477
32984
|
const isSafeCreateRefCallbackCurrentWrite = (referenceNode, accessedPropertyPath, targetPropertyPath, scopes) => {
|
|
32478
32985
|
if (accessedPropertyPath.length !== targetPropertyPath.length + 1 || !pathStartsWith$1(accessedPropertyPath, targetPropertyPath) || accessedPropertyPath[targetPropertyPath.length] !== "current") return false;
|
|
32479
32986
|
const memberExpression = collectMemberExpression(referenceNode);
|
|
@@ -35417,17 +35924,178 @@ const visitSynchronousFunctionBodies = (analysisFunctions, visitor) => {
|
|
|
35417
35924
|
walkInsideStatementBlocks(analysisFunction.body, visitor);
|
|
35418
35925
|
}
|
|
35419
35926
|
};
|
|
35420
|
-
const
|
|
35421
|
-
const
|
|
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();
|
|
35422
36010
|
visitSynchronousFunctionBodies(analysisFunctions, (child) => {
|
|
35423
36011
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
35424
36012
|
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
35425
36013
|
const stateName = setterToStateName.get(child.callee.name);
|
|
35426
|
-
if (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);
|
|
35427
36023
|
});
|
|
35428
|
-
return
|
|
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;
|
|
35429
36088
|
};
|
|
35430
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
|
+
]);
|
|
35431
36099
|
const isFunctionShapedReturn = (returnedValue, setterToStateName, isExplicitReturnStatement) => {
|
|
35432
36100
|
if (isNodeOfType(returnedValue, "ArrowFunctionExpression") || isNodeOfType(returnedValue, "FunctionExpression")) return true;
|
|
35433
36101
|
if (isNodeOfType(returnedValue, "CallExpression")) {
|
|
@@ -35478,27 +36146,135 @@ const callsOpaqueExternalSetter = (analysisFunctions, setterToStateName) => {
|
|
|
35478
36146
|
});
|
|
35479
36147
|
return didFindOpaqueSetterCall;
|
|
35480
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
|
+
};
|
|
35481
36256
|
const isExternalSyncNode = (node) => {
|
|
35482
36257
|
if (isNodeOfType(node, "NewExpression")) return isNodeOfType(node.callee, "Identifier") && EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS.has(node.callee.name);
|
|
35483
36258
|
if (isNodeOfType(node, "AssignmentExpression")) return isNodeOfType(node.left, "MemberExpression") && isNodeOfType(node.left.property, "Identifier") && node.left.property.name === "current";
|
|
35484
36259
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
35485
36260
|
if (isNodeOfType(node.callee, "Identifier")) return EXTERNAL_SYNC_DIRECT_CALLEE_NAMES.has(node.callee.name);
|
|
35486
|
-
if (!isNodeOfType(node.callee, "MemberExpression")
|
|
35487
|
-
const propertyName = node.callee
|
|
36261
|
+
if (!isNodeOfType(node.callee, "MemberExpression")) return false;
|
|
36262
|
+
const propertyName = getStaticPropertyName(node.callee);
|
|
36263
|
+
if (propertyName === null) return false;
|
|
35488
36264
|
if (EXTERNAL_SYNC_MEMBER_METHOD_NAMES.has(propertyName)) return true;
|
|
35489
36265
|
if (isBrowserStorageReceiver(node.callee.object)) return true;
|
|
35490
36266
|
if (!EXTERNAL_SYNC_AMBIGUOUS_HTTP_METHOD_NAMES.has(propertyName)) return false;
|
|
35491
36267
|
const receiverRootName = getRootIdentifierName(node.callee.object);
|
|
35492
36268
|
return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
|
|
35493
36269
|
};
|
|
35494
|
-
const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName) => {
|
|
36270
|
+
const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, scopes, allowCommittedDomSync) => {
|
|
35495
36271
|
if (!isFunctionLike$1(effectCallback)) return false;
|
|
35496
36272
|
if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
|
|
35497
36273
|
if (isFunctionShapedReturn(effectCallback.body, setterToStateName, false)) return true;
|
|
35498
36274
|
} else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, true)) return true;
|
|
35499
36275
|
let didFindExternalCall = false;
|
|
35500
36276
|
visitSynchronousFunctionBodies(analysisFunctions, (child) => {
|
|
35501
|
-
if (isExternalSyncNode(child)) didFindExternalCall = true;
|
|
36277
|
+
if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
|
|
35502
36278
|
});
|
|
35503
36279
|
return didFindExternalCall;
|
|
35504
36280
|
};
|
|
@@ -35514,32 +36290,45 @@ const noEffectChain = defineRule({
|
|
|
35514
36290
|
const useStateBindings = collectUseStateBindings(componentBody);
|
|
35515
36291
|
if (useStateBindings.length === 0) return;
|
|
35516
36292
|
const setterToStateName = /* @__PURE__ */ new Map();
|
|
35517
|
-
|
|
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
|
+
}
|
|
35518
36303
|
const storageSetterNames = collectStorageHookSetterNames(componentBody);
|
|
35519
36304
|
const effectInfos = [];
|
|
35520
36305
|
for (const effectCall of findTopLevelEffectCalls(componentBody)) {
|
|
35521
36306
|
const callback = getEffectCallback(effectCall, context.scopes);
|
|
35522
36307
|
if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
|
|
35523
36308
|
const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
|
|
35524
|
-
const
|
|
36309
|
+
const stateWrites = collectStateWritesInEffect(analysisFunctions, setterToStateName, context.scopes);
|
|
36310
|
+
const writtenStateNames = new Set(stateWrites.keys());
|
|
35525
36311
|
effectInfos.push({
|
|
35526
36312
|
node: effectCall,
|
|
35527
36313
|
depNames: collectDepIdentifierNames(effectCall),
|
|
35528
|
-
|
|
35529
|
-
|
|
36314
|
+
stateWrites,
|
|
36315
|
+
analysisFunctions,
|
|
36316
|
+
isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName, context.scopes, writtenStateNames.size === 0) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
|
|
35530
36317
|
});
|
|
35531
36318
|
}
|
|
35532
36319
|
if (effectInfos.length < 2) return;
|
|
35533
36320
|
const reportedNodes = /* @__PURE__ */ new Set();
|
|
35534
36321
|
for (const writerEffect of effectInfos) {
|
|
35535
36322
|
if (writerEffect.isExternalSync) continue;
|
|
35536
|
-
if (writerEffect.
|
|
36323
|
+
if (writerEffect.stateWrites.size === 0) continue;
|
|
35537
36324
|
for (const readerEffect of effectInfos) {
|
|
35538
36325
|
if (readerEffect === writerEffect) continue;
|
|
35539
36326
|
if (readerEffect.isExternalSync) continue;
|
|
35540
36327
|
if (readerEffect.depNames.size === 0) continue;
|
|
35541
36328
|
let chainedStateName = null;
|
|
35542
|
-
for (const writtenName of writerEffect.
|
|
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;
|
|
35543
36332
|
chainedStateName = writtenName;
|
|
35544
36333
|
break;
|
|
35545
36334
|
}
|
|
@@ -39206,6 +39995,18 @@ const functionContainsProvenReactHookCall = (functionNode, scopes) => {
|
|
|
39206
39995
|
return containsReactHookCall;
|
|
39207
39996
|
};
|
|
39208
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
|
|
39209
40010
|
//#region src/plugin/utils/function-returns-props-children.ts
|
|
39210
40011
|
const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
|
|
39211
40012
|
if (!isFunctionLike$1(functionNode) || functionNode.params.length === 0) return false;
|
|
@@ -39238,17 +40039,8 @@ const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
|
|
|
39238
40039
|
}, controlFlow);
|
|
39239
40040
|
};
|
|
39240
40041
|
//#endregion
|
|
39241
|
-
//#region src/plugin/utils/function-
|
|
39242
|
-
const
|
|
39243
|
-
const candidate = stripParenExpression(expression);
|
|
39244
|
-
return isNodeOfType(candidate, "Literal") && candidate.value === null;
|
|
39245
|
-
};
|
|
39246
|
-
const functionReturnsOnlyNull = (functionNode) => {
|
|
39247
|
-
if (!isFunctionLike$1(functionNode)) return false;
|
|
39248
|
-
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
|
|
39249
|
-
const returnStatements = collectFunctionReturnStatements(functionNode);
|
|
39250
|
-
return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
|
|
39251
|
-
};
|
|
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);
|
|
39252
40044
|
//#endregion
|
|
39253
40045
|
//#region src/plugin/utils/is-proven-styled-component-expression.ts
|
|
39254
40046
|
const findFactoryRoot = (node) => {
|
|
@@ -39281,17 +40073,16 @@ const isProvenStyledComponentExpression = (expression, scopes) => {
|
|
|
39281
40073
|
//#region src/plugin/utils/is-proven-react-component-symbol.ts
|
|
39282
40074
|
const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
39283
40075
|
const LEGACY_REACT_COMPONENT_FACTORY_NAMES = new Set(["createClass", "createReactClass"]);
|
|
39284
|
-
const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
|
|
39285
40076
|
const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
39286
40077
|
const candidate = stripParenExpression(expression);
|
|
39287
|
-
if (isInlineFunctionExpression(candidate)) return
|
|
40078
|
+
if (isInlineFunctionExpression(candidate)) return functionHasReactComponentEvidence(candidate, scopes, controlFlow);
|
|
39288
40079
|
if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
|
|
39289
40080
|
if (isProvenStyledComponentExpression(candidate, scopes)) return true;
|
|
39290
40081
|
if (isNodeOfType(candidate, "Identifier")) {
|
|
39291
40082
|
const symbol = scopes.symbolFor(candidate);
|
|
39292
40083
|
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
|
|
39293
40084
|
visitedSymbolIds.add(symbol.id);
|
|
39294
|
-
if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return
|
|
40085
|
+
if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasReactComponentEvidence(symbol.declarationNode, scopes, controlFlow);
|
|
39295
40086
|
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
|
|
39296
40087
|
return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
|
|
39297
40088
|
}
|
|
@@ -39318,7 +40109,7 @@ const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentRefe
|
|
|
39318
40109
|
for (const candidateSymbol of candidateSymbols) {
|
|
39319
40110
|
if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
|
|
39320
40111
|
if (isComponentDeclaration(candidateSymbol.declarationNode)) {
|
|
39321
|
-
if (
|
|
40112
|
+
if (functionHasReactComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
|
|
39322
40113
|
continue;
|
|
39323
40114
|
}
|
|
39324
40115
|
const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
|
|
@@ -43104,6 +43895,53 @@ const noPropCallbackInEffect = defineRule({
|
|
|
43104
43895
|
});
|
|
43105
43896
|
//#endregion
|
|
43106
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
|
+
};
|
|
43107
43945
|
const isPreservedThroughConciseArrow = (callExpression, scopes) => {
|
|
43108
43946
|
let node = callExpression;
|
|
43109
43947
|
let parent = node.parent;
|
|
@@ -43150,7 +43988,10 @@ const noPropCallbackInRender = defineRule({
|
|
|
43150
43988
|
create: (context) => ({ CallExpression(node) {
|
|
43151
43989
|
if (!isResultDiscardedCall(node)) return;
|
|
43152
43990
|
if (isPreservedThroughConciseArrow(node, context.scopes)) return;
|
|
43153
|
-
|
|
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;
|
|
43154
43995
|
const analysis = getProgramAnalysis(node);
|
|
43155
43996
|
if (!analysis) return;
|
|
43156
43997
|
const callee = stripParenExpression(node.callee);
|
|
@@ -43886,11 +44727,110 @@ const isSameRefCurrentMember = (node, refSymbol, scopes) => {
|
|
|
43886
44727
|
return isNodeOfType(receiver, "Identifier") && resolveConstIdentifierAlias(receiver, scopes)?.id === refSymbol.id;
|
|
43887
44728
|
};
|
|
43888
44729
|
const isSameRefCurrentAlias = (node, refSymbol, scopes) => {
|
|
43889
|
-
|
|
43890
|
-
if (
|
|
43891
|
-
|
|
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);
|
|
43892
44734
|
return aliasSymbol?.kind === "const" && aliasSymbol.initializer !== null && isSameRefCurrentMember(stripParenExpression(aliasSymbol.initializer), refSymbol, scopes);
|
|
43893
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
|
+
};
|
|
43894
44834
|
const isEmptySentinel = (node, scopes) => isNodeOfType(node, "Literal") && node.value === null || isNodeOfType(node, "Identifier") && node.name === "undefined" && scopes.isGlobalReference(node);
|
|
43895
44835
|
const hasRepeatedExecutionAncestor = (node, stop) => {
|
|
43896
44836
|
let ancestor = node.parent;
|
|
@@ -43939,18 +44879,22 @@ const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol
|
|
|
43939
44879
|
const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes) => {
|
|
43940
44880
|
if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return true;
|
|
43941
44881
|
if (assignmentExpression.operator !== "=") return false;
|
|
44882
|
+
const renderOwner = findRenderPhaseComponentOrHook(assignmentExpression, scopes);
|
|
44883
|
+
if (!renderOwner) return false;
|
|
43942
44884
|
let descendant = assignmentExpression;
|
|
43943
44885
|
let ancestor = descendant.parent;
|
|
43944
44886
|
while (ancestor) {
|
|
43945
|
-
|
|
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") && [
|
|
43946
44890
|
"===",
|
|
43947
44891
|
"==",
|
|
43948
44892
|
"!==",
|
|
43949
44893
|
"!="
|
|
43950
|
-
].includes(
|
|
43951
|
-
const { left, right } =
|
|
44894
|
+
].includes(test.operator)) {
|
|
44895
|
+
const { left, right } = test;
|
|
43952
44896
|
const comparesEmptySentinel = isSameRefCurrentAlias(left, refSymbol, scopes) && isEmptySentinel(right, scopes) || isSameRefCurrentAlias(right, refSymbol, scopes) && isEmptySentinel(left, scopes);
|
|
43953
|
-
const guardedBranch =
|
|
44897
|
+
const guardedBranch = test.operator === "===" || test.operator === "==" ? ancestor.consequent : ancestor.alternate;
|
|
43954
44898
|
if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch && !hasRepeatedExecutionAncestor(assignmentExpression, guardedBranch) && hasNoPriorCoExecutableWrite(assignmentExpression, guardedBranch, refSymbol, scopes)) return true;
|
|
43955
44899
|
}
|
|
43956
44900
|
descendant = ancestor;
|
|
@@ -46585,6 +47529,185 @@ const noUnescapedEntities = defineRule({
|
|
|
46585
47529
|
} })
|
|
46586
47530
|
});
|
|
46587
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
|
|
46588
47711
|
//#region src/plugin/rules/performance/no-unguarded-browser-global-in-render-or-hook-init.ts
|
|
46589
47712
|
const BROWSER_GLOBAL_NAMES = new Set([
|
|
46590
47713
|
"window",
|
|
@@ -46691,7 +47814,9 @@ const noUnguardedBrowserGlobalInRenderOrHookInit = defineRule({
|
|
|
46691
47814
|
if (fileIsEmailTemplate) return;
|
|
46692
47815
|
if (isGeneratedImageRenderContext(context, findEnclosingJsxOpeningElement(node) ?? node)) return;
|
|
46693
47816
|
if (isGatedByFalsyInitialState(node, context.scopes)) return;
|
|
47817
|
+
if (isGatedByFalsyServerSnapshot(node, context.scopes, context.filename)) return;
|
|
46694
47818
|
if (isAfterClientOnlyEarlyReturn(node, componentOrHookNode, context.scopes)) return;
|
|
47819
|
+
if (isAfterFalsyServerSnapshotEarlyReturn(node, componentOrHookNode, context.scopes, context.filename)) return;
|
|
46695
47820
|
if (isInsideAvailabilityGuard(node, browserGlobalName, context)) return;
|
|
46696
47821
|
if (isAfterAvailabilityEarlyExit(node, componentOrHookNode, browserGlobalName, context)) return;
|
|
46697
47822
|
reportedNodes.add(node);
|
|
@@ -50894,6 +52019,31 @@ const preferModuleScopePureFunction = defineRule({
|
|
|
50894
52019
|
}
|
|
50895
52020
|
});
|
|
50896
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
|
|
50897
52047
|
//#region src/plugin/rules/architecture/prefer-module-scope-static-value.ts
|
|
50898
52048
|
const MUTATING_RECEIVER_METHOD_NAMES = new Set([...MUTATING_ARRAY_METHODS, ...MUTATING_COLLECTION_METHODS]);
|
|
50899
52049
|
const isMutationContext = (referenceIdentifier) => {
|
|
@@ -51007,9 +52157,9 @@ const isImpureCall = (node, scopes) => {
|
|
|
51007
52157
|
const callee = node.callee;
|
|
51008
52158
|
if (isNodeOfType(callee, "Identifier")) return isImpureBareCallee(callee, scopes);
|
|
51009
52159
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return false;
|
|
51010
|
-
if (!isNodeOfType(callee.object, "Identifier")) return false;
|
|
51011
52160
|
if (!isNodeOfType(callee.property, "Identifier")) return false;
|
|
51012
|
-
|
|
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;
|
|
51013
52163
|
};
|
|
51014
52164
|
const containsImpureExpression = (expression, scopes) => {
|
|
51015
52165
|
let foundImpure = false;
|
|
@@ -55778,16 +56928,6 @@ const rnListCallbackPerRow = defineRule({
|
|
|
55778
56928
|
}
|
|
55779
56929
|
});
|
|
55780
56930
|
//#endregion
|
|
55781
|
-
//#region src/plugin/utils/get-require-call-source.ts
|
|
55782
|
-
const getRequireCallSource = (expression) => {
|
|
55783
|
-
if (isNodeOfType(expression, "MemberExpression")) return getRequireCallSource(expression.object);
|
|
55784
|
-
if (!isNodeOfType(expression, "CallExpression")) return null;
|
|
55785
|
-
if (!isNodeOfType(expression.callee, "Identifier") || expression.callee.name !== "require") return null;
|
|
55786
|
-
const [firstArgument] = expression.arguments ?? [];
|
|
55787
|
-
if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
|
|
55788
|
-
return typeof firstArgument.value === "string" ? firstArgument.value : null;
|
|
55789
|
-
};
|
|
55790
|
-
//#endregion
|
|
55791
56931
|
//#region src/plugin/utils/get-initializer-module-source.ts
|
|
55792
56932
|
const getInitializerModuleSource = (contextNode, initializer) => {
|
|
55793
56933
|
const requireSource = getRequireCallSource(initializer);
|
|
@@ -62566,9 +63706,35 @@ const serverDedupProps = defineRule({
|
|
|
62566
63706
|
});
|
|
62567
63707
|
//#endregion
|
|
62568
63708
|
//#region src/plugin/rules/server/server-fetch-without-revalidate.ts
|
|
62569
|
-
const
|
|
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) => {
|
|
62570
63734
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
62571
|
-
|
|
63735
|
+
const callee = stripParenExpression(node.callee);
|
|
63736
|
+
if (!isNodeOfType(callee, "Identifier") || callee.name !== "fetch") return false;
|
|
63737
|
+
return isExactGlobalFetchValue(callee, context);
|
|
62572
63738
|
};
|
|
62573
63739
|
const getPropertyKeyName$1 = (property) => {
|
|
62574
63740
|
if (!isNodeOfType(property, "Property")) return null;
|
|
@@ -62614,7 +63780,7 @@ const serverFetchWithoutRevalidate = defineRule({
|
|
|
62614
63780
|
},
|
|
62615
63781
|
CallExpression(node) {
|
|
62616
63782
|
if (!isServerSideFile) return;
|
|
62617
|
-
if (!isFetchCall(node)) return;
|
|
63783
|
+
if (!isFetchCall(node, context)) return;
|
|
62618
63784
|
if (isMutatingFetchCall(node)) return;
|
|
62619
63785
|
const optionsArg = node.arguments?.[1];
|
|
62620
63786
|
if (optionsArg) {
|
|
@@ -70096,6 +71262,7 @@ const CROSS_FILE_RULE_IDS = new Set([
|
|
|
70096
71262
|
"exhaustive-deps",
|
|
70097
71263
|
"no-barrel-import",
|
|
70098
71264
|
"nextjs-missing-metadata",
|
|
71265
|
+
"nextjs-no-img-element",
|
|
70099
71266
|
"nextjs-no-use-search-params-without-suspense",
|
|
70100
71267
|
"no-dynamic-import-path",
|
|
70101
71268
|
"no-full-lodash-import",
|
|
@@ -70327,12 +71494,12 @@ const CROSS_FILE_DEPENDENCY_COLLECTORS = new Map([
|
|
|
70327
71494
|
]);
|
|
70328
71495
|
/**
|
|
70329
71496
|
* Cross-file rules whose dependency set CANNOT be soundly bounded — they are
|
|
70330
|
-
* excluded from fingerprinting and re-lint every file on every scan.
|
|
70331
|
-
*
|
|
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
|
|
70332
71499
|
* `CROSS_FILE_DEPENDENCY_COLLECTORS` (the core guard test enforces the
|
|
70333
71500
|
* partition), forcing a conscious classification.
|
|
70334
71501
|
*/
|
|
70335
|
-
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"]);
|
|
70336
71503
|
/**
|
|
70337
71504
|
* Runs the collectors for `ruleIds` over one file and returns every
|
|
70338
71505
|
* filesystem probe they made — the file's cross-file dependency set.
|