mirror of
https://github.com/morluto/rea.git
synced 2026-10-30 03:15:54 +00:00
refactor(javascript): group domain semantics and contracts (#907)
This commit is contained in:
1 parent
d5f28fb29c
commit
f6a58f1062
253 files changed
+432
-410
No files matched your search
@@ -0,0 +1,300 @@
|
||||
import * as t from "@babel/types";
|
||||
|
||||
import { sanitizeEndpointCandidate } from "../browserObservation.js";
|
||||
import {
|
||||
addFindingOnce,
|
||||
addLocatedFinding,
|
||||
moduleAtOffset,
|
||||
} from "./javascriptStaticAnalysisFindings.js";
|
||||
import {
|
||||
STATIC_ROUTE_CALL_NAMES,
|
||||
argumentValue,
|
||||
calleeName,
|
||||
endpointArgument as endpointArgumentHelper,
|
||||
prefixedArgument,
|
||||
semanticStaticPropertyName,
|
||||
range,
|
||||
rangeForOffsets,
|
||||
sourceSlice,
|
||||
staticPath,
|
||||
staticPathResolutionContext,
|
||||
storageKind,
|
||||
stringValue,
|
||||
} from "./javascriptStaticAnalysisHelpers.js";
|
||||
import type {
|
||||
JavaScriptAnalysisAccumulator as AnalysisAccumulator,
|
||||
JavaScriptEndpointInput as EndpointInput,
|
||||
JavaScriptFindingContext as FindingContext,
|
||||
JavaScriptReferenceInput as ReferenceInput,
|
||||
} from "./javascriptStaticAnalysisState.js";
|
||||
|
||||
/** Inspect one call or new expression for references, endpoints, storage, and roles. */
|
||||
export const inspectCall = (
|
||||
source: string,
|
||||
node: t.CallExpression | t.NewExpression,
|
||||
context: FindingContext,
|
||||
): void => {
|
||||
const { accumulator } = context;
|
||||
const name = calleeName(node.callee);
|
||||
const first = stringValue(node.arguments[0]);
|
||||
const module = moduleAtOffset(node.start, context.accumulator.modules);
|
||||
const moduleRequireName = module?.requireName ?? null;
|
||||
const requireCall = requireCallSpecifier(node, name, moduleRequireName);
|
||||
if (requireCall !== null)
|
||||
addReference(context, {
|
||||
node,
|
||||
kind: "require",
|
||||
specifier: requireCall.specifier,
|
||||
});
|
||||
const chunkLoad = chunkLoadSpecifier(node, name, moduleRequireName);
|
||||
if (chunkLoad !== null)
|
||||
addReference(context, {
|
||||
node,
|
||||
kind: "dynamic-import",
|
||||
specifier: chunkLoad.specifier,
|
||||
});
|
||||
for (const specifier of vitePreloadDependencySpecifiers(source, node, name))
|
||||
addReference(context, { node, kind: "dynamic-import", specifier });
|
||||
if (name === "Worker" || name.endsWith(".Worker"))
|
||||
addReference(context, { node, kind: "worker", specifier: first });
|
||||
if (name.endsWith("serviceWorker.register"))
|
||||
addReference(context, { node, kind: "service-worker", specifier: first });
|
||||
inspectEndpointCall(node, name, first, context);
|
||||
inspectStorageCall(node, name, first, context);
|
||||
if (name.endsWith("loadFile") && first !== undefined)
|
||||
addLocatedFinding(context, {
|
||||
collection: accumulator.roles,
|
||||
key: `role\0renderer\0${first}`,
|
||||
node,
|
||||
value: {
|
||||
role: "renderer",
|
||||
path: first,
|
||||
resolution_context: "filesystem-expression",
|
||||
mechanism: `call:${name}`,
|
||||
module_key: null,
|
||||
location: range(node),
|
||||
},
|
||||
});
|
||||
if (name.endsWith("loadURL") && first !== undefined)
|
||||
addLocatedFinding(context, {
|
||||
collection: accumulator.roles,
|
||||
key: `role\0renderer\0${first}`,
|
||||
node,
|
||||
value: {
|
||||
role: "renderer",
|
||||
path: first,
|
||||
resolution_context: "module-specifier",
|
||||
mechanism: `call:${name}`,
|
||||
module_key: null,
|
||||
location: range(node),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const requireCallSpecifier = (
|
||||
node: t.CallExpression | t.NewExpression,
|
||||
name: string,
|
||||
moduleRequireName: string | null,
|
||||
): { readonly specifier: string | undefined } | null => {
|
||||
if (name === "require" || name.endsWith(".require"))
|
||||
return { specifier: argumentValue(node.arguments[0]) };
|
||||
if (name === "__webpack_require__" || name === moduleRequireName)
|
||||
return { specifier: argumentValue(node.arguments[0]) };
|
||||
if (
|
||||
name === "__webpack_require__.bind" ||
|
||||
(moduleRequireName !== null && name === `${moduleRequireName}.bind`)
|
||||
)
|
||||
return { specifier: argumentValue(node.arguments[1]) };
|
||||
return null;
|
||||
};
|
||||
|
||||
const chunkLoadSpecifier = (
|
||||
node: t.CallExpression | t.NewExpression,
|
||||
name: string,
|
||||
moduleRequireName: string | null,
|
||||
): { readonly specifier: string | undefined } | null => {
|
||||
if (
|
||||
name !== "__webpack_require__.e" &&
|
||||
(moduleRequireName === null || name !== `${moduleRequireName}.e`)
|
||||
)
|
||||
return null;
|
||||
return { specifier: prefixedArgument("chunk:", node.arguments[0]) };
|
||||
};
|
||||
|
||||
const vitePreloadDependencySpecifiers = (
|
||||
source: string,
|
||||
node: t.CallExpression | t.NewExpression,
|
||||
name: string,
|
||||
): readonly string[] => {
|
||||
if (name !== "__vite__mapDeps" || !t.isArrayExpression(node.arguments[0]))
|
||||
return [];
|
||||
const indexes = node.arguments[0].elements.flatMap((element) =>
|
||||
t.isNumericLiteral(element) ? [element.value] : [],
|
||||
);
|
||||
if (indexes.length !== node.arguments[0].elements.length) return [];
|
||||
const table = vitePreloadDependencyTable(source);
|
||||
return indexes.flatMap((index) => table[index] ?? []);
|
||||
};
|
||||
|
||||
const vitePreloadDependencyTable = (source: string): readonly string[] => {
|
||||
const match =
|
||||
/\.\s*f\s*\|\|\s*\(\s*[^)]*?\.f\s*=\s*\[([\s\S]*?)\]\s*\)/u.exec(source);
|
||||
const body = match?.[1];
|
||||
if (body === undefined) return [];
|
||||
return [
|
||||
...body.matchAll(/"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gu),
|
||||
].map((literal) => literal[1] ?? literal[2] ?? "");
|
||||
};
|
||||
|
||||
const inspectEndpointCall = (
|
||||
node: t.CallExpression | t.NewExpression,
|
||||
name: string,
|
||||
first: string | undefined,
|
||||
context: FindingContext,
|
||||
): void => {
|
||||
const route = STATIC_ROUTE_CALL_NAMES.some(
|
||||
(candidate) => name === candidate || name.endsWith(`.${candidate}`),
|
||||
);
|
||||
if (route && first !== undefined)
|
||||
addEndpoint(context, {
|
||||
node,
|
||||
kind: "route",
|
||||
value: first,
|
||||
mechanism: `call:${name}`,
|
||||
});
|
||||
const endpoint = endpointArgumentHelper(name, node.arguments, node.callee);
|
||||
if (endpoint !== undefined)
|
||||
addEndpoint(context, {
|
||||
node,
|
||||
kind: "network",
|
||||
value: endpoint,
|
||||
mechanism: `call:${name}`,
|
||||
});
|
||||
};
|
||||
|
||||
export const inspectRouteProperty = (
|
||||
node: t.ObjectProperty,
|
||||
context: FindingContext,
|
||||
): void => {
|
||||
const key = semanticStaticPropertyName(node.key, node.computed);
|
||||
if ((key === "path" || key === "route") && t.isStringLiteral(node.value))
|
||||
addEndpoint(context, {
|
||||
node,
|
||||
kind: "route",
|
||||
value: node.value.value,
|
||||
mechanism: `property:${key}`,
|
||||
});
|
||||
};
|
||||
|
||||
export const inspectRoleProperty = (
|
||||
node: t.ObjectProperty,
|
||||
context: FindingContext,
|
||||
): void => {
|
||||
if (semanticStaticPropertyName(node.key, node.computed) !== "preload") return;
|
||||
const path = staticPath(node.value);
|
||||
if (path === undefined) return;
|
||||
addLocatedFinding(context, {
|
||||
collection: context.accumulator.roles,
|
||||
key: `role\0preload\0${path}`,
|
||||
node,
|
||||
value: {
|
||||
role: "preload",
|
||||
path,
|
||||
resolution_context: staticPathResolutionContext(node.value),
|
||||
mechanism: "property:preload",
|
||||
module_key: null,
|
||||
location: range(node),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const inspectStorageCall = (
|
||||
node: t.CallExpression | t.NewExpression,
|
||||
name: string,
|
||||
first: string | undefined,
|
||||
context: FindingContext,
|
||||
): void => {
|
||||
const storage = storageKind(name);
|
||||
if (storage === undefined) return;
|
||||
addLocatedFinding(context, {
|
||||
collection: context.accumulator.storage,
|
||||
key: `storage\0${storage}\0${first ?? ""}`,
|
||||
node,
|
||||
value: {
|
||||
kind: storage,
|
||||
name: first ?? null,
|
||||
mechanism: `call:${name}`,
|
||||
module_key: null,
|
||||
location: range(node),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const addReference = (
|
||||
context: FindingContext,
|
||||
input: ReferenceInput,
|
||||
): void => {
|
||||
const specifier = input.specifier ?? null;
|
||||
const reference =
|
||||
specifier === null
|
||||
? {
|
||||
specifier: null,
|
||||
expression: sourceSlice(context.source, input.node),
|
||||
}
|
||||
: { specifier, expression: null };
|
||||
addLocatedFinding(context, {
|
||||
collection: context.accumulator.references,
|
||||
key: `reference\0${input.kind}\0${reference.specifier ?? reference.expression}`,
|
||||
node: input.node,
|
||||
value: {
|
||||
kind: input.kind,
|
||||
...reference,
|
||||
module_key: null,
|
||||
location: range(input.node),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const addEndpoint = (context: FindingContext, input: EndpointInput): void =>
|
||||
addLocatedFinding(context, {
|
||||
collection: context.accumulator.endpoints,
|
||||
key: `endpoint\0${input.kind}\0${input.value}`,
|
||||
node: input.node,
|
||||
value: {
|
||||
kind: input.kind,
|
||||
value: sanitizeEndpointCandidate(input.value),
|
||||
mechanism: input.mechanism,
|
||||
module_key: null,
|
||||
location: range(input.node),
|
||||
},
|
||||
});
|
||||
|
||||
export const addSourceMapDirectives = (
|
||||
source: string,
|
||||
comments: readonly t.Comment[],
|
||||
accumulator: AnalysisAccumulator,
|
||||
): void => {
|
||||
// Read real comment tokens only; quoted text can contain identical markers.
|
||||
// Keep both current/legacy line annotations and minifier block annotations.
|
||||
for (const comment of comments) {
|
||||
const start = comment.start;
|
||||
const end = comment.end;
|
||||
if (
|
||||
start === undefined ||
|
||||
start === null ||
|
||||
end === undefined ||
|
||||
end === null
|
||||
)
|
||||
continue;
|
||||
const match = /^(?:\/\/|\/\*)[#@]\s*sourceMappingURL\s*=\s*([^\s*]+)/u.exec(
|
||||
source.slice(start, end),
|
||||
);
|
||||
if (match === null) continue;
|
||||
const declared = match[1];
|
||||
if (declared === undefined || declared.length === 0) continue;
|
||||
const location = rangeForOffsets(source, start, start + match[0].length);
|
||||
addFindingOnce(accumulator, `source-map\0${declared}`, () =>
|
||||
accumulator.sourceMaps.push({ declared_url: declared, location }),
|
||||
);
|
||||
}
|
||||
};
|
||||
Reference in new issue
Block a user