mirror of
https://github.com/morluto/rea.git
synced 2026-11-06 14:56:32 +00:00
fix(javascript): preserve renderer path anchors
This commit is contained in:
1 parent
d0e695b42a
commit
a6488a96a9
7 files changed
+259
-65
No files matched your search
@@ -187,14 +187,25 @@ export const staticPathResolutionContext = (
|
||||
? "filesystem-expression"
|
||||
: "module-specifier";
|
||||
|
||||
interface StaticPathFact {
|
||||
readonly path: string;
|
||||
readonly base: "source-directory" | "literal-path" | "unknown";
|
||||
}
|
||||
|
||||
/** Resolve a path string composed only from inert literal syntax. */
|
||||
export const staticPath = (
|
||||
node: t.Node,
|
||||
pathIntrinsics?: JavaScriptStaticPathIntrinsicQuery,
|
||||
): string | undefined => {
|
||||
node = unwrapJavaScriptExpression(node).node;
|
||||
): string | undefined => staticPathFact(node, pathIntrinsics)?.path;
|
||||
|
||||
/** Parse spelling and its source-directory provenance together at the owning boundary. */
|
||||
const staticPathFact = (
|
||||
raw: t.Node,
|
||||
pathIntrinsics?: JavaScriptStaticPathIntrinsicQuery,
|
||||
): StaticPathFact | undefined => {
|
||||
const node = unwrapJavaScriptExpression(raw).node;
|
||||
const value = stringValue(node);
|
||||
if (value !== undefined) return value;
|
||||
if (value !== undefined) return { path: value, base: "literal-path" };
|
||||
if (t.isBinaryExpression(node, { operator: "+" }))
|
||||
return staticConcatenation(node, pathIntrinsics);
|
||||
if (t.isCallExpression(node) || t.isNewExpression(node))
|
||||
@@ -202,6 +213,22 @@ export const staticPath = (
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/** Admit file expressions only when the normalized spelling retains a proven base. */
|
||||
export const staticFileExpressionPath = (
|
||||
node: t.Node,
|
||||
pathIntrinsics?: JavaScriptStaticPathIntrinsicQuery,
|
||||
):
|
||||
| {
|
||||
readonly path: string;
|
||||
readonly base: "source-directory" | "literal-path";
|
||||
}
|
||||
| undefined => {
|
||||
const fact = staticPathFact(node, pathIntrinsics);
|
||||
return fact === undefined || fact.base === "unknown"
|
||||
? undefined
|
||||
: { path: fact.path, base: fact.base };
|
||||
};
|
||||
|
||||
/**
|
||||
* Concatenate inert text. A leading source directory contributes no text:
|
||||
* `__dirname + "/preload.js"` names a file inside it, while text without a
|
||||
@@ -210,7 +237,7 @@ export const staticPath = (
|
||||
const staticConcatenation = (
|
||||
node: t.BinaryExpression,
|
||||
pathIntrinsics?: JavaScriptStaticPathIntrinsicQuery,
|
||||
): string | undefined => {
|
||||
): StaticPathFact | undefined => {
|
||||
const operands: t.Node[] = [];
|
||||
let current: t.Node = node;
|
||||
while (t.isBinaryExpression(current, { operator: "+" })) {
|
||||
@@ -220,43 +247,62 @@ const staticConcatenation = (
|
||||
const anchored = isDirectoryPath(current, pathIntrinsics);
|
||||
if (!anchored) operands.push(current);
|
||||
let text = "";
|
||||
let literal = true;
|
||||
for (const operand of operands.reverse()) {
|
||||
const value = staticPath(operand, pathIntrinsics);
|
||||
const value = staticPathFact(operand, pathIntrinsics);
|
||||
if (value === undefined) return undefined;
|
||||
text += value;
|
||||
text += value.path;
|
||||
literal &&= value.base === "literal-path";
|
||||
}
|
||||
if (!anchored) return text;
|
||||
return text.startsWith("/") ? posix.join(".", text) : undefined;
|
||||
if (!anchored)
|
||||
return { path: text, base: literal ? "literal-path" : "unknown" };
|
||||
return text.startsWith("/")
|
||||
? {
|
||||
path: posix.join(".", text),
|
||||
base: literal ? "source-directory" : "unknown",
|
||||
}
|
||||
: undefined;
|
||||
};
|
||||
|
||||
const staticCallPath = (
|
||||
node: t.CallExpression | t.NewExpression,
|
||||
pathIntrinsics?: JavaScriptStaticPathIntrinsicQuery,
|
||||
): string | undefined => {
|
||||
): StaticPathFact | undefined => {
|
||||
if (t.isCallExpression(node) && pathIntrinsics?.(node.callee))
|
||||
return staticResolvedPath(node, pathIntrinsics);
|
||||
if (
|
||||
t.isCallExpression(node) &&
|
||||
pathIntrinsics?.(node.callee, "file-url-to-path")
|
||||
)
|
||||
return staticFileUrlPath(node, pathIntrinsics);
|
||||
return staticFileUrlPathFact(node, pathIntrinsics);
|
||||
if (!t.isCallExpression(node) || !pathIntrinsics?.(node.callee, "join"))
|
||||
return undefined;
|
||||
const parts: string[] = [];
|
||||
const parts: StaticPathFact[] = [];
|
||||
for (const argument of node.arguments) {
|
||||
if (isDirectoryPath(argumentNode(argument), pathIntrinsics)) {
|
||||
// join never resets at an absolute segment, so `/preload.js` after the
|
||||
// source directory stays inside it.
|
||||
parts.push(".");
|
||||
// join never resets at an absolute segment after the source directory.
|
||||
parts.push({ path: ".", base: "source-directory" });
|
||||
continue;
|
||||
}
|
||||
const value = t.isNode(argument)
|
||||
? staticPath(argument, pathIntrinsics)
|
||||
? staticPathFact(argument, pathIntrinsics)
|
||||
: undefined;
|
||||
if (value === undefined) return undefined;
|
||||
parts.push(value);
|
||||
}
|
||||
return parts.length === 0 ? undefined : posix.join(...parts);
|
||||
if (parts.length === 0) return undefined;
|
||||
const first = parts.findIndex(({ path }) => path !== "");
|
||||
const sourceAnchors = parts.flatMap(({ base }, index) =>
|
||||
base === "source-directory" ? [index] : [],
|
||||
);
|
||||
const base = parts.some(({ base }) => base === "unknown")
|
||||
? "unknown"
|
||||
: sourceAnchors.length === 0
|
||||
? "literal-path"
|
||||
: sourceAnchors.length === 1 && sourceAnchors[0] === first
|
||||
? "source-directory"
|
||||
: "unknown";
|
||||
return { path: posix.join(...parts.map(({ path }) => path)), base };
|
||||
};
|
||||
|
||||
/** Normalized path facts from a source-relative file URL expression. */
|
||||
@@ -269,13 +315,22 @@ export interface JavaScriptStaticSourceUrlPath {
|
||||
export const staticFileUrlPath = (
|
||||
node: t.CallExpression | t.NewExpression,
|
||||
pathIntrinsics?: JavaScriptStaticPathIntrinsicQuery,
|
||||
): string | undefined => {
|
||||
): string | undefined => staticFileUrlPathFact(node, pathIntrinsics)?.path;
|
||||
|
||||
const staticFileUrlPathFact = (
|
||||
node: t.CallExpression | t.NewExpression,
|
||||
pathIntrinsics?: JavaScriptStaticPathIntrinsicQuery,
|
||||
): StaticPathFact | undefined => {
|
||||
if (node.arguments.length !== 1) return undefined;
|
||||
const rawArgument = argumentNode(node.arguments[0]);
|
||||
if (rawArgument === undefined) return undefined;
|
||||
const argument = unwrapJavaScriptExpression(rawArgument).node;
|
||||
if (t.isNewExpression(argument) && pathIntrinsics?.(argument, "source-url"))
|
||||
return staticSourceRelativeUrlPath(argument)?.path;
|
||||
if (t.isNewExpression(argument) && pathIntrinsics?.(argument, "source-url")) {
|
||||
const resolved = staticSourceRelativeUrlPath(argument);
|
||||
return resolved === undefined
|
||||
? undefined
|
||||
: { path: resolved.path, base: "source-directory" };
|
||||
}
|
||||
const value = staticPath(argument, pathIntrinsics);
|
||||
const rawPath = value?.split(/[?#]/u, 1)[0] ?? "";
|
||||
if (
|
||||
@@ -293,7 +348,9 @@ export const staticFileUrlPath = (
|
||||
// Canonical analysis paths use POSIX spelling so results do not depend on
|
||||
// the analyzer host. Drive and UNC paths need an explicit target platform.
|
||||
const path = fileURLToPath(url, { windows: false });
|
||||
return /^\/[a-z]:/iu.test(path) ? undefined : path;
|
||||
return /^\/[a-z]:/iu.test(path)
|
||||
? undefined
|
||||
: { path, base: "literal-path" };
|
||||
} catch (cause: unknown) {
|
||||
void cause;
|
||||
return undefined;
|
||||
@@ -345,30 +402,30 @@ export const staticSourceRelativeUrlPath = (
|
||||
const staticResolvedPath = (
|
||||
node: t.CallExpression | t.NewExpression,
|
||||
pathIntrinsics?: JavaScriptStaticPathIntrinsicQuery,
|
||||
): string | undefined => {
|
||||
): StaticPathFact | undefined => {
|
||||
const parts: string[] = [];
|
||||
let anchored = false;
|
||||
let base: StaticPathFact["base"] | undefined;
|
||||
// Node walks right-to-left and never inspects arguments before its anchor.
|
||||
for (let index = node.arguments.length - 1; index >= 0; index -= 1) {
|
||||
const argument = node.arguments[index];
|
||||
if (isDirectoryPath(argumentNode(argument), pathIntrinsics)) {
|
||||
anchored = true;
|
||||
base = "source-directory";
|
||||
break;
|
||||
}
|
||||
const value = t.isNode(argument)
|
||||
? staticPath(argument, pathIntrinsics)
|
||||
? staticPathFact(argument, pathIntrinsics)
|
||||
: undefined;
|
||||
if (value === undefined) return undefined;
|
||||
parts.push(value);
|
||||
if (posix.isAbsolute(value)) {
|
||||
anchored = true;
|
||||
if (value === undefined || value.base === "unknown") return undefined;
|
||||
parts.push(value.path);
|
||||
if (value.base === "source-directory" || posix.isAbsolute(value.path)) {
|
||||
base = value.base;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Relative-only calls depend on the target process cwd, which is unknown.
|
||||
if (!anchored) return undefined;
|
||||
if (base === undefined) return undefined;
|
||||
const path = posix.join(...parts.reverse());
|
||||
return path === "/" ? path : path.replace(/\/$/u, "");
|
||||
return { path: path === "/" ? path : path.replace(/\/$/u, ""), base };
|
||||
};
|
||||
|
||||
const isFilesystemPathExpression = (
|
||||
|
||||
Reference in new issue
Block a user