#!/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 isGeneratedAssetSourceFile(filePath) { return /(?:^|\/)extensions\/[^/]+\/assets\/[^/]+\.[cm]?js$/u.test( filePath.replaceAll(path.sep, "/"), ); } 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) && !isGeneratedAssetSourceFile(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) && !isGeneratedAssetSourceFile(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 = ""; 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) ?? ""; 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);