mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
refactor(javascript): centralize source coordinate operations
This commit is contained in:
1 parent
dd3497e133
commit
ddce4bb446
11 files changed
+78
-93
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import { javascriptSourceRangeKey } from "../../domain/javascript/javascriptSourceCoordinates.js";
|
||||
import type { ApplicationNode } from "../../domain/javascript/javascriptApplicationGraphSchemas.js";
|
||||
import type { ElectronCallPath } from "../../domain/javascript/electronStaticAnalysisTypes.js";
|
||||
import type { JavaScriptArtifactFile } from "../../domain/javascript/javascriptArtifactFiles.js";
|
||||
@@ -10,7 +11,6 @@ import {
|
||||
import {
|
||||
addElectronInferenceEdge,
|
||||
electronFindingSourceNode,
|
||||
electronRangeKey,
|
||||
} from "./ElectronBoundaryGraphContext.js";
|
||||
import {
|
||||
artifactLocalIdentity,
|
||||
@@ -191,13 +191,15 @@ const matches = (
|
||||
path.steps.some(
|
||||
(step) =>
|
||||
step.kind === sourceStepKind &&
|
||||
electronRangeKey(step.location) === electronRangeKey(finding.location),
|
||||
javascriptSourceRangeKey(step.location) ===
|
||||
javascriptSourceRangeKey(finding.location),
|
||||
) &&
|
||||
(invocation === undefined ||
|
||||
path.steps.some(
|
||||
(step) =>
|
||||
(step.kind === "call" || step.kind === "exposure") &&
|
||||
electronRangeKey(step.location) === electronRangeKey(invocation),
|
||||
javascriptSourceRangeKey(step.location) ===
|
||||
javascriptSourceRangeKey(invocation),
|
||||
))
|
||||
);
|
||||
};
|
||||
@@ -108,23 +108,3 @@ export const addElectronInferenceEdge = (
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
/** Deterministic source-range key for artifact-local graph identities. */
|
||||
export const electronRangeKey = (range: JavaScriptSourceRange): string =>
|
||||
`${String(range.start.line)}:${String(range.start.column)}-${String(range.end.line)}:${String(range.end.column)}`;
|
||||
|
||||
/** Test exact source-range containment without assuming runtime reachability. */
|
||||
export const electronRangeContains = (
|
||||
outer: JavaScriptSourceRange,
|
||||
inner: JavaScriptSourceRange,
|
||||
): boolean =>
|
||||
comparePoint(outer.start, inner.start) <= 0 &&
|
||||
comparePoint(outer.end, inner.end) >= 0;
|
||||
|
||||
const comparePoint = (
|
||||
left: JavaScriptSourceRange["start"],
|
||||
right: JavaScriptSourceRange["start"],
|
||||
): number =>
|
||||
left.line === right.line
|
||||
? left.column - right.column
|
||||
: left.line - right.line;
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
javascriptSourceRangeKey,
|
||||
javascriptSourceRangeContains,
|
||||
} from "../../domain/javascript/javascriptSourceCoordinates.js";
|
||||
import type { ApplicationNode } from "../../domain/javascript/javascriptApplicationGraphSchemas.js";
|
||||
import type { ElectronSenderValidationFinding } from "../../domain/javascript/electronStaticAnalysisTypes.js";
|
||||
import type { JavaScriptArtifactFile } from "../../domain/javascript/javascriptArtifactFiles.js";
|
||||
@@ -21,8 +25,6 @@ import {
|
||||
addElectronInferenceEdge,
|
||||
electronFindingSourceNode,
|
||||
electronObservationIdentity,
|
||||
electronRangeContains,
|
||||
electronRangeKey,
|
||||
} from "./ElectronBoundaryGraphContext.js";
|
||||
|
||||
interface IpcGraphState {
|
||||
@@ -182,7 +184,7 @@ const addHandlerNode = (input: IpcHandlerInput): ApplicationNode => {
|
||||
identity: artifactLocalIdentity(
|
||||
file.sha256,
|
||||
"electron-ipc-handler",
|
||||
`${finding.side}:${finding.operation}:${finding.channel ?? finding.channel_expression}:${electronRangeKey(handlerRange)}`,
|
||||
`${finding.side}:${finding.operation}:${finding.channel ?? finding.channel_expression}:${javascriptSourceRangeKey(handlerRange)}`,
|
||||
),
|
||||
observations: [
|
||||
{
|
||||
@@ -338,7 +340,7 @@ const containingHandler = (
|
||||
(record) =>
|
||||
record.file.path === file.path &&
|
||||
record.finding.handler_location !== null &&
|
||||
electronRangeContains(
|
||||
javascriptSourceRangeContains(
|
||||
record.finding.handler_location,
|
||||
validation.location,
|
||||
),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { javascriptSourceRangeKey } from "../../domain/javascript/javascriptSourceCoordinates.js";
|
||||
import type { ElectronNativeAddonBindingFinding } from "../../domain/javascript/electronStaticAnalysisTypes.js";
|
||||
import type { JavaScriptArtifactFile } from "../../domain/javascript/javascriptArtifactFiles.js";
|
||||
import {
|
||||
@@ -13,7 +14,6 @@ import {
|
||||
addElectronInferenceEdge,
|
||||
electronFindingSourceNode,
|
||||
electronObservationIdentity,
|
||||
electronRangeKey,
|
||||
} from "./ElectronBoundaryGraphContext.js";
|
||||
|
||||
interface NativeBindingInput {
|
||||
@@ -65,7 +65,7 @@ const addNativeBinding = (input: NativeBindingInput): void => {
|
||||
value.members,
|
||||
value.namespace_access ?? null,
|
||||
value.dynamic_member_access ?? null,
|
||||
electronRangeKey(value.location),
|
||||
javascriptSourceRangeKey(value.location),
|
||||
]),
|
||||
),
|
||||
observations: [
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { javascriptSourceRangeKey } from "../../domain/javascript/javascriptSourceCoordinates.js";
|
||||
import type { ApplicationNode } from "../../domain/javascript/javascriptApplicationGraphSchemas.js";
|
||||
import type {
|
||||
ElectronBrowserWindowFinding,
|
||||
@@ -21,7 +22,6 @@ import {
|
||||
addElectronAstEdge,
|
||||
addElectronInferenceEdge,
|
||||
electronFindingSourceNode,
|
||||
electronRangeKey,
|
||||
} from "./ElectronBoundaryGraphContext.js";
|
||||
import {
|
||||
artifactPathResolutionProperties,
|
||||
@@ -86,11 +86,11 @@ const addBrowserWindow = (
|
||||
identity: artifactLocalIdentity(
|
||||
file.sha256,
|
||||
"electron-browser-window",
|
||||
electronRangeKey(value.location),
|
||||
javascriptSourceRangeKey(value.location),
|
||||
),
|
||||
observations: [
|
||||
{
|
||||
label: `BrowserWindow@${electronRangeKey(value.location)}`,
|
||||
label: `BrowserWindow@${javascriptSourceRangeKey(value.location)}`,
|
||||
properties: {
|
||||
source_path: file.path,
|
||||
options_status: value.options_status,
|
||||
@@ -202,7 +202,7 @@ const addContextBridge = (
|
||||
identity: artifactLocalIdentity(
|
||||
file.sha256,
|
||||
"electron-context-bridge",
|
||||
`${value.world}:${value.api_key ?? value.api_key_expression ?? "[missing]"}:${electronRangeKey(value.location)}`,
|
||||
`${value.world}:${value.api_key ?? value.api_key_expression ?? "[missing]"}:${javascriptSourceRangeKey(value.location)}`,
|
||||
),
|
||||
observations: [
|
||||
{
|
||||
@@ -271,7 +271,7 @@ const addUtilityProcess = (
|
||||
identity: artifactLocalIdentity(
|
||||
file.sha256,
|
||||
"electron-utility-process",
|
||||
`${value.module_path ?? value.module_expression}:${electronRangeKey(value.location)}`,
|
||||
`${value.module_path ?? value.module_expression}:${javascriptSourceRangeKey(value.location)}`,
|
||||
),
|
||||
observations: [
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { javascriptSourceRangeKey } from "../../domain/javascript/javascriptSourceCoordinates.js";
|
||||
import type { z } from "zod";
|
||||
import type { JavaScriptApplicationGraph } from "../../domain/javascript/javascriptApplicationGraph.js";
|
||||
import type { JsonValue } from "../../domain/jsonValue.js";
|
||||
@@ -338,7 +339,5 @@ const applicationLocationKey = (
|
||||
[
|
||||
artifactSha256,
|
||||
path,
|
||||
range === null
|
||||
? "artifact-path"
|
||||
: `${range.start.line}:${range.start.column}-${range.end.line}:${range.end.column}`,
|
||||
range === null ? "artifact-path" : javascriptSourceRangeKey(range),
|
||||
].join("\u0000");
|
||||
@@ -1,3 +1,4 @@
|
||||
import { javascriptSourceRangeKey } from "../../domain/javascript/javascriptSourceCoordinates.js";
|
||||
import type { JavaScriptSemanticGraphNode } from "../../domain/javascript/javascriptSemanticGraphSchemas.js";
|
||||
import { javaScriptSemanticNodeId } from "../../domain/javascript/javascriptSemanticGraph.js";
|
||||
import type {
|
||||
@@ -221,9 +222,6 @@ interface ObjectReadIndex {
|
||||
|
||||
const objectReadIndexes = new WeakMap<JavaScriptSemanticIr, ObjectReadIndex>();
|
||||
|
||||
const objectReadRangeKey = (location: JavaScriptSourceRange): string =>
|
||||
`${String(location.start.line)}:${String(location.start.column)}:${String(location.end.line)}:${String(location.end.column)}`;
|
||||
|
||||
/**
|
||||
* The object projection owns a member read's occurrence. Index it once per
|
||||
* file so return and argument flow can connect that expression without
|
||||
@@ -250,7 +248,7 @@ const objectReadOccurrences = (
|
||||
}),
|
||||
);
|
||||
if (node !== undefined)
|
||||
nodes.set(objectReadRangeKey(operation.location), node);
|
||||
nodes.set(javascriptSourceRangeKey(operation.location), node);
|
||||
}
|
||||
objectReadIndexes.set(context.ir, { state: context.state, nodes });
|
||||
return nodes;
|
||||
@@ -263,7 +261,7 @@ export const semanticObjectReadOccurrence = (
|
||||
): JavaScriptSemanticGraphNode | undefined =>
|
||||
location === null
|
||||
? undefined
|
||||
: objectReadOccurrences(context).get(objectReadRangeKey(location));
|
||||
: objectReadOccurrences(context).get(javascriptSourceRangeKey(location));
|
||||
|
||||
/** Link direct return expressions to their retained semantic references. */
|
||||
export const projectSemanticReturnValues = (
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
compareJavaScriptSourcePoints,
|
||||
javascriptSourceRangeContains,
|
||||
javascriptSourceRangeKey,
|
||||
} from "../../domain/javascript/javascriptSourceCoordinates.js";
|
||||
import { compareUnicodeCodePoints } from "../../domain/unicodeCodePointOrder.js";
|
||||
import type { JavaScriptSemanticGraphNode } from "../../domain/javascript/javascriptSemanticGraphSchemas.js";
|
||||
import { JAVASCRIPT_SEMANTIC_RELATION_FAMILIES } from "../../domain/javascript/javascriptSemanticGraphSchemas.js";
|
||||
@@ -40,7 +45,7 @@ export const createSemanticCallableOwnerLookup = (
|
||||
}))
|
||||
.sort(
|
||||
(left, right) =>
|
||||
pointCompare(left.range.start, right.range.start) ||
|
||||
compareJavaScriptSourcePoints(left.range.start, right.range.start) ||
|
||||
left.order - right.order,
|
||||
);
|
||||
const tree = buildSourceIntervalTree(entries, 0, entries.length);
|
||||
@@ -54,12 +59,13 @@ export const createSemanticCallableOwnerLookup = (
|
||||
const current = pending.pop();
|
||||
if (
|
||||
current === undefined ||
|
||||
pointCompare(current.minimumStart, location.start) > 0 ||
|
||||
pointCompare(current.maximumEnd, location.end) < 0
|
||||
compareJavaScriptSourcePoints(current.minimumStart, location.start) >
|
||||
0 ||
|
||||
compareJavaScriptSourcePoints(current.maximumEnd, location.end) < 0
|
||||
)
|
||||
continue;
|
||||
const candidate = current.entry;
|
||||
if (contains(candidate.range, location)) {
|
||||
if (javascriptSourceRangeContains(candidate.range, location)) {
|
||||
const node = nodes.get(candidate.callableId);
|
||||
const rank =
|
||||
best === undefined
|
||||
@@ -93,9 +99,15 @@ const buildSourceIntervalTree = <Interval extends SourceInterval>(
|
||||
let minimumEnd = entry.range.end;
|
||||
let maximumEnd = entry.range.end;
|
||||
for (const child of [left, right]) {
|
||||
if (child !== null && pointCompare(child.minimumEnd, minimumEnd) < 0)
|
||||
if (
|
||||
child !== null &&
|
||||
compareJavaScriptSourcePoints(child.minimumEnd, minimumEnd) < 0
|
||||
)
|
||||
minimumEnd = child.minimumEnd;
|
||||
if (child !== null && pointCompare(child.maximumEnd, maximumEnd) > 0)
|
||||
if (
|
||||
child !== null &&
|
||||
compareJavaScriptSourcePoints(child.maximumEnd, maximumEnd) > 0
|
||||
)
|
||||
maximumEnd = child.maximumEnd;
|
||||
}
|
||||
return {
|
||||
@@ -120,7 +132,7 @@ export const createSemanticNodeRangeLookup = (
|
||||
})
|
||||
.sort(
|
||||
(left, right) =>
|
||||
pointCompare(left.range.start, right.range.start) ||
|
||||
compareJavaScriptSourcePoints(left.range.start, right.range.start) ||
|
||||
left.order - right.order,
|
||||
);
|
||||
const tree = buildSourceIntervalTree(entries, 0, entries.length);
|
||||
@@ -132,11 +144,12 @@ export const createSemanticNodeRangeLookup = (
|
||||
const current = pending.pop();
|
||||
if (
|
||||
current === undefined ||
|
||||
pointCompare(current.maximumStart, range.start) < 0 ||
|
||||
pointCompare(current.minimumEnd, range.end) > 0
|
||||
compareJavaScriptSourcePoints(current.maximumStart, range.start) < 0 ||
|
||||
compareJavaScriptSourcePoints(current.minimumEnd, range.end) > 0
|
||||
)
|
||||
continue;
|
||||
if (contains(range, current.entry.range)) matches.push(current.entry);
|
||||
if (javascriptSourceRangeContains(range, current.entry.range))
|
||||
matches.push(current.entry);
|
||||
if (current.left !== null) pending.push(current.left);
|
||||
if (current.right !== null) pending.push(current.right);
|
||||
}
|
||||
@@ -155,18 +168,15 @@ export const createSemanticCallSiteLookup = (
|
||||
) => JavaScriptSemanticGraphNode | undefined) => {
|
||||
const identifiers = new Map<string, string>();
|
||||
for (const call of ir.callSites) {
|
||||
const key = rangeKey(call.location);
|
||||
const key = javascriptSourceRangeKey(call.location);
|
||||
if (!identifiers.has(key)) identifiers.set(key, call.callSiteId);
|
||||
}
|
||||
return (range) => {
|
||||
const identifier = identifiers.get(rangeKey(range));
|
||||
const identifier = identifiers.get(javascriptSourceRangeKey(range));
|
||||
return identifier === undefined ? undefined : nodes.get(identifier);
|
||||
};
|
||||
};
|
||||
|
||||
const rangeKey = (range: JavaScriptSourceRange): string =>
|
||||
`${range.start.line}:${range.start.column}-${range.end.line}:${range.end.column}`;
|
||||
|
||||
/** Report extractor support without treating missing families as absence. */
|
||||
export const semanticFamilyStatus = (
|
||||
family: (typeof JAVASCRIPT_SEMANTIC_RELATION_FAMILIES)[number],
|
||||
@@ -191,17 +201,5 @@ export const semanticFamilyStatus = (
|
||||
return "partial";
|
||||
};
|
||||
|
||||
const contains = (
|
||||
outer: JavaScriptSourceRange,
|
||||
inner: JavaScriptSourceRange,
|
||||
): boolean =>
|
||||
pointCompare(outer.start, inner.start) <= 0 &&
|
||||
pointCompare(outer.end, inner.end) >= 0;
|
||||
|
||||
const pointCompare = (
|
||||
left: JavaScriptSourceRange["start"],
|
||||
right: JavaScriptSourceRange["start"],
|
||||
): number => left.line - right.line || left.column - right.column;
|
||||
|
||||
const positionKey = (range: JavaScriptSourceRange): string =>
|
||||
`${String(range.start.line).padStart(12, "0")}:${String(range.start.column).padStart(12, "0")}`;
|
||||
@@ -1,3 +1,4 @@
|
||||
import { javascriptSourceRangeContains } from "./javascriptSourceCoordinates.js";
|
||||
import * as t from "@babel/types";
|
||||
|
||||
import type {
|
||||
@@ -270,7 +271,7 @@ const parameterAcceptsArgument = (
|
||||
if (!t.isFunction(node)) return false;
|
||||
const parameters = runtimeFunctionParameters(node);
|
||||
const parameterIndex = parameters.findIndex((parameter) =>
|
||||
rangeContains(range(parameter), location),
|
||||
javascriptSourceRangeContains(range(parameter), location),
|
||||
);
|
||||
if (parameterIndex === argumentIndex) return true;
|
||||
return (
|
||||
@@ -280,16 +281,5 @@ const parameterAcceptsArgument = (
|
||||
);
|
||||
};
|
||||
|
||||
const rangeContains = (
|
||||
outer: JavaScriptSemanticArgumentFlow["parameterLocation"],
|
||||
inner: JavaScriptSemanticArgumentFlow["parameterLocation"],
|
||||
): boolean =>
|
||||
(outer.start.line < inner.start.line ||
|
||||
(outer.start.line === inner.start.line &&
|
||||
outer.start.column <= inner.start.column)) &&
|
||||
(outer.end.line > inner.end.line ||
|
||||
(outer.end.line === inner.end.line &&
|
||||
outer.end.column >= inner.end.column));
|
||||
|
||||
const uniqueCallableIds = (values: readonly string[]): string[] =>
|
||||
[...new Set(values)].sort(compareUnicodeCodePoints);
|
||||
@@ -1,3 +1,4 @@
|
||||
import { javascriptSourceRangeKey } from "./javascriptSourceCoordinates.js";
|
||||
import * as t from "@babel/types";
|
||||
|
||||
import type {
|
||||
@@ -340,7 +341,7 @@ const indexReturnSites = (
|
||||
): ReadonlyMap<string, string> => {
|
||||
const output = new Map<string, string>();
|
||||
for (const site of callable.returnSites) {
|
||||
const key = returnSiteKey(site.location);
|
||||
const key = javascriptSourceRangeKey(site.location);
|
||||
if (!output.has(key)) output.set(key, site.returnSiteId);
|
||||
}
|
||||
return output;
|
||||
@@ -366,7 +367,7 @@ const createReturnSiteLookup = (
|
||||
returnSiteIds = indexReturnSites(callable);
|
||||
returnSiteIdsByCallable.set(callableId, returnSiteIds);
|
||||
}
|
||||
return returnSiteIds.get(returnSiteKey(location)) ?? null;
|
||||
return returnSiteIds.get(javascriptSourceRangeKey(location)) ?? null;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -381,11 +382,6 @@ const indexCallablesById = (
|
||||
return output;
|
||||
};
|
||||
|
||||
const returnSiteKey = (
|
||||
location: JavaScriptSemanticPromiseOperation["location"],
|
||||
): string =>
|
||||
`${location.start.line}:${location.start.column}:${location.end.line}:${location.end.column}`;
|
||||
|
||||
const promiseSources = (
|
||||
candidate: PromiseCandidate,
|
||||
candidateByNode: WeakMap<t.Node, PromiseCandidate>,
|
||||
|
||||
@@ -1,4 +1,28 @@
|
||||
import { z } from "zod";
|
||||
import type {
|
||||
JavaScriptSourcePoint,
|
||||
JavaScriptSourceRange,
|
||||
} from "./javascriptStaticAnalysisTypes.js";
|
||||
|
||||
/** Compare parser coordinates by line, then UTF-16 column. */
|
||||
export const compareJavaScriptSourcePoints = (
|
||||
left: JavaScriptSourcePoint,
|
||||
right: JavaScriptSourcePoint,
|
||||
): number => left.line - right.line || left.column - right.column;
|
||||
|
||||
/** Include equal endpoints without implying runtime reachability. */
|
||||
export const javascriptSourceRangeContains = (
|
||||
outer: JavaScriptSourceRange,
|
||||
inner: JavaScriptSourceRange,
|
||||
): boolean =>
|
||||
compareJavaScriptSourcePoints(outer.start, inner.start) <= 0 &&
|
||||
compareJavaScriptSourcePoints(outer.end, inner.end) >= 0;
|
||||
|
||||
/** Exact artifact-local coordinate key; callers supply the artifact identity. */
|
||||
export const javascriptSourceRangeKey = (
|
||||
range: JavaScriptSourceRange,
|
||||
): string =>
|
||||
`${range.start.line}:${range.start.column}-${range.end.line}:${range.end.column}`;
|
||||
|
||||
const sourcePointSchema = z.strictObject({
|
||||
line: z.number().int().min(1),
|
||||
@@ -9,11 +33,7 @@ const sourcePointSchema = z.strictObject({
|
||||
export const javascriptSourceRangeSchema = z
|
||||
.strictObject({ start: sourcePointSchema, end: sourcePointSchema })
|
||||
.superRefine((range, context) => {
|
||||
if (
|
||||
range.end.line < range.start.line ||
|
||||
(range.end.line === range.start.line &&
|
||||
range.end.column < range.start.column)
|
||||
)
|
||||
if (compareJavaScriptSourcePoints(range.end, range.start) < 0)
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
path: ["end"],
|
||||
|
||||
Reference in new issue
Block a user