fix(javascript): preserve lexical open and exact template semantics

This commit is contained in:
morluto
2026-10-09 01:16:24 +08:00
parent fe297e56e8
commit d6cc9ca3a6
8 changed files with 106 additions and 20 deletions
@@ -11,7 +11,10 @@ import type {
JavaScriptStaticPathContext,
JavaScriptStaticStorage,
} from "./javascriptStaticAnalysisTypes.js";
import { semanticStaticPropertyName } from "./javascriptAstValues.js";
import {
readExactJavaScriptLiteral,
semanticStaticPropertyName,
} from "./javascriptAstValues.js";
import { compareCodePoints } from "../canonicalOrdering.js";
export {
@@ -179,10 +182,8 @@ export const staticPathResolutionContext = (
const staticPathAt = (node: t.Node): string | undefined => {
if (t.isStringLiteral(node)) return node.value;
if (t.isTemplateLiteral(node) && node.expressions.length === 0) {
const value = node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw;
return value;
}
if (t.isTemplateLiteral(node) && node.expressions.length === 0)
return stringValue(node);
if (t.isBinaryExpression(node, { operator: "+" })) {
const left = staticPathAt(node.left);
const right = staticPathAt(node.right);
@@ -447,6 +448,9 @@ const xhrOpenUrl = (
): string | undefined => {
const url = stringValue(urlNode);
if (url === undefined) return undefined;
// A lexical fact is stronger than the receiver's spelling. In particular,
// browser Window and Document globals overload `open` with URL/content APIs.
if (receiverFact === "window") return undefined;
const method = stringValue(methodNode);
// IndexedDB and Cache Storage `open` take a name and a version, so a
// receiver spelled as storage needs a standard HTTP method literal.
@@ -458,8 +462,7 @@ const xhrOpenUrl = (
storageKind(name) !== undefined;
if (method === undefined)
return storage ||
receiverFact === "window" ||
WINDOW_OPEN_CALL.test(name) ||
(receiverFact === undefined && WINDOW_OPEN_CALL.test(name)) ||
FS_OPEN_FLAGS.has(url) ||
BROWSING_CONTEXT_KEYWORDS.has(url.toLowerCase())
? undefined
@@ -600,10 +603,11 @@ const memberPropertyName = (
export const stringValue = (
node: t.Node | null | undefined,
): string | undefined => {
if (t.isStringLiteral(node)) return node.value;
if (t.isTemplateLiteral(node) && node.expressions.length === 0)
return node.quasis[0]?.value.cooked ?? undefined;
return undefined;
if (node === null || node === undefined) return undefined;
const literal = readExactJavaScriptLiteral(node);
return literal.found && typeof literal.value === "string"
? literal.value
: undefined;
};
/** Return a string or numeric argument literal. */