mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
fix(javascript): retain stored receiver capture authority
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { runInNewContext } from "node:vm";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js";
|
||||
@@ -69,6 +70,8 @@ describe("Electron constant option evidence", () => {
|
||||
"const { webPreferences: alias } = options; mutate(alias);",
|
||||
"Object.assign(options, unknown);",
|
||||
"const box = {}; box.options = options; box.options.webPreferences.sandbox = false;",
|
||||
"const box = {}; box.options = options; const saved = box.options; box.options = {}; saved.webPreferences.sandbox = false;",
|
||||
"const values = [options]; values[Symbol.iterator] = input; const box = {}; [box.options] = values; box.options.webPreferences.sandbox = false;",
|
||||
"options = replacement;",
|
||||
])("keeps changed or escaped constant options unknown: %s", (mutation) => {
|
||||
const analysis = analyzeJavaScriptStaticSource(`
|
||||
@@ -87,6 +90,77 @@ describe("Electron constant option evidence", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
"const box = {}; box.options = options; box.options = { webPreferences: { sandbox: false } }; box.options.webPreferences.sandbox = false;",
|
||||
"let box = { options: { webPreferences: { sandbox: false } } }; const saved = box; box = {}; box.options = options; saved.options.webPreferences.sandbox = false;",
|
||||
"const values = [{ webPreferences: { sandbox: false } }]; values[Symbol.iterator] = input; const box = {}; [box.options] = values; box.options.webPreferences.sandbox = false;",
|
||||
])(
|
||||
"keeps independent options intact after stored receiver replacement: %s",
|
||||
(mutation) => {
|
||||
const analysis = analyzeJavaScriptStaticSource(`
|
||||
const { BrowserWindow } = require("electron");
|
||||
const options = { webPreferences: { sandbox: true } };
|
||||
${mutation}
|
||||
new BrowserWindow(options);
|
||||
`);
|
||||
expect(analysis.electron.browser_windows).toMatchObject([
|
||||
{
|
||||
options_status: "object-literal",
|
||||
web_preferences_status: "object-literal",
|
||||
web_preferences: [
|
||||
{ name: "sandbox", value: { status: "literal", value: true } },
|
||||
],
|
||||
},
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
["inner.current = second;", [true, false], ["object-literal", "dynamic"]],
|
||||
[
|
||||
"source.inner = { current: second };",
|
||||
[false, true],
|
||||
["dynamic", "object-literal"],
|
||||
],
|
||||
] as const)(
|
||||
"preserves shallow-copy capture depth across later writes: %s",
|
||||
(replacement, runtime, statuses) => {
|
||||
const body = `
|
||||
const first = { webPreferences: { sandbox: true } };
|
||||
const second = { webPreferences: { sandbox: true } };
|
||||
const inner = { current: first };
|
||||
const source = { inner };
|
||||
const box = {};
|
||||
({...box.options} = source);
|
||||
${replacement}
|
||||
box.options.inner.current.webPreferences.sandbox = false;
|
||||
`;
|
||||
expect(
|
||||
Array.from(
|
||||
runInNewContext(
|
||||
`${body};[first.webPreferences.sandbox, second.webPreferences.sandbox]`,
|
||||
),
|
||||
),
|
||||
).toEqual(runtime);
|
||||
const analysis = analyzeJavaScriptStaticSource(`
|
||||
const { BrowserWindow } = require("electron");
|
||||
${body}
|
||||
new BrowserWindow(first);
|
||||
new BrowserWindow(second);
|
||||
`);
|
||||
expect(
|
||||
analysis.electron.browser_windows.map(
|
||||
({ options_status }) => options_status,
|
||||
),
|
||||
).toEqual(statuses);
|
||||
const intact =
|
||||
analysis.electron.browser_windows[statuses.indexOf("object-literal")];
|
||||
expect(intact?.web_preferences).toMatchObject([
|
||||
{ name: "sandbox", value: { status: "literal", value: true } },
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
"new BrowserWindow(options); const options = { webPreferences: { sandbox: true } };",
|
||||
"const options = alias; const alias = { webPreferences: { sandbox: true } }; new BrowserWindow(options);",
|
||||
|
||||
@@ -33,6 +33,13 @@ import {
|
||||
evaluateSemanticExpression,
|
||||
} from "./javascriptSemanticValues.js";
|
||||
import { semanticContainer } from "./javascriptSemanticSlots.js";
|
||||
import {
|
||||
semanticProjectedSourceFactsAtRead,
|
||||
semanticShiftCapturedSelections,
|
||||
semanticNormalizeCapturedSelections,
|
||||
type SemanticCapturedSelection,
|
||||
} from "./javascriptSemanticProjectedSources.js";
|
||||
|
||||
import {
|
||||
semanticArrayPropertyReferences,
|
||||
semanticClassCandidates,
|
||||
@@ -84,6 +91,13 @@ import {
|
||||
} from "./javascriptAstValues.js";
|
||||
|
||||
type PropertyPath = JavaScriptSemanticPropertyPath;
|
||||
interface MutationSelectionContext {
|
||||
readonly memberReadAt?: t.Node | undefined;
|
||||
readonly capturedSelections?:
|
||||
| readonly SemanticCapturedSelection[]
|
||||
| undefined;
|
||||
}
|
||||
|
||||
type ValueEffect = "write" | "escape";
|
||||
|
||||
/** Only replacing or possibly selecting an iterator-bearing prototype slot changes iteration. */
|
||||
@@ -570,7 +584,7 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
// A capture key already ends the array; a path stays one nested element.
|
||||
const lifetimeKey = (captureKey: string, path: PropertyPath): string =>
|
||||
`${captureKey.slice(0, -1)},${JSON.stringify(path)}]`;
|
||||
const pendingReferences: {
|
||||
const pendingReferences: (MutationSelectionContext & {
|
||||
readonly initializer: JavaScriptSemanticBindingState["referenceInitializers"][number];
|
||||
readonly path: PropertyPath;
|
||||
readonly bindings: ReadonlySet<string>;
|
||||
@@ -580,7 +594,7 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
readonly widenedGetterResult?: true;
|
||||
readonly definiteReceiver: boolean;
|
||||
readonly assignedSources?: readonly SemanticIterationSource[];
|
||||
}[] = [];
|
||||
})[] = [];
|
||||
const iterableReferences: IterableReference[] = [];
|
||||
const iterableReferenceKeys = new WeakMap<t.Node, Set<string>>();
|
||||
const deferIterable = (reference: IterableReference): void => {
|
||||
@@ -594,6 +608,14 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
reference.mutation?.end,
|
||||
reference.originAt?.start,
|
||||
reference.originAt?.end,
|
||||
reference.memberReadAt?.start,
|
||||
reference.memberReadAt?.end,
|
||||
reference.capturedSelections?.map(({ at, depth, ownEnumerable }) => [
|
||||
at.start,
|
||||
at.end,
|
||||
depth,
|
||||
ownEnumerable,
|
||||
]),
|
||||
reference.iterationOnly,
|
||||
reference.widenedGetterResult === true,
|
||||
reference.definiteReceiver === true,
|
||||
@@ -637,26 +659,41 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
widenedGetterResult = false,
|
||||
assignedSources?: readonly SemanticIterationSource[],
|
||||
definiteReceiver = true,
|
||||
selection?: MutationSelectionContext,
|
||||
): void => {
|
||||
const pending: (ReferencedValue & {
|
||||
readonly bindings: ReadonlySet<string>;
|
||||
readonly originAt: t.Node | undefined;
|
||||
readonly definiteReceiver: boolean;
|
||||
})[] = [
|
||||
const pending: (ReferencedValue &
|
||||
MutationSelectionContext & {
|
||||
readonly bindings: ReadonlySet<string>;
|
||||
readonly originAt: t.Node | undefined;
|
||||
readonly definiteReceiver: boolean;
|
||||
})[] = [
|
||||
{
|
||||
node,
|
||||
path,
|
||||
bindings,
|
||||
originAt,
|
||||
definiteReceiver,
|
||||
memberReadAt: selection?.memberReadAt ?? mutation ?? originAt,
|
||||
capturedSelections: selection?.capturedSelections,
|
||||
...(ownerInitializer === undefined ? {} : { ownerInitializer }),
|
||||
},
|
||||
];
|
||||
// Visit shallower alias traversals first so their exact prefixes suppress
|
||||
// longer loop paths before those paths enumerate every assignment order.
|
||||
for (let cursor = 0; cursor < pending.length; cursor++) {
|
||||
const current = pending[cursor];
|
||||
if (current === undefined) break;
|
||||
const candidate = pending[cursor];
|
||||
if (candidate === undefined) break;
|
||||
// A capture deeper than the selected path is no longer applicable. Keeping
|
||||
// it would manufacture new contexts on a repeated source alias cycle.
|
||||
const current = candidate.capturedSelections?.length
|
||||
? {
|
||||
...candidate,
|
||||
capturedSelections: semanticNormalizeCapturedSelections(
|
||||
candidate.capturedSelections,
|
||||
candidate.path.length,
|
||||
),
|
||||
}
|
||||
: candidate;
|
||||
const expression = unwrapJavaScriptExpression(current.node).node;
|
||||
const required = semanticRequireOrigin(expression, state);
|
||||
if (required !== undefined) {
|
||||
@@ -719,11 +756,23 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
// An alias restored from a saved reference can revisit the same binding
|
||||
// at an earlier capture or a shorter getter-receiver path. Only a
|
||||
// repeated capture and path closes a cycle.
|
||||
const captureKey = captureKeyOf(
|
||||
binding,
|
||||
current.originAt,
|
||||
current.ownerInitializer,
|
||||
);
|
||||
const captureSelectionIdentity = JSON.stringify([
|
||||
current.memberReadAt?.start,
|
||||
current.memberReadAt?.end,
|
||||
current.capturedSelections?.map(({ at, depth, ownEnumerable }) => [
|
||||
at.start,
|
||||
at.end,
|
||||
depth,
|
||||
ownEnumerable,
|
||||
]),
|
||||
]);
|
||||
// Ancestry closes repeated source identities before recursively selected
|
||||
// copy vectors grow; independent contexts remain distinct in coverage.
|
||||
const captureKey = JSON.stringify([
|
||||
captureKeyOf(binding, current.originAt, current.ownerInitializer),
|
||||
current.memberReadAt?.start,
|
||||
current.memberReadAt?.end,
|
||||
]);
|
||||
const lifetime = lifetimeKey(captureKey, current.path);
|
||||
if (current.bindings.has(lifetime)) continue;
|
||||
// A self alias such as `node = node[key]` revisits its capture with the
|
||||
@@ -756,6 +805,9 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
current.originAt,
|
||||
current.ownerInitializer,
|
||||
widenedGetterResult,
|
||||
undefined,
|
||||
undefined,
|
||||
current,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -788,6 +840,7 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
current.originAt?.end,
|
||||
current.ownerInitializer?.start,
|
||||
current.ownerInitializer?.end,
|
||||
captureSelectionIdentity,
|
||||
]);
|
||||
const previous = selections.get(selectionIdentity);
|
||||
const excludedKeys =
|
||||
@@ -844,6 +897,10 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
mutation,
|
||||
current.originAt,
|
||||
current.ownerInitializer,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
current,
|
||||
);
|
||||
}
|
||||
// Keep an antichain of exact paths. Widening distinct member keys would
|
||||
@@ -860,7 +917,7 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
current.originAt?.start ?? -1
|
||||
}\u0001${current.originAt?.end ?? -1}\u0001${
|
||||
current.ownerInitializer?.start ?? -1
|
||||
}\u0001${current.ownerInitializer?.end ?? -1}\u0001${widenedGetterResult}\u0001${current.definiteReceiver}\u0001${assignedSources?.[0]?.target?.start ?? -1}`;
|
||||
}\u0001${current.ownerInitializer?.end ?? -1}\u0001${widenedGetterResult}\u0001${current.definiteReceiver}\u0001${assignedSources?.[0]?.target?.start ?? -1}\u0001${captureSelectionIdentity}`;
|
||||
let covering = coveringPaths.get(pathState);
|
||||
if (covering === undefined) {
|
||||
covering = new SemanticPropertyPathCoverage();
|
||||
@@ -883,6 +940,7 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
nested.add(captureKey);
|
||||
// Returning a literal's receiver reaches that allocation, not the
|
||||
// binding's older values read while evaluating its initializer.
|
||||
let retainsInitializers = true;
|
||||
if (!primitiveWrite) {
|
||||
for (const origin of origins.directOrigins) {
|
||||
const shared = semanticMutableBuiltinOrigin(origin, path);
|
||||
@@ -898,6 +956,27 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
...origins.initializers,
|
||||
...origins.referenceInitializers,
|
||||
];
|
||||
const receiver =
|
||||
effect === "write" &&
|
||||
current.path.length > 1 &&
|
||||
binding.memberWrites.some(
|
||||
(write) =>
|
||||
write.assignedSources !== undefined &&
|
||||
write.path.length < current.path.length,
|
||||
)
|
||||
? semanticProjectedSourceFactsAtRead(
|
||||
binding,
|
||||
current.node,
|
||||
current.path.slice(0, -1),
|
||||
state,
|
||||
false,
|
||||
current.memberReadAt ?? current.originAt ?? current.node,
|
||||
current.capturedSelections,
|
||||
)
|
||||
: undefined;
|
||||
// A defined owner result already includes every eligible initializer;
|
||||
// replaying them separately duplicates selection/capture histories.
|
||||
retainsInitializers = receiver === undefined;
|
||||
if (effect === "escape")
|
||||
binding.escapedPaths.push({
|
||||
path,
|
||||
@@ -905,7 +984,7 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
receiverInitializers,
|
||||
...(current.originAt === undefined
|
||||
? {}
|
||||
: { receiverAt: current.originAt }),
|
||||
: { receiverAt: current.memberReadAt ?? current.originAt }),
|
||||
});
|
||||
else
|
||||
binding.memberWrites.push({
|
||||
@@ -917,11 +996,34 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
receiverInitializers,
|
||||
...(current.originAt === undefined
|
||||
? {}
|
||||
: { receiverAt: current.originAt }),
|
||||
: { receiverAt: current.memberReadAt ?? current.originAt }),
|
||||
...(assignedSources === undefined ? {} : { assignedSources }),
|
||||
});
|
||||
clearSemanticValueCaches(state);
|
||||
for (const initializer of origins.initializers)
|
||||
// Resolve a stored child at the receiver capture, before selecting
|
||||
// the final written slot. The source owner preserves replacements
|
||||
// and captures; a local container does not isolate its children.
|
||||
if (receiver !== undefined) {
|
||||
for (const source of receiver.sources)
|
||||
pending.push({
|
||||
node: source.node,
|
||||
path: [...source.path, current.path.at(-1)!],
|
||||
bindings: nested,
|
||||
definiteReceiver: false,
|
||||
originAt: source.node,
|
||||
memberReadAt: current.memberReadAt,
|
||||
capturedSelections: semanticNormalizeCapturedSelections([
|
||||
...(semanticShiftCapturedSelections(
|
||||
current.capturedSelections,
|
||||
source.path.length - (current.path.length - 1),
|
||||
) ?? []),
|
||||
...(source.capturedSelections ?? []),
|
||||
]),
|
||||
});
|
||||
}
|
||||
for (const initializer of retainsInitializers
|
||||
? origins.initializers
|
||||
: [])
|
||||
pending.push({
|
||||
node: initializer.node,
|
||||
path: [...initializer.projection, ...path],
|
||||
@@ -932,9 +1034,16 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
origins.referenceInitializers.length ===
|
||||
1,
|
||||
originAt: initializer.node,
|
||||
memberReadAt: current.memberReadAt,
|
||||
capturedSelections: semanticShiftCapturedSelections(
|
||||
current.capturedSelections,
|
||||
initializer.projection.length,
|
||||
),
|
||||
});
|
||||
}
|
||||
for (const initializer of origins.referenceInitializers) {
|
||||
for (const initializer of retainsInitializers
|
||||
? origins.referenceInitializers
|
||||
: []) {
|
||||
const copiedPath = semanticCopiedReferencePath(
|
||||
initializer,
|
||||
path,
|
||||
@@ -957,6 +1066,11 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
effect,
|
||||
...(widenedGetterResult ? { widenedGetterResult: true } : {}),
|
||||
originAt: initializer.node,
|
||||
memberReadAt: current.memberReadAt,
|
||||
capturedSelections: semanticShiftCapturedSelections(
|
||||
current.capturedSelections,
|
||||
copiedPath.length - path.length,
|
||||
),
|
||||
...(mutation === undefined ? {} : { mutation }),
|
||||
});
|
||||
}
|
||||
@@ -974,6 +1088,17 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
bindings: current.bindings,
|
||||
definiteReceiver: current.definiteReceiver && key !== null,
|
||||
originAt: current.originAt,
|
||||
memberReadAt: current.memberReadAt,
|
||||
capturedSelections: semanticNormalizeCapturedSelections([
|
||||
...(semanticShiftCapturedSelections(
|
||||
current.capturedSelections,
|
||||
1,
|
||||
) ?? []),
|
||||
...(current.originAt !== undefined &&
|
||||
unwrapJavaScriptExpression(current.originAt).node === expression
|
||||
? [{ at: current.originAt, depth: 1 }]
|
||||
: []),
|
||||
]),
|
||||
});
|
||||
} else {
|
||||
const references =
|
||||
@@ -985,6 +1110,13 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
)
|
||||
: expandedValues(expression, current.path, effect);
|
||||
for (const value of references) {
|
||||
if (
|
||||
value.inherited &&
|
||||
current.capturedSelections?.some(
|
||||
(capture) => capture.depth === 1 && capture.ownEnumerable,
|
||||
)
|
||||
)
|
||||
continue;
|
||||
if (
|
||||
value.escapes &&
|
||||
(effect !== "escape" ||
|
||||
@@ -999,6 +1131,9 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
current.originAt,
|
||||
value.ownerInitializer,
|
||||
widenedGetterResult || value.widenedGetterResult === true,
|
||||
undefined,
|
||||
undefined,
|
||||
current,
|
||||
);
|
||||
else if (value.iterableFallbackPath !== undefined)
|
||||
deferIterable({
|
||||
@@ -1011,6 +1146,11 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
...(assignedSources === undefined ? {} : { assignedSources }),
|
||||
effect,
|
||||
originAt: current.originAt,
|
||||
memberReadAt: current.memberReadAt,
|
||||
capturedSelections: semanticShiftCapturedSelections(
|
||||
current.capturedSelections,
|
||||
value.path.length - current.path.length,
|
||||
),
|
||||
...(widenedGetterResult ? { widenedGetterResult: true } : {}),
|
||||
...(mutation === undefined ? {} : { mutation }),
|
||||
});
|
||||
@@ -1034,6 +1174,24 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
(key) => key !== null && typeof key !== "object",
|
||||
),
|
||||
originAt: current.originAt,
|
||||
memberReadAt: current.memberReadAt,
|
||||
capturedSelections: semanticNormalizeCapturedSelections([
|
||||
...(semanticShiftCapturedSelections(
|
||||
current.capturedSelections,
|
||||
value.path.length - current.path.length,
|
||||
) ?? []),
|
||||
...(value.capturedSelectionAt === undefined
|
||||
? []
|
||||
: [
|
||||
{
|
||||
at: value.capturedSelectionAt,
|
||||
depth: value.path.length - current.path.length + 1,
|
||||
...(value.ownEnumerable
|
||||
? { ownEnumerable: true as const }
|
||||
: {}),
|
||||
},
|
||||
]),
|
||||
]),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1369,6 +1527,8 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
? { widenedGetterResult: true }
|
||||
: {}),
|
||||
originAt: initializer.node,
|
||||
memberReadAt: reference.memberReadAt,
|
||||
capturedSelections: reference.capturedSelections,
|
||||
...(reference.mutation === undefined
|
||||
? {}
|
||||
: { mutation: reference.mutation }),
|
||||
@@ -1394,6 +1554,8 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
? { widenedGetterResult: true }
|
||||
: {}),
|
||||
originAt: reference.originAt,
|
||||
memberReadAt: reference.memberReadAt,
|
||||
capturedSelections: reference.capturedSelections,
|
||||
...(reference.mutation === undefined
|
||||
? {}
|
||||
: { mutation: reference.mutation }),
|
||||
@@ -1410,6 +1572,7 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
reference.widenedGetterResult === true,
|
||||
reference.assignedSources,
|
||||
reference.definiteReceiver,
|
||||
reference,
|
||||
);
|
||||
}
|
||||
for (const [index, reference] of [...iterableReferences].entries()) {
|
||||
@@ -1438,6 +1601,7 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
reference.widenedGetterResult === true,
|
||||
reference.assignedSources,
|
||||
reference.definiteReceiver === true,
|
||||
reference,
|
||||
);
|
||||
}
|
||||
// Native Map lookup methods do not expose their receiver to user code.
|
||||
@@ -1470,7 +1634,7 @@ interface ReferencedValue extends JavaScriptSemanticObjectReference {
|
||||
readonly iterableFallbackPath?: PropertyPath;
|
||||
}
|
||||
|
||||
interface IterableReference extends ReferencedValue {
|
||||
interface IterableReference extends ReferencedValue, MutationSelectionContext {
|
||||
readonly projection: PropertyPath;
|
||||
readonly fallbackPath: PropertyPath;
|
||||
readonly bindings: ReadonlySet<string>;
|
||||
|
||||
@@ -178,10 +178,33 @@ export const semanticProjectedSourceEligibilityAtRead = (
|
||||
)?.sources;
|
||||
|
||||
/** Keep imported origins subject to the same selected writes as other values. */
|
||||
export const semanticProjectedSourceFactsAtRead = (
|
||||
// Projection alternatives are introduced by callers. An ordinary property path
|
||||
// remains ordinary while the owner selects stored and copied source prefixes.
|
||||
export function semanticProjectedSourceFactsAtRead(
|
||||
binding: JavaScriptSemanticBindingState,
|
||||
reference: t.Node,
|
||||
path: JavaScriptSemanticPropertyPath,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
includeUnknown: boolean,
|
||||
memberReadAt?: t.Node,
|
||||
capturedSelections?: readonly SemanticCapturedSelection[],
|
||||
retainEligibility?: boolean,
|
||||
):
|
||||
| {
|
||||
readonly sources: readonly (Omit<ProjectedSource, "path"> & {
|
||||
readonly path: JavaScriptSemanticPropertyPath;
|
||||
})[];
|
||||
readonly retainsOrigin: boolean;
|
||||
}
|
||||
| undefined;
|
||||
export function semanticProjectedSourceFactsAtRead(
|
||||
...arguments_: Parameters<typeof projectedSourceFactsAtRead>
|
||||
): ReturnType<typeof projectedSourceFactsAtRead> =>
|
||||
projectedSourceFactsAtRead(...arguments_);
|
||||
): ReturnType<typeof projectedSourceFactsAtRead>;
|
||||
export function semanticProjectedSourceFactsAtRead(
|
||||
...arguments_: Parameters<typeof projectedSourceFactsAtRead>
|
||||
): ReturnType<typeof projectedSourceFactsAtRead> {
|
||||
return projectedSourceFactsAtRead(...arguments_);
|
||||
}
|
||||
|
||||
const projectedSourceFactsAtRead = (
|
||||
binding: JavaScriptSemanticBindingState,
|
||||
|
||||
Reference in New Issue
Block a user