Files
openclaw/scripts/check-database-first-legacy-stores.mjs
Galin Iliev 50c82b3020 fix(scripts): add database-first legacy store guard
Adds a required database-first legacy-store guard and regression coverage for legacy runtime state write patterns.

The guard is wired into architecture/preflight/changed checks, narrows the documented guard contract to the implemented filesystem-write scope, and tightens extension migration exemptions to explicit owner APIs. Also includes a small memory-core lint unblocker after current CI flagged an unnecessary non-null assertion.

Verification:
- pnpm check:database-first-legacy-stores
- pnpm lint:scripts
- node scripts/run-vitest.mjs test/scripts/check-database-first-legacy-stores.test.ts -- --reporter=verbose
- node scripts/run-oxlint.mjs extensions/memory-core/src/memory/manager-embedding-ops.ts
- git diff --check
- .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main
- GitHub CI green for PR head 34dde2c620

Closes #91628.
2026-06-14 20:08:06 -07:00

9783 lines
344 KiB
JavaScript

#!/usr/bin/env node
// Guards database-first state ownership by blocking legacy store writes in runtime code.
import { promises as fs } from "node:fs";
import path from "node:path";
import ts from "typescript";
import { resolveRepoRoot, runAsScript, toLine, unwrapExpression } from "./lib/ts-guard-utils.mjs";
export const databaseFirstLegacyStoreSourceRoots = ["src", "extensions", "packages"];
const legacyWriteCallees = new Set([
"appendFile",
"appendFileSync",
"cp",
"cpSync",
"copyFile",
"copyFileSync",
"createWriteStream",
"open",
"openSync",
"rm",
"rmSync",
"unlink",
"unlinkSync",
"rename",
"renameSync",
"writeFile",
"writeFileSync",
]);
const fsModuleSpecifiers = new Set(["node:fs", "node:fs/promises", "fs", "fs/promises"]);
const helperWriteCallees = new Set([
"appendRegularFile",
"appendRegularFileSync",
"replaceFileAtomic",
"replaceFileAtomicSync",
"saveJsonFile",
"writeJson",
"writeJsonAtomic",
"writeJsonFileAtomically",
"writeJsonSync",
"writeTextAtomic",
]);
const fsSafeStoreFactoryCallees = new Set([
"fileStore",
"fileStoreSync",
"privateFileStore",
"privateFileStoreSync",
"root",
]);
const fsSafeJsonStoreFactoryCallees = new Set(["jsonStore"]);
const fsSafeStoreWriteMethods = new Set([
"append",
"copyIn",
"create",
"createJson",
"mkdir",
"move",
"openWritable",
"remove",
"write",
"writeJson",
"writeStream",
"writeText",
]);
const fsSafeJsonStoreWriteMethods = new Set(["update", "updateOr", "write"]);
const helperWriteModulePattern =
/(?:^|\/)(?:fs-safe|json-files|json-store|private-file-store|replace-file)(?:\.[cm]?[jt]s)?$/u;
const fsSafePackageModulePattern = /^@openclaw\/fs-safe(?:\/(?:root|store))?$/u;
const bridgeMarkerPattern = /\btranscriptLocator\b|sqlite-transcript:\/\//u;
const legacyStorePatterns = [
/\bsessions\.json\b/u,
/\.trajectory\.jsonl\b/u,
/\.acp-stream\.jsonl\b/u,
/\bacp\/event-ledger\.json\b/u,
/\bcache\/[^"'`]*\.json\b/u,
/\bagents\/[^"'`]+\/agent\/(?:auth|models)\.json\b/u,
/\b(?:credentials\/oauth|github-copilot\.token|openrouter-models|auth-profiles|auth-state|exec-approvals|workspace-state)\.json\b/u,
/\bcron\/(?:runs\/[^"'`]+\.jsonl|jobs\.json|jobs-state\.json)\b/u,
/\b(?:process-leases|session-toggles|known-users|msteams-conversations|msteams-polls|msteams-sso-tokens|bot-storage|sync-store|thread-bindings|inbound-dedupe|startup-verification|storage-meta|crypto-idb-snapshot|command-deploy-cache|plugin-binding-approvals|plugins\/installs|config-health|port-guard|restart-sentinel|gateway-restart-intent|gateway-supervisor-restart-handoff)\.json\b/u,
/\b(?:calls|ref-index|audit\/file-transfer|audit\/crestodian)\.jsonl\b/u,
/\b(?:reply-cache|sent-echoes|events|claims)\.jsonl\b/u,
/\bplugin-state\/state\.sqlite\b/u,
/\btasks\/(?:runs\.sqlite|flows\/registry\.sqlite)\b/u,
/\bopenclaw-state\.sqlite\b/u,
];
const allowedRuntimeMigrationPaths = [
"src/commands/doctor/",
"src/infra/session-state-migration.ts",
"src/infra/state-migrations.ts",
"src/commands/session-state-migration.ts",
"src/commands/doctor-state-migrations.test.ts",
];
const allowedFixturePaths = new Set([
"extensions/qa-lab/src/providers/shared/auth-store.ts",
"extensions/qa-matrix/src/runners/contract/scenario-runtime-e2ee-destructive.ts",
]);
const allowedCurrentLegacyWriteViolations = [
"extensions/matrix/src/matrix/client/storage.ts:legacy store filesystem write:writeStoredRootMetadata(path.join(params.rootDir, STORAGE_META_FILENAME), { homeserver: metadata.homeserver, userId: metadata.userId, accountId: metadata.accountId ?? DEFAULT_ACCOUNT_KEY, accessTokenHash: metadata.accessTokenHash, deviceId: metadata.deviceId ?? null, currentTokenStateClaimed: true, createdAt: metadata.createdAt ?? new Date().toISOString(), })",
"extensions/matrix/src/matrix/client/storage.ts:legacy store filesystem write:writeStoredRootMetadata(path.join(params.rootDir, STORAGE_META_FILENAME), { homeserver: metadata.homeserver, userId: metadata.userId, accountId: metadata.accountId ?? DEFAULT_ACCOUNT_KEY, accessTokenHash: metadata.accessTokenHash, deviceId, currentTokenStateClaimed: metadata.currentTokenStateClaimed === true, createdAt: metadata.createdAt ?? new Date().toISOString(), })",
"extensions/memory-wiki/src/compile.ts:legacy store filesystem write:root.write(relativePath, content)",
];
const sourceFileExtensions = new Set([".cjs", ".cts", ".js", ".mjs", ".mts", ".ts", ".tsx"]);
const sourceTestSuffixes = [
".e2e-harness.js",
".e2e-harness.mjs",
".e2e-harness.ts",
".test-fixtures.js",
".test-fixtures.mjs",
".test-fixtures.ts",
".test-helper.js",
".test-helper.mjs",
".test-helper.ts",
".test-helpers.js",
".test-helpers.mjs",
".test-helpers.ts",
".test-harness.js",
".test-harness.mjs",
".test-harness.ts",
".test-mocks.js",
".test-mocks.mjs",
".test-mocks.ts",
".test-support.js",
".test-support.mjs",
".test-support.ts",
".test-utils.js",
".test-utils.mjs",
".test-utils.ts",
".test.js",
".test.mjs",
".test.ts",
"test-fixtures.js",
"test-fixtures.mjs",
"test-fixtures.ts",
"test-helper.js",
"test-helper.mjs",
"test-helper.ts",
"test-helpers.js",
"test-helpers.mjs",
"test-helpers.ts",
"test-harness.js",
"test-harness.mjs",
"test-harness.ts",
"test-mocks.js",
"test-mocks.mjs",
"test-mocks.ts",
"test-support.js",
"test-support.mjs",
"test-support.ts",
"test-utils.js",
"test-utils.mjs",
"test-utils.ts",
];
function isAllowedLegacyOwnerPath(relativePath) {
return (
allowedFixturePaths.has(relativePath) ||
allowedRuntimeMigrationPaths.some((allowed) => relativePath.startsWith(allowed)) ||
/^extensions\/[^/]+\/(?:doctor-contract-api|legacy-state-migrations-api)\.ts$/u.test(
relativePath,
)
);
}
function normalizedSourceText(sourceFile, node) {
return node.getText(sourceFile).replace(/\s+/gu, " ");
}
function currentLegacyWriteViolationAllowances(relativePath = null) {
const allowances = new Map();
const relativePrefix = typeof relativePath === "string" ? relativePath.concat(":") : null;
for (const fingerprint of allowedCurrentLegacyWriteViolations) {
if (relativePrefix !== null && !fingerprint.startsWith(relativePrefix)) {
continue;
}
allowances.set(fingerprint, (allowances.get(fingerprint) ?? 0) + 1);
}
return allowances;
}
function currentLegacyWriteViolationPath(fingerprint) {
const marker = ":legacy store filesystem write:";
const markerIndex = fingerprint.indexOf(marker);
return markerIndex === -1 ? null : fingerprint.slice(0, markerIndex);
}
function consumeAllowedCurrentLegacyViolation(
allowances,
relativePath,
sourceFile,
fingerprintNode,
kind,
) {
const fingerprint = `${relativePath}:${kind}:${normalizedSourceText(sourceFile, fingerprintNode)}`;
const remaining = allowances.get(fingerprint) ?? 0;
if (remaining === 0) {
return false;
}
if (remaining === 1) {
allowances.delete(fingerprint);
} else {
allowances.set(fingerprint, remaining - 1);
}
return true;
}
function isSourceFile(filePath) {
return sourceFileExtensions.has(path.extname(filePath));
}
function isTestLikeSourceFile(filePath) {
return sourceTestSuffixes.some((suffix) => filePath.endsWith(suffix));
}
async function collectSourceFiles(targetPath) {
let stat;
try {
stat = await fs.stat(targetPath);
} catch (error) {
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
return [];
}
throw error;
}
if (stat.isFile()) {
return isSourceFile(targetPath) && !isTestLikeSourceFile(targetPath) ? [targetPath] : [];
}
const entries = await fs.readdir(targetPath, { withFileTypes: true });
const files = [];
for (const entry of entries) {
if (entry.name === "node_modules") {
continue;
}
const entryPath = path.join(targetPath, entry.name);
if (entry.isDirectory()) {
files.push(...(await collectSourceFiles(entryPath)));
continue;
}
if (entry.isFile() && isSourceFile(entryPath) && !isTestLikeSourceFile(entryPath)) {
files.push(entryPath);
}
}
return files;
}
export async function collectDatabaseFirstLegacyStoreSourceFiles(sourceRoots) {
return (await Promise.all(sourceRoots.map((root) => collectSourceFiles(root)))).flat();
}
function importSource(node) {
const moduleSpecifier = node.moduleSpecifier;
return ts.isStringLiteral(moduleSpecifier) ? moduleSpecifier.text : "";
}
function isHelperWriteModuleSource(source) {
return (
source === "openclaw/plugin-sdk/file-access-runtime" ||
source === "openclaw/plugin-sdk/security-runtime" ||
fsSafePackageModulePattern.test(source) ||
helperWriteModulePattern.test(source)
);
}
function collectCreateRequireBindings(sourceFile) {
const bindings = new Set();
function visit(node) {
if (ts.isImportDeclaration(node) && ["node:module", "module"].includes(importSource(node))) {
const namedBindings = node.importClause?.namedBindings;
if (namedBindings && ts.isNamedImports(namedBindings)) {
for (const element of namedBindings.elements) {
const importedName = element.propertyName?.text ?? element.name.text;
if (importedName === "createRequire") {
bindings.add(element.name.text);
}
}
}
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
return bindings;
}
function isFsRequireExpression(expression, isRequireName = (name) => name === "require") {
const call = unwrapExpression(expression);
if (!ts.isCallExpression(call) || !ts.isIdentifier(unwrapExpression(call.expression))) {
return false;
}
const requireName = unwrapExpression(call.expression).text;
const [specifier] = call.arguments;
return (
isRequireName(requireName) &&
specifier &&
ts.isStringLiteralLike(specifier) &&
fsModuleSpecifiers.has(specifier.text)
);
}
function unwrapAwaitExpression(expression) {
const unwrapped = unwrapExpression(expression);
return ts.isAwaitExpression(unwrapped) ? unwrapExpression(unwrapped.expression) : unwrapped;
}
function isFsDynamicImportExpression(expression) {
const call = unwrapAwaitExpression(expression);
if (!ts.isCallExpression(call) || call.expression.kind !== ts.SyntaxKind.ImportKeyword) {
return false;
}
const [specifier] = call.arguments;
return (
specifier !== undefined &&
ts.isStringLiteralLike(specifier) &&
fsModuleSpecifiers.has(specifier.text)
);
}
function collectFsBindings(sourceFile) {
const fsModuleBindings = new Set();
const fsWriteAliases = new Map();
const fsSafeStoreFactoryAliases = new Map();
for (const statement of sourceFile.statements) {
if (!ts.isImportDeclaration(statement)) {
continue;
}
const source = importSource(statement);
const clause = statement.importClause;
if (!clause) {
continue;
}
if (clause.name && fsModuleSpecifiers.has(source)) {
fsModuleBindings.add(clause.name.text);
}
const namedBindings = clause.namedBindings;
if (!namedBindings) {
continue;
}
if (ts.isNamespaceImport(namedBindings)) {
if (fsModuleSpecifiers.has(source)) {
fsModuleBindings.add(namedBindings.name.text);
}
if (isHelperWriteModuleSource(source)) {
for (const helperName of helperWriteCallees) {
fsWriteAliases.set(`${namedBindings.name.text}.${helperName}`, helperName);
}
for (const factoryName of [
...fsSafeStoreFactoryCallees,
...fsSafeJsonStoreFactoryCallees,
]) {
fsSafeStoreFactoryAliases.set(`${namedBindings.name.text}.${factoryName}`, factoryName);
}
}
continue;
}
for (const element of namedBindings.elements) {
const importedName = element.propertyName?.text ?? element.name.text;
if (fsModuleSpecifiers.has(source) && importedName === "promises") {
fsModuleBindings.add(element.name.text);
}
if (fsModuleSpecifiers.has(source) && legacyWriteCallees.has(importedName)) {
fsWriteAliases.set(element.name.text, importedName);
}
if (isHelperWriteModuleSource(source) && helperWriteCallees.has(importedName)) {
fsWriteAliases.set(element.name.text, importedName);
}
if (
isHelperWriteModuleSource(source) &&
(fsSafeStoreFactoryCallees.has(importedName) ||
fsSafeJsonStoreFactoryCallees.has(importedName))
) {
fsSafeStoreFactoryAliases.set(element.name.text, importedName);
}
}
}
return { fsModuleBindings, fsWriteAliases, fsSafeStoreFactoryAliases };
}
function templateCandidateText(current) {
let text = current.head.text;
for (const span of current.templateSpans) {
text += `*${span.literal.text}`;
}
return text || "*";
}
function legacyCandidateTexts(sourceFile, node) {
const candidates = node.pos >= 0 && node.end >= 0 ? [node.getText(sourceFile)] : [];
const stringSegments = [];
function binaryExpressionCandidateText(current) {
if (current.operatorToken.kind !== ts.SyntaxKind.PlusToken) {
return null;
}
const left = pathSegmentCandidateText(current.left);
const right = pathSegmentCandidateText(current.right);
if (!left && !right) {
return null;
}
return `${left ?? "*"}${right ?? "*"}`;
}
function pathSegmentCandidateText(current) {
const unwrapped = unwrapExpression(current);
if (ts.isStringLiteralLike(unwrapped)) {
return unwrapped.text;
}
if (ts.isTemplateExpression(unwrapped)) {
return templateCandidateText(unwrapped);
}
if (ts.isBinaryExpression(unwrapped)) {
return binaryExpressionCandidateText(unwrapped);
}
return "*";
}
if (candidates.length === 0) {
const syntheticPathSegment = pathSegmentCandidateText(node);
if (syntheticPathSegment !== "*") {
candidates.push(syntheticPathSegment);
}
}
function maybeAddCallPathCandidate(current) {
if (!ts.isCallExpression(current) || current.arguments.length < 2) {
return;
}
const segments = current.arguments.map((argument) => pathSegmentCandidateText(argument));
if (!segments.some((segment) => segment !== "*")) {
return;
}
candidates.push(segments.join("/"));
}
function visit(current) {
maybeAddCallPathCandidate(current);
if (ts.isStringLiteralLike(current)) {
stringSegments.push(current.text);
}
ts.forEachChild(current, visit);
}
visit(node);
if (stringSegments.length > 1) {
candidates.push(stringSegments.join("/"));
}
return candidates;
}
/**
* Finds database-first legacy-store violations in one TypeScript/JavaScript source file.
*/
export function collectDatabaseFirstLegacyStoreViolations(
content,
relativePath = "source.ts",
scanOptions = {},
) {
if (isAllowedLegacyOwnerPath(relativePath)) {
return [];
}
const sourceFile = ts.createSourceFile(relativePath, content, ts.ScriptTarget.Latest, true);
const currentLegacyWriteAllowances =
scanOptions.currentLegacyWriteAllowances ?? currentLegacyWriteViolationAllowances(relativePath);
const createRequireBindings = collectCreateRequireBindings(sourceFile);
const { fsModuleBindings, fsWriteAliases, fsSafeStoreFactoryAliases } =
collectFsBindings(sourceFile);
const violations = [];
const seenViolations = new Set();
const fsModuleBindingScopes = [new Map([...fsModuleBindings].map((name) => [name, true]))];
const fsModulePropertyScopes = [new Map()];
const fsWriteAliasScopes = [fsWriteAliases];
const fsSafeStoreFactoryAliasScopes = [fsSafeStoreFactoryAliases];
const fsSafeStoreScopes = [new Map()];
const fsSafeJsonStoreScopes = [new Map()];
const requireAliasScopes = [new Map([["require", true]])];
const requireShadowScopes = [new Set()];
const createRequireShadowScopes = [new Set()];
const legacyPathScopes = [new Map()];
const literalTextScopes = [new Map()];
const knownUndefinedScopes = [new Map()];
const legacyKnownObjectLiteralScopes = [new Map()];
const legacyObjectPropertyScopes = [new Map()];
const wrapperFunctionScopes = [new Map()];
const conditionalExecutionScopes = [false];
const branchEffectScopes = [];
function addViolation(node, kind, fingerprintNode = node) {
const line = toLine(sourceFile, node);
if (
consumeAllowedCurrentLegacyViolation(
currentLegacyWriteAllowances,
relativePath,
sourceFile,
fingerprintNode,
kind,
)
) {
return;
}
const key = `${line}:${kind}`;
if (seenViolations.has(key)) {
return;
}
seenViolations.add(key);
violations.push({ kind, line });
}
function currentLegacyPathScope() {
return legacyPathScopes[legacyPathScopes.length - 1];
}
function currentLiteralTextScope() {
return literalTextScopes[literalTextScopes.length - 1];
}
function currentKnownUndefinedScope() {
return knownUndefinedScopes[knownUndefinedScopes.length - 1];
}
function currentFsWriteAliasScope() {
return fsWriteAliasScopes[fsWriteAliasScopes.length - 1];
}
function currentFsModuleBindingScope() {
return fsModuleBindingScopes[fsModuleBindingScopes.length - 1];
}
function currentFsModulePropertyScope() {
return fsModulePropertyScopes[fsModulePropertyScopes.length - 1];
}
function currentRequireShadowScope() {
return requireShadowScopes[requireShadowScopes.length - 1];
}
function currentRequireAliasScope() {
return requireAliasScopes[requireAliasScopes.length - 1];
}
function resolveRequireAlias(name) {
for (let index = requireAliasScopes.length - 1; index >= 0; index--) {
const scope = requireAliasScopes[index];
if (scope.has(name)) {
return scope.get(name) === true;
}
}
return false;
}
function isNodeRequireName(name) {
return resolveRequireAlias(name);
}
function isCreateRequireShadowed(name) {
return createRequireShadowScopes.some((scope) => scope.has(name));
}
function isCreateRequireExpression(expression) {
const call = unwrapExpression(expression);
return (
ts.isCallExpression(call) &&
ts.isIdentifier(unwrapExpression(call.expression)) &&
createRequireBindings.has(unwrapExpression(call.expression).text) &&
!isCreateRequireShadowed(unwrapExpression(call.expression).text)
);
}
function isRequireAliasExpression(expression) {
const value = unwrapExpression(expression);
return (
isCreateRequireExpression(value) ||
(ts.isIdentifier(value) && resolveRequireAlias(value.text))
);
}
function resolveFsModuleBinding(name) {
for (let index = fsModuleBindingScopes.length - 1; index >= 0; index--) {
const scope = fsModuleBindingScopes[index];
if (scope.has(name)) {
return scope.get(name) === true;
}
}
return false;
}
function resolveFsModuleProperty(pathParts) {
const fullPath = pathParts.join(".");
const prefixes = pathParts.map((_, index) => pathParts.slice(0, index + 1).join("."));
for (let index = fsModulePropertyScopes.length - 1; index >= 0; index--) {
const scope = fsModulePropertyScopes[index];
if (scope.has(fullPath)) {
return scope.get(fullPath) === true;
}
for (const prefix of prefixes) {
if (scope.get(prefix) === false) {
return false;
}
}
}
return false;
}
function visibleFsModuleBindings() {
const bindings = new Map();
for (const scope of fsModuleBindingScopes) {
for (const [name, value] of scope) {
bindings.set(name, value);
}
}
return bindings;
}
function visibleFsModuleProperties() {
const properties = new Map();
for (const scope of fsModulePropertyScopes) {
for (const [name, value] of scope) {
properties.set(name, value);
}
}
return properties;
}
function resolveFsWriteAlias(name) {
for (let index = fsWriteAliasScopes.length - 1; index >= 0; index--) {
const scope = fsWriteAliasScopes[index];
if (scope.has(name)) {
return scope.get(name) ?? null;
}
}
return null;
}
function resolveFsSafeStoreFactoryAlias(name) {
for (let index = fsSafeStoreFactoryAliasScopes.length - 1; index >= 0; index--) {
const scope = fsSafeStoreFactoryAliasScopes[index];
if (scope.has(name)) {
return scope.get(name) ?? null;
}
}
return null;
}
function resolveFsSafeStore(name) {
const value = lookupFsSafeStore(name);
return value === true;
}
function lookupFsSafeStore(name) {
for (let index = fsSafeStoreScopes.length - 1; index >= 0; index--) {
const scope = fsSafeStoreScopes[index];
if (scope.has(name)) {
return scope.get(name) === true;
}
}
return null;
}
function resolveFsSafeJsonStore(name) {
const value = lookupFsSafeJsonStore(name);
return value === true;
}
function lookupFsSafeJsonStore(name) {
for (let index = fsSafeJsonStoreScopes.length - 1; index >= 0; index--) {
const scope = fsSafeJsonStoreScopes[index];
if (scope.has(name)) {
return scope.get(name) === true;
}
}
return null;
}
function visibleFsWriteAliases() {
const aliases = new Map();
for (const scope of fsWriteAliasScopes) {
for (const [name, value] of scope) {
aliases.set(name, value);
}
}
return aliases;
}
function visibleRequireAliasSnapshot(maxScopeIndex = requireAliasScopes.length - 1) {
const aliases = new Map();
const sourceScopes = new Map();
for (let index = 0; index <= maxScopeIndex; index++) {
const scope = requireAliasScopes[index];
if (!scope) {
continue;
}
for (const [name, value] of scope) {
aliases.set(name, value);
sourceScopes.set(name, index);
}
}
return { aliases, sourceScopes };
}
function visibleCreateRequireShadows() {
const shadows = new Set();
for (const scope of createRequireShadowScopes) {
for (const name of scope) {
shadows.add(name);
}
}
return shadows;
}
function fsModuleBindingWriteScope(name) {
for (let index = fsModuleBindingScopes.length - 1; index >= 0; index--) {
const scope = fsModuleBindingScopes[index];
if (scope.has(name)) {
return scope;
}
}
return currentFsModuleBindingScope();
}
function fsWriteAliasWriteScope(name) {
for (let index = fsWriteAliasScopes.length - 1; index >= 0; index--) {
const scope = fsWriteAliasScopes[index];
if (scope.has(name)) {
return scope;
}
}
return currentFsWriteAliasScope();
}
function fsSafeStoreWriteScope(name) {
for (let index = fsSafeStoreScopes.length - 1; index >= 0; index--) {
const scope = fsSafeStoreScopes[index];
if (scope.has(name)) {
return scope;
}
}
return currentFsSafeStoreScope();
}
function fsSafeStoreFactoryAliasWriteScope(name) {
for (let index = fsSafeStoreFactoryAliasScopes.length - 1; index >= 0; index--) {
const scope = fsSafeStoreFactoryAliasScopes[index];
if (scope.has(name)) {
return scope;
}
}
return currentFsSafeStoreFactoryAliasScope();
}
function fsSafeJsonStoreWriteScope(name) {
for (let index = fsSafeJsonStoreScopes.length - 1; index >= 0; index--) {
const scope = fsSafeJsonStoreScopes[index];
if (scope.has(name)) {
return scope;
}
}
return currentFsSafeJsonStoreScope();
}
function currentLegacyObjectPropertyScope() {
return legacyObjectPropertyScopes[legacyObjectPropertyScopes.length - 1];
}
function currentLegacyKnownObjectLiteralScope() {
return legacyKnownObjectLiteralScopes[legacyKnownObjectLiteralScopes.length - 1];
}
function lookupKnownLegacyObjectLiteral(name) {
for (let index = legacyKnownObjectLiteralScopes.length - 1; index >= 0; index--) {
const scope = legacyKnownObjectLiteralScopes[index];
if (scope.has(name)) {
return scope.get(name) === true;
}
if (legacyPathScopes[index].has(name)) {
return false;
}
}
return false;
}
function isKnownLegacyObjectLiteralExpression(expression) {
const unwrapped = unwrapExpression(expression);
return (
ts.isObjectLiteralExpression(unwrapped) ||
(ts.isIdentifier(unwrapped) && lookupKnownLegacyObjectLiteral(unwrapped.text))
);
}
function markKnownLegacyObjectLiteral(
name,
initializer,
targetScope = currentLegacyKnownObjectLiteralScope(),
) {
targetScope.set(name, isKnownLegacyObjectLiteralExpression(initializer));
}
function currentFsSafeStoreFactoryAliasScope() {
return fsSafeStoreFactoryAliasScopes[fsSafeStoreFactoryAliasScopes.length - 1];
}
function currentFsSafeStoreScope() {
return fsSafeStoreScopes[fsSafeStoreScopes.length - 1];
}
function currentFsSafeJsonStoreScope() {
return fsSafeJsonStoreScopes[fsSafeJsonStoreScopes.length - 1];
}
function currentWrapperFunctionScope() {
return wrapperFunctionScopes[wrapperFunctionScopes.length - 1];
}
function currentConditionalExecutionScope() {
return conditionalExecutionScopes[conditionalExecutionScopes.length - 1];
}
function currentBranchEffectScope() {
return branchEffectScopes[branchEffectScopes.length - 1] ?? null;
}
function createBranchEffects() {
return {
fsIdentifierAssignments: new Map(),
fsSafePropertyAssignments: new Map(),
identifierAssignments: new Map(),
propertyAssignments: new Map(),
wrapperAssignments: new Map(),
};
}
function objectPropertyKey(objectName, propertyName) {
return `${objectName}.${propertyName}`;
}
function resolveLegacyPathIdentifier(name) {
for (let index = legacyPathScopes.length - 1; index >= 0; index--) {
const scope = legacyPathScopes[index];
if (scope.has(name)) {
return scope.get(name) === true;
}
}
return false;
}
function resolveLiteralTextIdentifier(name) {
for (let index = literalTextScopes.length - 1; index >= 0; index--) {
const scope = literalTextScopes[index];
if (scope.has(name)) {
return scope.get(name) ?? [];
}
}
return [];
}
function literalTextWriteScope(name) {
for (let index = literalTextScopes.length - 1; index >= 0; index--) {
const scope = literalTextScopes[index];
if (scope.has(name)) {
return scope;
}
}
return currentLiteralTextScope();
}
function resolveKnownUndefinedIdentifier(name) {
for (let index = knownUndefinedScopes.length - 1; index >= 0; index--) {
const scope = knownUndefinedScopes[index];
if (scope.has(name)) {
return scope.get(name) === true;
}
}
return false;
}
function knownUndefinedWriteScope(name) {
for (let index = knownUndefinedScopes.length - 1; index >= 0; index--) {
const scope = knownUndefinedScopes[index];
if (scope.has(name)) {
return scope;
}
}
return currentKnownUndefinedScope();
}
function requireAliasWriteTarget(name) {
for (let index = requireAliasScopes.length - 1; index >= 0; index--) {
const scope = requireAliasScopes[index];
if (scope.has(name)) {
return { index, scope };
}
}
return { index: requireAliasScopes.length - 1, scope: currentRequireAliasScope() };
}
function expressionLiteralCandidateTexts(node) {
const candidates = legacyCandidateTexts(sourceFile, node);
const segmentOptions = [];
function combineSegmentOptions(left, right) {
const joined = left.flatMap((leftOption) =>
right.map((rightOption) => `${leftOption}${rightOption}`),
);
return joined.length > 32 ? joined.slice(0, 32) : joined;
}
function expressionSegmentOptions(current) {
const unwrapped = unwrapExpression(current);
if (ts.isStringLiteralLike(unwrapped)) {
return [unwrapped.text];
}
if (ts.isTemplateExpression(unwrapped)) {
let joined = [unwrapped.head.text];
for (const span of unwrapped.templateSpans) {
joined = combineSegmentOptions(joined, expressionSegmentOptions(span.expression));
joined = combineSegmentOptions(joined, [span.literal.text]);
}
return joined.length > 0 ? joined : ["*"];
}
if (
ts.isBinaryExpression(unwrapped) &&
unwrapped.operatorToken.kind === ts.SyntaxKind.PlusToken
) {
return combineSegmentOptions(
expressionSegmentOptions(unwrapped.left),
expressionSegmentOptions(unwrapped.right),
);
}
if (ts.isIdentifier(unwrapped)) {
const texts = resolveLiteralTextIdentifier(unwrapped.text);
return texts.length > 0 ? texts : ["*"];
}
return ["*"];
}
function maybeAddCallLiteralCandidate(current) {
if (!ts.isCallExpression(current) || current.arguments.length < 2) {
return;
}
const argumentOptions = current.arguments.map((argument) =>
expressionSegmentOptions(argument),
);
if (!argumentOptions.some((options) => options.some((option) => option !== "*"))) {
return;
}
let joined = [""];
for (const options of argumentOptions) {
joined = joined.flatMap((prefix) =>
options.map((option) => (prefix.length === 0 ? option : `${prefix}/${option}`)),
);
if (joined.length > 32) {
joined = joined.slice(0, 32);
}
}
candidates.push(...joined);
}
function visitCandidate(current) {
maybeAddCallLiteralCandidate(current);
if (ts.isStringLiteralLike(current)) {
segmentOptions.push([current.text]);
return;
}
if (ts.isIdentifier(current)) {
const texts = resolveLiteralTextIdentifier(current.text);
if (texts.length > 0) {
segmentOptions.push(texts);
}
}
ts.forEachChild(current, visitCandidate);
}
const expressionOptions = expressionSegmentOptions(node);
if (expressionOptions.some((option) => option !== "*")) {
candidates.push(...expressionOptions);
}
visitCandidate(node);
if (segmentOptions.length > 1) {
let joined = [""];
for (const options of segmentOptions) {
joined = joined.flatMap((prefix) =>
options.map((option) => (prefix.length === 0 ? option : `${prefix}/${option}`)),
);
if (joined.length > 32) {
joined = joined.slice(0, 32);
}
}
candidates.push(...joined);
}
return candidates;
}
function expressionTextContainsLegacyStore(node) {
return expressionLiteralCandidateTexts(node).some((text) =>
legacyStorePatterns.some((pattern) => pattern.test(text)),
);
}
function literalTextsFromExpression(expression) {
const unwrapped = unwrapExpression(expression);
if (ts.isStringLiteralLike(unwrapped)) {
return [unwrapped.text];
}
return [];
}
function arrayLiteralElementAt(expression, index) {
const unwrapped = unwrapExpression(expression);
if (!ts.isArrayLiteralExpression(unwrapped)) {
return null;
}
const element = unwrapped.elements[index];
return element && !ts.isSpreadElement(element) ? element : null;
}
function mergeConditionalLiteralTexts(previous, next) {
if (next.length === 0) {
return previous ?? null;
}
return [...new Set([...(previous ?? []), ...next])];
}
function mergeExhaustiveLiteralTexts(left, right) {
if (left.length === 0 && right.length === 0) {
return null;
}
return [...new Set([...left, ...right])];
}
function mergeLegacyObjectPropertyValues(left, right) {
if (left === true || right === true) {
return true;
}
if (
left === explicitUndefinedLegacyObjectPropertyValue ||
right === explicitUndefinedLegacyObjectPropertyValue ||
left === undefined ||
right === undefined
) {
return explicitUndefinedLegacyObjectPropertyValue;
}
return false;
}
function mergeConditionalLegacyObjectPropertyValue(previous, next) {
if (previous === undefined && next === false) {
return null;
}
return mergeLegacyObjectPropertyValues(previous, next);
}
function legacyObjectPropertyRewriteValues(objectName, initializer, existingScope) {
const values = new Map();
markLegacyObjectProperties(objectName, initializer, values, null);
if (isKnownLegacyObjectLiteralExpression(initializer)) {
const descendantPrefix = `${objectName}.`;
for (const key of existingScope.keys()) {
if (key.startsWith(descendantPrefix) && !values.has(key)) {
values.set(key, explicitUndefinedLegacyObjectPropertyValue);
}
}
}
return values;
}
function branchAssignmentPropertyValue(assignment, propertyKey) {
if (assignment.objectProperties.has(propertyKey)) {
return { known: true, value: assignment.objectProperties.get(propertyKey) };
}
if (assignment.knownObjectLiteral) {
return { known: true, value: explicitUndefinedLegacyObjectPropertyValue };
}
return { known: false, value: null };
}
function mergeBranchLegacyObjectPropertyValue(leftAssignment, rightAssignment, propertyKey) {
const left = branchAssignmentPropertyValue(leftAssignment, propertyKey);
const right = branchAssignmentPropertyValue(rightAssignment, propertyKey);
if (!left.known && !right.known) {
return null;
}
if (left.value === true || right.value === true) {
return true;
}
if (
left.value === explicitUndefinedLegacyObjectPropertyValue ||
right.value === explicitUndefinedLegacyObjectPropertyValue
) {
return explicitUndefinedLegacyObjectPropertyValue;
}
return left.known && right.known ? false : null;
}
function lookupLegacyObjectProperty(
objectName,
propertyName,
maxScopeIndex = legacyObjectPropertyScopes.length - 1,
) {
const result = lookupLegacyObjectPropertyEntry(objectName, propertyName, maxScopeIndex);
if (result.found) {
return result.value === true;
}
if (result.objectKnown) {
return result.objectValue ? null : false;
}
return null;
}
function lookupLegacyObjectPropertyEntry(
objectName,
propertyName,
maxScopeIndex = legacyObjectPropertyScopes.length - 1,
) {
const key = objectPropertyKey(objectName, propertyName);
for (
let index = Math.min(maxScopeIndex, legacyObjectPropertyScopes.length - 1);
index >= 0;
index--
) {
const propertyScope = legacyObjectPropertyScopes[index];
if (propertyScope.has(key)) {
return { found: true, value: propertyScope.get(key) };
}
if (legacyPathScopes[index].has(objectName)) {
return {
found: false,
objectKnown: true,
objectValue: legacyPathScopes[index].get(objectName) === true,
};
}
}
return { found: false, objectKnown: false, objectValue: false };
}
function lookupScopedLegacyObjectPropertyEntry(
objectName,
propertyPath,
propertyScope,
knownObjectLiteralScope,
) {
const propertyName = propertyPath.join(".");
const key = objectPropertyKey(objectName, propertyName);
if (propertyScope.has(key)) {
return { found: true, value: propertyScope.get(key) };
}
const parentPath = propertyPath.slice(0, -1).join(".");
const parentKey = parentPath ? objectPropertyKey(objectName, parentPath) : objectName;
if (knownObjectLiteralScope.get(parentKey) === true) {
return { found: false, objectKnown: true, objectValue: false };
}
return { found: false, objectKnown: false, objectValue: false };
}
function legacyObjectPropertyValueFromExpression(expression) {
return isKnownUndefinedExpression(expression)
? explicitUndefinedLegacyObjectPropertyValue
: expressionContainsLegacyStore(expression);
}
function elementAccessName(expression) {
const argument = unwrapExpression(expression);
return ts.isStringLiteral(argument) || ts.isNumericLiteral(argument) ? argument.text : null;
}
function propertyAccessPath(expression) {
const unwrapped = unwrapExpression(expression);
if (ts.isIdentifier(unwrapped)) {
return [unwrapped.text];
}
if (ts.isPropertyAccessExpression(unwrapped)) {
const parentPath = propertyAccessPath(unwrapped.expression);
return parentPath ? [...parentPath, unwrapped.name.text] : null;
}
if (ts.isElementAccessExpression(unwrapped)) {
const propertyName = elementAccessName(unwrapped.argumentExpression);
if (!propertyName) {
return null;
}
const parentPath = propertyAccessPath(unwrapped.expression);
return parentPath ? [...parentPath, propertyName] : null;
}
return null;
}
function namedObjectPropertyAccess(expression) {
if (ts.isPropertyAccessExpression(expression) && ts.isIdentifier(expression.expression)) {
return {
objectName: expression.expression.text,
propertyName: expression.name.text,
};
}
if (ts.isElementAccessExpression(expression) && ts.isIdentifier(expression.expression)) {
const propertyName = elementAccessName(expression.argumentExpression);
return propertyName
? {
objectName: expression.expression.text,
propertyName,
}
: null;
}
return null;
}
function legacyObjectPropertyWriteTarget(objectName, propertyName) {
const key = objectPropertyKey(objectName, propertyName);
for (let index = legacyObjectPropertyScopes.length - 1; index >= 0; index--) {
const propertyScope = legacyObjectPropertyScopes[index];
if (propertyScope.has(key) || legacyPathScopes[index].has(objectName)) {
return { index, scope: propertyScope };
}
}
return {
index: legacyObjectPropertyScopes.length - 1,
scope: currentLegacyObjectPropertyScope(),
};
}
function legacyIdentifierWriteScopes(name) {
for (let index = legacyPathScopes.length - 1; index >= 0; index--) {
if (legacyPathScopes[index].has(name)) {
return {
index,
pathScope: legacyPathScopes[index],
propertyScope: legacyObjectPropertyScopes[index],
wrapperScope: wrapperFunctionScopes[index],
};
}
}
return {
index: legacyPathScopes.length - 1,
pathScope: currentLegacyPathScope(),
propertyScope: currentLegacyObjectPropertyScope(),
wrapperScope: currentWrapperFunctionScope(),
};
}
function isConditionallyExecutedScope(node) {
const parent = node.parent;
return Boolean(
(ts.isBlock(node) &&
parent &&
((ts.isIfStatement(parent) &&
(parent.thenStatement === node || parent.elseStatement === node)) ||
(ts.isIterationStatement(parent, false) && parent.statement === node) ||
(ts.isTryStatement(parent) && parent.tryBlock === node))) ||
ts.isCaseBlock(node) ||
ts.isCatchClause(node),
);
}
function expressionContainsLegacyStore(node) {
if (expressionTextContainsLegacyStore(node)) {
return true;
}
let found = false;
function visitExpression(current) {
if (found) {
return;
}
if (ts.isIdentifier(current) && resolveLegacyPathIdentifier(current.text)) {
found = true;
return;
}
const propertyAccess = rootedPropertyAccessPath(current);
if (propertyAccess?.properties.length > 0) {
const propertyValue = lookupLegacyObjectProperty(
propertyAccess.rootName,
propertyAccess.properties.join("."),
);
if (propertyValue !== null) {
found = propertyValue;
return;
}
}
ts.forEachChild(current, visitExpression);
}
visitExpression(node);
return found;
}
function visitWithChildScope(node) {
fsWriteAliasScopes.push(new Map());
fsSafeStoreFactoryAliasScopes.push(new Map());
fsSafeStoreScopes.push(new Map());
fsSafeJsonStoreScopes.push(new Map());
fsModuleBindingScopes.push(new Map());
fsModulePropertyScopes.push(new Map());
requireAliasScopes.push(new Map());
requireShadowScopes.push(new Set());
createRequireShadowScopes.push(new Set());
legacyPathScopes.push(new Map());
literalTextScopes.push(new Map());
knownUndefinedScopes.push(new Map());
legacyKnownObjectLiteralScopes.push(new Map());
legacyObjectPropertyScopes.push(new Map());
wrapperFunctionScopes.push(new Map());
conditionalExecutionScopes.push(
currentConditionalExecutionScope() || isConditionallyExecutedScope(node),
);
if ("statements" in node) {
registerHoistedWrapperFunctions(node.statements);
}
ts.forEachChild(node, visit);
conditionalExecutionScopes.pop();
wrapperFunctionScopes.pop();
legacyObjectPropertyScopes.pop();
legacyKnownObjectLiteralScopes.pop();
knownUndefinedScopes.pop();
literalTextScopes.pop();
legacyPathScopes.pop();
fsModulePropertyScopes.pop();
fsModuleBindingScopes.pop();
fsSafeJsonStoreScopes.pop();
fsSafeStoreScopes.pop();
fsSafeStoreFactoryAliasScopes.pop();
fsWriteAliasScopes.pop();
createRequireShadowScopes.pop();
requireShadowScopes.pop();
requireAliasScopes.pop();
}
function registerFsBindingParameter(name) {
if (ts.isIdentifier(name)) {
currentFsModuleBindingScope().set(name.text, true);
return;
}
if (!ts.isObjectBindingPattern(name)) {
return;
}
for (const element of name.elements) {
const importedName = element.propertyName
? propertyNameText(element.propertyName)
: ts.isIdentifier(element.name)
? element.name.text
: null;
if (importedName === "promises") {
if (ts.isIdentifier(element.name)) {
currentFsModuleBindingScope().set(element.name.text, true);
} else if (ts.isObjectBindingPattern(element.name)) {
registerFsPromisesBindingParameter(element.name);
}
}
if (importedName && legacyWriteCallees.has(importedName) && ts.isIdentifier(element.name)) {
currentFsWriteAliasScope().set(element.name.text, importedName);
}
}
}
function registerFsPromisesBindingParameter(name) {
if (!ts.isObjectBindingPattern(name)) {
return;
}
for (const element of name.elements) {
const importedName = element.propertyName
? propertyNameText(element.propertyName)
: ts.isIdentifier(element.name)
? element.name.text
: null;
if (importedName && legacyWriteCallees.has(importedName) && ts.isIdentifier(element.name)) {
currentFsWriteAliasScope().set(element.name.text, importedName);
}
if (ts.isObjectBindingPattern(element.name)) {
registerFsPromisesBindingParameter(element.name);
}
}
}
function visitFunctionLike(node, fsBindingParameterIndexes = new Set()) {
fsWriteAliasScopes.push(new Map());
fsSafeStoreFactoryAliasScopes.push(new Map());
fsSafeStoreScopes.push(new Map());
fsSafeJsonStoreScopes.push(new Map());
fsModuleBindingScopes.push(new Map());
fsModulePropertyScopes.push(new Map());
requireAliasScopes.push(new Map());
requireShadowScopes.push(new Set());
createRequireShadowScopes.push(new Set());
legacyPathScopes.push(new Map());
literalTextScopes.push(new Map());
knownUndefinedScopes.push(new Map());
legacyKnownObjectLiteralScopes.push(new Map());
legacyObjectPropertyScopes.push(new Map());
wrapperFunctionScopes.push(new Map());
conditionalExecutionScopes.push(false);
node.parameters.forEach((parameter, index) => {
for (const name of bindingPatternNames(parameter.name)) {
currentLegacyPathScope().set(name, false);
currentLegacyKnownObjectLiteralScope().set(name, false);
currentKnownUndefinedScope().set(name, false);
currentLiteralTextScope().set(name, null);
currentWrapperFunctionScope().set(name, null);
currentRequireAliasScope().set(name, false);
}
markFsWriteAliasShadows(parameter.name);
markFsSafeStoreShadows(parameter.name);
markFsModuleBindingShadows(parameter.name);
markFsModulePropertyShadows(parameter.name);
markRequireShadows(parameter.name);
markCreateRequireShadows(parameter.name);
registerFsModuleTypeProperties(parameter.name, parameter.type);
if (fsBindingParameterIndexes.has(index)) {
registerFsBindingParameter(parameter.name);
}
});
ts.forEachChild(node, visit);
conditionalExecutionScopes.pop();
wrapperFunctionScopes.pop();
legacyObjectPropertyScopes.pop();
legacyKnownObjectLiteralScopes.pop();
knownUndefinedScopes.pop();
literalTextScopes.pop();
legacyPathScopes.pop();
fsModulePropertyScopes.pop();
fsModuleBindingScopes.pop();
fsSafeJsonStoreScopes.pop();
fsSafeStoreScopes.pop();
fsSafeStoreFactoryAliasScopes.pop();
fsWriteAliasScopes.pop();
createRequireShadowScopes.pop();
requireShadowScopes.pop();
requireAliasScopes.pop();
}
function dynamicFsImportThenCallback(node) {
const callee = unwrapExpression(node.expression);
if (
!ts.isPropertyAccessExpression(callee) ||
callee.name.text !== "then" ||
!isFsDynamicImportExpression(callee.expression)
) {
return null;
}
const [callback] = node.arguments;
return callback && ts.isFunctionLike(callback) ? callback : null;
}
function isFsModuleExpression(expression) {
const receiver = unwrapExpression(expression);
if (
isFsRequireExpression(receiver, isNodeRequireName) ||
isFsDynamicImportExpression(receiver)
) {
return true;
}
if (ts.isIdentifier(receiver)) {
return resolveFsModuleBinding(receiver.text);
}
const receiverPath = propertyAccessPath(receiver);
if (receiverPath && resolveFsModuleProperty(receiverPath)) {
return true;
}
return (
ts.isPropertyAccessExpression(receiver) &&
receiver.name.text === "promises" &&
(isFsRequireExpression(receiver.expression, isNodeRequireName) ||
isFsDynamicImportExpression(receiver.expression) ||
(ts.isIdentifier(receiver.expression) &&
resolveFsModuleBinding(receiver.expression.text)) ||
(propertyAccessPath(receiver.expression) &&
resolveFsModuleProperty(propertyAccessPath(receiver.expression))))
);
}
function legacyFsWriteName(expression, aliases = null) {
const callee = unwrapExpression(expression);
if (ts.isPropertyAccessExpression(callee)) {
const aliasedName = callExpressionName(callee);
const writeAlias = aliasedName ? resolveFsWriteAlias(aliasedName) : null;
if (writeAlias) {
return writeAlias;
}
return legacyWriteCallees.has(callee.name.text) && isFsModuleExpression(callee.expression)
? callee.name.text
: null;
}
if (ts.isElementAccessExpression(callee)) {
const aliasedName = callExpressionName(callee);
const writeAlias = aliasedName ? resolveFsWriteAlias(aliasedName) : null;
if (writeAlias) {
return writeAlias;
}
const writeName = elementAccessName(callee.argumentExpression);
return writeName &&
legacyWriteCallees.has(writeName) &&
isFsModuleExpression(callee.expression)
? writeName
: null;
}
if (!ts.isIdentifier(callee)) {
return null;
}
return aliases && aliases.has(callee.text)
? aliases.get(callee.text)
: resolveFsWriteAlias(callee.text);
}
function fsSafeStoreFactoryAliasName(expression) {
const callee = unwrapExpression(expression);
if (ts.isIdentifier(callee)) {
return resolveFsSafeStoreFactoryAlias(callee.text);
}
const name = callExpressionName(callee);
return name ? resolveFsSafeStoreFactoryAlias(name) : null;
}
function isFsSafeStoreFactoryCall(expression) {
const unwrapped = unwrapExpression(expression);
const call = ts.isAwaitExpression(unwrapped)
? unwrapExpression(unwrapped.expression)
: unwrapped;
if (!ts.isCallExpression(call)) {
return false;
}
const callee = unwrapExpression(call.expression);
if (ts.isPropertyAccessExpression(callee) || ts.isElementAccessExpression(callee)) {
const methodName = ts.isPropertyAccessExpression(callee)
? callee.name.text
: elementAccessName(callee.argumentExpression);
if (methodName === "root" && isFsSafeStoreExpression(callee.expression)) {
return true;
}
}
const name = callExpressionName(call.expression);
const factoryName = name ? resolveFsSafeStoreFactoryAlias(name) : null;
return Boolean(factoryName && fsSafeStoreFactoryCallees.has(factoryName));
}
function isFsSafeStoreExpression(expression) {
const unwrapped = unwrapExpression(expression);
if (isFsSafeStoreFactoryCall(unwrapped)) {
return true;
}
if (ts.isIdentifier(unwrapped)) {
return resolveFsSafeStore(unwrapped.text);
}
const receiverPath = propertyAccessPath(unwrapped);
if (receiverPath) {
return resolveFsSafeStore(receiverPath.join("."));
}
return false;
}
function objectFilePathContainsLegacyStore(expression) {
const unwrapped = unwrapExpression(expression);
if (ts.isIdentifier(unwrapped)) {
return lookupLegacyObjectProperty(unwrapped.text, "filePath") === true;
}
if (!ts.isObjectLiteralExpression(unwrapped)) {
return expressionContainsLegacyStore(unwrapped);
}
return objectLiteralPropertyContainsLegacyStore(unwrapped, "filePath");
}
function expressionContainsFsSafeJsonStoreLegacyPath(expression) {
const unwrapped = unwrapExpression(expression);
if (ts.isIdentifier(unwrapped)) {
return resolveFsSafeJsonStore(unwrapped.text);
}
const receiverPath = propertyAccessPath(unwrapped);
if (receiverPath && resolveFsSafeJsonStore(receiverPath.join("."))) {
return true;
}
if (!ts.isCallExpression(unwrapped)) {
return false;
}
const callName = callExpressionName(unwrapped.expression);
const factoryName = callName ? resolveFsSafeStoreFactoryAlias(callName) : null;
if (factoryName && fsSafeJsonStoreFactoryCallees.has(factoryName)) {
const options = unwrapped.arguments[0];
return options ? objectFilePathContainsLegacyStore(options) : false;
}
const callee = unwrapExpression(unwrapped.expression);
if (!ts.isPropertyAccessExpression(callee) && !ts.isElementAccessExpression(callee)) {
return false;
}
const methodName = ts.isPropertyAccessExpression(callee)
? callee.name.text
: elementAccessName(callee.argumentExpression);
if (methodName !== "json" || !isFsSafeStoreExpression(callee.expression)) {
return false;
}
const pathArgument = unwrapped.arguments[0];
return pathArgument ? pathArgumentContainsLegacyStore(pathArgument) : false;
}
function fsSafeJsonStoreWriteContainsLegacyStore(call) {
const callee = unwrapExpression(call.expression);
if (!ts.isPropertyAccessExpression(callee) && !ts.isElementAccessExpression(callee)) {
return false;
}
const methodName = ts.isPropertyAccessExpression(callee)
? callee.name.text
: elementAccessName(callee.argumentExpression);
if (!methodName || !fsSafeJsonStoreWriteMethods.has(methodName)) {
return false;
}
return expressionContainsFsSafeJsonStoreLegacyPath(callee.expression);
}
function fsSafeStoreWritePathArguments(call) {
const callee = unwrapExpression(call.expression);
if (!ts.isPropertyAccessExpression(callee) && !ts.isElementAccessExpression(callee)) {
return [];
}
const methodName = ts.isPropertyAccessExpression(callee)
? callee.name.text
: elementAccessName(callee.argumentExpression);
if (!methodName || !fsSafeStoreWriteMethods.has(methodName)) {
return [];
}
if (!isFsSafeStoreExpression(callee.expression)) {
return [];
}
if (methodName === "move") {
return [...call.arguments].slice(0, 2);
}
return call.arguments[0] ? [call.arguments[0]] : [];
}
function markFsWriteAliasShadows(name) {
for (const bindingName of bindingPatternNames(name)) {
if (resolveFsWriteAlias(bindingName)) {
currentFsWriteAliasScope().set(bindingName, null);
}
shadowVisibleFsWriteObjectAliases(bindingName);
}
}
function markFsSafeStoreShadows(name) {
for (const bindingName of bindingPatternNames(name)) {
if (resolveFsSafeStoreFactoryAlias(bindingName)) {
currentFsSafeStoreFactoryAliasScope().set(bindingName, null);
}
const prefix = `${bindingName}.`;
for (const scope of fsSafeStoreFactoryAliasScopes) {
for (const alias of scope.keys()) {
if (alias.startsWith(prefix)) {
currentFsSafeStoreFactoryAliasScope().set(alias, null);
}
}
}
if (resolveFsSafeStore(bindingName)) {
currentFsSafeStoreScope().set(bindingName, false);
}
if (resolveFsSafeJsonStore(bindingName)) {
currentFsSafeJsonStoreScope().set(bindingName, false);
}
const storePrefix = `${bindingName}.`;
for (const scope of fsSafeStoreScopes) {
for (const alias of scope.keys()) {
if (alias.startsWith(storePrefix)) {
currentFsSafeStoreScope().set(alias, false);
}
}
}
for (const scope of fsSafeJsonStoreScopes) {
for (const alias of scope.keys()) {
if (alias.startsWith(storePrefix)) {
currentFsSafeJsonStoreScope().set(alias, false);
}
}
}
}
}
function markFsModuleBindingShadows(name) {
for (const bindingName of bindingPatternNames(name)) {
if (resolveFsModuleBinding(bindingName)) {
currentFsModuleBindingScope().set(bindingName, false);
}
}
}
function markFsModulePropertyShadows(name) {
for (const bindingName of bindingPatternNames(name)) {
clearFsModuleObjectProperties(currentFsModulePropertyScope(), bindingName);
}
}
function markRequireShadows(name) {
if (bindingPatternNames(name).includes("require")) {
currentRequireShadowScope().add("require");
}
}
function markCreateRequireShadows(name) {
for (const bindingName of bindingPatternNames(name)) {
if (createRequireBindings.has(bindingName)) {
createRequireShadowScopes[createRequireShadowScopes.length - 1].add(bindingName);
}
}
}
function isFsModuleTypeNode(type) {
return Boolean(
type &&
/\btypeof\s+import\s*\(\s*["'](?:node:fs|node:fs\/promises|fs|fs\/promises)["']\s*\)/u.test(
type.getText(sourceFile),
),
);
}
function fsModulePropertyPathsFromType(type) {
const paths = [];
if (!type || !ts.isTypeLiteralNode(type)) {
return paths;
}
for (const member of type.members) {
if (!ts.isPropertySignature(member) || !member.type) {
continue;
}
const propertyName = propertyNameText(member.name);
if (!propertyName) {
continue;
}
if (isFsModuleTypeNode(member.type)) {
paths.push([propertyName]);
}
for (const nestedPath of fsModulePropertyPathsFromType(member.type)) {
paths.push([propertyName, ...nestedPath]);
}
}
return paths;
}
function registerFsModuleTypeProperties(name, type) {
if (!ts.isIdentifier(name) || !type) {
return;
}
if (isFsModuleTypeNode(type)) {
currentFsModuleBindingScope().set(name.text, true);
}
for (const pathParts of fsModulePropertyPathsFromType(type)) {
currentFsModulePropertyScope().set([name.text, ...pathParts].join("."), true);
}
}
function collectFsWriteAliasesFromBinding(node) {
collectFsWriteAliasesFromBindingInto(node, currentFsWriteAliasScope());
}
function clearFsWriteObjectAliases(scope, objectName) {
const prefix = `${objectName}.`;
for (const name of scope.keys()) {
if (name.startsWith(prefix)) {
scope.set(name, null);
}
}
}
function shadowVisibleFsWriteObjectAliases(objectName) {
const prefix = `${objectName}.`;
const currentScope = currentFsWriteAliasScope();
for (const scope of fsWriteAliasScopes) {
for (const name of scope.keys()) {
if (name.startsWith(prefix)) {
currentScope.set(name, null);
}
}
}
}
function setFsWriteObjectAlias(scope, name, writeName, conditionalWrite) {
if (writeName) {
scope.set(name, writeName);
} else if (!conditionalWrite) {
scope.set(name, null);
}
}
function registerFsWriteObjectAliases(
objectName,
initializer,
scope = currentFsWriteAliasScope(),
conditionalWrite = false,
) {
const objectLiteral = unwrapExpression(initializer);
if (!ts.isObjectLiteralExpression(objectLiteral)) {
return;
}
for (const property of objectLiteral.properties) {
if (ts.isPropertyAssignment(property)) {
const name = propertyNameText(property.name);
if (name) {
setFsWriteObjectAlias(
scope,
`${objectName}.${name}`,
legacyFsWriteName(property.initializer),
conditionalWrite,
);
}
continue;
}
if (ts.isShorthandPropertyAssignment(property)) {
setFsWriteObjectAlias(
scope,
`${objectName}.${property.name.text}`,
resolveFsWriteAlias(property.name.text),
conditionalWrite,
);
}
}
}
function clearFsSafeStoreObjectAliases(storeScope, jsonStoreScope, objectName) {
const prefix = `${objectName}.`;
for (const name of storeScope.keys()) {
if (name.startsWith(prefix)) {
storeScope.set(name, false);
}
}
for (const name of jsonStoreScope.keys()) {
if (name.startsWith(prefix)) {
jsonStoreScope.set(name, false);
}
}
}
function shadowVisibleFsSafeStoreObjectAliases(objectName) {
const prefix = `${objectName}.`;
const currentStoreScope = currentFsSafeStoreScope();
const currentJsonStoreScope = currentFsSafeJsonStoreScope();
for (const scope of fsSafeStoreScopes) {
for (const name of scope.keys()) {
if (name.startsWith(prefix)) {
currentStoreScope.set(name, false);
}
}
}
for (const scope of fsSafeJsonStoreScopes) {
for (const name of scope.keys()) {
if (name.startsWith(prefix)) {
currentJsonStoreScope.set(name, false);
}
}
}
}
function setFsSafeStoreObjectAlias(
storeScope,
jsonStoreScope,
name,
isStore,
isJsonStore,
conditionalWrite,
) {
if (isStore) {
storeScope.set(name, true);
} else if (!conditionalWrite) {
storeScope.set(name, false);
}
if (isJsonStore) {
jsonStoreScope.set(name, true);
} else if (!conditionalWrite) {
jsonStoreScope.set(name, false);
}
}
function copyFsSafeStoreObjectAliases(
targetName,
sourceName,
storeScope = currentFsSafeStoreScope(),
jsonStoreScope = currentFsSafeJsonStoreScope(),
) {
const sourcePrefix = `${sourceName}.`;
for (let index = fsSafeStoreScopes.length - 1; index >= 0; index--) {
const sourceStoreScope = fsSafeStoreScopes[index];
const sourceJsonStoreScope = fsSafeJsonStoreScopes[index];
let copied = false;
for (const [key, value] of sourceStoreScope) {
if (key.startsWith(sourcePrefix)) {
storeScope.set(`${targetName}.${key.slice(sourcePrefix.length)}`, value);
copied = true;
}
}
for (const [key, value] of sourceJsonStoreScope) {
if (key.startsWith(sourcePrefix)) {
jsonStoreScope.set(`${targetName}.${key.slice(sourcePrefix.length)}`, value);
copied = true;
}
}
if (copied || sourceStoreScope.has(sourceName) || sourceJsonStoreScope.has(sourceName)) {
return;
}
}
}
function registerFsSafeStoreObjectAliases(
objectName,
initializer,
storeScope = currentFsSafeStoreScope(),
jsonStoreScope = currentFsSafeJsonStoreScope(),
conditionalWrite = false,
) {
const objectLiteral = unwrapExpression(initializer);
if (!ts.isObjectLiteralExpression(objectLiteral)) {
return;
}
for (const property of objectLiteral.properties) {
if (ts.isPropertyAssignment(property)) {
const name = propertyNameText(property.name);
if (name) {
setFsSafeStoreObjectAlias(
storeScope,
jsonStoreScope,
`${objectName}.${name}`,
isFsSafeStoreExpression(property.initializer),
expressionContainsFsSafeJsonStoreLegacyPath(property.initializer),
conditionalWrite,
);
if (ts.isObjectLiteralExpression(unwrapExpression(property.initializer))) {
registerFsSafeStoreObjectAliases(
`${objectName}.${name}`,
property.initializer,
storeScope,
jsonStoreScope,
conditionalWrite,
);
}
}
continue;
}
if (ts.isShorthandPropertyAssignment(property)) {
setFsSafeStoreObjectAlias(
storeScope,
jsonStoreScope,
`${objectName}.${property.name.text}`,
resolveFsSafeStore(property.name.text),
resolveFsSafeJsonStore(property.name.text),
conditionalWrite,
);
continue;
}
if (ts.isSpreadAssignment(property)) {
const spreadExpression = unwrapExpression(property.expression);
if (ts.isIdentifier(spreadExpression)) {
copyFsSafeStoreObjectAliases(
objectName,
spreadExpression.text,
storeScope,
jsonStoreScope,
);
} else if (ts.isObjectLiteralExpression(spreadExpression)) {
registerFsSafeStoreObjectAliases(
objectName,
spreadExpression,
storeScope,
jsonStoreScope,
conditionalWrite,
);
}
}
}
}
function setFsModuleObjectProperty(scope, name, isFsModule, conditionalWrite) {
if (isFsModule) {
scope.set(name, true);
} else if (!conditionalWrite) {
scope.set(name, false);
}
}
function clearFsModuleObjectProperties(scope, objectName) {
const prefix = `${objectName}.`;
scope.set(objectName, false);
for (const name of scope.keys()) {
if (name.startsWith(prefix)) {
scope.set(name, false);
}
}
}
function registerFsModuleObjectProperties(
objectName,
initializer,
scope = currentFsModulePropertyScope(),
conditionalWrite = false,
) {
const objectLiteral = unwrapExpression(initializer);
if (!ts.isObjectLiteralExpression(objectLiteral)) {
return;
}
for (const property of objectLiteral.properties) {
if (ts.isPropertyAssignment(property)) {
const name = propertyNameText(property.name);
if (name) {
setFsModuleObjectProperty(
scope,
`${objectName}.${name}`,
isFsModuleExpression(property.initializer),
conditionalWrite,
);
}
continue;
}
if (ts.isShorthandPropertyAssignment(property)) {
setFsModuleObjectProperty(
scope,
`${objectName}.${property.name.text}`,
resolveFsModuleBinding(property.name.text),
conditionalWrite,
);
}
}
}
function collectFsModuleBindingsFromBinding(node) {
if (
!ts.isVariableDeclaration(node) ||
!ts.isObjectBindingPattern(node.name) ||
!node.initializer ||
!isFsBindingExpression(node.initializer)
) {
return;
}
for (const element of node.name.elements) {
const propertyName = element.propertyName;
const bindingName = element.name;
const importedName = propertyName
? propertyNameText(propertyName)
: ts.isIdentifier(bindingName)
? bindingName.text
: null;
if (importedName === "promises" && ts.isIdentifier(bindingName)) {
currentFsModuleBindingScope().set(bindingName.text, true);
}
}
}
function isFsBindingExpression(expression) {
const initializer = unwrapExpression(expression);
if (
isFsRequireExpression(initializer, isNodeRequireName) ||
isFsDynamicImportExpression(initializer)
) {
return true;
}
if (ts.isIdentifier(initializer)) {
return resolveFsModuleBinding(initializer.text);
}
return (
ts.isPropertyAccessExpression(initializer) &&
initializer.name.text === "promises" &&
(isFsRequireExpression(initializer.expression, isNodeRequireName) ||
isFsDynamicImportExpression(initializer.expression) ||
(ts.isIdentifier(initializer.expression) &&
resolveFsModuleBinding(initializer.expression.text)))
);
}
function collectFsWriteAliasesFromBindingInto(
node,
aliases,
isFsBinding = isFsBindingExpression,
) {
if (
!ts.isVariableDeclaration(node) ||
!ts.isObjectBindingPattern(node.name) ||
!node.initializer
) {
return;
}
if (!isFsBinding(node.initializer)) {
return;
}
collectFsWriteAliasesFromPattern(node.name, aliases);
}
function collectFsWriteAliasesFromPattern(pattern, aliases) {
for (const element of pattern.elements) {
const propertyName = element.propertyName;
const bindingName = element.name;
const importedName = propertyName
? propertyNameText(propertyName)
: ts.isIdentifier(bindingName)
? bindingName.text
: null;
if (!importedName) {
continue;
}
if (legacyWriteCallees.has(importedName) && ts.isIdentifier(bindingName)) {
aliases.set(bindingName.text, importedName);
}
if (importedName === "promises" && ts.isObjectBindingPattern(bindingName)) {
collectFsWriteAliasesFromPattern(bindingName, aliases);
}
}
}
function markArrayBindingPatternFromForOf(initializer, expression) {
if (!ts.isVariableDeclarationList(initializer)) {
return;
}
const declaration = initializer.declarations[0];
if (!declaration || !ts.isArrayBindingPattern(declaration.name)) {
return;
}
const iterable = unwrapExpression(expression);
if (!ts.isArrayLiteralExpression(iterable)) {
return;
}
declaration.name.elements.forEach((bindingElement, index) => {
if (ts.isOmittedExpression(bindingElement) || !ts.isIdentifier(bindingElement.name)) {
return;
}
const elementsAtIndex = iterable.elements
.map((element) => arrayLiteralElementAt(element, index))
.filter(Boolean);
if (elementsAtIndex.length === 0) {
return;
}
currentLegacyPathScope().set(
bindingElement.name.text,
elementsAtIndex.some((element) => expressionContainsLegacyStore(element)),
);
currentLiteralTextScope().set(
bindingElement.name.text,
mergeExhaustiveLiteralTexts(
[],
elementsAtIndex.flatMap((element) => literalTextsFromExpression(element)),
),
);
});
}
function pathArgumentsForFsWrite(name, args) {
if (
name === "appendRegularFile" ||
name === "appendRegularFileSync" ||
name === "replaceFileAtomic" ||
name === "replaceFileAtomicSync"
) {
const first = args[0];
if (!first || !ts.isObjectLiteralExpression(unwrapExpression(first))) {
return first ? [first] : [];
}
const objectArg = unwrapExpression(first);
return objectArg.properties.flatMap((property) => {
if (ts.isPropertyAssignment(property)) {
const key = property.name;
const propertyName =
ts.isIdentifier(key) || ts.isStringLiteral(key) || ts.isNumericLiteral(key)
? key.text
: null;
return propertyName === "filePath" ? [property.initializer] : [];
}
if (ts.isShorthandPropertyAssignment(property) && property.name.text === "filePath") {
return [property.name];
}
return [];
});
}
if (
name === "saveJsonFile" ||
name === "writeJson" ||
name === "writeJsonAtomic" ||
name === "writeJsonFileAtomically" ||
name === "writeJsonSync" ||
name === "writeTextAtomic"
) {
return args.slice(0, 1);
}
if (name === "copyFile" || name === "copyFileSync" || name === "cp" || name === "cpSync") {
return args.slice(1, 2);
}
if (name === "rename" || name === "renameSync") {
return args.slice(0, 2);
}
return args.slice(0, 1);
}
function openFlagsMayWrite(flags) {
if (!flags) {
return false;
}
const unwrapped = unwrapExpression(flags);
if (ts.isStringLiteralLike(unwrapped)) {
return /[wa+]/u.test(unwrapped.text);
}
return true;
}
function fsWriteCallMayWrite(name, args) {
if (name === "open" || name === "openSync") {
return openFlagsMayWrite(args[1]);
}
return true;
}
function propertyNameText(name) {
return ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)
? name.text
: null;
}
const unknownObjectLiteralPropertyInitializer = Symbol(
"unknown object literal property initializer",
);
const explicitUndefinedLegacyObjectPropertyValue = Symbol(
"explicit undefined legacy object property value",
);
const explicitUndefinedNestedWrapperValue = Symbol("explicit undefined nested wrapper value");
const knownObjectLiteralNestedWrapperValue = Symbol("known object literal nested wrapper value");
const unknownNestedWrapperObjectValue = Symbol("unknown nested wrapper object value");
function isVarVariableDeclaration(node) {
return (
ts.isVariableDeclarationList(node.parent) &&
(node.parent.flags & ts.NodeFlags.BlockScoped) === 0
);
}
function isAmbientVariableDeclaration(node) {
let current = node.parent;
while (current && !ts.isSourceFile(current)) {
const modifiers = ts.canHaveModifiers(current) ? (ts.getModifiers(current) ?? []) : [];
if (modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.DeclareKeyword)) {
return true;
}
current = current.parent;
}
return false;
}
function isTypeSyntaxNode(node) {
return node.kind >= ts.SyntaxKind.FirstTypeNode && node.kind <= ts.SyntaxKind.LastTypeNode;
}
function objectLiteralPropertyLegacyValue(objectLiteral, propertyName) {
let result = null;
for (const property of objectLiteral.properties) {
if (ts.isSpreadAssignment(property)) {
const spreadExpression = unwrapExpression(property.expression);
if (ts.isIdentifier(spreadExpression)) {
const propertyValue = lookupLegacyObjectProperty(spreadExpression.text, propertyName);
if (propertyValue !== null) {
result = propertyValue;
}
continue;
}
if (ts.isObjectLiteralExpression(spreadExpression)) {
const propertyValue = objectLiteralPropertyLegacyValue(spreadExpression, propertyName);
if (propertyValue !== null) {
result = propertyValue;
}
}
continue;
}
if (ts.isPropertyAssignment(property) && propertyNameText(property.name) === propertyName) {
result = expressionContainsLegacyStore(property.initializer);
continue;
}
if (ts.isShorthandPropertyAssignment(property) && property.name.text === propertyName) {
result = expressionContainsLegacyStore(property.name);
}
}
return result;
}
function objectLiteralPropertyInitializerState(
objectLiteral,
propertyName,
resolveSpreadProperty = null,
) {
let result = { kind: "missing" };
for (const property of objectLiteral.properties) {
if (ts.isSpreadAssignment(property)) {
const spreadExpression = unwrapExpression(property.expression);
if (ts.isIdentifier(spreadExpression) && resolveSpreadProperty) {
const spreadResult = resolveSpreadProperty(spreadExpression.text, propertyName);
if (spreadResult.kind !== "missing") {
result = spreadResult;
}
continue;
}
if (ts.isObjectLiteralExpression(spreadExpression)) {
const spreadResult = objectLiteralPropertyInitializerState(
spreadExpression,
propertyName,
resolveSpreadProperty,
);
if (spreadResult.kind !== "missing") {
result = spreadResult;
}
continue;
}
result = { kind: "unknown" };
continue;
}
if (ts.isPropertyAssignment(property) && propertyNameText(property.name) === propertyName) {
result = isKnownUndefinedExpression(property.initializer)
? { kind: "undefined" }
: { kind: "initializer", initializer: property.initializer };
continue;
}
if (ts.isShorthandPropertyAssignment(property) && property.name.text === propertyName) {
result = isKnownUndefinedExpression(property.name)
? { kind: "undefined" }
: { kind: "initializer", initializer: property.name };
}
}
return result;
}
function objectLiteralPropertyInitializer(objectLiteral, propertyName) {
const result = objectLiteralPropertyInitializerState(objectLiteral, propertyName);
if (result.kind === "missing" || result.kind === "undefined") {
return null;
}
if (result.kind === "unknown") {
return unknownObjectLiteralPropertyInitializer;
}
return result.initializer;
}
function objectLiteralPropertyContainsLegacyStore(objectLiteral, propertyName) {
return objectLiteralPropertyLegacyValue(objectLiteral, propertyName) === true;
}
function clearLegacyObjectProperties(scope, objectName) {
const prefix = `${objectName}.`;
for (const key of scope.keys()) {
if (key.startsWith(prefix)) {
scope.delete(key);
}
}
}
function clearKnownLegacyObjectLiterals(scope, objectName) {
const prefix = `${objectName}.`;
for (const key of scope.keys()) {
if (key.startsWith(prefix)) {
scope.delete(key);
}
}
}
function legacyObjectPropertiesFromAssignment(
objectName,
initializer,
existingScope = new Map(),
) {
return legacyObjectPropertyRewriteValues(objectName, initializer, existingScope);
}
function legacyKnownObjectLiteralsFromAssignment(objectName, initializer) {
const knownObjectLiterals = new Map();
markLegacyObjectProperties(objectName, initializer, new Map(), knownObjectLiterals);
return knownObjectLiterals;
}
function branchIdentifierAssignmentKey(index, name) {
return `${index}:${name}`;
}
function branchPropertyAssignmentKey(index, objectName, propertyName) {
return `${index}:${objectPropertyKey(objectName, propertyName)}`;
}
function branchWrapperAssignmentKey(index, name) {
return `${index}:${name}`;
}
function recordBranchIdentifierAssignment(
index,
name,
value,
initializer,
literalTexts,
objectProperties = null,
knownUndefined = isKnownUndefinedExpression(initializer),
) {
const effects = currentBranchEffectScope();
if (!effects) {
return;
}
effects.identifierAssignments.set(branchIdentifierAssignmentKey(index, name), {
index,
knownUndefined,
knownObjectLiteral: isKnownLegacyObjectLiteralExpression(initializer),
knownObjectLiterals: legacyKnownObjectLiteralsFromAssignment(name, initializer),
literalTexts,
name,
value,
objectProperties: objectProperties ?? legacyObjectPropertiesFromAssignment(name, initializer),
});
const prefix = `${index}:${name}.`;
for (const key of effects.propertyAssignments.keys()) {
if (key.startsWith(prefix)) {
effects.propertyAssignments.delete(key);
}
}
}
function recordBranchPropertyAssignment(
index,
objectName,
propertyName,
value,
knownObjectLiteral = false,
) {
const effects = currentBranchEffectScope();
if (!effects) {
return;
}
const identifierAssignment = effects.identifierAssignments.get(
branchIdentifierAssignmentKey(index, objectName),
);
const propertyKey = objectPropertyKey(objectName, propertyName);
if (identifierAssignment) {
identifierAssignment.objectProperties.set(propertyKey, value);
identifierAssignment.knownObjectLiterals.set(propertyKey, knownObjectLiteral);
return;
}
effects.propertyAssignments.set(branchPropertyAssignmentKey(index, objectName, propertyName), {
index,
objectName,
propertyName,
value,
knownObjectLiteral,
});
}
function recordBranchWrapperAssignment(index, name, value) {
const effects = currentBranchEffectScope();
if (!effects) {
return;
}
effects.wrapperAssignments.set(branchWrapperAssignmentKey(index, name), {
index,
name,
value: cloneWrapperFunctionValue(value),
});
}
function recordBranchFsIdentifierAssignment(
index,
name,
moduleValue,
writeAlias,
fsSafeFactoryAlias,
fsSafeStoreValue,
fsSafeJsonStoreValue,
requireAlias,
) {
const effects = currentBranchEffectScope();
if (!effects) {
return;
}
effects.fsIdentifierAssignments.set(branchIdentifierAssignmentKey(index, name), {
fsSafeFactoryAlias,
fsSafeJsonStoreValue,
fsSafeStoreValue,
index,
moduleValue,
name,
requireAlias,
writeAlias,
});
}
function recordBranchFsSafePropertyAssignment(
index,
objectName,
propertyName,
storeValue,
jsonStoreValue,
) {
const effects = currentBranchEffectScope();
if (!effects) {
return;
}
effects.fsSafePropertyAssignments.set(
branchPropertyAssignmentKey(index, objectName, propertyName),
{
index,
jsonStoreValue,
objectName,
propertyName,
storeValue,
},
);
}
function recordBranchFsSafeObjectPropertyAssignment(
index,
objectName,
propertyName,
initializer,
storeValue,
jsonStoreValue,
) {
const assignmentRoot = objectPropertyKey(objectName, propertyName);
const storeAssignments = new Map([[assignmentRoot, storeValue]]);
const jsonStoreAssignments = new Map([[assignmentRoot, jsonStoreValue]]);
const descendantPrefix = `${assignmentRoot}.`;
for (const scope of fsSafeStoreScopes) {
for (const key of scope.keys()) {
if (key.startsWith(descendantPrefix)) {
storeAssignments.set(key, false);
}
}
}
for (const scope of fsSafeJsonStoreScopes) {
for (const key of scope.keys()) {
if (key.startsWith(descendantPrefix)) {
jsonStoreAssignments.set(key, false);
}
}
}
registerFsSafeStoreObjectAliases(
assignmentRoot,
initializer,
storeAssignments,
jsonStoreAssignments,
);
const assignmentKeys = new Set([...storeAssignments.keys(), ...jsonStoreAssignments.keys()]);
for (const key of assignmentKeys) {
recordBranchFsSafePropertyAssignment(
index,
objectName,
key.slice(`${objectName}.`.length),
storeAssignments.get(key) === true,
jsonStoreAssignments.get(key) === true,
);
}
}
function mergeWrapperAssignmentValues(left, right) {
const records = [
...wrapperRecords(left).map(cloneWrapperRecord),
...wrapperRecords(right).map(cloneWrapperRecord),
];
if (records.length === 0) {
return null;
}
return records.length === 1 ? records[0] : records;
}
function mergeExhaustiveBranchEffects(thenEffects, elseEffects) {
const mergedIdentifierNames = new Set();
const parentEffect = currentBranchEffectScope();
const applyToTargetScopes = !currentConditionalExecutionScope() && !parentEffect;
for (const [key, thenAssignment] of thenEffects.fsIdentifierAssignments) {
const elseAssignment = elseEffects.fsIdentifierAssignments.get(key);
if (!elseAssignment) {
continue;
}
const { index, name } = thenAssignment;
const mergedModuleValue =
thenAssignment.moduleValue === true || elseAssignment.moduleValue === true;
const mergedWriteAlias = thenAssignment.writeAlias ?? elseAssignment.writeAlias;
const mergedFsSafeFactoryAlias =
thenAssignment.fsSafeFactoryAlias ?? elseAssignment.fsSafeFactoryAlias;
const mergedFsSafeStoreValue =
thenAssignment.fsSafeStoreValue === true || elseAssignment.fsSafeStoreValue === true;
const mergedFsSafeJsonStoreValue =
thenAssignment.fsSafeJsonStoreValue === true ||
elseAssignment.fsSafeJsonStoreValue === true;
const mergedRequireAlias =
thenAssignment.requireAlias === true || elseAssignment.requireAlias === true;
if (applyToTargetScopes) {
fsModuleBindingScopes[index].set(name, mergedModuleValue);
fsWriteAliasScopes[index].set(name, mergedWriteAlias);
fsSafeStoreFactoryAliasScopes[index].set(name, mergedFsSafeFactoryAlias);
fsSafeStoreScopes[index].set(name, mergedFsSafeStoreValue);
fsSafeJsonStoreScopes[index].set(name, mergedFsSafeJsonStoreValue);
requireAliasScopes[index].set(name, mergedRequireAlias);
}
currentFsModuleBindingScope().set(name, mergedModuleValue);
currentFsWriteAliasScope().set(name, mergedWriteAlias);
currentFsSafeStoreFactoryAliasScope().set(name, mergedFsSafeFactoryAlias);
currentFsSafeStoreScope().set(name, mergedFsSafeStoreValue);
currentFsSafeJsonStoreScope().set(name, mergedFsSafeJsonStoreValue);
currentRequireAliasScope().set(name, mergedRequireAlias);
refreshCurrentWrapperFunctionAliases();
if (parentEffect) {
parentEffect.fsIdentifierAssignments.set(branchIdentifierAssignmentKey(index, name), {
fsSafeFactoryAlias: mergedFsSafeFactoryAlias,
fsSafeJsonStoreValue: mergedFsSafeJsonStoreValue,
fsSafeStoreValue: mergedFsSafeStoreValue,
index,
moduleValue: mergedModuleValue,
name,
requireAlias: mergedRequireAlias,
writeAlias: mergedWriteAlias,
});
}
}
for (const [key, thenAssignment] of thenEffects.identifierAssignments) {
const elseAssignment = elseEffects.identifierAssignments.get(key);
if (!elseAssignment) {
continue;
}
const { index, name } = thenAssignment;
mergedIdentifierNames.add(branchIdentifierAssignmentKey(index, name));
const mergedValue = thenAssignment.value === true || elseAssignment.value === true;
const propertyKeys = new Set([
...thenAssignment.objectProperties.keys(),
...elseAssignment.objectProperties.keys(),
]);
const mergedProperties = new Map();
for (const propertyKey of propertyKeys) {
const mergedPropertyValue = mergeBranchLegacyObjectPropertyValue(
thenAssignment,
elseAssignment,
propertyKey,
);
if (mergedPropertyValue !== null) {
mergedProperties.set(propertyKey, mergedPropertyValue);
}
}
const mergedKnownObjectLiteral =
thenAssignment.knownObjectLiteral && elseAssignment.knownObjectLiteral;
const mergedKnownUndefined = thenAssignment.knownUndefined || elseAssignment.knownUndefined;
const knownObjectLiteralKeys = new Set([
...thenAssignment.knownObjectLiterals.keys(),
...elseAssignment.knownObjectLiterals.keys(),
]);
const mergedKnownObjectLiterals = new Map();
for (const knownObjectLiteralKey of knownObjectLiteralKeys) {
mergedKnownObjectLiterals.set(
knownObjectLiteralKey,
thenAssignment.knownObjectLiterals.get(knownObjectLiteralKey) === true &&
elseAssignment.knownObjectLiterals.get(knownObjectLiteralKey) === true,
);
}
if (applyToTargetScopes) {
const pathScope = legacyPathScopes[index];
const literalScope = literalTextScopes[index];
const knownUndefinedScope = knownUndefinedScopes[index];
const propertyScope = legacyObjectPropertyScopes[index];
const knownObjectLiteralScope = legacyKnownObjectLiteralScopes[index];
clearKnownLegacyObjectLiterals(knownObjectLiteralScope, name);
knownObjectLiteralScope.set(name, mergedKnownObjectLiteral);
for (const [knownObjectLiteralKey, value] of mergedKnownObjectLiterals) {
knownObjectLiteralScope.set(knownObjectLiteralKey, value);
}
pathScope.set(name, mergedValue);
knownUndefinedScope.set(name, mergedKnownUndefined);
literalScope.set(
name,
mergeExhaustiveLiteralTexts(thenAssignment.literalTexts, elseAssignment.literalTexts),
);
clearLegacyObjectProperties(propertyScope, name);
for (const [propertyKey, value] of mergedProperties) {
propertyScope.set(propertyKey, value);
}
}
clearKnownLegacyObjectLiterals(currentLegacyKnownObjectLiteralScope(), name);
currentLegacyKnownObjectLiteralScope().set(name, mergedKnownObjectLiteral);
for (const [knownObjectLiteralKey, value] of mergedKnownObjectLiterals) {
currentLegacyKnownObjectLiteralScope().set(knownObjectLiteralKey, value);
}
currentLegacyPathScope().set(name, mergedValue);
currentKnownUndefinedScope().set(name, mergedKnownUndefined);
currentLiteralTextScope().set(
name,
mergeExhaustiveLiteralTexts(thenAssignment.literalTexts, elseAssignment.literalTexts),
);
clearLegacyObjectProperties(currentLegacyObjectPropertyScope(), name);
for (const [propertyKey, value] of mergedProperties) {
currentLegacyObjectPropertyScope().set(propertyKey, value);
}
if (parentEffect) {
parentEffect.identifierAssignments.set(branchIdentifierAssignmentKey(index, name), {
index,
knownUndefined: mergedKnownUndefined,
knownObjectLiteral: mergedKnownObjectLiteral,
knownObjectLiterals: mergedKnownObjectLiterals,
literalTexts:
mergeExhaustiveLiteralTexts(thenAssignment.literalTexts, elseAssignment.literalTexts) ??
[],
name,
value: mergedValue,
objectProperties: mergedProperties,
});
}
}
for (const [key, thenAssignment] of thenEffects.fsSafePropertyAssignments) {
const elseAssignment = elseEffects.fsSafePropertyAssignments.get(key);
if (!elseAssignment) {
continue;
}
const mergedStoreValue =
thenAssignment.storeValue === true || elseAssignment.storeValue === true;
const mergedJsonStoreValue =
thenAssignment.jsonStoreValue === true || elseAssignment.jsonStoreValue === true;
const propertyKey = objectPropertyKey(thenAssignment.objectName, thenAssignment.propertyName);
if (applyToTargetScopes) {
fsSafeStoreScopes[thenAssignment.index].set(propertyKey, mergedStoreValue);
fsSafeJsonStoreScopes[thenAssignment.index].set(propertyKey, mergedJsonStoreValue);
}
currentFsSafeStoreScope().set(propertyKey, mergedStoreValue);
currentFsSafeJsonStoreScope().set(propertyKey, mergedJsonStoreValue);
if (parentEffect) {
recordBranchFsSafePropertyAssignment(
thenAssignment.index,
thenAssignment.objectName,
thenAssignment.propertyName,
mergedStoreValue,
mergedJsonStoreValue,
);
}
}
for (const [key, thenAssignment] of thenEffects.propertyAssignments) {
const elseAssignment = elseEffects.propertyAssignments.get(key);
if (!elseAssignment) {
continue;
}
const identifierKey = branchIdentifierAssignmentKey(
thenAssignment.index,
thenAssignment.objectName,
);
if (mergedIdentifierNames.has(identifierKey)) {
continue;
}
const mergedValue = mergeLegacyObjectPropertyValues(
thenAssignment.value,
elseAssignment.value,
);
const mergedKnownObjectLiteral =
thenAssignment.knownObjectLiteral && elseAssignment.knownObjectLiteral;
const propertyKey = objectPropertyKey(thenAssignment.objectName, thenAssignment.propertyName);
if (applyToTargetScopes) {
legacyObjectPropertyScopes[thenAssignment.index].set(propertyKey, mergedValue);
legacyKnownObjectLiteralScopes[thenAssignment.index].set(
propertyKey,
mergedKnownObjectLiteral,
);
}
currentLegacyObjectPropertyScope().set(propertyKey, mergedValue);
currentLegacyKnownObjectLiteralScope().set(propertyKey, mergedKnownObjectLiteral);
if (parentEffect) {
recordBranchPropertyAssignment(
thenAssignment.index,
thenAssignment.objectName,
thenAssignment.propertyName,
mergedValue,
mergedKnownObjectLiteral,
);
}
}
for (const [key, thenAssignment] of thenEffects.wrapperAssignments) {
const elseAssignment = elseEffects.wrapperAssignments.get(key);
if (!elseAssignment) {
continue;
}
const { index, name } = thenAssignment;
const mergedValue = mergeWrapperAssignmentValues(thenAssignment.value, elseAssignment.value);
if (applyToTargetScopes) {
wrapperFunctionScopes[index].set(name, cloneWrapperFunctionValue(mergedValue));
}
currentWrapperFunctionScope().set(name, cloneWrapperFunctionValue(mergedValue));
if (parentEffect) {
parentEffect.wrapperAssignments.set(branchWrapperAssignmentKey(index, name), {
index,
name,
value: cloneWrapperFunctionValue(mergedValue),
});
}
}
}
function markLegacyObjectProperties(
objectName,
initializer,
targetScope = currentLegacyObjectPropertyScope(),
knownObjectLiteralScope = currentLegacyKnownObjectLiteralScope(),
) {
const objectLiteral = unwrapExpression(initializer);
if (ts.isIdentifier(objectLiteral)) {
copyLegacyObjectProperties(objectName, objectLiteral.text, targetScope);
if (knownObjectLiteralScope) {
copyKnownLegacyObjectLiterals(objectName, objectLiteral.text, knownObjectLiteralScope);
}
return;
}
if (!ts.isObjectLiteralExpression(objectLiteral)) {
knownObjectLiteralScope?.set(objectName, false);
return;
}
knownObjectLiteralScope?.set(objectName, true);
for (const property of objectLiteral.properties) {
if (ts.isPropertyAssignment(property)) {
const name = propertyNameText(property.name);
if (name) {
const propertyKey = `${objectName}.${name}`;
clearLegacyObjectProperties(targetScope, propertyKey);
if (knownObjectLiteralScope) {
clearKnownLegacyObjectLiterals(knownObjectLiteralScope, propertyKey);
}
targetScope.set(
propertyKey,
legacyObjectPropertyValueFromExpression(property.initializer),
);
const propertyInitializer = unwrapExpression(property.initializer);
if (ts.isIdentifier(propertyInitializer)) {
copyLegacyObjectProperties(propertyKey, propertyInitializer.text, targetScope);
if (knownObjectLiteralScope) {
copyKnownLegacyObjectLiterals(
propertyKey,
propertyInitializer.text,
knownObjectLiteralScope,
);
}
} else {
knownObjectLiteralScope?.set(
propertyKey,
isKnownLegacyObjectLiteralExpression(property.initializer),
);
}
if (ts.isObjectLiteralExpression(propertyInitializer)) {
markLegacyObjectProperties(
propertyKey,
property.initializer,
targetScope,
knownObjectLiteralScope,
);
}
}
continue;
}
if (ts.isShorthandPropertyAssignment(property)) {
const propertyKey = `${objectName}.${property.name.text}`;
targetScope.set(propertyKey, legacyObjectPropertyValueFromExpression(property.name));
copyLegacyObjectProperties(propertyKey, property.name.text, targetScope);
if (knownObjectLiteralScope) {
copyKnownLegacyObjectLiterals(propertyKey, property.name.text, knownObjectLiteralScope);
}
continue;
}
if (ts.isSpreadAssignment(property)) {
const spreadExpression = unwrapExpression(property.expression);
if (ts.isIdentifier(spreadExpression)) {
copyLegacyObjectProperties(objectName, spreadExpression.text, targetScope);
if (knownObjectLiteralScope) {
copyKnownLegacyObjectLiterals(
objectName,
spreadExpression.text,
knownObjectLiteralScope,
);
}
} else if (ts.isObjectLiteralExpression(spreadExpression)) {
markLegacyObjectProperties(
objectName,
spreadExpression,
targetScope,
knownObjectLiteralScope,
);
} else {
knownObjectLiteralScope?.set(objectName, false);
}
}
}
}
function copyLegacyObjectProperties(
targetName,
sourceName,
targetScope = currentLegacyObjectPropertyScope(),
) {
const sourcePrefix = `${sourceName}.`;
for (let index = legacyObjectPropertyScopes.length - 1; index >= 0; index--) {
const scope = legacyObjectPropertyScopes[index];
const copiedEntries = [];
for (const [key, value] of scope) {
if (key.startsWith(sourcePrefix)) {
copiedEntries.push([`${targetName}.${key.slice(sourcePrefix.length)}`, value]);
}
}
copiedEntries.sort((left, right) => left[0].length - right[0].length);
for (const [key, value] of copiedEntries) {
clearLegacyObjectProperties(targetScope, key);
targetScope.set(key, value);
}
const copied = copiedEntries.length > 0;
if (copied || legacyPathScopes[index].has(sourceName)) {
return;
}
}
}
function copyKnownLegacyObjectLiterals(
targetName,
sourceName,
targetScope = currentLegacyKnownObjectLiteralScope(),
) {
targetScope.set(targetName, lookupKnownLegacyObjectLiteral(sourceName));
const sourcePrefix = `${sourceName}.`;
for (let index = legacyKnownObjectLiteralScopes.length - 1; index >= 0; index--) {
const scope = legacyKnownObjectLiteralScopes[index];
const copiedEntries = [];
for (const [key, value] of scope) {
if (key.startsWith(sourcePrefix)) {
copiedEntries.push([`${targetName}.${key.slice(sourcePrefix.length)}`, value]);
}
}
copiedEntries.sort((left, right) => left[0].length - right[0].length);
for (const [key, value] of copiedEntries) {
clearKnownLegacyObjectLiterals(targetScope, key);
targetScope.set(key, value);
}
const copied = copiedEntries.length > 0;
if (copied || scope.has(sourceName) || legacyPathScopes[index].has(sourceName)) {
return;
}
}
}
function copyScopedLegacyObjectProperties(targetName, sourceName, sourceScope) {
const sourcePrefix = `${sourceName}.`;
const copiedEntries = [];
for (const [key, value] of sourceScope) {
if (key.startsWith(sourcePrefix)) {
copiedEntries.push([`${targetName}.${key.slice(sourcePrefix.length)}`, value]);
}
}
copiedEntries.sort((left, right) => left[0].length - right[0].length);
for (const [key, value] of copiedEntries) {
clearLegacyObjectProperties(currentLegacyObjectPropertyScope(), key);
currentLegacyObjectPropertyScope().set(key, value);
}
}
function copyScopedKnownLegacyObjectLiterals(targetName, sourceName, sourceScope) {
currentLegacyKnownObjectLiteralScope().set(targetName, sourceScope.get(sourceName) === true);
const sourcePrefix = `${sourceName}.`;
const copiedEntries = [];
for (const [key, value] of sourceScope) {
if (key.startsWith(sourcePrefix)) {
copiedEntries.push([`${targetName}.${key.slice(sourcePrefix.length)}`, value]);
}
}
copiedEntries.sort((left, right) => left[0].length - right[0].length);
for (const [key, value] of copiedEntries) {
clearKnownLegacyObjectLiterals(currentLegacyKnownObjectLiteralScope(), key);
currentLegacyKnownObjectLiteralScope().set(key, value);
}
}
function collectPathPropertyUses(
expression,
fsWriteName,
resolveParameterIndex,
resolveDestructuredParameterProperty,
resolveParameterPropertyUse = null,
resolveDestructuredParameterPropertyUses = null,
) {
function appendUses(uses, value) {
if (!value) {
return;
}
if (Array.isArray(value)) {
uses.push(...value);
return;
}
uses.push(value);
}
function isPathLikeWrapperPropertyName(propertyName) {
const normalized = propertyName.toLowerCase();
return (
normalized === "path" ||
normalized === "store" ||
normalized === "file" ||
normalized.endsWith("path") ||
normalized.endsWith("dir")
);
}
const uses = [];
function visitExpression(current) {
if (
ts.isIdentifier(current) &&
usesFilePathOptionsObject(fsWriteName) &&
resolveParameterIndex(current.text) !== null
) {
const propertyUse = resolveParameterPropertyUse?.(current.text, "filePath");
if (propertyUse !== null) {
appendUses(
uses,
propertyUse ?? { index: resolveParameterIndex(current.text), propertyName: "filePath" },
);
}
return;
}
const propertyAccess = rootedPropertyAccessPath(current);
if (propertyAccess?.properties.length > 0) {
const index = resolveParameterIndex(propertyAccess.rootName);
if (index !== null) {
for (let length = propertyAccess.properties.length; length > 0; length--) {
const lastPropertyName = propertyAccess.properties[length - 1];
if (
length !== propertyAccess.properties.length &&
!isPathLikeWrapperPropertyName(lastPropertyName)
) {
continue;
}
const propertyName = propertyAccess.properties.slice(0, length).join(".");
const propertyUse = resolveParameterPropertyUse?.(
propertyAccess.rootName,
propertyName,
);
if (propertyUse !== null) {
appendUses(uses, propertyUse ?? { index, propertyName });
}
}
}
return;
}
if (ts.isIdentifier(current)) {
const destructuredUses = resolveDestructuredParameterPropertyUses?.(current.text);
if (destructuredUses) {
appendUses(uses, destructuredUses);
} else if (resolveDestructuredParameterProperty(current.text)) {
uses.push(resolveDestructuredParameterProperty(current.text));
} else {
const index = resolveParameterIndex(current.text);
if (index !== null) {
uses.push({ index, propertyName: null });
}
}
}
ts.forEachChild(current, visitExpression);
}
visitExpression(expression);
return uses;
}
function usesFilePathOptionsObject(name) {
return (
name === "appendRegularFile" ||
name === "appendRegularFileSync" ||
name === "replaceFileAtomic" ||
name === "replaceFileAtomicSync"
);
}
function parameterPropertyBindings(parameter, index) {
if (!ts.isObjectBindingPattern(parameter.name)) {
return new Map();
}
return objectBindingParameterProperties(parameter.name, index);
}
function bindingPatternNames(name) {
const names = [];
function visitName(current) {
if (ts.isIdentifier(current)) {
names.push(current.text);
return;
}
if (ts.isObjectBindingPattern(current) || ts.isArrayBindingPattern(current)) {
for (const element of current.elements) {
if (ts.isBindingElement(element)) {
visitName(element.name);
}
}
}
}
visitName(name);
return names;
}
function isParameterPropertyDestructure(node, parameterIndexes) {
return (
ts.isObjectBindingPattern(node.name) &&
node.initializer &&
ts.isIdentifier(node.initializer) &&
parameterIndexes.has(node.initializer.text)
);
}
function objectBindingParameterProperties(bindingPattern, index, propertyPath = []) {
const bindings = new Map();
for (const element of bindingPattern.elements) {
const propertyName = element.propertyName
? propertyNameText(element.propertyName)
: ts.isIdentifier(element.name)
? element.name.text
: null;
if (!propertyName) {
continue;
}
const nextPath = [...propertyPath, propertyName];
if (ts.isIdentifier(element.name)) {
bindings.set(element.name.text, { index, propertyName: nextPath.join(".") });
continue;
}
if (ts.isObjectBindingPattern(element.name)) {
for (const [name, binding] of objectBindingParameterProperties(
element.name,
index,
nextPath,
)) {
bindings.set(name, binding);
}
}
}
return bindings;
}
function markLegacyPathsFromObjectBinding(bindingPattern, sourceName, propertyPath = []) {
for (const element of bindingPattern.elements) {
const propertyName = element.propertyName
? propertyNameText(element.propertyName)
: ts.isIdentifier(element.name)
? element.name.text
: null;
if (!propertyName) {
continue;
}
const nextPath = [...propertyPath, propertyName];
if (ts.isIdentifier(element.name)) {
const trackedPropertyEntry = lookupLegacyObjectPropertyEntry(
sourceName,
nextPath.join("."),
);
const usesDefaultInitializer = trackedPropertyEntry.found
? trackedPropertyEntry.value === explicitUndefinedLegacyObjectPropertyValue
: trackedPropertyEntry.objectKnown;
const trackedPropertyValue = trackedPropertyEntry.found
? trackedPropertyEntry.value === explicitUndefinedLegacyObjectPropertyValue
? null
: trackedPropertyEntry.value === true
: null;
const propertyValue =
trackedPropertyValue === null
? element.initializer
? expressionContainsLegacyStore(element.initializer)
: false
: trackedPropertyValue;
currentLegacyPathScope().set(element.name.text, propertyValue);
currentKnownUndefinedScope().set(
element.name.text,
usesDefaultInitializer
? element.initializer
? isKnownUndefinedExpression(element.initializer)
: true
: false,
);
const sourcePropertyName = `${sourceName}.${nextPath.join(".")}`;
copyLegacyObjectProperties(element.name.text, sourcePropertyName);
copyKnownLegacyObjectLiterals(element.name.text, sourcePropertyName);
currentWrapperFunctionScope().set(
element.name.text,
cloneWrapperFunctionValue(resolveWrapperFunction(sourcePropertyName)),
);
continue;
}
if (ts.isObjectBindingPattern(element.name)) {
markLegacyPathsFromObjectBinding(element.name, sourceName, nextPath);
}
}
}
function markLegacyPathsFromInlineObjectBinding(bindingPattern, initializer, propertyPath = []) {
const sourceName = "<inline-object-binding>";
const propertyScope = new Map();
const knownObjectLiteralScope = new Map();
markLegacyObjectProperties(sourceName, initializer, propertyScope, knownObjectLiteralScope);
function visitBinding(currentBindingPattern, currentPath) {
for (const element of currentBindingPattern.elements) {
const propertyName = element.propertyName
? propertyNameText(element.propertyName)
: ts.isIdentifier(element.name)
? element.name.text
: null;
if (!propertyName) {
continue;
}
const nextPath = [...currentPath, propertyName];
if (ts.isIdentifier(element.name)) {
const trackedPropertyEntry = lookupScopedLegacyObjectPropertyEntry(
sourceName,
nextPath,
propertyScope,
knownObjectLiteralScope,
);
const usesDefaultInitializer = trackedPropertyEntry.found
? trackedPropertyEntry.value === explicitUndefinedLegacyObjectPropertyValue
: trackedPropertyEntry.objectKnown;
const propertyValue = trackedPropertyEntry.found
? trackedPropertyEntry.value === explicitUndefinedLegacyObjectPropertyValue
? element.initializer
? expressionContainsLegacyStore(element.initializer)
: false
: trackedPropertyEntry.value === true
: trackedPropertyEntry.objectKnown && element.initializer
? expressionContainsLegacyStore(element.initializer)
: false;
currentLegacyPathScope().set(element.name.text, propertyValue);
currentKnownUndefinedScope().set(
element.name.text,
usesDefaultInitializer
? element.initializer
? isKnownUndefinedExpression(element.initializer)
: true
: false,
);
const sourcePropertyName = objectPropertyKey(sourceName, nextPath.join("."));
copyScopedLegacyObjectProperties(element.name.text, sourcePropertyName, propertyScope);
copyScopedKnownLegacyObjectLiterals(
element.name.text,
sourcePropertyName,
knownObjectLiteralScope,
);
continue;
}
if (ts.isObjectBindingPattern(element.name)) {
visitBinding(element.name, nextPath);
}
}
}
visitBinding(bindingPattern, propertyPath);
}
function markFsSafeStoresFromObjectBinding(bindingPattern, sourceName, propertyPath = []) {
for (const element of bindingPattern.elements) {
const propertyName = element.propertyName
? propertyNameText(element.propertyName)
: ts.isIdentifier(element.name)
? element.name.text
: null;
if (!propertyName) {
continue;
}
const nextPath = [...propertyPath, propertyName];
if (ts.isIdentifier(element.name)) {
const key = `${sourceName}.${nextPath.join(".")}`;
const trackedStore = lookupFsSafeStore(key);
const trackedJsonStore = lookupFsSafeJsonStore(key);
currentFsSafeStoreScope().set(
element.name.text,
trackedStore ??
(element.initializer ? isFsSafeStoreExpression(element.initializer) : false),
);
currentFsSafeJsonStoreScope().set(
element.name.text,
trackedJsonStore ??
(element.initializer
? expressionContainsFsSafeJsonStoreLegacyPath(element.initializer)
: false),
);
continue;
}
if (ts.isObjectBindingPattern(element.name)) {
markFsSafeStoresFromObjectBinding(element.name, sourceName, nextPath);
}
}
}
function markFsSafeFactoryAliasesFromObjectBinding(bindingPattern, sourceName) {
for (const element of bindingPattern.elements) {
if (!ts.isIdentifier(element.name)) {
continue;
}
const propertyName = element.propertyName
? propertyNameText(element.propertyName)
: element.name.text;
const factoryAlias = propertyName
? resolveFsSafeStoreFactoryAlias(`${sourceName}.${propertyName}`)
: null;
if (factoryAlias) {
currentFsSafeStoreFactoryAliasScope().set(element.name.text, factoryAlias);
}
}
}
function collectLegacyPathPropertyParameters(
node,
baseFsWriteAliases,
baseFsModuleBindings,
baseFsModuleProperties,
baseRequireAliases,
baseCreateRequireShadows,
activeWrapperNodes = new Set(),
baseNestedWrapperFunctions = null,
) {
if (activeWrapperNodes.has(node)) {
return new Map();
}
activeWrapperNodes.add(node);
const parameterIndexes = new Map();
const bodyFsWriteAliasScopes = [new Map(baseFsWriteAliases)];
const bodyFsModuleBindingScopes = [new Map(baseFsModuleBindings)];
const bodyFsModulePropertyScopes = [new Map(baseFsModuleProperties)];
const bodyRequireAliasScopes = [new Map(baseRequireAliases)];
const wrapperCreateRequireShadowScopes = [new Set(baseCreateRequireShadows)];
const destructuredParameterPropertyScopes = [new Map()];
const destructuredParameterPropertyMergeScopes = [new Map()];
const parameterObjectBindingScopes = [new Map()];
const parameterPropertyUseScopes = [new Map()];
const conditionalDestructuredParameterPropertyScopes = [new Map()];
const conditionalParameterObjectScopes = [new Map()];
const conditionalParameterPropertyUseScopes = [new Map()];
const conditionalWrapperBodyScopes = [false];
const parameterObjectAssignmentShadowScopes = [new Set()];
const shadowScopes = [new Set()];
const fsAliasShadowScopes = [new Set()];
const fsModuleShadowScopes = [new Set()];
const wrapperRequireShadowScopes = [new Set()];
const parameterObjectShadowScopes = [new Set()];
const wrapperBranchEffectScopes = [];
const rootNestedWrapperFunctionScope = new Map(baseNestedWrapperFunctions ?? []);
const nestedWrapperFunctionScopes = [rootNestedWrapperFunctionScope];
const nestedWrapperFunctionScopeParents = new Map([[rootNestedWrapperFunctionScope, null]]);
function currentBodyFsWriteAliasScope() {
return bodyFsWriteAliasScopes[bodyFsWriteAliasScopes.length - 1];
}
function visibleBodyFsWriteAliases() {
const aliases = new Map();
for (const scope of bodyFsWriteAliasScopes) {
for (const [name, value] of scope) {
aliases.set(name, value);
}
}
return aliases;
}
function currentBodyFsModuleBindingScope() {
return bodyFsModuleBindingScopes[bodyFsModuleBindingScopes.length - 1];
}
function visibleBodyFsModuleBindings() {
const bindings = new Map();
for (const scope of bodyFsModuleBindingScopes) {
for (const [name, value] of scope) {
bindings.set(name, value);
}
}
return bindings;
}
function currentBodyFsModulePropertyScope() {
return bodyFsModulePropertyScopes[bodyFsModulePropertyScopes.length - 1];
}
function visibleBodyFsModuleProperties() {
const properties = new Map();
for (const scope of bodyFsModulePropertyScopes) {
for (const [name, value] of scope) {
properties.set(name, value);
}
}
return properties;
}
function currentBodyRequireAliasScope() {
return bodyRequireAliasScopes[bodyRequireAliasScopes.length - 1];
}
function visibleBodyRequireAliasSnapshot() {
const aliases = new Map();
const sourceScopes = new Map();
bodyRequireAliasScopes.forEach((scope, index) => {
for (const [name, value] of scope) {
aliases.set(name, value);
sourceScopes.set(name, index);
}
});
return { aliases, sourceScopes };
}
function currentDestructuredParameterPropertyScope() {
return destructuredParameterPropertyScopes[destructuredParameterPropertyScopes.length - 1];
}
function currentDestructuredParameterPropertyMergeScope() {
return destructuredParameterPropertyMergeScopes[
destructuredParameterPropertyMergeScopes.length - 1
];
}
function currentParameterObjectBindingScope() {
return parameterObjectBindingScopes[parameterObjectBindingScopes.length - 1];
}
function currentParameterPropertyUseScope() {
return parameterPropertyUseScopes[parameterPropertyUseScopes.length - 1];
}
function currentConditionalDestructuredParameterPropertyScope() {
return conditionalDestructuredParameterPropertyScopes[
conditionalDestructuredParameterPropertyScopes.length - 1
];
}
function currentConditionalParameterObjectScope() {
return conditionalParameterObjectScopes[conditionalParameterObjectScopes.length - 1];
}
function currentConditionalParameterPropertyUseScope() {
return conditionalParameterPropertyUseScopes[
conditionalParameterPropertyUseScopes.length - 1
];
}
function currentConditionalWrapperBodyScope() {
return conditionalWrapperBodyScopes[conditionalWrapperBodyScopes.length - 1];
}
function currentShadowScope() {
return shadowScopes[shadowScopes.length - 1];
}
function currentFsAliasShadowScope() {
return fsAliasShadowScopes[fsAliasShadowScopes.length - 1];
}
function currentFsModuleShadowScope() {
return fsModuleShadowScopes[fsModuleShadowScopes.length - 1];
}
function currentWrapperCreateRequireShadowScope() {
return wrapperCreateRequireShadowScopes[wrapperCreateRequireShadowScopes.length - 1];
}
function visibleWrapperCreateRequireShadows() {
const shadows = new Set();
for (const scope of wrapperCreateRequireShadowScopes) {
for (const name of scope) {
shadows.add(name);
}
}
return shadows;
}
function currentNestedWrapperFunctionScope() {
return nestedWrapperFunctionScopes[nestedWrapperFunctionScopes.length - 1];
}
function currentParameterObjectShadowScope() {
return parameterObjectShadowScopes[parameterObjectShadowScopes.length - 1];
}
function currentParameterObjectAssignmentShadowScope() {
return parameterObjectAssignmentShadowScopes[
parameterObjectAssignmentShadowScopes.length - 1
];
}
function currentWrapperBranchEffectScope() {
return wrapperBranchEffectScopes[wrapperBranchEffectScopes.length - 1] ?? null;
}
function createWrapperBranchEffects() {
return {
destructuredAssignments: new Map(),
fsIdentifierAssignments: new Map(),
nestedWrapperAssignments: new Map(),
nestedWrapperAssignmentScopes: new Map(),
parameterObjectAssignments: new Map(),
parameterPropertyAssignments: new Map(),
};
}
function bindingUses(binding) {
return binding === null || binding === undefined ? [] : [binding];
}
function recordWrapperBranchParameterObjectAssignment(name, objectIndex) {
const effects = currentWrapperBranchEffectScope();
if (effects) {
effects.parameterObjectAssignments.set(name, bindingUses(objectIndex));
}
}
function recordWrapperBranchParameterPropertyAssignment(key, binding) {
const effects = currentWrapperBranchEffectScope();
if (effects) {
effects.parameterPropertyAssignments.set(key, bindingUses(binding));
}
}
function recordWrapperBranchDestructuredAssignment(name, binding) {
const effects = currentWrapperBranchEffectScope();
if (effects) {
effects.destructuredAssignments.set(name, bindingUses(binding));
}
}
function recordWrapperBranchNestedWrapperAssignment(name, value, targetScope) {
const effects = currentWrapperBranchEffectScope();
if (effects) {
clearBranchNestedWrapperObjectAssignments(effects, name);
effects.nestedWrapperAssignments.set(name, cloneWrapperFunctionValue(value));
effects.nestedWrapperAssignmentScopes.set(name, targetScope);
}
}
function recordWrapperBranchFsIdentifierAssignment(
name,
moduleValue,
writeAlias,
requireAlias,
moduleScope,
writeAliasScope,
requireAliasScope,
) {
const effects = currentWrapperBranchEffectScope();
if (effects) {
effects.fsIdentifierAssignments.set(name, {
moduleScope,
moduleValue,
name,
requireAlias,
requireAliasScope,
writeAlias,
writeAliasScope,
});
}
}
function clearBranchNestedWrapperObjectAssignments(effects, objectName) {
const prefix = `${objectName}.`;
for (const name of effects.nestedWrapperAssignments.keys()) {
if (name.startsWith(prefix)) {
effects.nestedWrapperAssignments.delete(name);
effects.nestedWrapperAssignmentScopes.delete(name);
}
}
}
function wrapperAssignmentMergeOrder(left, right) {
return left.split(".").length - right.split(".").length;
}
function mergeBindingUses(left, right) {
return [...left, ...right];
}
function applyMergedParameterPropertyAssignment(key, uses) {
currentParameterPropertyUseScope().set(key, null);
for (const use of uses) {
appendConditionalUse(currentConditionalParameterPropertyUseScope(), key, use);
}
recordWrapperBranchParameterPropertyAssignment(key, uses[0] ?? null);
const parentEffect = currentWrapperBranchEffectScope();
if (parentEffect && uses.length > 1) {
parentEffect.parameterPropertyAssignments.set(key, uses);
}
}
function applyMergedDestructuredAssignment(name, uses) {
currentDestructuredParameterPropertyScope().set(name, null);
currentDestructuredParameterPropertyMergeScope().set(name, null);
for (const use of uses) {
appendConditionalUse(currentConditionalDestructuredParameterPropertyScope(), name, use);
}
recordWrapperBranchDestructuredAssignment(name, uses[0] ?? null);
const parentEffect = currentWrapperBranchEffectScope();
if (parentEffect && uses.length > 1) {
parentEffect.destructuredAssignments.set(name, uses);
}
}
function applyMergedParameterObjectAssignment(name, uses) {
if (uses.length === 0) {
currentParameterObjectShadowScope().add(name);
currentParameterObjectAssignmentShadowScope().add(name);
} else {
currentParameterObjectBindingScope().set(name, uses[0]);
for (const use of uses.slice(1)) {
appendConditionalUse(currentConditionalParameterObjectScope(), name, use);
}
}
const parentEffect = currentWrapperBranchEffectScope();
if (parentEffect) {
parentEffect.parameterObjectAssignments.set(name, uses);
}
}
function applyMergedNestedWrapperAssignment(name, value, targetScope = null) {
const resolvedTargetScope = targetScope ?? nestedWrapperFunctionWriteScope(name);
if (!resolvedTargetScope) {
return;
}
clearNestedWrapperObjectMethods(resolvedTargetScope, name);
resolvedTargetScope.set(name, cloneWrapperFunctionValue(value));
const parentEffect = currentWrapperBranchEffectScope();
if (parentEffect) {
parentEffect.nestedWrapperAssignments.set(name, cloneWrapperFunctionValue(value));
parentEffect.nestedWrapperAssignmentScopes.set(name, resolvedTargetScope);
}
}
function applyMergedFsIdentifierAssignment(thenAssignment, elseAssignment) {
const { name } = thenAssignment;
const moduleScope =
thenAssignment.moduleScope === elseAssignment.moduleScope
? thenAssignment.moduleScope
: null;
const writeAliasScope =
thenAssignment.writeAliasScope === elseAssignment.writeAliasScope
? thenAssignment.writeAliasScope
: null;
const requireAliasScope =
thenAssignment.requireAliasScope === elseAssignment.requireAliasScope
? thenAssignment.requireAliasScope
: null;
const moduleValue =
thenAssignment.moduleValue === true || elseAssignment.moduleValue === true;
const writeAlias = thenAssignment.writeAlias ?? elseAssignment.writeAlias;
const requireAlias =
thenAssignment.requireAlias === true || elseAssignment.requireAlias === true;
if (moduleScope && bodyFsModuleBindingScopes.includes(moduleScope)) {
moduleScope.set(name, moduleValue);
}
if (writeAliasScope && bodyFsWriteAliasScopes.includes(writeAliasScope)) {
writeAliasScope.set(name, writeAlias);
}
if (requireAliasScope && bodyRequireAliasScopes.includes(requireAliasScope)) {
requireAliasScope.set(name, requireAlias);
}
refreshCurrentNestedWrapperFunctionAliases();
const parentEffect = currentWrapperBranchEffectScope();
if (parentEffect) {
parentEffect.fsIdentifierAssignments.set(name, {
moduleScope,
moduleValue,
name,
requireAlias,
requireAliasScope,
writeAlias,
writeAliasScope,
});
}
}
function mergeExhaustiveWrapperBranchEffects(thenEffects, elseEffects) {
for (const [name, thenAssignment] of thenEffects.fsIdentifierAssignments) {
const elseAssignment = elseEffects.fsIdentifierAssignments.get(name);
if (elseAssignment) {
applyMergedFsIdentifierAssignment(thenAssignment, elseAssignment);
}
}
for (const [key, thenUses] of thenEffects.parameterPropertyAssignments) {
const elseUses = elseEffects.parameterPropertyAssignments.get(key);
if (elseUses) {
applyMergedParameterPropertyAssignment(key, mergeBindingUses(thenUses, elseUses));
}
}
for (const [name, thenUses] of thenEffects.destructuredAssignments) {
const elseUses = elseEffects.destructuredAssignments.get(name);
if (elseUses) {
applyMergedDestructuredAssignment(name, mergeBindingUses(thenUses, elseUses));
}
}
for (const [name, thenUses] of thenEffects.parameterObjectAssignments) {
const elseUses = elseEffects.parameterObjectAssignments.get(name);
if (elseUses) {
applyMergedParameterObjectAssignment(name, mergeBindingUses(thenUses, elseUses));
}
}
const nestedWrapperAssignmentNames = new Set([
...thenEffects.nestedWrapperAssignments.keys(),
...elseEffects.nestedWrapperAssignments.keys(),
]);
for (const name of [...nestedWrapperAssignmentNames].toSorted(wrapperAssignmentMergeOrder)) {
const thenScope = thenEffects.nestedWrapperAssignmentScopes.get(name);
const elseScope = elseEffects.nestedWrapperAssignmentScopes.get(name);
const targetScope = thenScope ?? elseScope;
if (
targetScope === undefined ||
(thenScope !== undefined && elseScope !== undefined && thenScope !== elseScope) ||
!nestedWrapperFunctionScopes.includes(targetScope)
) {
continue;
}
const previousValue = targetScope.get(name);
applyMergedNestedWrapperAssignment(
name,
mergeWrapperAssignmentValues(
thenEffects.nestedWrapperAssignments.has(name)
? thenEffects.nestedWrapperAssignments.get(name)
: previousValue,
elseEffects.nestedWrapperAssignments.has(name)
? elseEffects.nestedWrapperAssignments.get(name)
: previousValue,
),
targetScope,
);
}
}
function resolveParameterIndex(name) {
for (let index = parameterObjectShadowScopes.length - 1; index >= 0; index--) {
if (parameterObjectShadowScopes[index].has(name)) {
return null;
}
if (parameterObjectBindingScopes[index].has(name)) {
return parameterObjectBindingScopes[index].get(name);
}
}
return parameterIndexes.has(name) ? parameterIndexes.get(name) : null;
}
function resolveDestructuredParameterProperty(name) {
for (let index = destructuredParameterPropertyScopes.length - 1; index >= 0; index--) {
if (shadowScopes[index].has(name)) {
return null;
}
if (destructuredParameterPropertyScopes[index].has(name)) {
return destructuredParameterPropertyScopes[index].get(name);
}
}
return null;
}
function appendConditionalUse(scope, key, value) {
const values = scope.get(key) ?? [];
values.push(value);
scope.set(key, values);
}
function conditionalUsesFor(key, scopes) {
const uses = [];
for (const scope of scopes) {
uses.push(...(scope.get(key) ?? []));
}
return uses;
}
function conditionalObjectPropertyUses(objectName, propertyName) {
const uses = [];
for (const scope of conditionalParameterObjectScopes) {
for (const index of scope.get(objectName) ?? []) {
uses.push({ index, propertyName });
}
}
return uses;
}
function resolveParameterPropertyUse(objectName, propertyName) {
const key = `${objectName}.${propertyName}`;
let baseUse = undefined;
for (let index = parameterPropertyUseScopes.length - 1; index >= 0; index--) {
if (parameterObjectShadowScopes[index].has(objectName)) {
return null;
}
if (parameterPropertyUseScopes[index].has(key)) {
baseUse = parameterPropertyUseScopes[index].get(key);
break;
}
}
const extraUses = [
...conditionalUsesFor(key, conditionalParameterPropertyUseScopes),
...conditionalObjectPropertyUses(objectName, propertyName),
];
if (extraUses.length === 0) {
return baseUse;
}
if (baseUse === null) {
return extraUses;
}
const fallbackIndex = resolveParameterIndex(objectName);
const baseUses = baseUse
? [baseUse]
: fallbackIndex !== null
? [{ index: fallbackIndex, propertyName }]
: [];
return [...baseUses, ...extraUses];
}
function resolveDestructuredParameterPropertyUses(name) {
const baseUse = resolveDestructuredParameterProperty(name);
const extraUses = conditionalUsesFor(name, conditionalDestructuredParameterPropertyScopes);
if (extraUses.length === 0) {
return baseUse;
}
return baseUse ? [baseUse, ...extraUses] : extraUses;
}
function resolveParameterPropertyBinding(expression) {
const unwrapped = unwrapExpression(expression);
const propertyAccess = rootedPropertyAccessPath(unwrapped);
if (propertyAccess?.properties.length > 0) {
const index = resolveParameterIndex(propertyAccess.rootName);
if (index !== null) {
return {
index,
propertyName: propertyAccess.properties.join("."),
};
}
}
if (ts.isIdentifier(unwrapped)) {
return resolveDestructuredParameterProperty(unwrapped.text);
}
return null;
}
function resolveParameterObjectBindingExpression(expression) {
const unwrapped = unwrapExpression(expression);
return ts.isIdentifier(unwrapped) ? resolveParameterIndex(unwrapped.text) : null;
}
function collectForwardedWrapperPropertyUses(
argument,
propertyName,
parameter = null,
wrapperNode = null,
argumentsList = [],
options = {},
) {
if (propertyName === null) {
return collectPathPropertyUses(
argument,
"writeFile",
resolveParameterIndex,
resolveDestructuredParameterProperty,
resolveParameterPropertyUse,
resolveDestructuredParameterPropertyUses,
);
}
const propertyPath = propertyName.split(".");
function collectWrapperBindingDefaultUses(sourceExpression) {
if (!parameter || !ts.isObjectBindingPattern(parameter.name)) {
return [];
}
const initializer = appliedBindingElementDefaultInitializer(
parameter.name,
propertyPath,
sourceExpression,
nestedWrapperObjectLiteralSpreadPropertyState,
);
const defaultExpression = initializer
? resolveBindingDefaultInitializerExpression(
initializer,
wrapperNode,
argumentsList,
parameter,
options,
)
: null;
return defaultExpression
? collectPathPropertyUses(
defaultExpression,
"writeFile",
resolveParameterIndex,
resolveDestructuredParameterProperty,
resolveParameterPropertyUse,
resolveDestructuredParameterPropertyUses,
)
: [];
}
function collectForwardedWrapperPropertyUseState(currentArgument, currentPropertyPath) {
const currentUnwrapped = unwrapExpression(currentArgument);
const currentPropertyName = currentPropertyPath.join(".");
if (ts.isIdentifier(currentUnwrapped)) {
const index = resolveParameterIndex(currentUnwrapped.text);
if (index !== null) {
const propertyUse = resolveParameterPropertyUse(
currentUnwrapped.text,
currentPropertyName,
);
return propertyUse === null
? []
: [propertyUse ?? { index, propertyName: currentPropertyName }];
}
return null;
}
if (ts.isObjectLiteralExpression(currentUnwrapped)) {
let result = null;
for (const property of currentUnwrapped.properties) {
if (ts.isSpreadAssignment(property)) {
const spreadUses = collectForwardedWrapperPropertyUseState(
property.expression,
currentPropertyPath,
);
if (spreadUses !== null) {
result = spreadUses;
}
continue;
}
const [nextPropertyName, ...remainingPropertyPath] = currentPropertyPath;
if (
ts.isPropertyAssignment(property) &&
propertyNameText(property.name) === nextPropertyName
) {
if (isKnownUndefinedExpression(property.initializer)) {
result = null;
continue;
}
if (remainingPropertyPath.length > 0) {
result = collectForwardedWrapperPropertyUseState(
property.initializer,
remainingPropertyPath,
);
continue;
}
result = collectPathPropertyUses(
property.initializer,
"writeFile",
resolveParameterIndex,
resolveDestructuredParameterProperty,
resolveParameterPropertyUse,
resolveDestructuredParameterPropertyUses,
);
continue;
}
if (
remainingPropertyPath.length === 0 &&
ts.isShorthandPropertyAssignment(property) &&
property.name.text === nextPropertyName
) {
result = collectPathPropertyUses(
property.name,
"writeFile",
resolveParameterIndex,
resolveDestructuredParameterProperty,
resolveParameterPropertyUse,
resolveDestructuredParameterPropertyUses,
);
}
if (
remainingPropertyPath.length > 0 &&
ts.isShorthandPropertyAssignment(property) &&
property.name.text === nextPropertyName
) {
result = collectForwardedWrapperPropertyUseState(
property.name,
remainingPropertyPath,
);
}
}
return result;
}
const uses = collectPathPropertyUses(
currentArgument,
"writeFile",
resolveParameterIndex,
resolveDestructuredParameterProperty,
resolveParameterPropertyUse,
resolveDestructuredParameterPropertyUses,
);
return uses.length > 0 ? uses : null;
}
const unwrapped = unwrapExpression(argument);
if (ts.isIdentifier(unwrapped)) {
const index = resolveParameterIndex(unwrapped.text);
if (index !== null) {
const propertyUse = resolveParameterPropertyUse(unwrapped.text, propertyName);
return propertyUse === null ? [] : [propertyUse ?? { index, propertyName }];
}
}
if (ts.isObjectLiteralExpression(unwrapped)) {
return (
collectForwardedWrapperPropertyUseState(unwrapped, propertyPath) ??
collectWrapperBindingDefaultUses(unwrapped)
);
}
return collectPathPropertyUses(
argument,
"writeFile",
resolveParameterIndex,
resolveDestructuredParameterProperty,
resolveParameterPropertyUse,
resolveDestructuredParameterPropertyUses,
);
}
function markParameterAssignment(assignmentNode) {
if (
!ts.isBinaryExpression(assignmentNode) ||
assignmentNode.operatorToken.kind !== ts.SyntaxKind.EqualsToken
) {
return;
}
if (ts.isIdentifier(assignmentNode.left)) {
if (resolveParameterIndex(assignmentNode.left.text) !== null) {
const objectIndex = resolveParameterObjectBindingExpression(assignmentNode.right);
if (currentConditionalWrapperBodyScope()) {
recordWrapperBranchParameterObjectAssignment(assignmentNode.left.text, objectIndex);
if (objectIndex !== null) {
appendConditionalUse(
currentConditionalParameterObjectScope(),
assignmentNode.left.text,
objectIndex,
);
}
} else if (objectIndex !== null) {
currentParameterObjectBindingScope().set(assignmentNode.left.text, objectIndex);
} else {
currentParameterObjectShadowScope().add(assignmentNode.left.text);
currentParameterObjectAssignmentShadowScope().add(assignmentNode.left.text);
}
}
if (resolveDestructuredParameterProperty(assignmentNode.left.text)) {
const binding = resolveParameterPropertyBinding(assignmentNode.right);
if (currentConditionalWrapperBodyScope()) {
recordWrapperBranchDestructuredAssignment(assignmentNode.left.text, binding);
if (binding) {
appendConditionalUse(
currentConditionalDestructuredParameterPropertyScope(),
assignmentNode.left.text,
binding,
);
}
} else {
const updatesOuterBinding = !currentDestructuredParameterPropertyScope().has(
assignmentNode.left.text,
);
currentDestructuredParameterPropertyScope().set(assignmentNode.left.text, binding);
if (updatesOuterBinding) {
currentDestructuredParameterPropertyMergeScope().set(
assignmentNode.left.text,
binding,
);
}
}
}
return;
}
const propertyAccess = rootedPropertyAccessPath(assignmentNode.left);
if (
propertyAccess?.properties.length > 0 &&
resolveParameterIndex(propertyAccess.rootName) !== null
) {
const binding = resolveParameterPropertyBinding(assignmentNode.right);
const key = `${propertyAccess.rootName}.${propertyAccess.properties.join(".")}`;
if (currentConditionalWrapperBodyScope()) {
recordWrapperBranchParameterPropertyAssignment(key, binding);
if (binding) {
appendConditionalUse(currentConditionalParameterPropertyUseScope(), key, binding);
}
} else {
currentParameterPropertyUseScope().set(key, binding);
}
}
}
function mergeConditionalUses(source, target) {
for (const [key, uses] of source) {
for (const use of uses) {
appendConditionalUse(target, key, use);
}
}
}
function mergeMapEntries(source, target) {
for (const [key, value] of source) {
target.set(key, value);
}
}
function mergeParameterObjectBindings(source, target) {
for (const [key, value] of source) {
if (parameterIndexes.has(key)) {
target.set(key, value);
}
}
}
function pushWrapperBodyScope(
conditional = currentConditionalWrapperBodyScope(),
branchEffects = null,
) {
bodyFsWriteAliasScopes.push(new Map());
bodyFsModuleBindingScopes.push(new Map());
bodyFsModulePropertyScopes.push(new Map());
destructuredParameterPropertyScopes.push(new Map());
destructuredParameterPropertyMergeScopes.push(new Map());
parameterObjectBindingScopes.push(new Map());
parameterPropertyUseScopes.push(new Map());
conditionalDestructuredParameterPropertyScopes.push(new Map());
conditionalParameterObjectScopes.push(new Map());
conditionalParameterPropertyUseScopes.push(new Map());
conditionalWrapperBodyScopes.push(conditional);
shadowScopes.push(new Set());
fsAliasShadowScopes.push(new Set());
fsModuleShadowScopes.push(new Set());
wrapperRequireShadowScopes.push(new Set());
wrapperCreateRequireShadowScopes.push(new Set());
bodyRequireAliasScopes.push(new Map());
parameterObjectShadowScopes.push(new Set());
parameterObjectAssignmentShadowScopes.push(new Set());
wrapperBranchEffectScopes.push(branchEffects);
const nestedWrapperFunctionScope = new Map();
nestedWrapperFunctionScopeParents.set(
nestedWrapperFunctionScope,
currentNestedWrapperFunctionScope(),
);
nestedWrapperFunctionScopes.push(nestedWrapperFunctionScope);
}
function popWrapperBodyScope() {
nestedWrapperFunctionScopes.pop();
wrapperBranchEffectScopes.pop();
const parameterObjectAssignmentShadows = parameterObjectAssignmentShadowScopes.pop();
parameterObjectShadowScopes.pop();
wrapperRequireShadowScopes.pop();
wrapperCreateRequireShadowScopes.pop();
bodyRequireAliasScopes.pop();
fsModuleShadowScopes.pop();
fsAliasShadowScopes.pop();
shadowScopes.pop();
const parameterPropertyUses = conditionalParameterPropertyUseScopes.pop();
const parameterObjectUses = conditionalParameterObjectScopes.pop();
const destructuredUses = conditionalDestructuredParameterPropertyScopes.pop();
const wasConditional = conditionalWrapperBodyScopes.pop();
const directParameterPropertyUses = parameterPropertyUseScopes.pop();
const directParameterObjectBindings = parameterObjectBindingScopes.pop();
destructuredParameterPropertyScopes.pop();
const directDestructuredBindings = destructuredParameterPropertyMergeScopes.pop();
if (wasConditional) {
mergeConditionalUses(parameterPropertyUses, currentConditionalParameterPropertyUseScope());
mergeConditionalUses(parameterObjectUses, currentConditionalParameterObjectScope());
mergeConditionalUses(
destructuredUses,
currentConditionalDestructuredParameterPropertyScope(),
);
} else {
mergeMapEntries(directParameterPropertyUses, currentParameterPropertyUseScope());
mergeParameterObjectBindings(
directParameterObjectBindings,
currentParameterObjectBindingScope(),
);
for (const name of parameterObjectAssignmentShadows) {
currentParameterObjectShadowScope().add(name);
currentParameterObjectAssignmentShadowScope().add(name);
}
mergeMapEntries(directDestructuredBindings, currentDestructuredParameterPropertyScope());
}
bodyFsModuleBindingScopes.pop();
bodyFsModulePropertyScopes.pop();
bodyFsWriteAliasScopes.pop();
}
function resolveBodyFsWriteAlias(name) {
for (let index = bodyFsWriteAliasScopes.length - 1; index >= 0; index--) {
const scope = bodyFsWriteAliasScopes[index];
if (scope.has(name)) {
return scope.get(name) ?? null;
}
}
return null;
}
function resolveBodyFsModuleBinding(name) {
for (let index = bodyFsModuleBindingScopes.length - 1; index >= 0; index--) {
const scope = bodyFsModuleBindingScopes[index];
if (scope.has(name)) {
return scope.get(name) === true;
}
}
return false;
}
function resolveBodyFsModuleProperty(pathParts) {
const fullPath = pathParts.join(".");
const prefixes = pathParts.map((_, index) => pathParts.slice(0, index + 1).join("."));
for (let index = bodyFsModulePropertyScopes.length - 1; index >= 0; index--) {
const scope = bodyFsModulePropertyScopes[index];
if (scope.has(fullPath)) {
return scope.get(fullPath) === true;
}
for (const prefix of prefixes) {
if (scope.get(prefix) === false) {
return false;
}
}
}
return false;
}
function isFsModuleShadowed(name) {
for (let index = fsModuleShadowScopes.length - 1; index >= 0; index--) {
if (fsModuleShadowScopes[index].has(name)) {
return true;
}
}
return false;
}
function isWrapperRequireName(name) {
return resolveBodyRequireAlias(name);
}
function markWrapperRequireShadows(name) {
for (const bindingName of bindingPatternNames(name)) {
if (
bindingName === "require" ||
resolveBodyRequireAlias(bindingName) ||
resolveRequireAlias(bindingName)
) {
wrapperRequireShadowScopes[wrapperRequireShadowScopes.length - 1].add(bindingName);
}
currentBodyRequireAliasScope().set(bindingName, false);
}
}
function isWrapperCreateRequireShadowed(name) {
return wrapperCreateRequireShadowScopes.some((scope) => scope.has(name));
}
function isWrapperCreateRequireExpression(expression) {
const call = unwrapExpression(expression);
return (
ts.isCallExpression(call) &&
ts.isIdentifier(unwrapExpression(call.expression)) &&
createRequireBindings.has(unwrapExpression(call.expression).text) &&
!isWrapperCreateRequireShadowed(unwrapExpression(call.expression).text)
);
}
function isWrapperRequireAliasExpression(expression) {
const value = unwrapExpression(expression);
return (
isWrapperCreateRequireExpression(value) ||
(ts.isIdentifier(value) && resolveBodyRequireAlias(value.text))
);
}
function markWrapperCreateRequireShadows(name) {
for (const bindingName of bindingPatternNames(name)) {
if (createRequireBindings.has(bindingName)) {
currentWrapperCreateRequireShadowScope().add(bindingName);
}
}
}
function resolveBodyRequireAlias(name) {
for (let index = bodyRequireAliasScopes.length - 1; index >= 0; index--) {
const scope = bodyRequireAliasScopes[index];
if (scope.has(name)) {
return scope.get(name) === true;
}
}
return false;
}
function bodyFsWriteAliasWriteScope(name) {
for (let index = bodyFsWriteAliasScopes.length - 1; index >= 0; index--) {
const scope = bodyFsWriteAliasScopes[index];
if (scope.has(name)) {
return scope;
}
}
return currentBodyFsWriteAliasScope();
}
function bodyFsModuleBindingWriteScope(name) {
for (let index = bodyFsModuleBindingScopes.length - 1; index >= 0; index--) {
const scope = bodyFsModuleBindingScopes[index];
if (scope.has(name)) {
return scope;
}
}
return currentBodyFsModuleBindingScope();
}
function bodyRequireAliasWriteScope(name) {
for (let index = bodyRequireAliasScopes.length - 1; index >= 0; index--) {
const scope = bodyRequireAliasScopes[index];
if (scope.has(name)) {
return scope;
}
}
return currentBodyRequireAliasScope();
}
function shadowVisibleBodyFsWriteObjectAliases(objectName) {
const prefix = `${objectName}.`;
const currentScope = currentBodyFsWriteAliasScope();
for (const scope of bodyFsWriteAliasScopes) {
for (const name of scope.keys()) {
if (name.startsWith(prefix)) {
currentScope.set(name, null);
}
}
}
}
function clearBodyFsWriteObjectAliases(scope, objectName) {
const prefix = `${objectName}.`;
for (const name of scope.keys()) {
if (name.startsWith(prefix)) {
scope.set(name, null);
}
}
}
function setBodyFsWriteObjectAlias(scope, name, writeName) {
scope.set(name, writeName ?? null);
}
function registerBodyFsWriteObjectAliases(
objectName,
initializer,
scope = currentBodyFsWriteAliasScope(),
) {
const objectLiteral = unwrapExpression(initializer);
if (!ts.isObjectLiteralExpression(objectLiteral)) {
return;
}
for (const property of objectLiteral.properties) {
if (ts.isPropertyAssignment(property)) {
const name = propertyNameText(property.name);
if (name) {
setBodyFsWriteObjectAlias(
scope,
`${objectName}.${name}`,
legacyWrapperFsWriteName(property.initializer),
);
}
continue;
}
if (ts.isShorthandPropertyAssignment(property)) {
setBodyFsWriteObjectAlias(
scope,
`${objectName}.${property.name.text}`,
resolveBodyFsWriteAlias(property.name.text),
);
}
}
}
function isWrapperFsBindingExpression(expression) {
const initializer = unwrapExpression(expression);
if (
isFsRequireExpression(initializer, isWrapperRequireName) ||
isFsDynamicImportExpression(initializer)
) {
return true;
}
if (ts.isIdentifier(initializer)) {
return (
!isFsModuleShadowed(initializer.text) && resolveBodyFsModuleBinding(initializer.text)
);
}
return (
ts.isPropertyAccessExpression(initializer) &&
initializer.name.text === "promises" &&
(isFsRequireExpression(initializer.expression, isWrapperRequireName) ||
isFsDynamicImportExpression(initializer.expression) ||
(ts.isIdentifier(initializer.expression) &&
!isFsModuleShadowed(initializer.expression.text) &&
resolveBodyFsModuleBinding(initializer.expression.text)))
);
}
function isFsAliasShadowed(name) {
for (let index = fsAliasShadowScopes.length - 1; index >= 0; index--) {
if (fsAliasShadowScopes[index].has(name)) {
return true;
}
}
return false;
}
function isWrapperFsModuleExpression(expression) {
const receiver = unwrapExpression(expression);
if (
isFsRequireExpression(receiver, isWrapperRequireName) ||
isFsDynamicImportExpression(receiver)
) {
return true;
}
if (ts.isIdentifier(receiver)) {
return !isFsModuleShadowed(receiver.text) && resolveBodyFsModuleBinding(receiver.text);
}
const receiverPath = propertyAccessPath(receiver);
if (receiverPath && resolveBodyFsModuleProperty(receiverPath)) {
return true;
}
return (
ts.isPropertyAccessExpression(receiver) &&
receiver.name.text === "promises" &&
(isFsRequireExpression(receiver.expression, isWrapperRequireName) ||
isFsDynamicImportExpression(receiver.expression) ||
(ts.isIdentifier(receiver.expression) &&
!isFsModuleShadowed(receiver.expression.text) &&
resolveBodyFsModuleBinding(receiver.expression.text)) ||
(propertyAccessPath(receiver.expression) &&
resolveBodyFsModuleProperty(propertyAccessPath(receiver.expression))))
);
}
function legacyWrapperFsWriteName(expression) {
const callee = unwrapExpression(expression);
if (ts.isPropertyAccessExpression(callee)) {
const aliasedName = callExpressionName(callee);
const writeAlias =
aliasedName && !isFsAliasShadowed(aliasedName)
? resolveBodyFsWriteAlias(aliasedName)
: null;
if (writeAlias) {
return writeAlias;
}
return legacyWriteCallees.has(callee.name.text) &&
isWrapperFsModuleExpression(callee.expression)
? callee.name.text
: null;
}
if (ts.isElementAccessExpression(callee)) {
const aliasedName = callExpressionName(callee);
const writeAlias =
aliasedName && !isFsAliasShadowed(aliasedName)
? resolveBodyFsWriteAlias(aliasedName)
: null;
if (writeAlias) {
return writeAlias;
}
const writeName = elementAccessName(callee.argumentExpression);
return writeName &&
legacyWriteCallees.has(writeName) &&
isWrapperFsModuleExpression(callee.expression)
? writeName
: null;
}
if (ts.isIdentifier(callee) && isFsAliasShadowed(callee.text)) {
return null;
}
return ts.isIdentifier(callee) ? resolveBodyFsWriteAlias(callee.text) : null;
}
function markFsAliasShadows(name) {
for (const bindingName of bindingPatternNames(name)) {
if (resolveBodyFsWriteAlias(bindingName)) {
currentFsAliasShadowScope().add(bindingName);
}
}
}
function markFsModuleShadows(name) {
for (const bindingName of bindingPatternNames(name)) {
if (resolveBodyFsModuleBinding(bindingName)) {
currentFsModuleShadowScope().add(bindingName);
currentBodyFsModuleBindingScope().set(bindingName, false);
}
currentBodyFsModulePropertyScope().set(bindingName, false);
}
}
function registerBodyFsModuleTypeProperties(name, type) {
if (!ts.isIdentifier(name) || !type) {
return;
}
if (isFsModuleTypeNode(type)) {
currentBodyFsModuleBindingScope().set(name.text, true);
}
for (const pathParts of fsModulePropertyPathsFromType(type)) {
currentBodyFsModulePropertyScope().set([name.text, ...pathParts].join("."), true);
}
}
node.parameters.forEach((parameter, index) => {
if (ts.isIdentifier(parameter.name)) {
parameterIndexes.set(parameter.name.text, index);
}
for (const bindingName of bindingPatternNames(parameter.name)) {
currentNestedWrapperFunctionScope().set(bindingName, null);
}
markFsAliasShadows(parameter.name);
markFsModuleShadows(parameter.name);
markWrapperRequireShadows(parameter.name);
markWrapperCreateRequireShadows(parameter.name);
registerBodyFsModuleTypeProperties(parameter.name, parameter.type);
for (const [name, binding] of parameterPropertyBindings(parameter, index)) {
currentDestructuredParameterPropertyScope().set(name, binding);
}
});
const propertyUses = new Map();
function nestedWrapperRecordForNode(nestedNode) {
const requireAliasSnapshot = visibleBodyRequireAliasSnapshot();
return {
aliases: visibleBodyFsWriteAliases(),
closesOverCurrentWrapper: true,
createRequireShadows: visibleWrapperCreateRequireShadows(),
lexicalScope: currentNestedWrapperFunctionScope(),
moduleBindings: visibleBodyFsModuleBindings(),
moduleProperties: visibleBodyFsModuleProperties(),
node: nestedNode,
requireAliases: requireAliasSnapshot.aliases,
requireAliasSourceScopes: requireAliasSnapshot.sourceScopes,
};
}
function resolveNestedWrapperFunction(name) {
for (let index = nestedWrapperFunctionScopes.length - 1; index >= 0; index--) {
const scope = nestedWrapperFunctionScopes[index];
if (scope.has(name)) {
return scope.get(name);
}
}
return undefined;
}
function isNestedWrapperScopeDescendant(scope, ancestor) {
let current = scope;
while (current) {
if (current === ancestor) {
return true;
}
current = nestedWrapperFunctionScopeParents.get(current) ?? null;
}
return false;
}
function refreshCurrentNestedWrapperFunctionAliases() {
const aliases = visibleBodyFsWriteAliases();
const moduleBindings = visibleBodyFsModuleBindings();
const moduleProperties = visibleBodyFsModuleProperties();
const requireAliasSnapshot = visibleBodyRequireAliasSnapshot();
const createRequireShadows = visibleWrapperCreateRequireShadows();
const currentLexicalScope = currentNestedWrapperFunctionScope();
function refreshNestedWrapperRecord(record) {
if (!isNestedWrapperScopeDescendant(record.lexicalScope, currentLexicalScope)) {
return;
}
if (record.lexicalScope === currentLexicalScope) {
record.aliases = aliases;
record.moduleBindings = moduleBindings;
record.moduleProperties = moduleProperties;
record.requireAliases = requireAliasSnapshot.aliases;
record.requireAliasSourceScopes = requireAliasSnapshot.sourceScopes;
record.createRequireShadows = createRequireShadows;
return;
}
record.aliases = new Map([...aliases, ...record.aliases]);
record.moduleBindings = new Map([...moduleBindings, ...record.moduleBindings]);
record.moduleProperties = new Map([...moduleProperties, ...record.moduleProperties]);
record.requireAliases = new Map([
...requireAliasSnapshot.aliases,
...record.requireAliases,
]);
record.requireAliasSourceScopes = new Map([
...requireAliasSnapshot.sourceScopes,
...record.requireAliasSourceScopes,
]);
record.createRequireShadows = new Set([
...createRequireShadows,
...record.createRequireShadows,
]);
}
function refreshNestedWrapperRecords(values) {
for (const value of values) {
for (const record of wrapperRecords(value)) {
refreshNestedWrapperRecord(record);
}
}
}
for (const scope of nestedWrapperFunctionScopes) {
refreshNestedWrapperRecords(scope.values());
}
const branchEffects = currentWrapperBranchEffectScope();
if (branchEffects) {
refreshNestedWrapperRecords(branchEffects.nestedWrapperAssignments.values());
}
}
function nestedWrapperFunctionWriteScope(name) {
for (let index = nestedWrapperFunctionScopes.length - 1; index >= 0; index--) {
const scope = nestedWrapperFunctionScopes[index];
if (scope.has(name)) {
return scope;
}
}
return currentNestedWrapperFunctionScope();
}
function nestedWrapperObjectMethodWriteScope(objectName, propertyName) {
const key = objectPropertyKey(objectName, propertyName);
for (let index = nestedWrapperFunctionScopes.length - 1; index >= 0; index--) {
const scope = nestedWrapperFunctionScopes[index];
if (scope.has(key) || scope.has(objectName)) {
return scope;
}
}
return currentNestedWrapperFunctionScope();
}
function markNestedWrapperFunctionShadows(name) {
for (const bindingName of bindingPatternNames(name)) {
currentNestedWrapperFunctionScope().set(bindingName, null);
}
}
function clearNestedWrapperObjectMethods(scope, objectName) {
const prefix = `${objectName}.`;
for (const name of scope.keys()) {
if (name.startsWith(prefix)) {
scope.set(name, null);
}
}
}
function shadowVisibleNestedWrapperObjectMethods(objectName) {
const prefix = `${objectName}.`;
const currentScope = currentNestedWrapperFunctionScope();
for (const scope of nestedWrapperFunctionScopes) {
for (const name of scope.keys()) {
if (name.startsWith(prefix)) {
currentScope.set(name, null);
}
}
}
}
function markNestedWrapperObjectUnknown(
objectName,
scope = currentNestedWrapperFunctionScope(),
recordBranchAssignments = false,
recordBranchAssignmentScope = scope,
) {
clearNestedWrapperObjectMethods(scope, objectName);
scope.set(objectName, unknownNestedWrapperObjectValue);
if (recordBranchAssignments) {
recordWrapperBranchNestedWrapperAssignment(
objectName,
unknownNestedWrapperObjectValue,
recordBranchAssignmentScope,
);
}
}
function copyNestedWrapperObjectMethods(
targetName,
sourceName,
scope = currentNestedWrapperFunctionScope(),
recordBranchAssignments = false,
recordBranchAssignmentScope = scope,
) {
const copiedMethods = new Map();
const sourcePrefix = `${sourceName}.`;
for (const sourceScope of nestedWrapperFunctionScopes) {
for (const [name, value] of sourceScope) {
if (!name.startsWith(sourcePrefix)) {
continue;
}
const key = `${targetName}.${name.slice(sourcePrefix.length)}`;
const copiedValue = cloneWrapperFunctionValue(value);
scope.set(key, copiedValue);
copiedMethods.set(key, copiedValue);
if (recordBranchAssignments) {
recordWrapperBranchNestedWrapperAssignment(
key,
copiedValue,
recordBranchAssignmentScope,
);
}
}
}
return copiedMethods;
}
function isKnownNestedWrapperObjectSource(sourceName) {
const source = resolveNestedWrapperBindingValue(sourceName);
return source.found && source.value === knownObjectLiteralNestedWrapperValue;
}
function resolveNestedWrapperValue(name) {
const nestedWrapper = resolveNestedWrapperFunction(name);
return nestedWrapper === undefined ? resolveWrapperFunction(name) : nestedWrapper;
}
function resolveNestedWrapperBindingValue(name) {
for (let index = nestedWrapperFunctionScopes.length - 1; index >= 0; index--) {
const scope = nestedWrapperFunctionScopes[index];
if (scope.has(name)) {
return { found: true, value: scope.get(name) };
}
}
for (let index = wrapperFunctionScopes.length - 1; index >= 0; index--) {
const scope = wrapperFunctionScopes[index];
if (scope.has(name)) {
return { found: true, value: scope.get(name) };
}
}
return { found: false, value: null };
}
function resolveNestedWrapperExpression(expression) {
const unwrapped = unwrapExpression(expression);
if (ts.isIdentifier(unwrapped)) {
return resolveNestedWrapperValue(unwrapped.text);
}
const name = callExpressionName(unwrapped);
return name ? resolveNestedWrapperValue(name) : null;
}
function registerNestedWrapperObjectMethods(
objectName,
initializer,
scope = currentNestedWrapperFunctionScope(),
recordBranchAssignments = false,
recordBranchAssignmentScope = scope,
) {
const registeredMethods = new Map();
const objectLiteral = unwrapExpression(initializer);
if (ts.isIdentifier(objectLiteral)) {
return copyNestedWrapperObjectMethods(
objectName,
objectLiteral.text,
scope,
recordBranchAssignments,
recordBranchAssignmentScope,
);
}
const propertyAccessSource = callExpressionName(objectLiteral);
if (propertyAccessSource) {
return copyNestedWrapperObjectMethods(
objectName,
propertyAccessSource,
scope,
recordBranchAssignments,
recordBranchAssignmentScope,
);
}
if (!ts.isObjectLiteralExpression(objectLiteral)) {
return registeredMethods;
}
for (const property of objectLiteral.properties) {
if (ts.isSpreadAssignment(property)) {
const spreadExpression = unwrapExpression(property.expression);
const spreadSource = ts.isIdentifier(spreadExpression)
? spreadExpression.text
: callExpressionName(spreadExpression);
if (spreadSource) {
const sourceIsKnownObject = isKnownNestedWrapperObjectSource(spreadSource);
if (!sourceIsKnownObject) {
markNestedWrapperObjectUnknown(
objectName,
scope,
recordBranchAssignments,
recordBranchAssignmentScope,
);
}
for (const [key, value] of copyNestedWrapperObjectMethods(
objectName,
spreadSource,
scope,
recordBranchAssignments,
recordBranchAssignmentScope,
)) {
registeredMethods.set(key, value);
}
continue;
}
markNestedWrapperObjectUnknown(
objectName,
scope,
recordBranchAssignments,
recordBranchAssignmentScope,
);
continue;
}
if (ts.isMethodDeclaration(property)) {
const name = propertyNameText(property.name);
if (name) {
const key = `${objectName}.${name}`;
clearNestedWrapperObjectMethods(scope, key);
const value = nestedWrapperRecordForNode(property);
scope.set(key, value);
registeredMethods.set(key, value);
if (recordBranchAssignments) {
recordWrapperBranchNestedWrapperAssignment(key, value, recordBranchAssignmentScope);
}
}
continue;
}
if (
ts.isPropertyAssignment(property) &&
(ts.isFunctionExpression(unwrapExpression(property.initializer)) ||
ts.isArrowFunction(unwrapExpression(property.initializer)))
) {
const name = propertyNameText(property.name);
if (name) {
const key = `${objectName}.${name}`;
clearNestedWrapperObjectMethods(scope, key);
const value = nestedWrapperRecordForNode(unwrapExpression(property.initializer));
scope.set(key, value);
registeredMethods.set(key, value);
if (recordBranchAssignments) {
recordWrapperBranchNestedWrapperAssignment(key, value, recordBranchAssignmentScope);
}
}
continue;
}
if (
ts.isPropertyAssignment(property) &&
ts.isIdentifier(unwrapExpression(property.initializer))
) {
const name = propertyNameText(property.name);
if (!name) {
continue;
}
if (isKnownUndefinedExpression(property.initializer)) {
const key = `${objectName}.${name}`;
clearNestedWrapperObjectMethods(scope, key);
scope.set(key, explicitUndefinedNestedWrapperValue);
registeredMethods.set(key, explicitUndefinedNestedWrapperValue);
if (recordBranchAssignments) {
recordWrapperBranchNestedWrapperAssignment(
key,
explicitUndefinedNestedWrapperValue,
recordBranchAssignmentScope,
);
}
continue;
}
const sourceName = unwrapExpression(property.initializer).text;
const wrapper = resolveNestedWrapperValue(sourceName);
if (wrapper) {
const key = `${objectName}.${name}`;
clearNestedWrapperObjectMethods(scope, key);
const value =
wrapperRecords(wrapper).length > 0 ? cloneWrapperFunctionValue(wrapper) : null;
scope.set(key, value);
registeredMethods.set(key, value);
if (recordBranchAssignments) {
recordWrapperBranchNestedWrapperAssignment(key, value, recordBranchAssignmentScope);
}
if (wrapperRecords(wrapper).length === 0) {
const copiedMethods = copyNestedWrapperObjectMethods(
key,
sourceName,
scope,
recordBranchAssignments,
recordBranchAssignmentScope,
);
for (const [copiedKey, copiedValue] of copiedMethods) {
registeredMethods.set(copiedKey, copiedValue);
}
}
} else {
const key = `${objectName}.${name}`;
clearNestedWrapperObjectMethods(scope, key);
const copiedMethods = copyNestedWrapperObjectMethods(
key,
sourceName,
scope,
recordBranchAssignments,
recordBranchAssignmentScope,
);
if (copiedMethods.size > 0) {
for (const [copiedKey, value] of copiedMethods) {
registeredMethods.set(copiedKey, value);
}
} else {
scope.set(key, null);
registeredMethods.set(key, null);
if (recordBranchAssignments) {
recordWrapperBranchNestedWrapperAssignment(key, null, recordBranchAssignmentScope);
}
}
}
continue;
}
if (ts.isShorthandPropertyAssignment(property)) {
const wrapper = resolveNestedWrapperValue(property.name.text);
const key = `${objectName}.${property.name.text}`;
clearNestedWrapperObjectMethods(scope, key);
if (wrapper) {
const value =
wrapperRecords(wrapper).length > 0 ? cloneWrapperFunctionValue(wrapper) : null;
scope.set(key, value);
registeredMethods.set(key, value);
if (recordBranchAssignments) {
recordWrapperBranchNestedWrapperAssignment(key, value, recordBranchAssignmentScope);
}
if (wrapperRecords(wrapper).length === 0) {
const copiedMethods = copyNestedWrapperObjectMethods(
key,
property.name.text,
scope,
recordBranchAssignments,
recordBranchAssignmentScope,
);
for (const [copiedKey, copiedValue] of copiedMethods) {
registeredMethods.set(copiedKey, copiedValue);
}
}
} else {
const copiedMethods = copyNestedWrapperObjectMethods(
key,
property.name.text,
scope,
recordBranchAssignments,
recordBranchAssignmentScope,
);
if (copiedMethods.size > 0) {
for (const [copiedKey, value] of copiedMethods) {
registeredMethods.set(copiedKey, value);
}
} else {
scope.set(key, null);
registeredMethods.set(key, null);
if (recordBranchAssignments) {
recordWrapperBranchNestedWrapperAssignment(key, null, recordBranchAssignmentScope);
}
}
}
continue;
}
if (ts.isPropertyAssignment(property)) {
const name = propertyNameText(property.name);
if (name) {
const key = `${objectName}.${name}`;
clearNestedWrapperObjectMethods(scope, key);
const propertyInitializer = unwrapExpression(property.initializer);
if (ts.isObjectLiteralExpression(propertyInitializer)) {
registerNestedWrapperObjectMethods(
key,
propertyInitializer,
scope,
recordBranchAssignments,
recordBranchAssignmentScope,
);
}
const wrapper = isKnownUndefinedExpression(property.initializer)
? explicitUndefinedNestedWrapperValue
: resolveNestedWrapperExpression(property.initializer);
const value = wrapper ? cloneWrapperFunctionValue(wrapper) : null;
scope.set(key, value);
registeredMethods.set(key, value);
if (recordBranchAssignments) {
recordWrapperBranchNestedWrapperAssignment(key, value, recordBranchAssignmentScope);
}
}
}
}
return registeredMethods;
}
function registerNestedWrapperObjectBinding(
bindingPattern,
sourceName,
propertyPath = [],
scope = currentNestedWrapperFunctionScope(),
) {
for (const element of bindingPattern.elements) {
const propertyName = element.propertyName
? propertyNameText(element.propertyName)
: ts.isIdentifier(element.name)
? element.name.text
: null;
if (!propertyName) {
continue;
}
const nextPath = [...propertyPath, propertyName];
if (ts.isIdentifier(element.name)) {
const sourcePath = `${sourceName}.${nextPath.join(".")}`;
const source = resolveNestedWrapperBindingValue(sourcePath);
const wrapper = source.found
? source.value === explicitUndefinedNestedWrapperValue
? nestedWrapperObjectBindingDefaultValue(element)
: source.value
: nestedWrapperObjectBindingMissingValue(sourceName, nextPath, element);
clearNestedWrapperObjectMethods(scope, element.name.text);
scope.set(element.name.text, cloneWrapperFunctionValue(wrapper));
copyNestedWrapperObjectMethods(element.name.text, sourcePath, scope);
continue;
}
if (ts.isObjectBindingPattern(element.name)) {
registerNestedWrapperObjectBinding(element.name, sourceName, nextPath, scope);
}
}
}
function nestedWrapperObjectBindingMissingValue(sourceName, propertyPath, element) {
for (let index = propertyPath.length - 1; index >= 0; index--) {
const parentPath = propertyPath.slice(0, index);
const parentName =
parentPath.length === 0 ? sourceName : `${sourceName}.${parentPath.join(".")}`;
const parent = resolveNestedWrapperBindingValue(parentName);
if (parent.found && parent.value === unknownNestedWrapperObjectValue) {
return null;
}
}
return nestedWrapperObjectBindingDefaultValue(element);
}
function nestedWrapperObjectBindingDefaultValue(element) {
if (!element.initializer) {
return null;
}
const initializer = unwrapExpression(element.initializer);
return nestedWrapperValueFromExpression(initializer);
}
function nestedWrapperValueFromExpression(expression) {
const initializer = unwrapExpression(expression);
if (ts.isFunctionExpression(initializer) || ts.isArrowFunction(initializer)) {
return nestedWrapperRecordForNode(initializer);
}
return resolveNestedWrapperExpression(initializer);
}
function nestedWrapperObjectLiteralSpreadPropertyState(objectName, propertyName) {
const source = resolveNestedWrapperBindingValue(`${objectName}.${propertyName}`);
if (!source.found) {
const objectSource = resolveNestedWrapperBindingValue(objectName);
return objectSource.found && objectSource.value === knownObjectLiteralNestedWrapperValue
? { kind: "missing" }
: { kind: "unknown" };
}
if (source.value === explicitUndefinedNestedWrapperValue) {
return { kind: "undefined" };
}
if (source.value === unknownNestedWrapperObjectValue) {
return { kind: "unknown" };
}
return { kind: "value", value: source.value };
}
function nestedWrapperValueFromObjectLiteralPropertyState(propertyState, element) {
if (propertyState.kind === "unknown") {
return null;
}
if (propertyState.kind === "value") {
return propertyState.value;
}
if (propertyState.kind === "initializer") {
return nestedWrapperValueFromExpression(propertyState.initializer);
}
return nestedWrapperObjectBindingDefaultValue(element);
}
function registerNestedWrapperObjectLiteralBinding(
bindingPattern,
objectLiteral,
scope = currentNestedWrapperFunctionScope(),
) {
for (const element of bindingPattern.elements) {
const propertyName = element.propertyName
? propertyNameText(element.propertyName)
: ts.isIdentifier(element.name)
? element.name.text
: null;
if (!propertyName) {
continue;
}
const propertyState = objectLiteralPropertyInitializerState(
objectLiteral,
propertyName,
nestedWrapperObjectLiteralSpreadPropertyState,
);
if (ts.isIdentifier(element.name)) {
const wrapper = nestedWrapperValueFromObjectLiteralPropertyState(propertyState, element);
scope.set(element.name.text, cloneWrapperFunctionValue(wrapper));
continue;
}
if (
ts.isObjectBindingPattern(element.name) &&
propertyState.kind === "initializer" &&
ts.isObjectLiteralExpression(unwrapExpression(propertyState.initializer))
) {
registerNestedWrapperObjectLiteralBinding(
element.name,
unwrapExpression(propertyState.initializer),
scope,
);
}
}
}
function registerNestedWrapperObjectBindingInitializer(
bindingPattern,
initializer,
scope = currentNestedWrapperFunctionScope(),
) {
const unwrapped = unwrapExpression(initializer);
if (ts.isIdentifier(unwrapped)) {
registerNestedWrapperObjectBinding(bindingPattern, unwrapped.text, [], scope);
return;
}
if (ts.isObjectLiteralExpression(unwrapped)) {
registerNestedWrapperObjectLiteralBinding(bindingPattern, unwrapped, scope);
return;
}
const propertyAccess = rootedPropertyAccessPath(unwrapped);
if (propertyAccess?.properties.length > 0) {
registerNestedWrapperObjectBinding(
bindingPattern,
objectPropertyKey(propertyAccess.rootName, propertyAccess.properties.join(".")),
[],
scope,
);
}
}
function resolveRecordFsModuleProperty(record, pathParts) {
const fullPath = pathParts.join(".");
const prefixes = pathParts.map((_, index) => pathParts.slice(0, index + 1).join("."));
if (record.moduleProperties.get(fullPath) === true) {
return true;
}
for (const prefix of prefixes) {
if (record.moduleProperties.get(prefix) === false) {
return false;
}
}
return false;
}
function collectClosedOverPathPropertyUses(
record,
activeClosedOverNodes = new Set(),
argumentsList = null,
) {
if (activeClosedOverNodes.has(record.node)) {
return new Map();
}
activeClosedOverNodes.add(record.node);
const closedOverUses = new Map();
const localBindingScopes = [new Set()];
const localFsWriteAliasScopes = [new Map(record.aliases)];
const localFsModuleBindingScopes = [new Map(record.moduleBindings)];
const localRequireAliasScopes = [new Map(record.requireAliases)];
const localNestedFunctionScopes = [new Map()];
const localNestedFunctionScopeParents = new Map([[localNestedFunctionScopes[0], null]]);
const localNestedBranchEffectScopes = [];
function currentLocalBindingScope() {
return localBindingScopes[localBindingScopes.length - 1];
}
function currentLocalFsWriteAliasScope() {
return localFsWriteAliasScopes[localFsWriteAliasScopes.length - 1];
}
function currentLocalFsModuleBindingScope() {
return localFsModuleBindingScopes[localFsModuleBindingScopes.length - 1];
}
function currentLocalRequireAliasScope() {
return localRequireAliasScopes[localRequireAliasScopes.length - 1];
}
function currentLocalNestedFunctionScope() {
return localNestedFunctionScopes[localNestedFunctionScopes.length - 1];
}
function pushLocalClosedOverScope() {
localBindingScopes.push(new Set());
localFsWriteAliasScopes.push(new Map());
localFsModuleBindingScopes.push(new Map());
localRequireAliasScopes.push(new Map());
const localNestedFunctionScope = new Map();
localNestedFunctionScopeParents.set(
localNestedFunctionScope,
currentLocalNestedFunctionScope(),
);
localNestedFunctionScopes.push(localNestedFunctionScope);
}
function popLocalClosedOverScope() {
localNestedFunctionScopes.pop();
localRequireAliasScopes.pop();
localFsModuleBindingScopes.pop();
localFsWriteAliasScopes.pop();
localBindingScopes.pop();
}
function currentLocalNestedBranchEffectScope() {
return localNestedBranchEffectScopes[localNestedBranchEffectScopes.length - 1] ?? null;
}
function createLocalNestedBranchEffects() {
return {
fsIdentifierAssignments: new Map(),
nestedAssignments: new Map(),
nestedAssignmentScopes: new Map(),
};
}
function recordLocalNestedBranchFsIdentifierAssignment(
name,
moduleValue,
writeAlias,
requireAlias,
moduleScope,
writeAliasScope,
requireAliasScope,
) {
const effects = currentLocalNestedBranchEffectScope();
if (!effects) {
return;
}
effects.fsIdentifierAssignments.set(name, {
moduleScope,
moduleValue,
name,
requireAlias,
requireAliasScope,
writeAlias,
writeAliasScope,
});
}
function recordLocalNestedBranchAssignment(name, value, targetScope) {
const effects = currentLocalNestedBranchEffectScope();
if (!effects) {
return;
}
clearLocalNestedBranchObjectAssignments(effects, name);
effects.nestedAssignments.set(name, cloneWrapperFunctionValue(value));
effects.nestedAssignmentScopes.set(name, targetScope);
}
function clearLocalNestedBranchObjectAssignments(effects, objectName) {
const prefix = `${objectName}.`;
for (const name of effects.nestedAssignments.keys()) {
if (name.startsWith(prefix)) {
effects.nestedAssignments.delete(name);
effects.nestedAssignmentScopes.delete(name);
}
}
}
function applyMergedLocalNestedBranchAssignment(name, value, targetScope) {
clearLocalNestedObjectMethods(name);
targetScope.set(name, cloneWrapperFunctionValue(value));
recordLocalNestedBranchAssignment(name, value, targetScope);
}
function applyMergedLocalNestedFsIdentifierAssignment(assignment, previous = null) {
const moduleValue = assignment.moduleValue === true || previous?.moduleValue === true;
const writeAlias = previous?.writeAlias ?? assignment.writeAlias;
const requireAlias = assignment.requireAlias === true || previous?.requireAlias === true;
if (assignment.moduleScope && localFsModuleBindingScopes.includes(assignment.moduleScope)) {
assignment.moduleScope.set(assignment.name, moduleValue);
}
if (
assignment.writeAliasScope &&
localFsWriteAliasScopes.includes(assignment.writeAliasScope)
) {
assignment.writeAliasScope.set(assignment.name, writeAlias);
}
if (
assignment.requireAliasScope &&
localRequireAliasScopes.includes(assignment.requireAliasScope)
) {
assignment.requireAliasScope.set(assignment.name, requireAlias);
}
}
function mergeExhaustiveLocalNestedBranchEffects(thenEffects, elseEffects) {
const fsIdentifierAssignmentNames = new Set([
...thenEffects.fsIdentifierAssignments.keys(),
...elseEffects.fsIdentifierAssignments.keys(),
]);
for (const name of fsIdentifierAssignmentNames) {
const thenAssignment = thenEffects.fsIdentifierAssignments.get(name);
const elseAssignment = elseEffects.fsIdentifierAssignments.get(name);
const assignment = thenAssignment ?? elseAssignment;
if (!assignment) {
continue;
}
applyMergedLocalNestedFsIdentifierAssignment(
assignment,
thenAssignment && elseAssignment ? elseAssignment : null,
);
}
const names = new Set([
...thenEffects.nestedAssignments.keys(),
...elseEffects.nestedAssignments.keys(),
]);
for (const name of [...names].toSorted(wrapperAssignmentMergeOrder)) {
const thenScope = thenEffects.nestedAssignmentScopes.get(name);
const elseScope = elseEffects.nestedAssignmentScopes.get(name);
const targetScope = thenScope ?? elseScope;
if (
targetScope === undefined ||
(thenScope !== undefined && elseScope !== undefined && thenScope !== elseScope)
) {
continue;
}
const previousValue = targetScope.get(name);
applyMergedLocalNestedBranchAssignment(
name,
mergeWrapperAssignmentValues(
thenEffects.nestedAssignments.has(name)
? thenEffects.nestedAssignments.get(name)
: previousValue,
elseEffects.nestedAssignments.has(name)
? elseEffects.nestedAssignments.get(name)
: previousValue,
),
targetScope,
);
}
}
function mergeOptionalLocalNestedBranchEffects(effects) {
for (const assignment of effects.fsIdentifierAssignments.values()) {
applyMergedLocalNestedFsIdentifierAssignment(assignment, {
moduleValue: assignment.moduleScope?.get(assignment.name) === true,
requireAlias: assignment.requireAliasScope?.get(assignment.name) === true,
writeAlias: assignment.writeAliasScope?.get(assignment.name) ?? null,
});
}
for (const [name, value] of effects.nestedAssignments) {
const targetScope = effects.nestedAssignmentScopes.get(name);
if (!targetScope) {
continue;
}
const previousValue = targetScope.get(name);
applyMergedLocalNestedBranchAssignment(
name,
mergeWrapperAssignmentValues(previousValue, value),
targetScope,
);
}
}
function localBindingWriteScope(name, scopes) {
for (let index = localBindingScopes.length - 1; index >= 0; index--) {
if (localBindingScopes[index].has(name)) {
return scopes[index];
}
}
return scopes[scopes.length - 1];
}
function localNestedObjectMethodWriteScope(objectName, propertyName) {
const key = objectPropertyKey(objectName, propertyName);
for (let index = localNestedFunctionScopes.length - 1; index >= 0; index--) {
const scope = localNestedFunctionScopes[index];
if (scope.has(key) || localBindingScopes[index].has(objectName)) {
return scope;
}
}
if (record.lexicalScope?.has(key) || record.lexicalScope?.has(objectName)) {
return record.lexicalScope;
}
return currentLocalNestedFunctionScope();
}
function visibleLocalFsWriteAliases() {
const aliases = new Map();
for (const scope of localFsWriteAliasScopes) {
for (const [name, value] of scope) {
aliases.set(name, value);
}
}
return aliases;
}
function visibleLocalFsModuleBindings() {
const bindings = new Map();
for (const scope of localFsModuleBindingScopes) {
for (const [name, value] of scope) {
bindings.set(name, value);
}
}
return bindings;
}
function visibleLocalRequireAliases() {
const aliases = new Map();
for (const scope of localRequireAliasScopes) {
for (const [name, value] of scope) {
aliases.set(name, value);
}
}
return aliases;
}
function registerLocalNestedFunction(
name,
nestedNode,
scope = currentLocalNestedFunctionScope(),
) {
registerLocalNestedFunctionRecord(name, localNestedRecordForNode(nestedNode, scope), scope);
}
function localNestedRecordForNode(
nestedNode,
scope = currentLocalNestedFunctionScope(),
localScope = scope,
) {
return {
aliases: visibleLocalFsWriteAliases(),
closesOverCurrentWrapper: true,
createRequireShadows: new Set(record.createRequireShadows),
lexicalScope: record.lexicalScope,
localScope,
moduleBindings: visibleLocalFsModuleBindings(),
moduleProperties: new Map(record.moduleProperties),
node: nestedNode,
requireAliases: visibleLocalRequireAliases(),
requireAliasSourceScopes: new Map(record.requireAliasSourceScopes),
};
}
function registerLocalNestedFunctionRecord(
name,
nestedRecord,
scope = currentLocalNestedFunctionScope(),
) {
scope.set(name, nestedRecord);
}
function clearLocalNestedObjectMethods(objectName) {
const prefix = `${objectName}.`;
for (const scope of localNestedFunctionScopes) {
for (const name of scope.keys()) {
if (name.startsWith(prefix)) {
scope.set(name, null);
}
}
}
}
function markLocalNestedObjectUnknown(
objectName,
scope = currentLocalNestedFunctionScope(),
recordBranchAssignments = false,
recordBranchAssignmentScope = scope,
) {
clearLocalNestedObjectMethods(objectName);
registerLocalNestedFunctionRecord(objectName, unknownNestedWrapperObjectValue, scope);
if (recordBranchAssignments) {
recordLocalNestedBranchAssignment(
objectName,
unknownNestedWrapperObjectValue,
recordBranchAssignmentScope,
);
}
}
function copyLocalNestedObjectMethods(
targetName,
sourceName,
scope = currentLocalNestedFunctionScope(),
recordBranchAssignments = false,
recordBranchAssignmentScope = scope,
) {
const sourceScopes =
!isLocalBinding(sourceName) && record.lexicalScope
? [record.lexicalScope, ...localNestedFunctionScopes]
: localNestedFunctionScopes;
const copiedMethods = new Map();
const sourcePrefix = `${sourceName}.`;
for (const sourceScope of sourceScopes) {
for (const [name, value] of sourceScope) {
if (!name.startsWith(sourcePrefix)) {
continue;
}
const key = `${targetName}.${name.slice(sourcePrefix.length)}`;
const copiedValue = cloneWrapperFunctionValue(value);
registerLocalNestedFunctionRecord(key, copiedValue, scope);
copiedMethods.set(key, copiedValue);
if (recordBranchAssignments) {
recordLocalNestedBranchAssignment(key, copiedValue, recordBranchAssignmentScope);
}
}
}
return copiedMethods;
}
function isKnownLocalNestedObjectSource(sourceName) {
const source = resolveLocalNestedFunctionBindingValue(sourceName);
return source.found && source.value === knownObjectLiteralNestedWrapperValue;
}
function registerLocalNestedObjectMethods(
objectName,
initializer,
scope = currentLocalNestedFunctionScope(),
localScope = scope,
recordBranchAssignments = false,
recordBranchAssignmentScope = scope,
) {
const objectLiteral = unwrapExpression(initializer);
if (ts.isIdentifier(objectLiteral)) {
copyLocalNestedObjectMethods(
objectName,
objectLiteral.text,
scope,
recordBranchAssignments,
recordBranchAssignmentScope,
);
return;
}
const propertyAccessSource = callExpressionName(objectLiteral);
if (propertyAccessSource) {
copyLocalNestedObjectMethods(
objectName,
propertyAccessSource,
scope,
recordBranchAssignments,
recordBranchAssignmentScope,
);
return;
}
if (!ts.isObjectLiteralExpression(objectLiteral)) {
return;
}
for (const property of objectLiteral.properties) {
if (ts.isSpreadAssignment(property)) {
const spreadExpression = unwrapExpression(property.expression);
if (ts.isIdentifier(spreadExpression)) {
const sourceIsKnownObject = isKnownLocalNestedObjectSource(spreadExpression.text);
if (!sourceIsKnownObject) {
markLocalNestedObjectUnknown(
objectName,
scope,
recordBranchAssignments,
recordBranchAssignmentScope,
);
}
copyLocalNestedObjectMethods(
objectName,
spreadExpression.text,
scope,
recordBranchAssignments,
recordBranchAssignmentScope,
);
continue;
}
markLocalNestedObjectUnknown(
objectName,
scope,
recordBranchAssignments,
recordBranchAssignmentScope,
);
continue;
}
if (ts.isMethodDeclaration(property)) {
const name = propertyNameText(property.name);
if (name) {
const key = `${objectName}.${name}`;
clearLocalNestedObjectMethods(key);
const value = localNestedRecordForNode(property, scope, localScope);
registerLocalNestedFunctionRecord(key, value, scope);
if (recordBranchAssignments) {
recordLocalNestedBranchAssignment(key, value, recordBranchAssignmentScope);
}
}
continue;
}
if (
ts.isPropertyAssignment(property) &&
(ts.isFunctionExpression(unwrapExpression(property.initializer)) ||
ts.isArrowFunction(unwrapExpression(property.initializer)))
) {
const name = propertyNameText(property.name);
if (name) {
const key = `${objectName}.${name}`;
clearLocalNestedObjectMethods(key);
const value = localNestedRecordForNode(
unwrapExpression(property.initializer),
scope,
localScope,
);
registerLocalNestedFunctionRecord(key, value, scope);
if (recordBranchAssignments) {
recordLocalNestedBranchAssignment(key, value, recordBranchAssignmentScope);
}
}
continue;
}
if (
ts.isPropertyAssignment(property) &&
ts.isIdentifier(unwrapExpression(property.initializer))
) {
const name = propertyNameText(property.name);
if (!name) {
continue;
}
if (isKnownUndefinedExpression(property.initializer)) {
const key = `${objectName}.${name}`;
clearLocalNestedObjectMethods(key);
registerLocalNestedFunctionRecord(key, explicitUndefinedNestedWrapperValue, scope);
if (recordBranchAssignments) {
recordLocalNestedBranchAssignment(
key,
explicitUndefinedNestedWrapperValue,
recordBranchAssignmentScope,
);
}
continue;
}
const sourceName = unwrapExpression(property.initializer).text;
const nested = resolveLocalNestedFunction(sourceName);
const key = `${objectName}.${name}`;
clearLocalNestedObjectMethods(key);
if (wrapperRecords(nested).length > 0) {
registerLocalNestedFunctionRecord(key, nested, scope);
if (recordBranchAssignments) {
recordLocalNestedBranchAssignment(key, nested, recordBranchAssignmentScope);
}
} else {
if (isNestedWrapperObjectMarker(nested)) {
registerLocalNestedFunctionRecord(key, nested, scope);
if (recordBranchAssignments) {
recordLocalNestedBranchAssignment(key, nested, recordBranchAssignmentScope);
}
}
const copiedMethods = copyLocalNestedObjectMethods(
key,
sourceName,
scope,
recordBranchAssignments,
recordBranchAssignmentScope,
);
if (copiedMethods.size === 0 && !isNestedWrapperObjectMarker(nested)) {
registerLocalNestedFunctionRecord(key, null, scope);
if (recordBranchAssignments) {
recordLocalNestedBranchAssignment(key, null, recordBranchAssignmentScope);
}
}
}
continue;
}
if (ts.isShorthandPropertyAssignment(property)) {
const nested = resolveLocalNestedFunction(property.name.text);
const key = `${objectName}.${property.name.text}`;
clearLocalNestedObjectMethods(key);
if (wrapperRecords(nested).length > 0) {
registerLocalNestedFunctionRecord(key, nested, scope);
if (recordBranchAssignments) {
recordLocalNestedBranchAssignment(key, nested, recordBranchAssignmentScope);
}
} else {
if (isNestedWrapperObjectMarker(nested)) {
registerLocalNestedFunctionRecord(key, nested, scope);
if (recordBranchAssignments) {
recordLocalNestedBranchAssignment(key, nested, recordBranchAssignmentScope);
}
}
const copiedMethods = copyLocalNestedObjectMethods(
key,
property.name.text,
scope,
recordBranchAssignments,
recordBranchAssignmentScope,
);
if (copiedMethods.size === 0 && !isNestedWrapperObjectMarker(nested)) {
registerLocalNestedFunctionRecord(key, null, scope);
if (recordBranchAssignments) {
recordLocalNestedBranchAssignment(key, null, recordBranchAssignmentScope);
}
}
}
continue;
}
if (ts.isPropertyAssignment(property)) {
const name = propertyNameText(property.name);
if (name) {
const key = `${objectName}.${name}`;
clearLocalNestedObjectMethods(key);
const propertyInitializer = unwrapExpression(property.initializer);
if (ts.isObjectLiteralExpression(propertyInitializer)) {
registerLocalNestedObjectMethods(
key,
propertyInitializer,
scope,
localScope,
recordBranchAssignments,
recordBranchAssignmentScope,
);
}
const value =
(isKnownUndefinedExpression(property.initializer)
? explicitUndefinedNestedWrapperValue
: resolveLocalNestedExpression(property.initializer)) ?? null;
registerLocalNestedFunctionRecord(key, value, scope);
if (recordBranchAssignments) {
recordLocalNestedBranchAssignment(key, value, recordBranchAssignmentScope);
}
}
}
}
}
function registerLocalNestedObjectBinding(
bindingPattern,
sourceName,
propertyPath = [],
scope = currentLocalNestedFunctionScope(),
localScope = scope,
) {
for (const element of bindingPattern.elements) {
const propertyName = element.propertyName
? propertyNameText(element.propertyName)
: ts.isIdentifier(element.name)
? element.name.text
: null;
if (!propertyName) {
continue;
}
const nextPath = [...propertyPath, propertyName];
if (ts.isIdentifier(element.name)) {
const sourcePath = `${sourceName}.${nextPath.join(".")}`;
const source = resolveLocalNestedFunctionBindingValue(sourcePath);
clearLocalNestedObjectMethods(element.name.text);
registerLocalNestedFunctionRecord(
element.name.text,
cloneWrapperFunctionValue(
source.found
? source.value === explicitUndefinedNestedWrapperValue
? localNestedObjectBindingDefaultValue(element, scope, localScope)
: source.value
: localNestedObjectBindingMissingValue(
sourceName,
nextPath,
element,
scope,
localScope,
),
),
scope,
);
copyLocalNestedObjectMethods(element.name.text, sourcePath, scope);
continue;
}
if (ts.isObjectBindingPattern(element.name)) {
registerLocalNestedObjectBinding(element.name, sourceName, nextPath, scope, localScope);
}
}
}
function localNestedObjectBindingMissingValue(
sourceName,
propertyPath,
element,
scope = currentLocalNestedFunctionScope(),
localScope = scope,
) {
for (let index = propertyPath.length - 1; index >= 0; index--) {
const parentPath = propertyPath.slice(0, index);
const parentName =
parentPath.length === 0 ? sourceName : `${sourceName}.${parentPath.join(".")}`;
const parent = resolveLocalNestedFunctionBindingValue(parentName);
if (parent.found && parent.value === unknownNestedWrapperObjectValue) {
return null;
}
}
return localNestedObjectBindingDefaultValue(element, scope, localScope);
}
function resolveLocalNestedFunctionBindingValue(name) {
for (let index = localNestedFunctionScopes.length - 1; index >= 0; index--) {
const scope = localNestedFunctionScopes[index];
if (scope.has(name)) {
return { found: true, value: scope.get(name) };
}
}
const rootName = name.split(".")[0] ?? name;
if (!isLocalBinding(rootName) && record.lexicalScope?.has(name)) {
return { found: true, value: record.lexicalScope.get(name) };
}
return { found: false, value: null };
}
function localNestedObjectBindingDefaultValue(
element,
scope = currentLocalNestedFunctionScope(),
localScope = scope,
) {
if (!element.initializer) {
return null;
}
const initializer = unwrapExpression(element.initializer);
return localNestedValueFromExpression(initializer, scope, localScope);
}
function localNestedValueFromExpression(
expression,
scope = currentLocalNestedFunctionScope(),
localScope = scope,
) {
const initializer = unwrapExpression(expression);
if (ts.isFunctionExpression(initializer) || ts.isArrowFunction(initializer)) {
return localNestedRecordForNode(initializer, scope, localScope);
}
return resolveLocalNestedExpression(initializer);
}
function localNestedObjectLiteralSpreadPropertyState(objectName, propertyName) {
const source = resolveLocalNestedFunctionBindingValue(`${objectName}.${propertyName}`);
if (!source.found) {
const objectSource = resolveLocalNestedFunctionBindingValue(objectName);
return objectSource.found && objectSource.value === knownObjectLiteralNestedWrapperValue
? { kind: "missing" }
: { kind: "unknown" };
}
if (source.value === explicitUndefinedNestedWrapperValue) {
return { kind: "undefined" };
}
if (source.value === unknownNestedWrapperObjectValue) {
return { kind: "unknown" };
}
return { kind: "value", value: source.value };
}
function localNestedValueFromObjectLiteralPropertyState(
propertyState,
element,
scope = currentLocalNestedFunctionScope(),
localScope = scope,
) {
if (propertyState.kind === "unknown") {
return null;
}
if (propertyState.kind === "value") {
return propertyState.value;
}
if (propertyState.kind === "initializer") {
return localNestedValueFromExpression(propertyState.initializer, scope, localScope);
}
return localNestedObjectBindingDefaultValue(element, scope, localScope);
}
function registerLocalNestedObjectLiteralBinding(
bindingPattern,
objectLiteral,
scope = currentLocalNestedFunctionScope(),
localScope = scope,
) {
for (const element of bindingPattern.elements) {
const propertyName = element.propertyName
? propertyNameText(element.propertyName)
: ts.isIdentifier(element.name)
? element.name.text
: null;
if (!propertyName) {
continue;
}
const propertyState = objectLiteralPropertyInitializerState(
objectLiteral,
propertyName,
localNestedObjectLiteralSpreadPropertyState,
);
if (ts.isIdentifier(element.name)) {
const wrapper = localNestedValueFromObjectLiteralPropertyState(
propertyState,
element,
scope,
localScope,
);
registerLocalNestedFunctionRecord(
element.name.text,
cloneWrapperFunctionValue(wrapper),
scope,
);
continue;
}
if (
ts.isObjectBindingPattern(element.name) &&
propertyState.kind === "initializer" &&
ts.isObjectLiteralExpression(unwrapExpression(propertyState.initializer))
) {
registerLocalNestedObjectLiteralBinding(
element.name,
unwrapExpression(propertyState.initializer),
scope,
localScope,
);
}
}
}
function refreshCurrentLocalNestedFunctionAliases() {
const currentScope = currentLocalNestedFunctionScope();
const aliases = visibleLocalFsWriteAliases();
const moduleBindings = visibleLocalFsModuleBindings();
const requireAliases = visibleLocalRequireAliases();
function isLocalNestedScopeDescendant(scope, ancestor) {
let current = scope;
while (current) {
if (current === ancestor) {
return true;
}
current = localNestedFunctionScopeParents.get(current) ?? null;
}
return false;
}
function refreshLocalNestedFunctionRecord(localRecord) {
if (!isLocalNestedScopeDescendant(localRecord.localScope, currentScope)) {
return;
}
if (localRecord.localScope === currentScope) {
localRecord.aliases = aliases;
localRecord.moduleBindings = moduleBindings;
localRecord.requireAliases = requireAliases;
return;
}
localRecord.aliases = new Map([...aliases, ...localRecord.aliases]);
localRecord.moduleBindings = new Map([...moduleBindings, ...localRecord.moduleBindings]);
localRecord.requireAliases = new Map([...requireAliases, ...localRecord.requireAliases]);
}
function refreshLocalNestedFunctionRecords(values) {
for (const value of values) {
for (const localRecord of wrapperRecords(value)) {
refreshLocalNestedFunctionRecord(localRecord);
}
}
}
for (const scope of localNestedFunctionScopes) {
refreshLocalNestedFunctionRecords(scope.values());
}
for (const branchEffects of localNestedBranchEffectScopes) {
refreshLocalNestedFunctionRecords(branchEffects.nestedAssignments.values());
}
if (record.lexicalScope) {
refreshLocalNestedFunctionRecords(record.lexicalScope.values());
}
}
function registerLocalNestedObjectBindingInitializer(
bindingPattern,
initializer,
scope = currentLocalNestedFunctionScope(),
localScope = scope,
) {
const unwrapped = unwrapExpression(initializer);
if (ts.isIdentifier(unwrapped)) {
registerLocalNestedObjectBinding(bindingPattern, unwrapped.text, [], scope, localScope);
return;
}
if (ts.isObjectLiteralExpression(unwrapped)) {
registerLocalNestedObjectLiteralBinding(bindingPattern, unwrapped, scope, localScope);
return;
}
const propertyAccess = rootedPropertyAccessPath(unwrapped);
if (propertyAccess?.properties.length > 0) {
registerLocalNestedObjectBinding(
bindingPattern,
objectPropertyKey(propertyAccess.rootName, propertyAccess.properties.join(".")),
[],
scope,
localScope,
);
}
}
function resolveLocalNestedFunction(name) {
const binding = resolveLocalNestedFunctionBindingValue(name);
return binding.found ? binding.value : null;
}
function resolveLocalNestedExpression(expression) {
const unwrapped = unwrapExpression(expression);
const name = ts.isIdentifier(unwrapped) ? unwrapped.text : callExpressionName(unwrapped);
if (!name) {
return null;
}
const binding = resolveLocalNestedFunctionBindingValue(name);
if (binding.found) {
return binding.value;
}
const rootName = name.split(".")[0] ?? name;
return isLocalBinding(rootName) ? null : resolveWrapperFunction(name);
}
function assignLocalNestedFunction(
name,
expression,
scope = currentLocalNestedFunctionScope(),
mergeExisting = false,
localScope = currentLocalNestedFunctionScope(),
) {
if (!mergeExisting) {
clearLocalNestedObjectMethods(name);
}
const unwrapped = unwrapExpression(expression);
const nextValue =
ts.isFunctionExpression(unwrapped) || ts.isArrowFunction(unwrapped)
? localNestedRecordForNode(unwrapped, scope, localScope)
: ts.isObjectLiteralExpression(unwrapped)
? knownObjectLiteralNestedWrapperValue
: cloneWrapperFunctionValue(resolveLocalNestedExpression(expression));
registerLocalNestedFunctionRecord(
name,
mergeExisting ? mergeWrapperAssignmentValues(scope.get(name), nextValue) : nextValue,
scope,
);
return nextValue;
}
function registerHoistedLocalNestedFunctions(statements) {
for (const statement of statements) {
if (ts.isFunctionDeclaration(statement) && statement.name) {
currentLocalBindingScope().add(statement.name.text);
currentLocalFsWriteAliasScope().set(statement.name.text, null);
currentLocalFsModuleBindingScope().set(statement.name.text, false);
currentLocalRequireAliasScope().set(statement.name.text, false);
registerLocalNestedFunction(statement.name.text, statement);
}
}
}
function localScopeStatements(current) {
if ("statements" in current) {
return current.statements;
}
if (ts.isCaseBlock(current)) {
return current.clauses.flatMap((clause) => [...clause.statements]);
}
return [];
}
function registerLocalDeclarationShadows(statements) {
for (const statement of statements) {
if (!ts.isVariableStatement(statement)) {
continue;
}
for (const declaration of statement.declarationList.declarations) {
markLocalBindings(declaration.name, localDeclarationScopes(declaration));
}
}
}
function localDeclarationScopes(declaration) {
if (!isVarVariableDeclaration(declaration)) {
return {
bindingScope: currentLocalBindingScope(),
fsModuleBindingScope: currentLocalFsModuleBindingScope(),
fsWriteAliasScope: currentLocalFsWriteAliasScope(),
nestedFunctionScope: currentLocalNestedFunctionScope(),
requireAliasScope: currentLocalRequireAliasScope(),
};
}
return {
bindingScope: localBindingScopes[0],
fsModuleBindingScope: localFsModuleBindingScopes[0],
fsWriteAliasScope: localFsWriteAliasScopes[0],
nestedFunctionScope: localNestedFunctionScopes[0],
requireAliasScope: localRequireAliasScopes[0],
};
}
function currentLocalDeclarationScopes() {
return {
bindingScope: currentLocalBindingScope(),
fsModuleBindingScope: currentLocalFsModuleBindingScope(),
fsWriteAliasScope: currentLocalFsWriteAliasScope(),
nestedFunctionScope: currentLocalNestedFunctionScope(),
requireAliasScope: currentLocalRequireAliasScope(),
};
}
function markLocalBindings(name, scopes = currentLocalDeclarationScopes()) {
for (const bindingName of bindingPatternNames(name)) {
scopes.bindingScope.add(bindingName);
scopes.fsWriteAliasScope.set(bindingName, null);
scopes.fsModuleBindingScope.set(bindingName, false);
scopes.requireAliasScope.set(bindingName, false);
clearLocalNestedObjectMethods(bindingName);
}
}
function isLocalBinding(name) {
return localBindingScopes.some((scope) => scope.has(name));
}
function resolveLocalFsWriteAlias(name) {
for (let index = localFsWriteAliasScopes.length - 1; index >= 0; index--) {
const alias = localFsWriteAliasScopes[index].get(name);
if (alias !== undefined) {
return alias ?? null;
}
}
return null;
}
function resolveLocalFsModuleBinding(name) {
for (let index = localFsModuleBindingScopes.length - 1; index >= 0; index--) {
const binding = localFsModuleBindingScopes[index].get(name);
if (binding !== undefined) {
return binding === true;
}
}
return false;
}
function resolveLocalRequireAlias(name) {
for (let index = localRequireAliasScopes.length - 1; index >= 0; index--) {
const alias = localRequireAliasScopes[index].get(name);
if (alias !== undefined) {
return alias === true;
}
}
if (name === "require") {
return true;
}
return false;
}
function resolveClosedParameterIndex(name) {
return isLocalBinding(name) ? null : resolveParameterIndex(name);
}
function resolveClosedDestructuredParameterProperty(name) {
return isLocalBinding(name) ? null : resolveDestructuredParameterProperty(name);
}
function resolveClosedParameterPropertyUse(objectName, propertyName) {
return isLocalBinding(objectName)
? null
: resolveParameterPropertyUse(objectName, propertyName);
}
function resolveClosedDestructuredParameterPropertyUses(name) {
return isLocalBinding(name) ? null : resolveDestructuredParameterPropertyUses(name);
}
function appendClosedUse(use) {
const properties = closedOverUses.get(use.index) ?? new Set();
properties.add(use.propertyName);
closedOverUses.set(use.index, properties);
}
function isClosedOverFsModuleExpression(expression) {
const receiver = unwrapExpression(expression);
if (
isFsRequireExpression(receiver, resolveLocalRequireAlias) ||
isFsDynamicImportExpression(receiver)
) {
return true;
}
if (ts.isIdentifier(receiver)) {
return resolveLocalFsModuleBinding(receiver.text);
}
const receiverPath = propertyAccessPath(receiver);
if (receiverPath && resolveRecordFsModuleProperty(record, receiverPath)) {
return true;
}
return (
ts.isPropertyAccessExpression(receiver) &&
receiver.name.text === "promises" &&
(isFsRequireExpression(receiver.expression, resolveLocalRequireAlias) ||
isFsDynamicImportExpression(receiver.expression) ||
(ts.isIdentifier(receiver.expression) &&
resolveLocalFsModuleBinding(receiver.expression.text)) ||
(propertyAccessPath(receiver.expression) &&
resolveRecordFsModuleProperty(record, propertyAccessPath(receiver.expression))))
);
}
function isClosedOverCreateRequireExpression(expression) {
const call = unwrapExpression(expression);
if (!ts.isCallExpression(call) || !ts.isIdentifier(unwrapExpression(call.expression))) {
return false;
}
const createRequireName = unwrapExpression(call.expression).text;
return (
createRequireBindings.has(createRequireName) &&
!record.createRequireShadows.has(createRequireName) &&
!isLocalBinding(createRequireName)
);
}
function isClosedOverRequireAliasExpression(expression) {
const value = unwrapExpression(expression);
return (
isClosedOverCreateRequireExpression(value) ||
(ts.isIdentifier(value) && resolveLocalRequireAlias(value.text))
);
}
function legacyClosedOverFsWriteName(expression) {
const callee = unwrapExpression(expression);
if (ts.isPropertyAccessExpression(callee)) {
const aliasedName = callExpressionName(callee);
const writeAlias = aliasedName ? resolveLocalFsWriteAlias(aliasedName) : null;
if (writeAlias) {
return writeAlias;
}
return legacyWriteCallees.has(callee.name.text) &&
isClosedOverFsModuleExpression(callee.expression)
? callee.name.text
: null;
}
if (ts.isElementAccessExpression(callee)) {
const aliasedName = callExpressionName(callee);
const writeAlias = aliasedName ? resolveLocalFsWriteAlias(aliasedName) : null;
if (writeAlias) {
return writeAlias;
}
const writeName = elementAccessName(callee.argumentExpression);
return writeName &&
legacyWriteCallees.has(writeName) &&
isClosedOverFsModuleExpression(callee.expression)
? writeName
: null;
}
return ts.isIdentifier(callee) ? resolveLocalFsWriteAlias(callee.text) : null;
}
function collectForwardedClosedOverPropertyUses(
argument,
propertyName,
parameter = null,
wrapperNode = null,
forwardedArguments = [],
options = {},
) {
if (propertyName === null) {
return collectPathPropertyUses(
argument,
"writeFile",
resolveClosedParameterIndex,
resolveClosedDestructuredParameterProperty,
resolveClosedParameterPropertyUse,
resolveClosedDestructuredParameterPropertyUses,
);
}
const propertyPath = propertyName.split(".");
function collectClosedOverBindingDefaultUses(sourceExpression) {
if (!parameter || !ts.isObjectBindingPattern(parameter.name)) {
return [];
}
const initializer = appliedBindingElementDefaultInitializer(
parameter.name,
propertyPath,
sourceExpression,
localNestedObjectLiteralSpreadPropertyState,
);
const defaultExpression = initializer
? resolveBindingDefaultInitializerExpression(
initializer,
wrapperNode,
forwardedArguments,
parameter,
options,
)
: null;
return defaultExpression
? collectPathPropertyUses(
defaultExpression,
"writeFile",
resolveClosedParameterIndex,
resolveClosedDestructuredParameterProperty,
resolveClosedParameterPropertyUse,
resolveClosedDestructuredParameterPropertyUses,
)
: [];
}
function collectForwardedClosedOverPropertyUseState(currentArgument, currentPropertyPath) {
const currentUnwrapped = unwrapExpression(currentArgument);
const currentPropertyName = currentPropertyPath.join(".");
if (ts.isIdentifier(currentUnwrapped)) {
const index = resolveClosedParameterIndex(currentUnwrapped.text);
if (index !== null) {
const propertyUse = resolveClosedParameterPropertyUse(
currentUnwrapped.text,
currentPropertyName,
);
return propertyUse === null
? []
: [propertyUse ?? { index, propertyName: currentPropertyName }];
}
return null;
}
if (ts.isObjectLiteralExpression(currentUnwrapped)) {
let result = null;
for (const property of currentUnwrapped.properties) {
if (ts.isSpreadAssignment(property)) {
const spreadUses = collectForwardedClosedOverPropertyUseState(
property.expression,
currentPropertyPath,
);
if (spreadUses !== null) {
result = spreadUses;
}
continue;
}
const [nextPropertyName, ...remainingPropertyPath] = currentPropertyPath;
if (
ts.isPropertyAssignment(property) &&
propertyNameText(property.name) === nextPropertyName
) {
if (isKnownUndefinedExpression(property.initializer)) {
result = null;
continue;
}
if (remainingPropertyPath.length > 0) {
result = collectForwardedClosedOverPropertyUseState(
property.initializer,
remainingPropertyPath,
);
continue;
}
result = collectPathPropertyUses(
property.initializer,
"writeFile",
resolveClosedParameterIndex,
resolveClosedDestructuredParameterProperty,
resolveClosedParameterPropertyUse,
resolveClosedDestructuredParameterPropertyUses,
);
continue;
}
if (
remainingPropertyPath.length === 0 &&
ts.isShorthandPropertyAssignment(property) &&
property.name.text === nextPropertyName
) {
result = collectPathPropertyUses(
property.name,
"writeFile",
resolveClosedParameterIndex,
resolveClosedDestructuredParameterProperty,
resolveClosedParameterPropertyUse,
resolveClosedDestructuredParameterPropertyUses,
);
}
if (
remainingPropertyPath.length > 0 &&
ts.isShorthandPropertyAssignment(property) &&
property.name.text === nextPropertyName
) {
result = collectForwardedClosedOverPropertyUseState(
property.name,
remainingPropertyPath,
);
}
}
return result;
}
const uses = collectPathPropertyUses(
currentArgument,
"writeFile",
resolveClosedParameterIndex,
resolveClosedDestructuredParameterProperty,
resolveClosedParameterPropertyUse,
resolveClosedDestructuredParameterPropertyUses,
);
return uses.length > 0 ? uses : null;
}
const unwrapped = unwrapExpression(argument);
if (ts.isIdentifier(unwrapped)) {
const index = resolveClosedParameterIndex(unwrapped.text);
if (index !== null) {
const propertyUse = resolveClosedParameterPropertyUse(unwrapped.text, propertyName);
return propertyUse === null ? [] : [propertyUse ?? { index, propertyName }];
}
}
if (ts.isObjectLiteralExpression(unwrapped)) {
return (
collectForwardedClosedOverPropertyUseState(unwrapped, propertyPath) ??
collectClosedOverBindingDefaultUses(unwrapped)
);
}
return collectPathPropertyUses(
argument,
"writeFile",
resolveClosedParameterIndex,
resolveClosedDestructuredParameterProperty,
resolveClosedParameterPropertyUse,
resolveClosedDestructuredParameterPropertyUses,
);
}
for (const parameter of record.node.parameters ?? []) {
markLocalBindings(parameter.name);
}
if (argumentsList) {
record.node.parameters.forEach((parameter, index) => {
if (!ts.isIdentifier(parameter.name) || !parameter.initializer) {
return;
}
const providedArgument = argumentsList[index] ?? null;
if (providedArgument && !isKnownUndefinedExpression(providedArgument)) {
return;
}
const unwrappedArgument = unwrapExpression(parameter.initializer);
if (
!ts.isFunctionExpression(unwrappedArgument) &&
!ts.isArrowFunction(unwrappedArgument)
) {
return;
}
registerLocalNestedFunctionRecord(
parameter.name.text,
localNestedRecordForNode(
unwrappedArgument,
currentLocalNestedFunctionScope(),
currentLocalNestedFunctionScope(),
),
);
});
}
if (
(ts.isFunctionDeclaration(record.node) || ts.isFunctionExpression(record.node)) &&
record.node.name
) {
markLocalBindings(record.node.name);
}
function visitClosedOverNode(current) {
if (isTypeSyntaxNode(current)) {
return;
}
if (current !== record.node && ts.isFunctionDeclaration(current)) {
if (current.name) {
currentLocalBindingScope().add(current.name.text);
currentLocalFsWriteAliasScope().set(current.name.text, null);
currentLocalFsModuleBindingScope().set(current.name.text, false);
currentLocalRequireAliasScope().set(current.name.text, false);
registerLocalNestedFunction(current.name.text, current);
}
return;
}
if (ts.isIfStatement(current)) {
visitClosedOverNode(current.expression);
if (!current.elseStatement) {
const effects = createLocalNestedBranchEffects();
localNestedBranchEffectScopes.push(effects);
pushLocalClosedOverScope();
visitClosedOverNode(current.thenStatement);
popLocalClosedOverScope();
localNestedBranchEffectScopes.pop();
mergeOptionalLocalNestedBranchEffects(effects);
return;
}
const thenEffects = createLocalNestedBranchEffects();
const elseEffects = createLocalNestedBranchEffects();
localNestedBranchEffectScopes.push(thenEffects);
visitClosedOverNode(current.thenStatement);
localNestedBranchEffectScopes.pop();
localNestedBranchEffectScopes.push(elseEffects);
visitClosedOverNode(current.elseStatement);
localNestedBranchEffectScopes.pop();
mergeExhaustiveLocalNestedBranchEffects(thenEffects, elseEffects);
return;
}
if (current !== record.node && ts.isFunctionLike(current)) {
return;
}
if (ts.isWhileStatement(current)) {
visitClosedOverNode(current.expression);
const bodyEffects = createLocalNestedBranchEffects();
localNestedBranchEffectScopes.push(bodyEffects);
pushLocalClosedOverScope();
visitClosedOverNode(current.statement);
popLocalClosedOverScope();
localNestedBranchEffectScopes.pop();
mergeOptionalLocalNestedBranchEffects(bodyEffects);
return;
}
if (ts.isDoStatement(current)) {
const bodyEffects = createLocalNestedBranchEffects();
localNestedBranchEffectScopes.push(bodyEffects);
pushLocalClosedOverScope();
visitClosedOverNode(current.statement);
popLocalClosedOverScope();
localNestedBranchEffectScopes.pop();
mergeOptionalLocalNestedBranchEffects(bodyEffects);
visitClosedOverNode(current.expression);
return;
}
if (ts.isForStatement(current)) {
pushLocalClosedOverScope();
if (current.initializer) {
visitClosedOverNode(current.initializer);
}
if (current.condition) {
visitClosedOverNode(current.condition);
}
if (current.incrementor) {
const incrementorEffects = createLocalNestedBranchEffects();
localNestedBranchEffectScopes.push(incrementorEffects);
pushLocalClosedOverScope();
visitClosedOverNode(current.incrementor);
popLocalClosedOverScope();
localNestedBranchEffectScopes.pop();
mergeOptionalLocalNestedBranchEffects(incrementorEffects);
}
const bodyEffects = createLocalNestedBranchEffects();
localNestedBranchEffectScopes.push(bodyEffects);
pushLocalClosedOverScope();
visitClosedOverNode(current.statement);
popLocalClosedOverScope();
localNestedBranchEffectScopes.pop();
mergeOptionalLocalNestedBranchEffects(bodyEffects);
popLocalClosedOverScope();
return;
}
if (ts.isForInStatement(current) || ts.isForOfStatement(current)) {
visitClosedOverNode(current.expression);
pushLocalClosedOverScope();
visitClosedOverNode(current.initializer);
const bodyEffects = createLocalNestedBranchEffects();
localNestedBranchEffectScopes.push(bodyEffects);
pushLocalClosedOverScope();
visitClosedOverNode(current.statement);
popLocalClosedOverScope();
localNestedBranchEffectScopes.pop();
mergeOptionalLocalNestedBranchEffects(bodyEffects);
popLocalClosedOverScope();
return;
}
if (ts.isTryStatement(current)) {
const tryEffects = createLocalNestedBranchEffects();
localNestedBranchEffectScopes.push(tryEffects);
pushLocalClosedOverScope();
visitClosedOverNode(current.tryBlock);
popLocalClosedOverScope();
localNestedBranchEffectScopes.pop();
mergeOptionalLocalNestedBranchEffects(tryEffects);
if (current.catchClause) {
const catchEffects = createLocalNestedBranchEffects();
localNestedBranchEffectScopes.push(catchEffects);
pushLocalClosedOverScope();
visitClosedOverNode(current.catchClause);
popLocalClosedOverScope();
localNestedBranchEffectScopes.pop();
mergeOptionalLocalNestedBranchEffects(catchEffects);
}
if (current.finallyBlock) {
pushLocalClosedOverScope();
visitClosedOverNode(current.finallyBlock);
popLocalClosedOverScope();
}
return;
}
if (
current !== record.node.body &&
(ts.isBlock(current) || ts.isModuleBlock(current) || ts.isCaseBlock(current))
) {
pushLocalClosedOverScope();
const statements = localScopeStatements(current);
registerHoistedLocalNestedFunctions(statements);
registerLocalDeclarationShadows(statements);
ts.forEachChild(current, visitClosedOverNode);
popLocalClosedOverScope();
return;
}
if (ts.isVariableDeclaration(current)) {
const declarationScopes = localDeclarationScopes(current);
const declarationUsesBranchEffects =
isVarVariableDeclaration(current) && Boolean(currentLocalNestedBranchEffectScope());
const assignmentScopes = declarationUsesBranchEffects
? currentLocalDeclarationScopes()
: declarationScopes;
markLocalBindings(current.name, declarationScopes);
if (current.initializer) {
visitClosedOverNode(current.initializer);
}
collectFsWriteAliasesFromBindingInto(
current,
assignmentScopes.fsWriteAliasScope,
isClosedOverFsModuleExpression,
);
if (ts.isIdentifier(current.name) && current.initializer) {
const nestedInitializer = unwrapExpression(current.initializer);
if (
ts.isFunctionExpression(nestedInitializer) ||
ts.isArrowFunction(nestedInitializer)
) {
registerLocalNestedFunctionRecord(
current.name.text,
localNestedRecordForNode(
nestedInitializer,
assignmentScopes.nestedFunctionScope,
currentLocalNestedFunctionScope(),
),
assignmentScopes.nestedFunctionScope,
);
} else {
registerLocalNestedFunctionRecord(
current.name.text,
ts.isObjectLiteralExpression(nestedInitializer)
? knownObjectLiteralNestedWrapperValue
: cloneWrapperFunctionValue(resolveLocalNestedExpression(current.initializer)),
assignmentScopes.nestedFunctionScope,
);
}
if (declarationUsesBranchEffects) {
recordLocalNestedBranchAssignment(
current.name.text,
assignmentScopes.nestedFunctionScope.get(current.name.text),
declarationScopes.nestedFunctionScope,
);
}
registerLocalNestedObjectMethods(
current.name.text,
current.initializer,
assignmentScopes.nestedFunctionScope,
currentLocalNestedFunctionScope(),
declarationUsesBranchEffects,
declarationScopes.nestedFunctionScope,
);
assignmentScopes.requireAliasScope.set(
current.name.text,
isClosedOverRequireAliasExpression(current.initializer),
);
assignmentScopes.fsModuleBindingScope.set(
current.name.text,
isClosedOverFsModuleExpression(current.initializer),
);
assignmentScopes.fsWriteAliasScope.set(
current.name.text,
legacyClosedOverFsWriteName(current.initializer),
);
if (declarationUsesBranchEffects) {
recordLocalNestedBranchFsIdentifierAssignment(
current.name.text,
isClosedOverFsModuleExpression(current.initializer),
legacyClosedOverFsWriteName(current.initializer),
isClosedOverRequireAliasExpression(current.initializer),
declarationScopes.fsModuleBindingScope,
declarationScopes.fsWriteAliasScope,
declarationScopes.requireAliasScope,
);
}
}
if (ts.isObjectBindingPattern(current.name) && current.initializer) {
registerLocalNestedObjectBindingInitializer(
current.name,
current.initializer,
assignmentScopes.nestedFunctionScope,
currentLocalNestedFunctionScope(),
);
}
refreshCurrentLocalNestedFunctionAliases();
return;
}
if (
ts.isBinaryExpression(current) &&
current.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
ts.isIdentifier(current.left)
) {
visitClosedOverNode(current.right);
const requireAliasScope = localBindingWriteScope(
current.left.text,
localRequireAliasScopes,
);
const fsModuleBindingScope = localBindingWriteScope(
current.left.text,
localFsModuleBindingScopes,
);
const fsWriteAliasScope = localBindingWriteScope(
current.left.text,
localFsWriteAliasScopes,
);
const writesEnclosingNestedFunction =
!isLocalBinding(current.left.text) && record.lexicalScope?.has(current.left.text);
const nestedFunctionOwnerScope = writesEnclosingNestedFunction
? record.lexicalScope
: localBindingWriteScope(current.left.text, localNestedFunctionScopes);
const nestedFunctionScope = currentLocalNestedBranchEffectScope()
? currentLocalNestedFunctionScope()
: nestedFunctionOwnerScope;
const crossesLocalBlock = requireAliasScope !== currentLocalRequireAliasScope();
const mergesNestedFunctionAssignment =
!currentLocalNestedBranchEffectScope() &&
(writesEnclosingNestedFunction || crossesLocalBlock);
const nextRequireAlias = isClosedOverRequireAliasExpression(current.right);
const nextFsModuleBinding = isClosedOverFsModuleExpression(current.right);
const nextFsWriteAlias = legacyClosedOverFsWriteName(current.right);
requireAliasScope.set(
current.left.text,
crossesLocalBlock
? requireAliasScope.get(current.left.text) === true || nextRequireAlias
: nextRequireAlias,
);
fsModuleBindingScope.set(
current.left.text,
crossesLocalBlock
? fsModuleBindingScope.get(current.left.text) === true || nextFsModuleBinding
: nextFsModuleBinding,
);
fsWriteAliasScope.set(
current.left.text,
crossesLocalBlock
? (fsWriteAliasScope.get(current.left.text) ?? nextFsWriteAlias)
: nextFsWriteAlias,
);
const assignedNestedFunction = assignLocalNestedFunction(
current.left.text,
current.right,
nestedFunctionScope,
mergesNestedFunctionAssignment,
);
recordLocalNestedBranchAssignment(
current.left.text,
assignedNestedFunction,
nestedFunctionOwnerScope,
);
registerLocalNestedObjectMethods(
current.left.text,
current.right,
nestedFunctionScope,
nestedFunctionScope,
Boolean(currentLocalNestedBranchEffectScope()),
nestedFunctionOwnerScope,
);
refreshCurrentLocalNestedFunctionAliases();
return;
}
if (
ts.isBinaryExpression(current) &&
current.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
rootedPropertyAccessPath(current.left)?.properties.length > 0
) {
visitClosedOverNode(current.right);
const propertyAccess = rootedPropertyAccessPath(current.left);
const propertyName = propertyAccess.properties.join(".");
const nestedFunctionOwnerScope = localNestedObjectMethodWriteScope(
propertyAccess.rootName,
propertyName,
);
const nestedFunctionScope = currentLocalNestedBranchEffectScope()
? currentLocalNestedFunctionScope()
: nestedFunctionOwnerScope;
const crossesLocalBlock = nestedFunctionOwnerScope !== currentLocalNestedFunctionScope();
const mergesNestedFunctionAssignment =
!currentLocalNestedBranchEffectScope() && crossesLocalBlock;
const key = objectPropertyKey(propertyAccess.rootName, propertyName);
const assignedNestedFunction = assignLocalNestedFunction(
key,
current.right,
nestedFunctionScope,
mergesNestedFunctionAssignment,
);
recordLocalNestedBranchAssignment(key, assignedNestedFunction, nestedFunctionOwnerScope);
registerLocalNestedObjectMethods(
key,
current.right,
nestedFunctionScope,
nestedFunctionScope,
Boolean(currentLocalNestedBranchEffectScope()),
nestedFunctionOwnerScope,
);
refreshCurrentLocalNestedFunctionAliases();
return;
}
if (ts.isParameter(current)) {
markLocalBindings(current.name);
return;
}
if (ts.isCallExpression(current)) {
const localNestedFunctionName = callExpressionName(current.expression);
const localNestedFunctionRootName = localNestedFunctionName?.split(".")[0] ?? null;
const localNestedFunctionShadowed = localNestedFunctionRootName
? isLocalBinding(localNestedFunctionRootName)
: false;
const localNestedBinding = localNestedFunctionName
? resolveLocalNestedFunctionBindingValue(localNestedFunctionName)
: { found: false, value: null };
const calledWrapper = localNestedFunctionName
? localNestedBinding.found
? localNestedBinding.value
: localNestedFunctionShadowed
? null
: resolveWrapperFunction(localNestedFunctionName)
: null;
if (calledWrapper) {
for (const localNestedRecord of wrapperRecords(calledWrapper)) {
if (localNestedBinding.found && localNestedRecord.closesOverCurrentWrapper === true) {
for (const [index, propertyNames] of collectClosedOverPathPropertyUses(
localNestedRecord,
activeClosedOverNodes,
current.arguments,
)) {
const properties = closedOverUses.get(index) ?? new Set();
for (const propertyName of propertyNames) {
properties.add(propertyName);
}
closedOverUses.set(index, properties);
}
}
const forwardedPropertyUses = collectLegacyPathPropertyParameters(
localNestedRecord.node,
localNestedRecord.aliases,
localNestedRecord.moduleBindings,
localNestedRecord.moduleProperties,
localNestedRecord.requireAliases,
localNestedRecord.createRequireShadows,
activeClosedOverNodes,
localNestedRecord.lexicalScope ?? null,
);
for (const [index, propertyNames] of forwardedPropertyUses) {
const argument = callArgumentOrParameterDefault(
localNestedRecord.node,
current.arguments,
index,
{
allowLexicalIdentifierDefault: Boolean(localNestedRecord.lexicalScope),
},
);
if (!argument) {
continue;
}
for (const propertyName of propertyNames) {
for (const use of collectForwardedClosedOverPropertyUses(
argument,
propertyName,
localNestedRecord.node.parameters[index] ?? null,
localNestedRecord.node,
current.arguments,
{
allowLexicalIdentifierDefault: Boolean(localNestedRecord.lexicalScope),
},
)) {
appendClosedUse(use);
}
}
}
}
}
const fsWriteName = legacyClosedOverFsWriteName(current.expression);
if (fsWriteName && fsWriteCallMayWrite(fsWriteName, [...current.arguments])) {
for (const argument of pathArgumentsForFsWrite(fsWriteName, [...current.arguments])) {
for (const use of collectPathPropertyUses(
argument,
fsWriteName,
resolveClosedParameterIndex,
resolveClosedDestructuredParameterProperty,
resolveClosedParameterPropertyUse,
resolveClosedDestructuredParameterPropertyUses,
)) {
appendClosedUse(use);
}
}
}
}
ts.forEachChild(current, visitClosedOverNode);
}
if (record.node.body && "statements" in record.node.body) {
registerHoistedLocalNestedFunctions(record.node.body.statements);
}
for (const parameter of record.node.parameters ?? []) {
if (parameter.initializer) {
visitClosedOverNode(parameter.initializer);
}
}
if (record.node.body && "statements" in record.node.body) {
registerLocalDeclarationShadows(record.node.body.statements);
}
visitClosedOverNode(record.node.body ?? record.node);
activeClosedOverNodes.delete(record.node);
return closedOverUses;
}
function registerHoistedWrapperFunctionShadows(statements) {
for (const statement of statements) {
if (ts.isFunctionDeclaration(statement) && statement.name) {
markWrapperRequireShadows(statement.name);
markWrapperCreateRequireShadows(statement.name);
currentNestedWrapperFunctionScope().set(
statement.name.text,
nestedWrapperRecordForNode(statement),
);
}
}
}
function wrapperScopeStatements(current) {
if ("statements" in current) {
return current.statements;
}
if (ts.isCaseBlock(current)) {
return current.clauses.flatMap((clause) => [...clause.statements]);
}
return [];
}
function visitBody(current) {
if (isTypeSyntaxNode(current)) {
return;
}
if (current !== node && ts.isFunctionLike(current)) {
return;
}
if (ts.isIfStatement(current)) {
visitBody(current.expression);
const thenEffects = current.elseStatement ? createWrapperBranchEffects() : null;
const elseEffects = current.elseStatement ? createWrapperBranchEffects() : null;
pushWrapperBodyScope(true, thenEffects);
visitBody(current.thenStatement);
popWrapperBodyScope();
if (current.elseStatement) {
pushWrapperBodyScope(true, elseEffects);
visitBody(current.elseStatement);
popWrapperBodyScope();
mergeExhaustiveWrapperBranchEffects(thenEffects, elseEffects);
}
return;
}
if (ts.isWhileStatement(current)) {
visitBody(current.expression);
pushWrapperBodyScope(true);
visitBody(current.statement);
popWrapperBodyScope();
return;
}
if (ts.isDoStatement(current)) {
pushWrapperBodyScope(true);
visitBody(current.statement);
popWrapperBodyScope();
visitBody(current.expression);
return;
}
if (ts.isForStatement(current)) {
pushWrapperBodyScope();
if (current.initializer) {
visitBody(current.initializer);
}
if (current.condition) {
visitBody(current.condition);
}
if (current.incrementor) {
pushWrapperBodyScope(true);
visitBody(current.incrementor);
popWrapperBodyScope();
}
pushWrapperBodyScope(true);
visitBody(current.statement);
popWrapperBodyScope();
popWrapperBodyScope();
return;
}
if (ts.isForInStatement(current) || ts.isForOfStatement(current)) {
visitBody(current.expression);
pushWrapperBodyScope();
visitBody(current.initializer);
pushWrapperBodyScope(true);
visitBody(current.statement);
popWrapperBodyScope();
popWrapperBodyScope();
return;
}
if (ts.isTryStatement(current)) {
pushWrapperBodyScope(true);
visitBody(current.tryBlock);
popWrapperBodyScope();
if (current.catchClause) {
pushWrapperBodyScope(true);
visitBody(current.catchClause);
popWrapperBodyScope();
}
if (current.finallyBlock) {
pushWrapperBodyScope();
visitBody(current.finallyBlock);
popWrapperBodyScope();
}
return;
}
if (
current !== node.body &&
(ts.isBlock(current) ||
ts.isModuleBlock(current) ||
ts.isCaseBlock(current) ||
ts.isCatchClause(current))
) {
const branchBlockEffects = currentConditionalWrapperBodyScope()
? currentWrapperBranchEffectScope()
: null;
pushWrapperBodyScope(currentConditionalWrapperBodyScope(), branchBlockEffects);
registerHoistedWrapperFunctionShadows(wrapperScopeStatements(current));
ts.forEachChild(current, visitBody);
popWrapperBodyScope();
return;
}
if (ts.isVariableDeclaration(current)) {
const isFsAliasBinding =
ts.isObjectBindingPattern(current.name) &&
current.initializer &&
isWrapperFsBindingExpression(current.initializer);
const nestedFunctionInitializer = current.initializer
? unwrapExpression(current.initializer)
: null;
const declarationIsVar = isVarVariableDeclaration(current);
const declarationWrapperBranchEffects = currentWrapperBranchEffectScope();
const declarationUsesConditionalScope =
declarationIsVar && currentConditionalWrapperBodyScope();
const declarationUsesBranchEffects =
declarationIsVar && Boolean(declarationWrapperBranchEffects);
const declarationFsWriteAliasOwnerScope = declarationIsVar
? bodyFsWriteAliasScopes[0]
: currentBodyFsWriteAliasScope();
const declarationFsModuleBindingOwnerScope = declarationIsVar
? bodyFsModuleBindingScopes[0]
: currentBodyFsModuleBindingScope();
const declarationRequireAliasOwnerScope = declarationIsVar
? bodyRequireAliasScopes[0]
: currentBodyRequireAliasScope();
const declarationNestedWrapperOwnerScope = declarationIsVar
? nestedWrapperFunctionScopes[0]
: currentNestedWrapperFunctionScope();
const declarationFsWriteAliasScope = declarationUsesConditionalScope
? currentBodyFsWriteAliasScope()
: declarationFsWriteAliasOwnerScope;
const declarationFsModuleBindingScope = declarationUsesConditionalScope
? currentBodyFsModuleBindingScope()
: declarationFsModuleBindingOwnerScope;
const declarationRequireAliasScope = declarationUsesConditionalScope
? currentBodyRequireAliasScope()
: declarationRequireAliasOwnerScope;
const declarationNestedWrapperScope = declarationUsesConditionalScope
? currentNestedWrapperFunctionScope()
: declarationNestedWrapperOwnerScope;
collectFsWriteAliasesFromBindingInto(
current,
declarationFsWriteAliasScope,
isWrapperFsBindingExpression,
);
if (ts.isIdentifier(current.name)) {
const nextRequireAlias = current.initializer
? isWrapperRequireAliasExpression(current.initializer)
: false;
const nextFsModuleBinding = current.initializer
? isWrapperFsBindingExpression(current.initializer)
: false;
const nextFsWriteAlias = current.initializer
? legacyWrapperFsWriteName(current.initializer)
: null;
shadowVisibleBodyFsWriteObjectAliases(current.name.text);
shadowVisibleNestedWrapperObjectMethods(current.name.text);
if (nextRequireAlias) {
declarationRequireAliasScope.set(current.name.text, true);
} else {
markWrapperRequireShadows(current.name);
if (declarationIsVar) {
declarationRequireAliasScope.set(current.name.text, false);
}
}
if (nextFsModuleBinding) {
declarationFsModuleBindingScope.set(current.name.text, true);
} else {
markFsModuleShadows(current.name);
if (declarationIsVar) {
declarationFsModuleBindingScope.set(current.name.text, false);
}
}
if (current.initializer) {
registerBodyFsWriteObjectAliases(current.name.text, current.initializer);
}
markWrapperCreateRequireShadows(current.name);
if (
!nestedFunctionInitializer ||
(!ts.isFunctionExpression(nestedFunctionInitializer) &&
!ts.isArrowFunction(nestedFunctionInitializer))
) {
declarationNestedWrapperScope.set(
current.name.text,
current.initializer && ts.isObjectLiteralExpression(nestedFunctionInitializer)
? knownObjectLiteralNestedWrapperValue
: current.initializer
? cloneWrapperFunctionValue(resolveNestedWrapperExpression(current.initializer))
: null,
);
if (current.initializer && declarationUsesBranchEffects) {
recordWrapperBranchNestedWrapperAssignment(
current.name.text,
declarationNestedWrapperScope.get(current.name.text),
declarationNestedWrapperOwnerScope,
);
}
if (
current.initializer &&
declarationUsesConditionalScope &&
!declarationUsesBranchEffects &&
declarationNestedWrapperOwnerScope !== declarationNestedWrapperScope
) {
declarationNestedWrapperOwnerScope.set(
current.name.text,
mergeWrapperAssignmentValues(
declarationNestedWrapperOwnerScope.get(current.name.text),
declarationNestedWrapperScope.get(current.name.text),
),
);
}
}
if (current.initializer && declarationUsesBranchEffects) {
recordWrapperBranchFsIdentifierAssignment(
current.name.text,
nextFsModuleBinding,
nextFsWriteAlias,
nextRequireAlias,
declarationFsModuleBindingOwnerScope,
declarationFsWriteAliasOwnerScope,
declarationRequireAliasOwnerScope,
);
}
} else if (!isFsAliasBinding) {
markFsModuleShadows(current.name);
markWrapperRequireShadows(current.name);
markWrapperCreateRequireShadows(current.name);
markNestedWrapperFunctionShadows(current.name);
}
const initializerPropertyAccess = current.initializer
? namedObjectPropertyAccess(current.initializer)
: null;
const initializerParameterIndex = initializerPropertyAccess
? resolveParameterIndex(initializerPropertyAccess.objectName)
: null;
const initializerObjectIndex =
current.initializer && ts.isIdentifier(unwrapExpression(current.initializer))
? resolveParameterIndex(unwrapExpression(current.initializer).text)
: null;
if (isParameterPropertyDestructure(current, parameterIndexes)) {
const index = parameterIndexes.get(current.initializer.text);
for (const [name, binding] of objectBindingParameterProperties(current.name, index)) {
currentDestructuredParameterPropertyScope().set(name, binding);
}
} else if (
ts.isIdentifier(current.name) &&
initializerPropertyAccess &&
initializerParameterIndex !== null
) {
currentDestructuredParameterPropertyScope().set(current.name.text, {
index: initializerParameterIndex,
propertyName: initializerPropertyAccess.propertyName,
});
} else if (ts.isIdentifier(current.name) && initializerObjectIndex !== null) {
currentParameterObjectBindingScope().set(current.name.text, initializerObjectIndex);
} else {
for (const name of bindingPatternNames(current.name)) {
if (resolveDestructuredParameterProperty(name)) {
currentShadowScope().add(name);
}
if (parameterIndexes.has(name)) {
currentParameterObjectShadowScope().add(name);
}
}
}
if (!isFsAliasBinding) {
markFsAliasShadows(current.name);
}
if (ts.isIdentifier(current.name) && current.initializer) {
declarationFsWriteAliasScope.set(
current.name.text,
legacyWrapperFsWriteName(current.initializer),
);
} else if (ts.isIdentifier(current.name)) {
declarationFsWriteAliasScope.set(current.name.text, null);
}
if (
ts.isIdentifier(current.name) &&
nestedFunctionInitializer &&
(ts.isFunctionExpression(nestedFunctionInitializer) ||
ts.isArrowFunction(nestedFunctionInitializer))
) {
declarationNestedWrapperScope.set(
current.name.text,
nestedWrapperRecordForNode(nestedFunctionInitializer),
);
if (declarationUsesBranchEffects) {
recordWrapperBranchNestedWrapperAssignment(
current.name.text,
declarationNestedWrapperScope.get(current.name.text),
declarationNestedWrapperOwnerScope,
);
}
if (
declarationUsesConditionalScope &&
!declarationUsesBranchEffects &&
declarationNestedWrapperOwnerScope !== declarationNestedWrapperScope
) {
declarationNestedWrapperOwnerScope.set(
current.name.text,
mergeWrapperAssignmentValues(
declarationNestedWrapperOwnerScope.get(current.name.text),
declarationNestedWrapperScope.get(current.name.text),
),
);
}
}
if (ts.isIdentifier(current.name) && current.initializer) {
const declarationObjectMethods = registerNestedWrapperObjectMethods(
current.name.text,
current.initializer,
declarationNestedWrapperScope,
declarationUsesBranchEffects,
declarationNestedWrapperOwnerScope,
);
if (
declarationUsesConditionalScope &&
!declarationUsesBranchEffects &&
declarationNestedWrapperOwnerScope !== declarationNestedWrapperScope
) {
for (const [key, value] of declarationObjectMethods) {
declarationNestedWrapperOwnerScope.set(
key,
mergeWrapperAssignmentValues(declarationNestedWrapperOwnerScope.get(key), value),
);
}
}
}
if (ts.isObjectBindingPattern(current.name) && current.initializer) {
registerNestedWrapperObjectBindingInitializer(
current.name,
current.initializer,
declarationNestedWrapperScope,
);
}
refreshCurrentNestedWrapperFunctionAliases();
}
if (
ts.isBinaryExpression(current) &&
current.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
ts.isIdentifier(current.left)
) {
const fsModuleBindingScope = bodyFsModuleBindingWriteScope(current.left.text);
const fsWriteAliasScope = bodyFsWriteAliasWriteScope(current.left.text);
const requireAliasScope = bodyRequireAliasWriteScope(current.left.text);
const nextFsModuleBinding = isWrapperFsBindingExpression(current.right);
const nextFsWriteAlias = legacyWrapperFsWriteName(current.right);
const nextRequireAlias = isWrapperRequireAliasExpression(current.right);
const wrapperBranchEffects = currentWrapperBranchEffectScope();
if (wrapperBranchEffects) {
currentBodyFsModuleBindingScope().set(current.left.text, nextFsModuleBinding);
currentBodyFsWriteAliasScope().set(current.left.text, nextFsWriteAlias);
currentBodyRequireAliasScope().set(current.left.text, nextRequireAlias);
recordWrapperBranchFsIdentifierAssignment(
current.left.text,
nextFsModuleBinding,
nextFsWriteAlias,
nextRequireAlias,
fsModuleBindingScope,
fsWriteAliasScope,
requireAliasScope,
);
} else {
fsModuleBindingScope.set(
current.left.text,
currentConditionalWrapperBodyScope()
? fsModuleBindingScope.get(current.left.text) === true || nextFsModuleBinding
: nextFsModuleBinding,
);
fsWriteAliasScope.set(
current.left.text,
currentConditionalWrapperBodyScope()
? (fsWriteAliasScope.get(current.left.text) ?? nextFsWriteAlias)
: nextFsWriteAlias,
);
requireAliasScope.set(
current.left.text,
currentConditionalWrapperBodyScope()
? requireAliasScope.get(current.left.text) === true || nextRequireAlias
: nextRequireAlias,
);
}
shadowVisibleBodyFsWriteObjectAliases(current.left.text);
clearBodyFsWriteObjectAliases(currentBodyFsWriteAliasScope(), current.left.text);
registerBodyFsWriteObjectAliases(current.left.text, current.right);
const exhaustiveNestedWrapperBranch = Boolean(wrapperBranchEffects);
const optionalNestedWrapperBranch =
currentConditionalWrapperBodyScope() && !exhaustiveNestedWrapperBranch;
const nestedWrapperOwnerScope = nestedWrapperFunctionWriteScope(current.left.text);
const nestedWrapperTargetScope = currentConditionalWrapperBodyScope()
? currentNestedWrapperFunctionScope()
: nestedWrapperOwnerScope;
clearNestedWrapperObjectMethods(nestedWrapperTargetScope, current.left.text);
const assignedNestedWrapper =
ts.isFunctionExpression(unwrapExpression(current.right)) ||
ts.isArrowFunction(unwrapExpression(current.right))
? nestedWrapperRecordForNode(unwrapExpression(current.right))
: ts.isObjectLiteralExpression(unwrapExpression(current.right))
? knownObjectLiteralNestedWrapperValue
: cloneWrapperFunctionValue(resolveNestedWrapperExpression(current.right));
nestedWrapperTargetScope.set(current.left.text, assignedNestedWrapper);
if (optionalNestedWrapperBranch && nestedWrapperOwnerScope !== nestedWrapperTargetScope) {
nestedWrapperOwnerScope.set(
current.left.text,
mergeWrapperAssignmentValues(
nestedWrapperOwnerScope.get(current.left.text),
assignedNestedWrapper,
),
);
}
if (exhaustiveNestedWrapperBranch) {
recordWrapperBranchNestedWrapperAssignment(
current.left.text,
assignedNestedWrapper,
nestedWrapperOwnerScope,
);
}
const assignedNestedWrapperObjectMethods = registerNestedWrapperObjectMethods(
current.left.text,
current.right,
nestedWrapperTargetScope,
exhaustiveNestedWrapperBranch,
nestedWrapperOwnerScope,
);
if (optionalNestedWrapperBranch && nestedWrapperOwnerScope !== nestedWrapperTargetScope) {
for (const [key, value] of assignedNestedWrapperObjectMethods) {
nestedWrapperOwnerScope.set(
key,
mergeWrapperAssignmentValues(nestedWrapperOwnerScope.get(key), value),
);
}
}
refreshCurrentNestedWrapperFunctionAliases();
}
if (
ts.isBinaryExpression(current) &&
current.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
rootedPropertyAccessPath(current.left)?.properties.length > 0
) {
const propertyAccess = rootedPropertyAccessPath(current.left);
const propertyName = propertyAccess.properties.join(".");
if (propertyAccess.properties.length === 1) {
setBodyFsWriteObjectAlias(
currentBodyFsWriteAliasScope(),
objectPropertyKey(propertyAccess.rootName, propertyName),
legacyWrapperFsWriteName(current.right),
);
}
const nestedWrapperKey = objectPropertyKey(propertyAccess.rootName, propertyName);
const assignedNestedWrapper =
ts.isFunctionExpression(unwrapExpression(current.right)) ||
ts.isArrowFunction(unwrapExpression(current.right))
? nestedWrapperRecordForNode(unwrapExpression(current.right))
: ts.isObjectLiteralExpression(unwrapExpression(current.right))
? knownObjectLiteralNestedWrapperValue
: cloneWrapperFunctionValue(resolveNestedWrapperExpression(current.right));
const exhaustiveNestedWrapperBranch = Boolean(currentWrapperBranchEffectScope());
const optionalNestedWrapperBranch =
currentConditionalWrapperBodyScope() && !exhaustiveNestedWrapperBranch;
const nestedWrapperOwnerScope = nestedWrapperObjectMethodWriteScope(
propertyAccess.rootName,
propertyName,
);
const nestedWrapperTargetScope = currentConditionalWrapperBodyScope()
? currentNestedWrapperFunctionScope()
: nestedWrapperOwnerScope;
clearNestedWrapperObjectMethods(nestedWrapperTargetScope, nestedWrapperKey);
nestedWrapperTargetScope.set(nestedWrapperKey, assignedNestedWrapper);
if (optionalNestedWrapperBranch && nestedWrapperOwnerScope !== nestedWrapperTargetScope) {
nestedWrapperOwnerScope.set(
nestedWrapperKey,
mergeWrapperAssignmentValues(
nestedWrapperOwnerScope.get(nestedWrapperKey),
assignedNestedWrapper,
),
);
}
if (exhaustiveNestedWrapperBranch) {
recordWrapperBranchNestedWrapperAssignment(
nestedWrapperKey,
assignedNestedWrapper,
nestedWrapperOwnerScope,
);
}
const assignedNestedWrapperObjectMethods = registerNestedWrapperObjectMethods(
nestedWrapperKey,
current.right,
nestedWrapperTargetScope,
exhaustiveNestedWrapperBranch,
nestedWrapperOwnerScope,
);
if (optionalNestedWrapperBranch && nestedWrapperOwnerScope !== nestedWrapperTargetScope) {
for (const [key, value] of assignedNestedWrapperObjectMethods) {
nestedWrapperOwnerScope.set(
key,
mergeWrapperAssignmentValues(nestedWrapperOwnerScope.get(key), value),
);
}
}
refreshCurrentNestedWrapperFunctionAliases();
}
markParameterAssignment(current);
if (ts.isCallExpression(current)) {
const fsWriteName = legacyWrapperFsWriteName(current.expression);
if (fsWriteName && fsWriteCallMayWrite(fsWriteName, [...current.arguments])) {
for (const argument of pathArgumentsForFsWrite(fsWriteName, [...current.arguments])) {
for (const use of collectPathPropertyUses(
argument,
fsWriteName,
resolveParameterIndex,
resolveDestructuredParameterProperty,
resolveParameterPropertyUse,
resolveDestructuredParameterPropertyUses,
)) {
const properties = propertyUses.get(use.index) ?? new Set();
properties.add(use.propertyName);
propertyUses.set(use.index, properties);
}
}
}
const wrapperName = callExpressionName(current.expression);
const nestedWrapperRecord = wrapperName
? resolveNestedWrapperFunction(wrapperName)
: undefined;
const wrapperRecord = wrapperName
? nestedWrapperRecord === undefined
? resolveWrapperFunction(wrapperName)
: nestedWrapperRecord
: null;
for (const record of wrapperRecords(wrapperRecord)) {
if (nestedWrapperRecord !== undefined && record.closesOverCurrentWrapper === true) {
for (const [index, propertyNames] of collectClosedOverPathPropertyUses(
record,
activeWrapperNodes,
current.arguments,
)) {
const properties = propertyUses.get(index) ?? new Set();
for (const propertyName of propertyNames) {
properties.add(propertyName);
}
propertyUses.set(index, properties);
}
}
const forwardedPropertyUses = collectLegacyPathPropertyParameters(
record.node,
record.aliases,
record.moduleBindings,
record.moduleProperties,
record.requireAliases,
record.createRequireShadows,
activeWrapperNodes,
record.lexicalScope ?? null,
);
for (const [index, propertyNames] of forwardedPropertyUses) {
const argument = callArgumentOrParameterDefault(record.node, current.arguments, index, {
allowLexicalIdentifierDefault: record.closesOverCurrentWrapper === true,
});
if (!argument) {
continue;
}
for (const propertyName of propertyNames) {
for (const use of collectForwardedWrapperPropertyUses(
argument,
propertyName,
record.node.parameters[index] ?? null,
record.node,
current.arguments,
{
allowLexicalIdentifierDefault: record.closesOverCurrentWrapper === true,
},
)) {
const properties = propertyUses.get(use.index) ?? new Set();
properties.add(use.propertyName);
propertyUses.set(use.index, properties);
}
}
}
}
}
ts.forEachChild(current, visitBody);
}
if (node.body) {
if ("statements" in node.body) {
registerHoistedWrapperFunctionShadows(node.body.statements);
}
visitBody(node.body);
}
activeWrapperNodes.delete(node);
return propertyUses;
}
function callArgumentOrParameterDefault(
wrapperNode,
argumentsList,
index,
optionsOrActiveDefaultIndexes = {},
maybeActiveDefaultIndexes = new Set(),
) {
const options =
optionsOrActiveDefaultIndexes instanceof Set ? {} : optionsOrActiveDefaultIndexes;
const activeDefaultIndexes =
optionsOrActiveDefaultIndexes instanceof Set
? optionsOrActiveDefaultIndexes
: maybeActiveDefaultIndexes;
const allowLexicalIdentifierDefault = options.allowLexicalIdentifierDefault ?? true;
const argument = argumentsList[index];
if (argument && !isKnownUndefinedExpression(argument)) {
return argument;
}
const initializer = wrapperNode.parameters[index]?.initializer ?? null;
if (!initializer) {
return null;
}
const unwrapped = unwrapExpression(initializer);
if (ts.isIdentifier(unwrapped)) {
const parameterBinding = earlierParameterBindingForIdentifier(
unwrapped.text,
wrapperNode,
index,
);
if (parameterBinding && !activeDefaultIndexes.has(parameterBinding.index)) {
activeDefaultIndexes.add(parameterBinding.index);
const resolved = resolveEarlierParameterBindingExpression(
parameterBinding,
wrapperNode,
argumentsList,
options,
activeDefaultIndexes,
);
activeDefaultIndexes.delete(parameterBinding.index);
return resolved ?? null;
}
if (!allowLexicalIdentifierDefault) {
return null;
}
}
if (!allowLexicalIdentifierDefault) {
const resolved = resolveEarlierParameterDefaultExpression(
unwrapped,
wrapperNode,
argumentsList,
index,
options,
activeDefaultIndexes,
);
if (resolved) {
return resolved;
}
const spreadOnlyObjectLiteral =
ts.isObjectLiteralExpression(unwrapped) &&
objectLiteralIdentifiersAreSpreadSourcesOnly(unwrapped);
return earlierParameterReferenceIndexes(unwrapped, wrapperNode, index).size === 0 &&
(!expressionContainsIdentifier(unwrapped) || spreadOnlyObjectLiteral)
? initializer
: null;
}
return initializer;
}
function earlierParameterBindingForIdentifier(name, wrapperNode, parameterPosition) {
for (let index = 0; index < parameterPosition; index++) {
const candidate = wrapperNode.parameters[index];
if (!candidate) {
continue;
}
if (ts.isIdentifier(candidate.name) && candidate.name.text === name) {
return { index, propertyName: null };
}
if (ts.isObjectBindingPattern(candidate.name)) {
const binding = objectBindingParameterProperties(candidate.name, index).get(name);
if (binding) {
return binding;
}
}
}
return null;
}
function earlierParameterReferenceBindings(expression, wrapperNode, parameterPosition) {
const bindings = new Map();
function addBinding(binding) {
bindings.set(`${binding.index}:${binding.propertyName ?? ""}`, binding);
}
function visitReference(current) {
if (ts.isPropertyAccessExpression(current)) {
visitReference(current.expression);
return;
}
if (ts.isPropertyAssignment(current)) {
visitReference(current.initializer);
return;
}
if (ts.isShorthandPropertyAssignment(current)) {
visitReference(current.name);
return;
}
if (ts.isIdentifier(current)) {
const binding = earlierParameterBindingForIdentifier(
current.text,
wrapperNode,
parameterPosition,
);
if (binding) {
addBinding(binding);
}
return;
}
ts.forEachChild(current, visitReference);
}
visitReference(expression);
return [...bindings.values()];
}
function earlierParameterReferenceIndexes(expression, wrapperNode, parameterPosition) {
return new Set(
earlierParameterReferenceBindings(expression, wrapperNode, parameterPosition).map(
(binding) => binding.index,
),
);
}
function expressionContainsIdentifier(expression) {
let found = false;
function visitIdentifier(current) {
if (ts.isPropertyAccessExpression(current)) {
visitIdentifier(current.expression);
return;
}
if (ts.isPropertyAssignment(current)) {
visitIdentifier(current.initializer);
return;
}
if (ts.isMethodDeclaration(current) || ts.isGetAccessor(current)) {
if (current.body) {
visitIdentifier(current.body);
}
return;
}
if (ts.isSetAccessor(current)) {
visitIdentifier(current.parameters[0]);
if (current.body) {
visitIdentifier(current.body);
}
return;
}
if (ts.isIdentifier(current)) {
found = true;
return;
}
ts.forEachChild(current, visitIdentifier);
}
visitIdentifier(expression);
return found;
}
function objectLiteralIdentifiersAreSpreadSourcesOnly(objectLiteral) {
let valid = true;
function visitExpression(current) {
if (!valid) {
return;
}
if (ts.isSpreadAssignment(current)) {
const spreadExpression = unwrapExpression(current.expression);
if (!ts.isIdentifier(spreadExpression)) {
visitExpression(spreadExpression);
}
return;
}
if (ts.isPropertyAssignment(current)) {
visitExpression(current.initializer);
return;
}
if (ts.isShorthandPropertyAssignment(current) || ts.isIdentifier(current)) {
valid = false;
return;
}
ts.forEachChild(current, visitExpression);
}
visitExpression(objectLiteral);
return valid;
}
function resolveEarlierParameterDefaultExpression(
expression,
wrapperNode,
argumentsList,
parameterPosition,
options,
activeDefaultIndexes,
) {
const resolvedExpressions = [];
for (const binding of earlierParameterReferenceBindings(
expression,
wrapperNode,
parameterPosition,
)) {
const referencedParameterIndex = binding.index;
if (activeDefaultIndexes.has(referencedParameterIndex)) {
continue;
}
activeDefaultIndexes.add(referencedParameterIndex);
const resolved = resolveEarlierParameterBindingExpression(
binding,
wrapperNode,
argumentsList,
options,
activeDefaultIndexes,
);
activeDefaultIndexes.delete(referencedParameterIndex);
if (resolved) {
resolvedExpressions.push(resolved);
}
}
if (resolvedExpressions.length === 0) {
return null;
}
return resolvedExpressions.length === 1
? resolvedExpressions[0]
: ts.factory.createArrayLiteralExpression(resolvedExpressions);
}
function propertyAccessExpressionForName(expression, propertyName) {
return /^[A-Za-z_$][\w$]*$/u.test(propertyName)
? ts.factory.createPropertyAccessExpression(expression, propertyName)
: ts.factory.createElementAccessExpression(
expression,
ts.factory.createStringLiteral(propertyName),
);
}
function propertyPathExpression(expression, propertyPath) {
let current = expression;
for (const propertyName of propertyPath) {
current = propertyAccessExpressionForName(current, propertyName);
}
return current;
}
function trackedPropertyPathExpression(expression, propertyPath) {
const unwrapped = unwrapExpression(expression);
if (!ts.isIdentifier(unwrapped)) {
return propertyPathExpression(expression, propertyPath);
}
const propertyName = propertyPath.join(".");
const property = lookupLegacyObjectPropertyEntry(unwrapped.text, propertyName);
if (property.found) {
if (property.value === true) {
return ts.factory.createStringLiteral("sessions.json");
}
return property.value === explicitUndefinedLegacyObjectPropertyValue
? ts.factory.createIdentifier("undefined")
: ts.factory.createStringLiteral("state/openclaw.sqlite");
}
return lookupKnownLegacyObjectLiteral(unwrapped.text)
? ts.factory.createIdentifier("undefined")
: null;
}
function objectLiteralPropertyPathInitializer(objectLiteral, propertyPath) {
let current = objectLiteral;
for (const [index, propertyName] of propertyPath.entries()) {
if (!ts.isObjectLiteralExpression(current)) {
return null;
}
const initializer = objectLiteralPropertyInitializer(current, propertyName);
if (!initializer || initializer === unknownObjectLiteralPropertyInitializer) {
return null;
}
if (index === propertyPath.length - 1) {
return initializer;
}
current = unwrapExpression(initializer);
if (ts.isIdentifier(current)) {
return trackedPropertyPathExpression(current, propertyPath.slice(index + 1));
}
}
return null;
}
function objectLiteralPropertyPathLegacyValue(
objectLiteral,
propertyPath,
maxScopeIndex = legacyObjectPropertyScopes.length - 1,
) {
if (propertyPath.length === 0) {
return expressionContainsLegacyStore(objectLiteral);
}
const [propertyName, ...remainingPath] = propertyPath;
let result = null;
for (const property of objectLiteral.properties) {
if (ts.isSpreadAssignment(property)) {
const spreadExpression = unwrapExpression(property.expression);
let spreadValue = null;
if (ts.isIdentifier(spreadExpression)) {
spreadValue = lookupLegacyObjectProperty(
spreadExpression.text,
propertyPath.join("."),
maxScopeIndex,
);
} else if (ts.isObjectLiteralExpression(spreadExpression)) {
spreadValue = objectLiteralPropertyPathLegacyValue(
spreadExpression,
propertyPath,
maxScopeIndex,
);
} else if (expressionContainsLegacyStore(property.expression)) {
spreadValue = true;
}
if (spreadValue !== null) {
result = spreadValue;
}
continue;
}
if (ts.isPropertyAssignment(property) && propertyNameText(property.name) === propertyName) {
if (remainingPath.length === 0) {
result = expressionContainsLegacyStore(property.initializer);
continue;
}
const unwrapped = unwrapExpression(property.initializer);
result = ts.isObjectLiteralExpression(unwrapped)
? objectLiteralPropertyPathLegacyValue(unwrapped, remainingPath, maxScopeIndex)
: ts.isIdentifier(unwrapped)
? lookupLegacyObjectProperty(unwrapped.text, remainingPath.join("."), maxScopeIndex)
: null;
continue;
}
if (ts.isShorthandPropertyAssignment(property) && property.name.text === propertyName) {
result =
remainingPath.length === 0
? expressionContainsLegacyStore(property.name)
: lookupLegacyObjectProperty(
property.name.text,
remainingPath.join("."),
maxScopeIndex,
);
}
}
return result;
}
function bindingElementForProperty(bindingPattern, propertyName) {
for (const element of bindingPattern.elements) {
const boundPropertyName = element.propertyName
? propertyNameText(element.propertyName)
: ts.isIdentifier(element.name)
? element.name.text
: null;
if (boundPropertyName === propertyName) {
return element;
}
}
return null;
}
function bindingElementDefaultInitializerForPath(bindingPattern, propertyPath) {
const [propertyName, ...remainingPath] = propertyPath;
const element = propertyName ? bindingElementForProperty(bindingPattern, propertyName) : null;
if (!element) {
return null;
}
if (remainingPath.length === 0) {
return element.initializer ?? null;
}
return ts.isObjectBindingPattern(element.name)
? bindingElementDefaultInitializerForPath(element.name, remainingPath)
: null;
}
function propertyPathInitializerFromExpression(expression, propertyPath) {
if (propertyPath.length === 0) {
return expression;
}
const unwrapped = unwrapExpression(expression);
if (ts.isObjectLiteralExpression(unwrapped)) {
return objectLiteralPropertyPathInitializer(unwrapped, propertyPath);
}
if (ts.isIdentifier(unwrapped)) {
return trackedPropertyPathExpression(unwrapped, propertyPath);
}
return null;
}
function bindingElementAncestorDefaultInitializerForObjectLiteral(
bindingPattern,
propertyPath,
objectLiteral,
resolveSpreadProperty,
) {
const [propertyName, ...remainingPath] = propertyPath;
const element = propertyName ? bindingElementForProperty(bindingPattern, propertyName) : null;
if (!element || remainingPath.length === 0) {
return null;
}
const propertyState = objectLiteralPropertyInitializerState(
objectLiteral,
propertyName,
resolveSpreadProperty,
);
if (
(propertyState.kind === "missing" || propertyState.kind === "undefined") &&
element.initializer
) {
return propertyPathInitializerFromExpression(element.initializer, remainingPath);
}
if (
propertyState.kind === "initializer" &&
ts.isObjectLiteralExpression(unwrapExpression(propertyState.initializer)) &&
ts.isObjectBindingPattern(element.name)
) {
return bindingElementAncestorDefaultInitializerForObjectLiteral(
element.name,
remainingPath,
unwrapExpression(propertyState.initializer),
resolveSpreadProperty,
);
}
if (
propertyState.kind === "initializer" &&
ts.isIdentifier(unwrapExpression(propertyState.initializer)) &&
ts.isObjectBindingPattern(element.name)
) {
return bindingElementAncestorDefaultInitializerForIdentifier(
element.name,
remainingPath,
unwrapExpression(propertyState.initializer).text,
);
}
return null;
}
function bindingElementAncestorDefaultInitializerForIdentifier(
bindingPattern,
propertyPath,
sourceName,
) {
const [propertyName, ...remainingPath] = propertyPath;
const element = propertyName ? bindingElementForProperty(bindingPattern, propertyName) : null;
if (!element || remainingPath.length === 0) {
return null;
}
const parentProperty = lookupLegacyObjectPropertyEntry(sourceName, propertyName);
const parentMissingOrUndefined =
(!parentProperty.found && lookupKnownLegacyObjectLiteral(sourceName)) ||
parentProperty.value === explicitUndefinedLegacyObjectPropertyValue;
if (parentMissingOrUndefined && element.initializer) {
return propertyPathInitializerFromExpression(element.initializer, remainingPath);
}
const parentObjectName = `${sourceName}.${propertyName}`;
if (
parentProperty.found &&
lookupKnownLegacyObjectLiteral(parentObjectName) &&
ts.isObjectBindingPattern(element.name)
) {
return bindingElementAncestorDefaultInitializerForIdentifier(
element.name,
remainingPath,
parentObjectName,
);
}
return null;
}
function bindingElementAncestorDefaultInitializer(
bindingPattern,
propertyPath,
sourceExpression,
resolveSpreadProperty = null,
) {
const source = unwrapExpression(sourceExpression);
if (ts.isObjectLiteralExpression(source)) {
return bindingElementAncestorDefaultInitializerForObjectLiteral(
bindingPattern,
propertyPath,
source,
resolveSpreadProperty,
);
}
if (ts.isIdentifier(source)) {
return bindingElementAncestorDefaultInitializerForIdentifier(
bindingPattern,
propertyPath,
source.text,
);
}
return null;
}
function appliedBindingElementDefaultInitializer(
bindingPattern,
propertyPath,
sourceExpression,
resolveSpreadProperty = null,
) {
const leafInitializer = bindingElementDefaultInitializerForPath(bindingPattern, propertyPath);
if (
leafInitializer &&
objectBindingPropertyDefaultApplies(
bindingPattern,
propertyPath,
sourceExpression,
resolveSpreadProperty,
)
) {
return leafInitializer;
}
return bindingElementAncestorDefaultInitializer(
bindingPattern,
propertyPath,
sourceExpression,
resolveSpreadProperty,
);
}
function objectBindingPropertyDefaultAppliesForObjectLiteral(
bindingPattern,
propertyPath,
objectLiteral,
resolveSpreadProperty,
) {
const [propertyName, ...remainingPath] = propertyPath;
const element = propertyName ? bindingElementForProperty(bindingPattern, propertyName) : null;
if (!element) {
return false;
}
const propertyState = objectLiteralPropertyInitializerState(
objectLiteral,
propertyName,
resolveSpreadProperty,
);
if (remainingPath.length === 0) {
return propertyState.kind === "missing" || propertyState.kind === "undefined";
}
if (!ts.isObjectBindingPattern(element.name)) {
return false;
}
if (
propertyState.kind === "initializer" &&
ts.isObjectLiteralExpression(unwrapExpression(propertyState.initializer))
) {
return objectBindingPropertyDefaultAppliesForObjectLiteral(
element.name,
remainingPath,
unwrapExpression(propertyState.initializer),
resolveSpreadProperty,
);
}
if (
propertyState.kind === "initializer" &&
ts.isIdentifier(unwrapExpression(propertyState.initializer))
) {
return objectBindingPropertyDefaultAppliesForIdentifier(
element.name,
remainingPath,
unwrapExpression(propertyState.initializer).text,
);
}
if (
(propertyState.kind === "missing" || propertyState.kind === "undefined") &&
element.initializer &&
ts.isObjectLiteralExpression(unwrapExpression(element.initializer))
) {
return objectBindingPropertyDefaultAppliesForObjectLiteral(
element.name,
remainingPath,
unwrapExpression(element.initializer),
resolveSpreadProperty,
);
}
return false;
}
function objectBindingPropertyDefaultAppliesForIdentifier(
bindingPattern,
propertyPath,
sourceName,
) {
const [propertyName, ...remainingPath] = propertyPath;
const element = propertyName ? bindingElementForProperty(bindingPattern, propertyName) : null;
if (!element) {
return false;
}
const exactProperty = lookupLegacyObjectPropertyEntry(sourceName, propertyPath.join("."));
if (remainingPath.length === 0) {
return exactProperty.found
? exactProperty.value === explicitUndefinedLegacyObjectPropertyValue
: lookupKnownLegacyObjectLiteral(sourceName);
}
if (exactProperty.found) {
return exactProperty.value === explicitUndefinedLegacyObjectPropertyValue;
}
if (!ts.isObjectBindingPattern(element.name)) {
return false;
}
const parentProperty = lookupLegacyObjectPropertyEntry(sourceName, propertyName);
const parentObjectName = `${sourceName}.${propertyName}`;
if (parentProperty.found && lookupKnownLegacyObjectLiteral(parentObjectName)) {
return objectBindingPropertyDefaultAppliesForIdentifier(
element.name,
remainingPath,
parentObjectName,
);
}
const parentMissingOrUndefined =
(!parentProperty.found && lookupKnownLegacyObjectLiteral(sourceName)) ||
parentProperty.value === explicitUndefinedLegacyObjectPropertyValue;
if (
parentMissingOrUndefined &&
element.initializer &&
ts.isObjectLiteralExpression(unwrapExpression(element.initializer))
) {
return objectBindingPropertyDefaultAppliesForObjectLiteral(
element.name,
remainingPath,
unwrapExpression(element.initializer),
null,
);
}
return false;
}
function objectBindingPropertyDefaultApplies(
bindingPattern,
propertyPath,
sourceExpression,
resolveSpreadProperty = null,
) {
if (propertyPath.length === 0) {
return false;
}
const source = unwrapExpression(sourceExpression);
if (ts.isObjectLiteralExpression(source)) {
return objectBindingPropertyDefaultAppliesForObjectLiteral(
bindingPattern,
propertyPath,
source,
resolveSpreadProperty,
);
}
if (ts.isIdentifier(source)) {
return objectBindingPropertyDefaultAppliesForIdentifier(
bindingPattern,
propertyPath,
source.text,
);
}
return false;
}
function resolveEarlierParameterBindingExpression(
binding,
wrapperNode,
argumentsList,
options,
activeDefaultIndexes,
) {
const resolved = callArgumentOrParameterDefault(
wrapperNode,
argumentsList,
binding.index,
options,
activeDefaultIndexes,
);
if (!resolved || !binding.propertyName) {
return resolved;
}
const propertyPath = binding.propertyName.split(".");
const unwrapped = unwrapExpression(resolved);
if (ts.isObjectLiteralExpression(unwrapped)) {
return objectLiteralPropertyPathInitializer(unwrapped, propertyPath);
}
return trackedPropertyPathExpression(resolved, propertyPath);
}
function resolveBindingDefaultInitializerExpression(
initializer,
wrapperNode,
argumentsList,
parameter,
options = {},
) {
if (!wrapperNode || !parameter) {
return initializer;
}
const parameterPosition = wrapperNode.parameters.findIndex(
(candidate) => candidate === parameter,
);
if (parameterPosition <= 0) {
return options.allowLexicalIdentifierDefault === false &&
expressionContainsIdentifier(unwrapExpression(initializer))
? null
: initializer;
}
const unwrapped = unwrapExpression(initializer);
if (!ts.isIdentifier(unwrapped)) {
if (options.allowLexicalIdentifierDefault !== false) {
return initializer;
}
const resolved = resolveEarlierParameterDefaultExpression(
unwrapped,
wrapperNode,
argumentsList,
parameterPosition,
options,
new Set(),
);
if (resolved) {
return resolved;
}
const spreadOnlyObjectLiteral =
ts.isObjectLiteralExpression(unwrapped) &&
objectLiteralIdentifiersAreSpreadSourcesOnly(unwrapped);
return earlierParameterReferenceIndexes(unwrapped, wrapperNode, parameterPosition).size ===
0 &&
(!expressionContainsIdentifier(unwrapped) || spreadOnlyObjectLiteral)
? initializer
: null;
}
const parameterBinding = earlierParameterBindingForIdentifier(
unwrapped.text,
wrapperNode,
parameterPosition,
);
if (!parameterBinding) {
return options.allowLexicalIdentifierDefault === false ? null : initializer;
}
return resolveEarlierParameterBindingExpression(
parameterBinding,
wrapperNode,
argumentsList,
options,
new Set(),
);
}
function wrapperRecordForNode(node) {
const requireAliasSnapshot = visibleRequireAliasSnapshot();
return {
aliases: visibleFsWriteAliases(),
createRequireShadows: visibleCreateRequireShadows(),
lexicalScopeIndex: wrapperFunctionScopes.length - 1,
moduleBindings: visibleFsModuleBindings(),
moduleProperties: visibleFsModuleProperties(),
node,
requireAliases: requireAliasSnapshot.aliases,
requireAliasSourceScopes: requireAliasSnapshot.sourceScopes,
};
}
function registerWrapperFunction(name, node) {
currentWrapperFunctionScope().set(name, wrapperRecordForNode(node));
}
function setWrapperFunctionValue(scope, name, value, conditionalWrite) {
if (value) {
if (conditionalWrite && scope.has(name)) {
scope.set(name, [...wrapperRecords(scope.get(name)), ...wrapperRecords(value)]);
} else {
scope.set(name, value);
}
} else if (!conditionalWrite) {
scope.set(name, null);
}
}
function clearWrapperObjectMethods(scope, objectName) {
const prefix = `${objectName}.`;
for (const name of scope.keys()) {
if (name.startsWith(prefix)) {
scope.set(name, null);
}
}
}
function clearWrapperObjectMethod(scope, methodName) {
scope.set(methodName, null);
clearWrapperObjectMethods(scope, methodName);
}
function shadowVisibleWrapperObjectMethods(objectName) {
const prefix = `${objectName}.`;
const currentScope = currentWrapperFunctionScope();
for (const scope of wrapperFunctionScopes) {
for (const name of scope.keys()) {
if (name.startsWith(prefix)) {
currentScope.set(name, null);
}
}
}
}
function copyWrapperObjectMethods(
targetName,
sourceName,
scope = currentWrapperFunctionScope(),
conditionalWrite = false,
) {
const sourcePrefix = `${sourceName}.`;
let copiedCount = 0;
const visibleScopeIndexes = [];
for (let index = wrapperFunctionScopes.length - 1; index >= 0; index--) {
visibleScopeIndexes.push(index);
if (wrapperFunctionScopes[index].has(sourceName) || legacyPathScopes[index].has(sourceName)) {
break;
}
}
for (const index of visibleScopeIndexes.toReversed()) {
const sourceScope = wrapperFunctionScopes[index];
for (const [name, value] of sourceScope) {
if (!name.startsWith(sourcePrefix)) {
continue;
}
setWrapperFunctionValue(
scope,
`${targetName}.${name.slice(sourcePrefix.length)}`,
cloneWrapperFunctionValue(value),
conditionalWrite,
);
copiedCount += 1;
}
}
return copiedCount;
}
function registerWrapperObjectMethods(
objectName,
initializer,
scope = currentWrapperFunctionScope(),
conditionalWrite = false,
) {
const objectLiteral = unwrapExpression(initializer);
if (!ts.isObjectLiteralExpression(objectLiteral)) {
return;
}
const seenProperties = new Set();
for (const property of objectLiteral.properties) {
const propertyName =
ts.isMethodDeclaration(property) || ts.isPropertyAssignment(property)
? propertyNameText(property.name)
: ts.isShorthandPropertyAssignment(property)
? property.name.text
: null;
const methodName = propertyName ? `${objectName}.${propertyName}` : null;
if (methodName && seenProperties.has(propertyName)) {
clearWrapperObjectMethod(scope, methodName);
}
if (propertyName) {
seenProperties.add(propertyName);
}
if (ts.isSpreadAssignment(property)) {
const spreadExpression = unwrapExpression(property.expression);
const spreadSource = ts.isIdentifier(spreadExpression)
? spreadExpression.text
: callExpressionName(spreadExpression);
const copiedCount = spreadSource
? copyWrapperObjectMethods(objectName, spreadSource, scope, conditionalWrite)
: 0;
if (copiedCount === 0) {
clearWrapperObjectMethods(scope, objectName);
}
continue;
}
if (ts.isMethodDeclaration(property)) {
if (methodName) {
setWrapperFunctionValue(
scope,
methodName,
wrapperRecordForNode(property),
conditionalWrite,
);
}
continue;
}
if (
ts.isPropertyAssignment(property) &&
(ts.isFunctionExpression(unwrapExpression(property.initializer)) ||
ts.isArrowFunction(unwrapExpression(property.initializer)))
) {
if (methodName) {
setWrapperFunctionValue(
scope,
methodName,
wrapperRecordForNode(unwrapExpression(property.initializer)),
conditionalWrite,
);
}
continue;
}
if (ts.isPropertyAssignment(property)) {
const propertyInitializer = unwrapExpression(property.initializer);
if (propertyName && ts.isObjectLiteralExpression(propertyInitializer)) {
registerWrapperObjectMethods(
`${objectName}.${propertyName}`,
propertyInitializer,
scope,
conditionalWrite,
);
}
}
if (
ts.isPropertyAssignment(property) &&
ts.isIdentifier(unwrapExpression(property.initializer))
) {
const sourceName = unwrapExpression(property.initializer).text;
const wrapper = resolveWrapperFunction(sourceName);
if (methodName && wrapper) {
setWrapperFunctionValue(
scope,
methodName,
cloneWrapperFunctionValue(wrapper),
conditionalWrite,
);
}
if (methodName) {
copyWrapperObjectMethods(methodName, sourceName, scope, conditionalWrite);
}
continue;
}
if (ts.isShorthandPropertyAssignment(property)) {
const wrapper = resolveWrapperFunction(property.name.text);
if (methodName && wrapper) {
setWrapperFunctionValue(
scope,
methodName,
cloneWrapperFunctionValue(wrapper),
conditionalWrite,
);
}
if (methodName) {
copyWrapperObjectMethods(methodName, property.name.text, scope, conditionalWrite);
}
}
}
}
function wrapperRecords(value) {
if (
!value ||
value === explicitUndefinedNestedWrapperValue ||
isNestedWrapperObjectMarker(value)
) {
return [];
}
return Array.isArray(value) ? value : [value];
}
function isNestedWrapperObjectMarker(value) {
return (
value === knownObjectLiteralNestedWrapperValue || value === unknownNestedWrapperObjectValue
);
}
function cloneWrapperRecord(record) {
return {
aliases: new Map(record.aliases),
closesOverCurrentWrapper: record.closesOverCurrentWrapper === true,
createRequireShadows: new Set(record.createRequireShadows),
lexicalScope: record.lexicalScope,
localScope: record.localScope,
lexicalScopeIndex: record.lexicalScopeIndex,
moduleBindings: new Map(record.moduleBindings),
moduleProperties: new Map(record.moduleProperties),
node: record.node,
requireAliases: new Map(record.requireAliases),
requireAliasSourceScopes: new Map(record.requireAliasSourceScopes),
};
}
function cloneWrapperFunctionValue(value) {
if (!value) {
return null;
}
if (value === explicitUndefinedNestedWrapperValue) {
return explicitUndefinedNestedWrapperValue;
}
if (value === knownObjectLiteralNestedWrapperValue) {
return knownObjectLiteralNestedWrapperValue;
}
if (value === unknownNestedWrapperObjectValue) {
return unknownNestedWrapperObjectValue;
}
const records = wrapperRecords(value).map(cloneWrapperRecord);
return Array.isArray(value) ? records : records[0];
}
function refreshCurrentWrapperFunctionAliases() {
const aliases = visibleFsWriteAliases();
const moduleBindings = visibleFsModuleBindings();
const moduleProperties = visibleFsModuleProperties();
const requireAliasSnapshot = visibleRequireAliasSnapshot();
const createRequireShadows = visibleCreateRequireShadows();
const currentLexicalScopeIndex = wrapperFunctionScopes.length - 1;
for (const value of currentWrapperFunctionScope().values()) {
for (const record of wrapperRecords(value)) {
if (record.lexicalScopeIndex !== currentLexicalScopeIndex) {
continue;
}
record.aliases = aliases;
record.moduleBindings = moduleBindings;
record.moduleProperties = moduleProperties;
record.requireAliases = requireAliasSnapshot.aliases;
record.requireAliasSourceScopes = requireAliasSnapshot.sourceScopes;
record.createRequireShadows = createRequireShadows;
}
}
}
function refreshWrapperRequireAliasesAtScope(scopeIndex) {
const wrapperScope = wrapperFunctionScopes[scopeIndex];
if (!wrapperScope) {
return;
}
const requireAliasSnapshot = visibleRequireAliasSnapshot(scopeIndex);
for (const value of wrapperScope.values()) {
for (const record of wrapperRecords(value)) {
if (record.lexicalScopeIndex === scopeIndex) {
record.requireAliases = requireAliasSnapshot.aliases;
record.requireAliasSourceScopes = requireAliasSnapshot.sourceScopes;
continue;
}
if (record.lexicalScopeIndex > scopeIndex) {
for (const [name, alias] of requireAliasSnapshot.aliases) {
const recordSourceScope = record.requireAliasSourceScopes.get(name);
if (recordSourceScope === undefined || recordSourceScope <= scopeIndex) {
record.requireAliases.set(name, alias);
record.requireAliasSourceScopes.set(
name,
requireAliasSnapshot.sourceScopes.get(name) ?? scopeIndex,
);
}
}
}
}
}
}
function refreshWrapperRequireAliasesFromScope(scopeIndex) {
for (let index = scopeIndex; index < wrapperFunctionScopes.length; index++) {
refreshWrapperRequireAliasesAtScope(index);
}
}
function registerHoistedWrapperFunctions(statements) {
for (const statement of statements) {
if (ts.isFunctionDeclaration(statement) && statement.name) {
markRequireShadows(statement.name);
markCreateRequireShadows(statement.name);
currentRequireAliasScope().set(statement.name.text, false);
registerWrapperFunction(statement.name.text, statement);
}
}
}
function resolveWrapperFunction(name) {
for (let index = wrapperFunctionScopes.length - 1; index >= 0; index--) {
const wrapperScope = wrapperFunctionScopes[index];
if (wrapperScope.has(name)) {
return wrapperScope.get(name);
}
if (legacyPathScopes[index].has(name)) {
return null;
}
}
return null;
}
function resolveWrapperExpression(expression) {
const unwrapped = unwrapExpression(expression);
if (ts.isIdentifier(unwrapped)) {
return resolveWrapperFunction(unwrapped.text);
}
const name = callExpressionName(unwrapped);
return name ? resolveWrapperFunction(name) : null;
}
function pathArgumentContainsLegacyStore(argument) {
return expressionContainsLegacyStore(argument);
}
function isUndefinedExpression(expression) {
const unwrapped = unwrapExpression(expression);
return (
(ts.isIdentifier(unwrapped) && unwrapped.text === "undefined") ||
ts.isVoidExpression(unwrapped)
);
}
function isKnownUndefinedExpression(expression) {
const unwrapped = unwrapExpression(expression);
return (
isUndefinedExpression(unwrapped) ||
(ts.isIdentifier(unwrapped) && resolveKnownUndefinedIdentifier(unwrapped.text))
);
}
function callExpressionName(expression) {
const callee = unwrapExpression(expression);
const pathParts = propertyAccessPath(callee);
return pathParts ? pathParts.join(".") : null;
}
function objectArgumentPropertyContainsLegacyStore(argument, propertyName) {
const propertyPath = propertyName.split(".");
const unwrapped = unwrapExpression(argument);
if (ts.isObjectLiteralExpression(unwrapped)) {
return objectExpressionPropertyPathContainsLegacyStore(unwrapped, propertyPath);
}
if (ts.isIdentifier(unwrapped)) {
return lookupLegacyObjectProperty(unwrapped.text, propertyPath.join(".")) === true;
}
return expressionContainsLegacyStore(argument);
}
function objectExpressionPropertyLegacyValue(
expression,
propertyName,
maxScopeIndex = legacyObjectPropertyScopes.length - 1,
) {
const propertyPath = propertyName.split(".");
const unwrapped = unwrapExpression(expression);
if (ts.isObjectLiteralExpression(unwrapped)) {
return objectLiteralPropertyPathLegacyValue(unwrapped, propertyPath, maxScopeIndex);
}
if (ts.isIdentifier(unwrapped)) {
return lookupLegacyObjectProperty(unwrapped.text, propertyPath.join("."), maxScopeIndex);
}
return null;
}
function objectExpressionPropertyPathMayUseBindingDefault(expression, propertyPath) {
const unwrapped = unwrapExpression(expression);
if (ts.isIdentifier(unwrapped)) {
const property = lookupLegacyObjectPropertyEntry(unwrapped.text, propertyPath.join("."));
if (property.found) {
return property.value === explicitUndefinedLegacyObjectPropertyValue;
}
return lookupKnownLegacyObjectLiteral(unwrapped.text);
}
if (!ts.isObjectLiteralExpression(unwrapped) || propertyPath.length === 0) {
return false;
}
const [propertyName, ...remainingPath] = propertyPath;
const state = objectLiteralPropertyInitializerState(unwrapped, propertyName);
if (remainingPath.length === 0) {
return state.kind === "missing" || state.kind === "undefined";
}
return state.kind === "initializer"
? objectExpressionPropertyPathMayUseBindingDefault(state.initializer, remainingPath)
: state.kind === "missing" || state.kind === "undefined";
}
function objectExpressionPropertyPathContainsLegacyStore(
expression,
propertyPath,
maxScopeIndex = legacyObjectPropertyScopes.length - 1,
) {
if (propertyPath.length === 0) {
return pathArgumentContainsLegacyStore(expression);
}
const unwrapped = unwrapExpression(expression);
if (ts.isIdentifier(unwrapped)) {
return (
lookupLegacyObjectProperty(unwrapped.text, propertyPath.join("."), maxScopeIndex) === true
);
}
if (!ts.isObjectLiteralExpression(unwrapped)) {
return expressionContainsLegacyStore(expression);
}
const [propertyName, ...remainingPath] = propertyPath;
if (remainingPath.length === 0) {
return objectLiteralPropertyContainsLegacyStore(unwrapped, propertyName);
}
let result = false;
for (const property of unwrapped.properties) {
if (ts.isSpreadAssignment(property)) {
const spreadExpression = unwrapExpression(property.expression);
let spreadValue = null;
if (ts.isIdentifier(spreadExpression)) {
const spreadProperty = lookupLegacyObjectPropertyEntry(
spreadExpression.text,
propertyPath.join("."),
maxScopeIndex,
);
spreadValue = spreadProperty.found ? spreadProperty.value === true : null;
} else if (ts.isObjectLiteralExpression(spreadExpression)) {
spreadValue = objectLiteralPropertyPathLegacyValue(
spreadExpression,
propertyPath,
maxScopeIndex,
);
} else if (expressionContainsLegacyStore(property.expression)) {
spreadValue = true;
}
if (spreadValue !== null) {
result = spreadValue === true;
}
continue;
}
if (ts.isPropertyAssignment(property) && propertyNameText(property.name) === propertyName) {
result =
!isKnownUndefinedExpression(property.initializer) &&
objectExpressionPropertyPathContainsLegacyStore(
property.initializer,
remainingPath,
maxScopeIndex,
);
}
if (ts.isShorthandPropertyAssignment(property) && property.name.text === propertyName) {
result = objectExpressionPropertyPathContainsLegacyStore(
property.name,
remainingPath,
maxScopeIndex,
);
}
}
return result;
}
function parameterDefaultContainsLegacyStore(
initializer,
wrapperNode,
argumentsList,
parameterIndex,
maxScopeIndex = legacyObjectPropertyScopes.length - 1,
) {
return defaultPathExpressionContainsLegacyStore(
initializer,
wrapperNode,
argumentsList,
parameterIndex,
new Set(),
maxScopeIndex,
);
}
function defaultPathExpressionContainsLegacyStore(
expression,
wrapperNode,
argumentsList,
parameterIndex,
activeDefaultIndexes,
maxScopeIndex,
) {
if (earlierParameterReferenceIndexes(expression, wrapperNode, parameterIndex).size === 0) {
return pathArgumentContainsLegacyStore(expression);
}
const unwrapped = unwrapExpression(expression);
if (ts.isIdentifier(unwrapped)) {
const binding = earlierParameterBindingForIdentifier(
unwrapped.text,
wrapperNode,
parameterIndex,
);
if (!binding || activeDefaultIndexes.has(binding.index)) {
return false;
}
activeDefaultIndexes.add(binding.index);
const resolved = resolveEarlierParameterBindingExpression(
binding,
wrapperNode,
argumentsList,
{ allowLexicalIdentifierDefault: false },
activeDefaultIndexes,
);
activeDefaultIndexes.delete(binding.index);
return resolved ? pathArgumentContainsLegacyStore(resolved) : false;
}
if (ts.isConditionalExpression(unwrapped)) {
return (
defaultPathExpressionContainsLegacyStore(
unwrapped.whenTrue,
wrapperNode,
argumentsList,
parameterIndex,
activeDefaultIndexes,
maxScopeIndex,
) ||
defaultPathExpressionContainsLegacyStore(
unwrapped.whenFalse,
wrapperNode,
argumentsList,
parameterIndex,
activeDefaultIndexes,
maxScopeIndex,
)
);
}
if (ts.isPropertyAccessExpression(unwrapped) || ts.isElementAccessExpression(unwrapped)) {
const propertyPath = rootedPropertyAccessPath(unwrapped);
if (propertyPath) {
const binding = earlierParameterBindingForIdentifier(
propertyPath.rootName,
wrapperNode,
parameterIndex,
);
if (binding && !activeDefaultIndexes.has(binding.index)) {
activeDefaultIndexes.add(binding.index);
const resolved = resolveEarlierParameterBindingExpression(
binding,
wrapperNode,
argumentsList,
{ allowLexicalIdentifierDefault: false },
activeDefaultIndexes,
);
activeDefaultIndexes.delete(binding.index);
return resolved
? objectExpressionPropertyPathContainsLegacyStore(
resolved,
propertyPath.properties,
maxScopeIndex,
)
: false;
}
}
return false;
}
if (ts.isBinaryExpression(unwrapped)) {
const operator = unwrapped.operatorToken.kind;
if (operator === ts.SyntaxKind.AmpersandAmpersandToken) {
return defaultPathExpressionContainsLegacyStore(
unwrapped.right,
wrapperNode,
argumentsList,
parameterIndex,
activeDefaultIndexes,
maxScopeIndex,
);
}
if (
operator === ts.SyntaxKind.BarBarToken ||
operator === ts.SyntaxKind.QuestionQuestionToken ||
operator === ts.SyntaxKind.PlusToken
) {
return (
defaultPathExpressionContainsLegacyStore(
unwrapped.left,
wrapperNode,
argumentsList,
parameterIndex,
activeDefaultIndexes,
maxScopeIndex,
) ||
defaultPathExpressionContainsLegacyStore(
unwrapped.right,
wrapperNode,
argumentsList,
parameterIndex,
activeDefaultIndexes,
maxScopeIndex,
)
);
}
if (
operator === ts.SyntaxKind.CommaToken ||
(operator >= ts.SyntaxKind.FirstAssignment && operator <= ts.SyntaxKind.LastAssignment)
) {
return defaultPathExpressionContainsLegacyStore(
unwrapped.right,
wrapperNode,
argumentsList,
parameterIndex,
activeDefaultIndexes,
maxScopeIndex,
);
}
return false;
}
if (ts.isTemplateExpression(unwrapped)) {
return unwrapped.templateSpans.some((span) =>
defaultPathExpressionContainsLegacyStore(
span.expression,
wrapperNode,
argumentsList,
parameterIndex,
activeDefaultIndexes,
maxScopeIndex,
),
);
}
if (ts.isCallExpression(unwrapped)) {
const receiver = ts.isPropertyAccessExpression(unwrapped.expression)
? unwrapped.expression.expression
: unwrapped.expression;
return (
defaultPathExpressionContainsLegacyStore(
receiver,
wrapperNode,
argumentsList,
parameterIndex,
activeDefaultIndexes,
maxScopeIndex,
) ||
[...unwrapped.arguments].some((argument) =>
defaultPathExpressionContainsLegacyStore(
argument,
wrapperNode,
argumentsList,
parameterIndex,
activeDefaultIndexes,
maxScopeIndex,
),
)
);
}
let containsLegacyStore = false;
ts.forEachChild(unwrapped, (child) => {
if (
defaultPathExpressionContainsLegacyStore(
child,
wrapperNode,
argumentsList,
parameterIndex,
activeDefaultIndexes,
maxScopeIndex,
)
) {
containsLegacyStore = true;
}
});
return containsLegacyStore;
}
function rootedPropertyAccessPath(expression) {
const properties = [];
let current = unwrapExpression(expression);
while (true) {
if (ts.isPropertyAccessExpression(current)) {
properties.unshift(current.name.text);
current = unwrapExpression(current.expression);
continue;
}
if (ts.isElementAccessExpression(current)) {
const propertyName = elementAccessName(current.argumentExpression);
if (!propertyName) {
return null;
}
properties.unshift(propertyName);
current = unwrapExpression(current.expression);
continue;
}
break;
}
return ts.isIdentifier(current) ? { rootName: current.text, properties } : null;
}
function wrapperObjectBindingDefaultContainsLegacyStore(
parameter,
propertyName,
sourceExpression,
wrapperNode,
argumentsList,
parameterIndex,
maxScopeIndex = legacyObjectPropertyScopes.length - 1,
) {
if (!parameter || !ts.isObjectBindingPattern(parameter.name) || !sourceExpression) {
return false;
}
const propertyPath = propertyName.split(".");
const initializer = appliedBindingElementDefaultInitializer(
parameter.name,
propertyPath,
sourceExpression,
);
if (!initializer) {
return false;
}
return parameterDefaultContainsLegacyStore(
initializer,
wrapperNode,
argumentsList,
parameterIndex,
maxScopeIndex,
);
}
function wrapperPathUseContainsLegacyStore(record, index, propertyName, argumentsList) {
const wrapperNode = record.node;
const maxScopeIndex = record.lexicalScopeIndex;
const parameter = wrapperNode.parameters[index] ?? null;
const argument = argumentsList[index];
const argumentUsesDefault = !argument || isKnownUndefinedExpression(argument);
if (propertyName === null) {
if (!argumentUsesDefault) {
return pathArgumentContainsLegacyStore(argument);
}
return parameter?.initializer
? parameterDefaultContainsLegacyStore(
parameter.initializer,
wrapperNode,
argumentsList,
index,
maxScopeIndex,
)
: false;
}
if (!argumentUsesDefault) {
if (objectArgumentPropertyContainsLegacyStore(argument, propertyName)) {
return true;
}
return wrapperObjectBindingDefaultContainsLegacyStore(
parameter,
propertyName,
argument,
wrapperNode,
argumentsList,
index,
);
}
if (parameter?.initializer) {
const propertyPath = propertyName.split(".");
const defaultPropertyValue = objectExpressionPropertyLegacyValue(
parameter.initializer,
propertyName,
maxScopeIndex,
);
if (defaultPropertyValue === true) {
return true;
}
if (
defaultPropertyValue === false &&
!objectExpressionPropertyPathMayUseBindingDefault(parameter.initializer, propertyPath)
) {
return false;
}
}
return wrapperObjectBindingDefaultContainsLegacyStore(
parameter,
propertyName,
parameter?.initializer ?? null,
wrapperNode,
argumentsList,
index,
maxScopeIndex,
);
}
function visitInConditionalExecution(node, branchEffects = null) {
conditionalExecutionScopes.push(true);
fsWriteAliasScopes.push(new Map());
fsSafeStoreFactoryAliasScopes.push(new Map());
fsSafeStoreScopes.push(new Map());
fsSafeJsonStoreScopes.push(new Map());
fsModuleBindingScopes.push(new Map());
fsModulePropertyScopes.push(new Map());
requireAliasScopes.push(new Map());
requireShadowScopes.push(new Set());
createRequireShadowScopes.push(new Set());
legacyPathScopes.push(new Map());
literalTextScopes.push(new Map());
knownUndefinedScopes.push(new Map());
legacyKnownObjectLiteralScopes.push(new Map());
legacyObjectPropertyScopes.push(new Map());
wrapperFunctionScopes.push(new Map());
branchEffectScopes.push(branchEffects);
visit(node);
branchEffectScopes.pop();
wrapperFunctionScopes.pop();
legacyObjectPropertyScopes.pop();
legacyKnownObjectLiteralScopes.pop();
knownUndefinedScopes.pop();
literalTextScopes.pop();
legacyPathScopes.pop();
fsModulePropertyScopes.pop();
fsModuleBindingScopes.pop();
fsSafeJsonStoreScopes.pop();
fsSafeStoreScopes.pop();
fsSafeStoreFactoryAliasScopes.pop();
fsWriteAliasScopes.pop();
createRequireShadowScopes.pop();
requireShadowScopes.pop();
requireAliasScopes.pop();
conditionalExecutionScopes.pop();
}
function visit(node) {
if (isTypeSyntaxNode(node)) {
return;
}
if (node === sourceFile) {
registerHoistedWrapperFunctions(sourceFile.statements);
}
if (ts.isIfStatement(node)) {
visit(node.expression);
const thenEffects = node.elseStatement ? createBranchEffects() : null;
const elseEffects = node.elseStatement ? createBranchEffects() : null;
visitInConditionalExecution(node.thenStatement, thenEffects);
if (node.elseStatement) {
visitInConditionalExecution(node.elseStatement, elseEffects);
mergeExhaustiveBranchEffects(thenEffects, elseEffects);
}
return;
}
if (ts.isWhileStatement(node)) {
visit(node.expression);
visitInConditionalExecution(node.statement);
return;
}
if (ts.isDoStatement(node)) {
visitInConditionalExecution(node.statement);
visit(node.expression);
return;
}
if (ts.isForStatement(node)) {
fsWriteAliasScopes.push(new Map());
fsSafeStoreFactoryAliasScopes.push(new Map());
fsSafeStoreScopes.push(new Map());
fsSafeJsonStoreScopes.push(new Map());
fsModuleBindingScopes.push(new Map());
fsModulePropertyScopes.push(new Map());
requireAliasScopes.push(new Map());
requireShadowScopes.push(new Set());
createRequireShadowScopes.push(new Set());
legacyPathScopes.push(new Map());
literalTextScopes.push(new Map());
knownUndefinedScopes.push(new Map());
legacyKnownObjectLiteralScopes.push(new Map());
legacyObjectPropertyScopes.push(new Map());
wrapperFunctionScopes.push(new Map());
conditionalExecutionScopes.push(false);
if (node.initializer) {
visit(node.initializer);
}
if (node.condition) {
visit(node.condition);
}
if (node.incrementor) {
visitInConditionalExecution(node.incrementor);
}
visitInConditionalExecution(node.statement);
conditionalExecutionScopes.pop();
wrapperFunctionScopes.pop();
legacyObjectPropertyScopes.pop();
legacyKnownObjectLiteralScopes.pop();
knownUndefinedScopes.pop();
literalTextScopes.pop();
legacyPathScopes.pop();
fsModulePropertyScopes.pop();
fsModuleBindingScopes.pop();
fsSafeJsonStoreScopes.pop();
fsSafeStoreScopes.pop();
fsSafeStoreFactoryAliasScopes.pop();
fsWriteAliasScopes.pop();
createRequireShadowScopes.pop();
requireShadowScopes.pop();
requireAliasScopes.pop();
return;
}
if (ts.isForInStatement(node) || ts.isForOfStatement(node)) {
visit(node.expression);
fsWriteAliasScopes.push(new Map());
fsSafeStoreFactoryAliasScopes.push(new Map());
fsSafeStoreScopes.push(new Map());
fsSafeJsonStoreScopes.push(new Map());
fsModuleBindingScopes.push(new Map());
fsModulePropertyScopes.push(new Map());
requireAliasScopes.push(new Map());
requireShadowScopes.push(new Set());
createRequireShadowScopes.push(new Set());
legacyPathScopes.push(new Map());
literalTextScopes.push(new Map());
knownUndefinedScopes.push(new Map());
legacyKnownObjectLiteralScopes.push(new Map());
legacyObjectPropertyScopes.push(new Map());
wrapperFunctionScopes.push(new Map());
conditionalExecutionScopes.push(true);
visit(node.initializer);
if (ts.isForOfStatement(node)) {
markArrayBindingPatternFromForOf(node.initializer, node.expression);
}
visit(node.statement);
conditionalExecutionScopes.pop();
wrapperFunctionScopes.pop();
legacyObjectPropertyScopes.pop();
legacyKnownObjectLiteralScopes.pop();
knownUndefinedScopes.pop();
literalTextScopes.pop();
legacyPathScopes.pop();
fsModulePropertyScopes.pop();
fsModuleBindingScopes.pop();
fsSafeJsonStoreScopes.pop();
fsSafeStoreScopes.pop();
fsSafeStoreFactoryAliasScopes.pop();
fsWriteAliasScopes.pop();
createRequireShadowScopes.pop();
requireShadowScopes.pop();
requireAliasScopes.pop();
return;
}
if (ts.isFunctionLike(node)) {
if (ts.isFunctionDeclaration(node) && node.name) {
registerWrapperFunction(node.name.text, node);
}
visitFunctionLike(node);
return;
}
if (ts.isCallExpression(node)) {
const callback = dynamicFsImportThenCallback(node);
if (callback) {
visitFunctionLike(callback, new Set([0]));
for (const argument of node.arguments.slice(1)) {
visit(argument);
}
return;
}
}
if (
ts.isBlock(node) ||
ts.isModuleBlock(node) ||
ts.isCaseBlock(node) ||
ts.isCatchClause(node)
) {
visitWithChildScope(node);
return;
}
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {
if (node.initializer) {
if (isFsBindingExpression(node.initializer)) {
currentFsModuleBindingScope().set(node.name.text, true);
} else {
markFsModuleBindingShadows(node.name);
}
markFsModulePropertyShadows(node.name);
registerFsModuleTypeProperties(node.name, node.type);
if (!(node.name.text === "require" && isCreateRequireExpression(node.initializer))) {
markRequireShadows(node.name);
}
currentRequireAliasScope().set(node.name.text, isRequireAliasExpression(node.initializer));
markCreateRequireShadows(node.name);
collectFsWriteAliasesFromBinding(node);
markFsWriteAliasShadows(node.name);
markFsSafeStoreShadows(node.name);
currentFsWriteAliasScope().set(node.name.text, legacyFsWriteName(node.initializer));
currentFsSafeStoreFactoryAliasScope().set(
node.name.text,
fsSafeStoreFactoryAliasName(node.initializer),
);
currentFsSafeStoreScope().set(node.name.text, isFsSafeStoreExpression(node.initializer));
currentFsSafeJsonStoreScope().set(
node.name.text,
expressionContainsFsSafeJsonStoreLegacyPath(node.initializer),
);
refreshCurrentWrapperFunctionAliases();
currentLiteralTextScope().set(node.name.text, literalTextsFromExpression(node.initializer));
currentKnownUndefinedScope().set(
node.name.text,
isKnownUndefinedExpression(node.initializer),
);
currentLegacyPathScope().set(
node.name.text,
expressionContainsLegacyStore(node.initializer),
);
markKnownLegacyObjectLiteral(node.name.text, node.initializer);
markLegacyObjectProperties(node.name.text, node.initializer);
registerFsWriteObjectAliases(node.name.text, node.initializer);
registerFsSafeStoreObjectAliases(node.name.text, node.initializer);
registerFsModuleObjectProperties(node.name.text, node.initializer);
if (ts.isFunctionExpression(node.initializer) || ts.isArrowFunction(node.initializer)) {
registerWrapperFunction(node.name.text, node.initializer);
} else {
currentWrapperFunctionScope().set(
node.name.text,
cloneWrapperFunctionValue(resolveWrapperExpression(node.initializer)),
);
registerWrapperObjectMethods(node.name.text, node.initializer);
const wrapperObjectSource = callExpressionName(node.initializer);
if (wrapperObjectSource) {
copyWrapperObjectMethods(node.name.text, wrapperObjectSource);
}
}
} else {
currentFsModuleBindingScope().set(node.name.text, false);
currentFsWriteAliasScope().set(node.name.text, null);
currentFsSafeStoreFactoryAliasScope().set(node.name.text, null);
currentFsSafeStoreScope().set(node.name.text, false);
currentFsSafeJsonStoreScope().set(node.name.text, false);
currentRequireAliasScope().set(node.name.text, false);
currentLegacyPathScope().set(node.name.text, false);
currentLegacyKnownObjectLiteralScope().set(node.name.text, false);
currentKnownUndefinedScope().set(node.name.text, !isAmbientVariableDeclaration(node));
currentLiteralTextScope().set(node.name.text, null);
currentWrapperFunctionScope().set(node.name.text, null);
markFsWriteAliasShadows(node.name);
markFsSafeStoreShadows(node.name);
markFsModuleBindingShadows(node.name);
markFsModulePropertyShadows(node.name);
registerFsModuleTypeProperties(node.name, node.type);
markRequireShadows(node.name);
markCreateRequireShadows(node.name);
refreshCurrentWrapperFunctionAliases();
}
}
if (ts.isVariableDeclaration(node) && !ts.isIdentifier(node.name)) {
const isFsAliasBinding =
node.initializer &&
ts.isObjectBindingPattern(node.name) &&
isFsBindingExpression(node.initializer);
collectFsModuleBindingsFromBinding(node);
collectFsWriteAliasesFromBinding(node);
markFsSafeStoreShadows(node.name);
if (!isFsAliasBinding) {
markFsWriteAliasShadows(node.name);
markFsModuleBindingShadows(node.name);
markFsModulePropertyShadows(node.name);
markRequireShadows(node.name);
markCreateRequireShadows(node.name);
}
refreshCurrentWrapperFunctionAliases();
for (const name of bindingPatternNames(node.name)) {
currentFsSafeStoreFactoryAliasScope().set(name, null);
currentFsSafeStoreScope().set(name, false);
currentFsSafeJsonStoreScope().set(name, false);
currentRequireAliasScope().set(name, false);
currentLegacyPathScope().set(name, false);
currentLegacyKnownObjectLiteralScope().set(name, false);
currentKnownUndefinedScope().set(name, false);
currentLiteralTextScope().set(name, null);
currentWrapperFunctionScope().set(name, null);
}
if (
ts.isObjectBindingPattern(node.name) &&
node.initializer &&
ts.isIdentifier(node.initializer)
) {
markLegacyPathsFromObjectBinding(node.name, node.initializer.text);
markFsSafeStoresFromObjectBinding(node.name, node.initializer.text);
markFsSafeFactoryAliasesFromObjectBinding(node.name, node.initializer.text);
} else if (
ts.isObjectBindingPattern(node.name) &&
node.initializer &&
rootedPropertyAccessPath(node.initializer)?.properties.length > 0
) {
const propertyAccess = rootedPropertyAccessPath(node.initializer);
const sourceName = objectPropertyKey(
propertyAccess.rootName,
propertyAccess.properties.join("."),
);
markLegacyPathsFromObjectBinding(node.name, sourceName);
markFsSafeStoresFromObjectBinding(node.name, sourceName);
markFsSafeFactoryAliasesFromObjectBinding(node.name, sourceName);
} else if (
ts.isObjectBindingPattern(node.name) &&
node.initializer &&
ts.isObjectLiteralExpression(unwrapExpression(node.initializer))
) {
markLegacyPathsFromInlineObjectBinding(node.name, node.initializer);
}
}
if (
ts.isBinaryExpression(node) &&
node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
ts.isIdentifier(node.left)
) {
const { index, pathScope, propertyScope, wrapperScope } = legacyIdentifierWriteScopes(
node.left.text,
);
const nextPathValue = expressionContainsLegacyStore(node.right);
const nextLiteralTexts = literalTextsFromExpression(node.right);
const nextKnownUndefined = isKnownUndefinedExpression(node.right);
const nextFsModuleValue = isFsBindingExpression(node.right);
const nextFsWriteAlias = legacyFsWriteName(node.right);
const nextFsSafeFactoryAlias = fsSafeStoreFactoryAliasName(node.right);
const nextFsSafeStoreValue = isFsSafeStoreExpression(node.right);
const nextFsSafeJsonStoreValue = expressionContainsFsSafeJsonStoreLegacyPath(node.right);
const nextRequireAlias = isRequireAliasExpression(node.right);
const conditionalWrite =
currentConditionalExecutionScope() && !conditionalExecutionScopes[index];
pathScope.set(
node.left.text,
conditionalWrite ? pathScope.get(node.left.text) === true || nextPathValue : nextPathValue,
);
const literalScope = literalTextWriteScope(node.left.text);
literalScope.set(
node.left.text,
conditionalWrite
? mergeConditionalLiteralTexts(literalScope.get(node.left.text), nextLiteralTexts)
: nextLiteralTexts,
);
const knownUndefinedScope = knownUndefinedWriteScope(node.left.text);
knownUndefinedScope.set(
node.left.text,
conditionalWrite
? knownUndefinedScope.get(node.left.text) === true || nextKnownUndefined
: nextKnownUndefined,
);
if (conditionalWrite) {
const nextPropertyScope = legacyObjectPropertyRewriteValues(
node.left.text,
node.right,
propertyScope,
);
recordBranchIdentifierAssignment(
index,
node.left.text,
nextPathValue,
node.right,
nextLiteralTexts,
nextPropertyScope,
);
for (const [key, value] of nextPropertyScope) {
const mergedValue = mergeConditionalLegacyObjectPropertyValue(
propertyScope.get(key),
value,
);
if (mergedValue !== null) {
propertyScope.set(key, mergedValue);
}
}
legacyKnownObjectLiteralScopes[index].set(
node.left.text,
legacyKnownObjectLiteralScopes[index].get(node.left.text) === true &&
isKnownLegacyObjectLiteralExpression(node.right),
);
currentLegacyPathScope().set(node.left.text, nextPathValue);
markKnownLegacyObjectLiteral(node.left.text, node.right);
clearLegacyObjectProperties(currentLegacyObjectPropertyScope(), node.left.text);
markLegacyObjectProperties(node.left.text, node.right, currentLegacyObjectPropertyScope());
} else {
fsModuleBindingWriteScope(node.left.text).set(node.left.text, nextFsModuleValue);
fsWriteAliasWriteScope(node.left.text).set(node.left.text, nextFsWriteAlias);
fsSafeStoreFactoryAliasWriteScope(node.left.text).set(
node.left.text,
nextFsSafeFactoryAlias,
);
fsSafeStoreWriteScope(node.left.text).set(node.left.text, nextFsSafeStoreValue);
fsSafeJsonStoreWriteScope(node.left.text).set(node.left.text, nextFsSafeJsonStoreValue);
const requireAliasTarget = requireAliasWriteTarget(node.left.text);
requireAliasTarget.scope.set(node.left.text, nextRequireAlias);
refreshCurrentWrapperFunctionAliases();
refreshWrapperRequireAliasesFromScope(requireAliasTarget.index);
markFsModulePropertyShadows(node.left);
clearLegacyObjectProperties(propertyScope, node.left.text);
markKnownLegacyObjectLiteral(
node.left.text,
node.right,
legacyKnownObjectLiteralScopes[index],
);
markLegacyObjectProperties(
node.left.text,
node.right,
propertyScope,
legacyKnownObjectLiteralScopes[index],
);
clearFsWriteObjectAliases(fsWriteAliasScopes[index], node.left.text);
registerFsWriteObjectAliases(node.left.text, node.right, fsWriteAliasScopes[index]);
clearFsSafeStoreObjectAliases(
fsSafeStoreScopes[index],
fsSafeJsonStoreScopes[index],
node.left.text,
);
registerFsSafeStoreObjectAliases(
node.left.text,
node.right,
fsSafeStoreScopes[index],
fsSafeJsonStoreScopes[index],
);
registerFsModuleObjectProperties(node.left.text, node.right, fsModulePropertyScopes[index]);
clearWrapperObjectMethods(wrapperScope, node.left.text);
registerWrapperObjectMethods(node.left.text, node.right, wrapperScope);
}
if (conditionalWrite) {
const fsModuleScope = fsModuleBindingScopes[index];
const fsWriteScope = fsWriteAliasScopes[index];
const fsSafeFactoryAliasScope = fsSafeStoreFactoryAliasScopes[index];
const fsSafeStoreScope = fsSafeStoreScopes[index];
const fsSafeJsonStoreScope = fsSafeJsonStoreScopes[index];
fsModuleScope.set(
node.left.text,
fsModuleScope.get(node.left.text) === true || nextFsModuleValue,
);
fsWriteScope.set(node.left.text, fsWriteScope.get(node.left.text) ?? nextFsWriteAlias);
fsSafeFactoryAliasScope.set(
node.left.text,
fsSafeFactoryAliasScope.get(node.left.text) ?? nextFsSafeFactoryAlias,
);
fsSafeStoreScope.set(
node.left.text,
fsSafeStoreScope.get(node.left.text) === true || nextFsSafeStoreValue,
);
fsSafeJsonStoreScope.set(
node.left.text,
fsSafeJsonStoreScope.get(node.left.text) === true || nextFsSafeJsonStoreValue,
);
requireAliasScopes[index].set(
node.left.text,
requireAliasScopes[index].get(node.left.text) === true || nextRequireAlias,
);
currentFsModuleBindingScope().set(node.left.text, nextFsModuleValue);
currentFsWriteAliasScope().set(node.left.text, nextFsWriteAlias);
currentFsSafeStoreFactoryAliasScope().set(node.left.text, nextFsSafeFactoryAlias);
currentFsSafeStoreScope().set(node.left.text, nextFsSafeStoreValue);
currentFsSafeJsonStoreScope().set(node.left.text, nextFsSafeJsonStoreValue);
currentRequireAliasScope().set(node.left.text, nextRequireAlias);
refreshCurrentWrapperFunctionAliases();
recordBranchFsIdentifierAssignment(
index,
node.left.text,
nextFsModuleValue,
nextFsWriteAlias,
nextFsSafeFactoryAlias,
nextFsSafeStoreValue,
nextFsSafeJsonStoreValue,
nextRequireAlias,
);
registerFsWriteObjectAliases(node.left.text, node.right, fsWriteAliasScopes[index], true);
registerFsSafeStoreObjectAliases(
node.left.text,
node.right,
fsSafeStoreScopes[index],
fsSafeJsonStoreScopes[index],
true,
);
registerFsModuleObjectProperties(
node.left.text,
node.right,
fsModulePropertyScopes[index],
true,
);
shadowVisibleFsWriteObjectAliases(node.left.text);
registerFsWriteObjectAliases(node.left.text, node.right);
registerFsSafeStoreObjectAliases(node.left.text, node.right);
registerFsModuleObjectProperties(node.left.text, node.right);
registerWrapperObjectMethods(node.left.text, node.right, wrapperScope, true);
shadowVisibleWrapperObjectMethods(node.left.text);
registerWrapperObjectMethods(node.left.text, node.right);
}
const assignedWrapper =
ts.isFunctionExpression(node.right) || ts.isArrowFunction(node.right)
? wrapperRecordForNode(node.right)
: cloneWrapperFunctionValue(resolveWrapperExpression(node.right));
if (conditionalWrite) {
recordBranchWrapperAssignment(index, node.left.text, assignedWrapper);
}
setWrapperFunctionValue(wrapperScope, node.left.text, assignedWrapper, conditionalWrite);
const wrapperObjectSource = callExpressionName(node.right);
if (wrapperObjectSource) {
copyWrapperObjectMethods(
node.left.text,
wrapperObjectSource,
wrapperScope,
conditionalWrite,
);
}
}
if (
ts.isBinaryExpression(node) &&
node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
rootedPropertyAccessPath(node.left)?.properties.length > 0
) {
const propertyAccess = rootedPropertyAccessPath(node.left);
const propertyName = propertyAccess.properties.join(".");
const target = legacyObjectPropertyWriteTarget(propertyAccess.rootName, propertyName);
const key = objectPropertyKey(propertyAccess.rootName, propertyName);
const nextValue = legacyObjectPropertyValueFromExpression(node.right);
const nextKnownObjectLiteral = isKnownLegacyObjectLiteralExpression(node.right);
const rewriteValues = legacyObjectPropertyRewriteValues(key, node.right, target.scope);
const conditionalPropertyWrite =
currentConditionalExecutionScope() && !conditionalExecutionScopes[target.index];
if (conditionalPropertyWrite) {
const previousKnownObjectLiteral = lookupKnownLegacyObjectLiteral(key);
clearKnownLegacyObjectLiterals(legacyKnownObjectLiteralScopes[target.index], key);
legacyKnownObjectLiteralScopes[target.index].set(
key,
previousKnownObjectLiteral && nextKnownObjectLiteral,
);
const previousValue = target.scope.has(key)
? target.scope.get(key)
: lookupKnownLegacyObjectLiteral(propertyAccess.rootName)
? explicitUndefinedLegacyObjectPropertyValue
: undefined;
const mergedValue = mergeConditionalLegacyObjectPropertyValue(previousValue, nextValue);
if (mergedValue !== null) {
target.scope.set(key, mergedValue);
}
} else {
target.scope.set(key, nextValue);
clearKnownLegacyObjectLiterals(legacyKnownObjectLiteralScopes[target.index], key);
legacyKnownObjectLiteralScopes[target.index].set(key, nextKnownObjectLiteral);
}
if (!conditionalPropertyWrite) {
clearLegacyObjectProperties(target.scope, key);
for (const [propertyKey, value] of rewriteValues) {
target.scope.set(propertyKey, value);
}
}
if (conditionalPropertyWrite) {
for (const [propertyKey, value] of rewriteValues) {
const mergedValue = mergeConditionalLegacyObjectPropertyValue(
target.scope.get(propertyKey),
value,
);
if (mergedValue !== null) {
target.scope.set(propertyKey, mergedValue);
recordBranchPropertyAssignment(
target.index,
propertyAccess.rootName,
propertyKey.slice(`${propertyAccess.rootName}.`.length),
value,
);
}
}
currentLegacyObjectPropertyScope().set(key, nextValue);
clearKnownLegacyObjectLiterals(currentLegacyKnownObjectLiteralScope(), key);
currentLegacyKnownObjectLiteralScope().set(key, nextKnownObjectLiteral);
clearLegacyObjectProperties(currentLegacyObjectPropertyScope(), key);
for (const [propertyKey, value] of rewriteValues) {
currentLegacyObjectPropertyScope().set(propertyKey, value);
}
recordBranchPropertyAssignment(
target.index,
propertyAccess.rootName,
propertyName,
nextValue,
nextKnownObjectLiteral,
);
}
const wrapperTarget = legacyIdentifierWriteScopes(propertyAccess.rootName);
const conditionalWrapperWrite =
currentConditionalExecutionScope() && !conditionalExecutionScopes[wrapperTarget.index];
setFsWriteObjectAlias(
fsWriteAliasScopes[wrapperTarget.index],
key,
legacyFsWriteName(node.right),
conditionalWrapperWrite,
);
setFsModuleObjectProperty(
fsModulePropertyScopes[wrapperTarget.index],
key,
isFsModuleExpression(node.right),
conditionalWrapperWrite,
);
if (!conditionalWrapperWrite) {
clearFsSafeStoreObjectAliases(
fsSafeStoreScopes[wrapperTarget.index],
fsSafeJsonStoreScopes[wrapperTarget.index],
key,
);
}
setFsSafeStoreObjectAlias(
fsSafeStoreScopes[wrapperTarget.index],
fsSafeJsonStoreScopes[wrapperTarget.index],
key,
isFsSafeStoreExpression(node.right),
expressionContainsFsSafeJsonStoreLegacyPath(node.right),
conditionalWrapperWrite,
);
if (!conditionalWrapperWrite) {
registerFsSafeStoreObjectAliases(
key,
node.right,
fsSafeStoreScopes[wrapperTarget.index],
fsSafeJsonStoreScopes[wrapperTarget.index],
);
}
if (conditionalWrapperWrite) {
currentFsWriteAliasScope().set(key, legacyFsWriteName(node.right));
currentFsModulePropertyScope().set(key, isFsModuleExpression(node.right));
shadowVisibleFsSafeStoreObjectAliases(key);
currentFsSafeStoreScope().set(key, isFsSafeStoreExpression(node.right));
currentFsSafeJsonStoreScope().set(
key,
expressionContainsFsSafeJsonStoreLegacyPath(node.right),
);
registerFsSafeStoreObjectAliases(key, node.right);
recordBranchFsSafeObjectPropertyAssignment(
wrapperTarget.index,
propertyAccess.rootName,
propertyName,
node.right,
isFsSafeStoreExpression(node.right),
expressionContainsFsSafeJsonStoreLegacyPath(node.right),
);
}
const assignedWrapper =
ts.isFunctionExpression(node.right) || ts.isArrowFunction(node.right)
? wrapperRecordForNode(node.right)
: cloneWrapperFunctionValue(resolveWrapperExpression(node.right));
if (conditionalWrapperWrite) {
currentWrapperFunctionScope().set(key, assignedWrapper);
recordBranchWrapperAssignment(wrapperTarget.index, key, assignedWrapper);
} else {
clearWrapperObjectMethods(wrapperTarget.wrapperScope, key);
}
setWrapperFunctionValue(
wrapperTarget.wrapperScope,
key,
assignedWrapper,
conditionalWrapperWrite,
);
registerWrapperObjectMethods(
key,
node.right,
wrapperTarget.wrapperScope,
conditionalWrapperWrite,
);
}
if (ts.isCallExpression(node)) {
const fsWriteName = legacyFsWriteName(node.expression);
if (
fsWriteName &&
fsWriteCallMayWrite(fsWriteName, [...node.arguments]) &&
pathArgumentsForFsWrite(fsWriteName, [...node.arguments]).some((argument) =>
pathArgumentContainsLegacyStore(argument),
)
) {
addViolation(node.expression, "legacy store filesystem write", node);
}
if (
fsSafeStoreWritePathArguments(node).some((argument) =>
pathArgumentContainsLegacyStore(argument),
)
) {
addViolation(node.expression, "legacy store filesystem write", node);
}
if (fsSafeJsonStoreWriteContainsLegacyStore(node)) {
addViolation(node.expression, "legacy store filesystem write", node);
}
const wrapperName = callExpressionName(node.expression);
const wrapperRecord = wrapperName ? resolveWrapperFunction(wrapperName) : null;
for (const record of wrapperRecords(wrapperRecord)) {
const propertyParameters = collectLegacyPathPropertyParameters(
record.node,
record.aliases,
record.moduleBindings,
record.moduleProperties,
record.requireAliases,
record.createRequireShadows,
);
for (const [index, propertyNames] of propertyParameters) {
if (
[...propertyNames].some((propertyName) =>
wrapperPathUseContainsLegacyStore(record, index, propertyName, node.arguments),
)
) {
addViolation(node.expression, "legacy store filesystem write", node);
break;
}
}
}
}
if (
(ts.isStringLiteralLike(node) || ts.isIdentifier(node) || ts.isTemplateExpression(node)) &&
bridgeMarkerPattern.test(node.getText(sourceFile))
) {
addViolation(node, "legacy transcript bridge marker");
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
if (
scanOptions.enforceCurrentLegacyAllowlist &&
!scanOptions.currentLegacyWriteAllowances &&
currentLegacyWriteAllowances.size > 0
) {
violations.push({ kind: "stale current legacy write allowlist", line: 1 });
}
return violations;
}
/**
* Runs the database-first legacy-store guard.
*/
export async function main() {
const repoRoot = resolveRepoRoot(import.meta.url);
const sourceRoots = databaseFirstLegacyStoreSourceRoots.map((root) => path.join(repoRoot, root));
const files = await collectDatabaseFirstLegacyStoreSourceFiles(sourceRoots);
const violations = [];
const currentLegacyWriteAllowances = currentLegacyWriteViolationAllowances();
for (const filePath of files) {
const relativePath = path.relative(repoRoot, filePath).replaceAll(path.sep, "/");
const content = await fs.readFile(filePath, "utf8");
for (const violation of collectDatabaseFirstLegacyStoreViolations(content, relativePath, {
currentLegacyWriteAllowances,
})) {
violations.push(`${relativePath}:${violation.line} ${violation.kind}`);
}
}
for (const fingerprint of currentLegacyWriteAllowances.keys()) {
const relativePath = currentLegacyWriteViolationPath(fingerprint) ?? "<unknown>";
violations.push(`${relativePath}:1 stale current legacy write allowlist`);
}
if (violations.length === 0) {
console.log("Database-first legacy-store guard passed.");
return;
}
console.error("Found database-first legacy-store guard violations:");
for (const violation of violations.toSorted()) {
console.error(`- ${violation}`);
}
console.error(
"Runtime state/cache writes must use the shared or per-agent SQLite stores. Keep legacy file import/removal under doctor or migration owners.",
);
process.exit(1);
}
runAsScript(import.meta.url, main);