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