67 lines
2.5 KiB
TypeScript
67 lines
2.5 KiB
TypeScript
import type {
|
|
JavaScriptBindingProvenance,
|
|
JavaScriptModuleOrigin,
|
|
JavaScriptSemanticPrimitive,
|
|
} from "./javascriptSemanticIr.js";
|
|
import type { JavaScriptSemanticAnalysisState } from "./javascriptSemanticState.js";
|
|
import { compareCodePoints } from "./javascriptStaticAnalysisHelpers.js";
|
|
|
|
/** Construct one immutable semantic provenance value. */
|
|
export const semanticProvenance = (
|
|
status: JavaScriptBindingProvenance["status"],
|
|
origins: readonly JavaScriptModuleOrigin[],
|
|
reason: string | null,
|
|
): JavaScriptBindingProvenance => ({ status, origins, reason });
|
|
|
|
/** Normalize bounded exact module origins into one provenance classification. */
|
|
export const semanticOriginsProvenance = (
|
|
origins: readonly JavaScriptModuleOrigin[],
|
|
state: JavaScriptSemanticAnalysisState,
|
|
): JavaScriptBindingProvenance => {
|
|
const unique = uniqueSemanticOrigins(origins);
|
|
if (unique.length > state.limits.maxUnionValues)
|
|
return semanticLimitProvenance(state, "maxUnionValues");
|
|
return unique.length === 1
|
|
? semanticProvenance("module", unique, null)
|
|
: semanticProvenance("ambiguous", unique, "Multiple module origins.");
|
|
};
|
|
|
|
/** Deduplicate and canonically order module origins. */
|
|
export const uniqueSemanticOrigins = (
|
|
origins: readonly JavaScriptModuleOrigin[],
|
|
): JavaScriptModuleOrigin[] =>
|
|
[
|
|
...new Map(
|
|
origins.map((origin) => [semanticOriginKey(origin), origin]),
|
|
).values(),
|
|
].sort((left, right) =>
|
|
compareCodePoints(semanticOriginKey(left), semanticOriginKey(right)),
|
|
);
|
|
|
|
/** Mark and return an explicit provenance-limit value. */
|
|
export const semanticLimitProvenance = (
|
|
state: JavaScriptSemanticAnalysisState,
|
|
limit: "maxValueDepth" | "maxUnionValues",
|
|
): JavaScriptBindingProvenance => {
|
|
reachSemanticValueLimit(state, limit);
|
|
return semanticProvenance("limit-reached", [], `${limit} reached.`);
|
|
};
|
|
|
|
/** Record one value-lattice limit against the shared semantic analysis state. */
|
|
export const reachSemanticValueLimit = (
|
|
state: JavaScriptSemanticAnalysisState,
|
|
limit: keyof JavaScriptSemanticAnalysisState["limits"],
|
|
): void => {
|
|
state.limitsReached.add(limit);
|
|
state.omittedCount += 1;
|
|
};
|
|
|
|
const semanticOriginKey = (origin: JavaScriptModuleOrigin): string =>
|
|
`${origin.specifier}\0${origin.importedPath.join("\0")}`;
|
|
|
|
/** Canonically distinguish primitive values across type boundaries. */
|
|
export const semanticPrimitiveKey = (
|
|
value: JavaScriptSemanticPrimitive,
|
|
): string =>
|
|
`${value === null ? "null" : typeof value}:${JSON.stringify(value)}`;
|