import { SparseBitVector } from '../../utils/SparseBitVector'; import { ArkModule, ModuleID } from '../../core/model/ArkModule'; import { ModuleDepGraph } from '../../core/graph/ModuleDepGraph'; import { ModuleDepthLevel } from './ModuleDepth'; import { ModuleAnalysisConfig } from './ModuleAnalysisConfig'; import type { Scene } from '../../Scene'; import type { SceneOptions } from '../../Config'; /** * ModuleBuilder holds the build logic for module-level analysis embedded in {@link Scene}. * * It is responsible for the build side of the module lifecycle: registration, SDK build, module * preparation, dependency graph construction and SCC analysis, and module loading. Persistent * state (the Canonicalizer, pathToId map, dependency graph, and progress flags) lives on the * owning {@link Scene}; this class reads and writes that state through the Scene accessors. * * Each module is identified by its absolute path and assigned a dense integer {@link ModuleID} via * the Scene's Canonicalizer using the objectIdentity strategy. The `pathToId` map on the Scene is * a persistent cache that survives graph construction, allowing runtime path-based lookups. * * @category core/model */ export declare class ModuleBuilder { private scene; /** Persistent map from absolute module path to ModuleID (survives graph construction). */ private pathToId; /** Reference to Scene's persistent memory monitor; set by initCacheManagement. */ private monitor?; /** Reference to Scene's persistent module cache; set by initCacheManagement. */ private cache?; /** Topological order of the current call, used for protected-set computation and Phase 2/3 reverse-topo iteration. */ private topoOrder; /** Map from moduleId to its index in topoOrder for O(1) lookup. */ private topoOrderIndex?; /** Load level to which protected dependency modules are downgraded during eviction Phase 2. */ private dependencyLoadLevel; /** * Estimated heapUsed bytes per file for each depth level, used for pre-load memory cost estimation. * Calibrated from scene_board_ext measurements (no-eviction, no GC runs): * - BODIES: P75 of per-module heapUsed-per-file at BODIES level = ~1.7MB/file * - SIGNATURES: P75 of per-module heapUsed-per-file at SIGNATURES level = ~700KB/file * - INDEX: hollow in-place IR (export-reachable shells); lighter than full SIGNATURES. * * No-GC calibration matches production conditions (no forced GC). Without GC, * heapUsed increments include uncollected garbage from parsing and type inference, * which is several times larger than surviving data. * * Used for pre-load estimation (deciding whether eviction is needed before loading) * and as a fallback for unload estimation when the heapUsed table has no entry. */ private static readonly HEAPUSED_PER_FILE; /** * Ratio of memory released by downgrading from BODIES to SIGNATURES, relative to * the BODIES-level heapUsed. Calibrated from GC trace test data: * downgrade releases ~60% of BODIES heapUsed (method bodies + AST + source code). */ private static readonly DOWNGRADE_RELEASE_RATIO; constructor(scene: Scene); /** Get a registered module by its ModuleID. Returns undefined when the id is out of bounds. */ getModule(id: ModuleID): ArkModule | undefined; /** Look up a module by its absolute path using the pathToId cache. */ getModuleByPath(modulePath: string): ArkModule | undefined; /** Total number of registered modules. */ getModuleCount(): number; /** * Iterate over all registered modules. * Iteration order follows ModuleID assignment order (0, 1, 2, ...). */ modulesIterator(): IterableIterator; /** Find the next module starting from the cursor. */ private nextModule; /** Topologically sorted module IDs (empty before dependency analysis completes). */ getTopoOrder(): ModuleID[]; /** * Resolve a dependency alias within the scope of the given module. * Looks up the alias in the module's per-module alias map, then resolves the * resulting ModuleID back to the depended-on ArkModule. */ resolveAlias(moduleId: ModuleID, alias: string): ArkModule | undefined; /** * Resolve the final set of target module IDs from the config's three selection dimensions. * Excluded IDs take precedence; type filter and explicit include IDs are unioned. */ resolveTargetModuleIds(config: ModuleAnalysisConfig): SparseBitVector; /** * Compute transitive closure of module IDs reachable from the given target IDs via * dependency edges (BFS over {@link ModuleDepGraph.getSuccModuleIds}). * SDK modules are never included in the closure. */ computeModuleClosureByIds(targetIds: SparseBitVector): SparseBitVector; /** Filter the topological order to only include module IDs present in the given closure. */ getFilteredTopoOrder(closure: SparseBitVector): ModuleID[]; /** Get the ModuleID assigned to a registered ArkModule. */ getModuleId(module: ArkModule): ModuleID; /** Create a {@link ModuleDepGraph} sharing the scene's module canonicalizer. */ createModuleDepGraph(): ModuleDepGraph; /** * Register a module by its absolute path. If the same path has already been registered, the * existing {@link ArkModule} is returned unchanged. A new ModuleID is allocated via the * Scene's Canonicalizer for first-time registrations, and cached on the module. * * @param modulePath - Absolute path of the module (primary identifier). * @param moduleName - Optional module name (auxiliary field, e.g. "@ohos/entry"). * @returns The registered ArkModule (newly created or previously registered). */ registerModule(modulePath: string, moduleName?: string): ArkModule; /** * Register and build SDK modules in one fused step. Each project-level SDK is treated as an * {@link ArkModule} (moduleType=SDK): its files are collected, parsed, and registered into the * module's `filesMap` as well as the scene's `sdkArkFilesMap`. SDK type inference and global * API merge run after all SDK files are built. * * This method merges the former `prepareSdkModules` (registration-only) and `SDKBuilder.buildSdks` * (building-only) into a single step — SDK modules are never just registered without being built. * * Module-level SDKs (those with `moduleName` set) are skipped entirely. * * Idempotent: guarded by {@link SceneBuildStage.SDK_INFERRED}. */ buildSdkModules(): void; /** * Collect and build all source files for a single SDK module. Each file is parsed via * {@link ArktsFrontend.buildArkFileFromSdkPath} (using the SDK name as projectName), default * method bodies are built and all BodyBuilders freed, then the file is registered into both * the module's `filesMap` and the scene's `sdkArkFilesMap`. C++ SDK files are skipped. */ private buildSdkModuleFiles; /** * Collect source file paths for an SDK. * * Built-in SDK uses {@link SdkUtils.fetchBuiltInFiles} (reference-based DFS over `lib.*.d.ts`); * other SDKs scan the directory with the scene's supported extensions and ignore patterns. */ private collectSdkFiles; /** * Parse a single SDK source file into an ArkFile, build default method bodies, free all * BodyBuilders, and register the file in both the module's `filesMap` and the scene's * `sdkArkFilesMap`. */ private parseAndRegisterSdkFile; /** * Read `build-profile.json5` from the project root and call * {@link SdkUtils.setEsVersion} so that {@link SdkUtils.fetchBuiltInFiles} selects the correct * `lib.*.d.ts` entry. */ private setEsVersionFromBuildProfile; /** * Prepare modules by discovering and registering project modules and oh_modules dependencies. * * Reads build-profile.json5 to discover project modules, then scans oh_modules directories * to discover third-party dependencies. Only basic info (paths) is registered — * oh-package.json5 is NOT read and no dependency graph is built. * * Idempotent: if module preparation has already been completed via * `Scene.setModulesRegistered(true)`, this method returns immediately. * Note: this method does NOT set modulesRegistered — that is done by * `Scene.analyseByModule` after both prepareModules and analyzeModuleDependencies complete. */ prepareModules(): void; /** * Read build-profile.json5 from the project root and register each module as * ArkModule(moduleType=PROJECT). The module's srcPath is resolved to an absolute path * via path.resolve(projectDir, srcPath). oh-package.json5 is NOT read. */ private registerProjectModules; /** * Scan oh_modules directories and register third-party dependencies as * ArkModule(moduleType=OH_MODULES). * * Scans two types of oh_modules directories: * 1. The project-level oh_modules/ under the project root. * 2. The oh_modules/ under each registered PROJECT module's directory. * * A Set is used to avoid scanning the same directory twice. */ private registerOhModulesModules; /** * Recursively scan a single oh_modules directory, registering discovered packages as * ArkModule(moduleType=OH_MODULES). * * Directories starting with '@' (scoped packages) are recursed into. * Regular directories have their symlinks resolved via fs.realpathSync() and are * registered if not already present (deduplication by resolved real path). * * @param ohModulesDir - Absolute path of the oh_modules directory to scan. */ private scanOhModulesDirectory; /** * Main entry point for module dependency analysis. * * Reads oh-package.json5 to update module names, builds the dependency graph, * runs SCC detection with post-processing refinement. The topological order and SCC groups * are stored inside the dependency graph on the Scene, accessed via `ModuleBuilder.getTopoOrder()`. * * Idempotent: if the dependency graph already exists on the Scene, this method returns immediately. */ analyzeModuleDependencies(): void; /** * Build the module dependency graph by reading each module's oh-package.json5. * * Creates a new {@link ModuleDepGraph} with the shared canonicalizer, adds all * registered modules as nodes, then resolves dependencies and adds edges. * Resolved dependencies create graph edges and update the module's alias map; * unresolved dependencies are recorded as unresolved dependencies. The built graph is * stored on the Scene via `Scene.setModuleDepGraph()`. */ private buildDependencyGraph; /** * Read the project root oh-package.json5 and extract `overrides` and `overrideDependencyMap`. * * Both fields are optional dependency override configurations. `overrides` maps a dependency * alias to an override path (local path, file: path, or @module: reference). * `overrideDependencyMap` maps a dependency alias to an override file path. When a dependency * alias is present in either map, the override path is used instead of the original dependency * value during resolution (checked before normal resolution). * * @returns An object with `overrides` and `overrideDependencyMap` string maps (empty when absent). */ private readProjectOverrides; /** * Coerce a record value to a `{ [k: string]: string }` map, dropping non-string entries. */ private toStringMap; /** * Extract dependency entries from oh-package.json5 content. * * Traverses dependencies (→ DEPENDENCIES), devDependencies (→ DEV_DEPENDENCIES), * and dynamicDependencies (→ DYNAMIC), returning a [alias, value, DependencyType] * triple for each entry. The DependencyType is used during SCC post-processing * to determine edge removal priority when splitting oversized groups. */ private extractDependenciesWithValues; /** * Resolve a dependency declaration to its target {@link ArkModule}. * * Resolution priority: * 0. overrides / overrideDependencyMap — when the alias is present in either project-level * override map, the override path replaces the original dependency value before resolution. * 1. {@link MODULE_PREFIX} prefix — find by moduleName among registered modules * 2. "./", "../", or "file:" prefix — resolve as local path relative to * scopeModulePath, look up in pathToId * 3. Version number — look in oh_modules directories in priority order: * a. current module's oh_modules (scopeModulePath/oh_modules/alias) * b. project-level oh_modules (projectDir/oh_modules/alias) * c. .ohpm cache (projectDir/oh_modules/.ohpm/@/oh_modules/) * Each candidate is verified as a directory, resolved via fs.realpathSync(), then looked * up in pathToId. For .har dependencies inside the .ohpm cache, the current module's * oh_modules base is recomputed from the enclosing oh_modules ancestor. * * @param alias - The dependency alias (key in oh-package.json5 dependencies). * @param depValue - The dependency value (path, version, or @module: reference). * @param scopeModulePath - The absolute path of the source module (for relative path resolution). * @param overrides - Optional project-level overrides map (alias -> override path). * @param overrideDependencyMap - Optional project-level overrideDependencyMap (alias -> override path). * @returns The target ArkModule if resolved, undefined otherwise. */ private resolveDepModule; /** * Find a module path in the .ohpm cache directory. * * Mirrors {@link ModuleUtils.findModulePathInOHPM}: the cache lives at * `/oh_modules/.ohpm` and stores packages as * `@/oh_modules/`. Exact versions are * matched directly; `^`-prefixed ranges pick the first directory whose version is not less * than the requested minimum. * * @param projectDir - The project root directory. * @param moduleName - The dependency alias / module name. * @param version - The dependency version value (exact or `^`-prefixed). * @returns The candidate directory path, or '' when not found. */ private findModulePathInOHPM; /** * Read oh-package.json5 for each registered module and update moduleName. * * The "name" field in oh-package.json5 (e.g. "@ohos/entry") is the canonical * module name used for "@module:" reference resolution. This method populates * {@link ArkModule.moduleName} from oh-package.json5, overriding the short name * from build-profile.json5 set during registration. */ private updateModuleNamesFromOhPkg; private registerModulePath; /** * Map a {@link ModuleDepthLevel} to the corresponding {@link ModuleLoadState}. * Used by {@link loadModule} to translate the configured depth level into a load state. */ private depthLevelToLoadState; /** * Reverse mapping of {@link depthLevelToLoadState}. Used to look up the heapUsed table * entry for a module's current load state. */ private loadStateToDepthLevel; /** * Run type inference on the module's files, following the same pattern as * {@link Scene.inferTypes} but scoped to a single module. * * Reuses the file topological order already computed by {@link analyzeFileDependencies} * (stored in the module's {@link FileDepGraph}) so that depended-on files are inferred first. * Falls back to unsorted iteration when no file dependency graph is available (e.g. when this * method is called directly without a prior {@link loadModule}). * * At SIGNATURES level (no method bodies), only the `preInfer` phase is effective (generic * types, parameter types, signature return types, import/export resolution). At BODIES level, * full type inference runs (including stmt-level propagation and return type aggregation). * * Does NOT call {@link Scene.getMethodsMap}(true): rebuilding the global index would clear * caches for other modules. Does NOT set {@link Scene.buildStage} to TYPE_INFERRED: that is a * whole-scene flag. Does NOT call {@link SdkUtils.dispose} / {@link ModuleUtils.dispose} / * {@link ValueUtil.dispose}: the global caches are shared across modules and should be * released by the caller after all modules are processed. * * @param module - The module whose files are to be type-inferred. * @param times - Number of inference iterations (clamped to 1–5). Default 1. */ inferModuleTypes(module: ArkModule, times?: number): void; /** * Load module data at the specified depth level. * * Dependencies are NOT recursively loaded here — the caller (e.g. * {@link Scene.analyseByModule}) must iterate modules in topological order so that * depended-on modules are loaded before their dependents. * * When cache management has been initialized via {@link initCacheManagement}, this method * automatically checks memory before loading (evicting cached modules if over threshold) * and registers the module in the cache after loading. * * Flow: * 1. SDK modules are skipped — their file content is built by {@link buildSdkModules}, not here. * 2. Idempotent: skip if the module's loadState already reaches the target load state. * 3. Build module data to the effective level via {@link buildModuleToLevel}, which integrates * intra-module file dependency analysis and topological-order parsing. * 4. When the effective level reaches BODIES, build method bodies (ArkBody/CFG/Stmt/Expr) via * {@link FrontendBuilder.buildModuleMethodBody}, following the same two-phase pattern as * {@link Scene.genArkFiles}. * 5. When the effective level reaches SIGNATURES, run type inference on the module's files * via {@link inferModuleTypes}, referencing the logic of {@link Scene.inferTypes}. * 6. Set the load state to the target level (after successful build). * * @param moduleId - ID of the module to load. * @param depthLevel - The depth level to load the module to. */ loadModule(moduleId: ModuleID, depthLevel?: ModuleDepthLevel): void; /** * Unload a module's data, releasing IR and resetting to NOT_LOADED. * SDK modules are not unloaded (built by {@link buildSdkModules}). */ unload(moduleId: ModuleID): void; /** * Break all internal references between IR objects in a module's ArkFiles. * Called after {@link Scene.disposeModule} to ensure that IR objects (ArkClass, ArkMethod, * ArkBody, ArkField, ExportInfo, ImportInfo) do not retain each other through cross-references, * allowing V8 GC to reclaim them individually. */ private breakInternalReferences; /** * Initialize or update cache management for module loading. * * On the first call, creates a {@link ModuleCache} and {@link MemoryMonitor} and stores * them persistently in {@link Scene}. On subsequent calls, reuses the persistent instances * and only updates the per-call topological order. * * @param options - Scene options containing memoryLimitMB. * @param topoOrder - Topological order of modules to be loaded (dependees before dependents). * @param dependencyLoadLevel - Load level to downgrade protected dependencies to during eviction. */ initCacheManagement(options: SceneOptions, topoOrder: ModuleID[], dependencyLoadLevel?: ModuleDepthLevel): void; /** * Check memory and evict cached modules if needed, before loading a new module. * * Estimates the heapUsed increment of the upcoming load (from the heapUsed table if available, * otherwise from HEAPUSED_PER_FILE), computes a heapUsed upper limit (heapUsedLimit - estimated * increment), takes one actual heapUsed measurement, and only proceeds with eviction if * heapUsed exceeds the upper limit. * * During eviction, the heapUsed estimate is tracked via {@link HeapUsedEstimateState}, * decremented by the heapUsed table value of each unloaded/downgraded module. */ private evictIfNeeded; private runPhase1Eviction; private runPhase2Downgrade; /** * Eviction phase 2b: if still over limit after BODIES→SIGNATURES, downgrade protected * deps further to INDEX (hollow in-place IR; same lookup APIs as SIGNATURES). */ private runPhase2bDowngradeToIndex; private runPhase3Eviction; /** * Estimate the memory cost of loading a module at the given depth level. * Used as a fallback when the heapUsed table has no entry for this module+level. */ private estimateLoadCost; /** * Estimate the heapUsed increment for loading a module at the given depth level. * Uses the heapUsed table value if available (from a prior first-load measurement), * otherwise falls back to {@link estimateLoadCost} (fileCount × HEAPUSED_PER_FILE). */ private estimateLoadHeapUsed; /** * Estimate the heapUsed decrease from unloading a module (full unload). * Uses the heapUsed table value for the module's current load level, * or falls back to HEAPUSED_PER_FILE estimate. */ private estimateUnloadHeapUsedDecrease; /** * Estimate the heapUsed decrease from downgrading a module from its current level * to a target level. Uses a ratio-based approach: downgrade releases a fixed * proportion of the current-level heapUsed, avoiding the need for target-level * table entries (which are never populated since downgradeModule doesn't write * to the heapUsed table). */ private estimateDowngradeHeapUsedDecrease; /** * Downgrade a module to the given target level. Used by eviction Phase 2 / 2b. * * - {@link ModuleDepthLevel.SIGNATURES}: clear method bodies / view trees / AST while keeping * full ArkFile signature IR. * - {@link ModuleDepthLevel.INDEX}: hollow in place (strip + prune non-exports). Use * {@link unload} to drop IR entirely. * * No-op if already at or below the target. */ private downgradeModule; /** * Drop method bodies / view trees / AST / source text while keeping ArkFile shells. * Shared by SIGNATURES and INDEX downgrade paths. */ private stripModuleBodiesAndAst; /** * INDEX retention: strip heavy payload in place, prune non-exported IR, keep export-reachable * shells registered on the module / Scene so existing lookup APIs still resolve. */ private hollowModuleToIndex; private collectExportReachableSets; private addExportToKeepSets; private pruneNonExportedIr; private pruneNonKeptClasses; private pruneNonKeptNamespaces; private namespaceOwnsKeptClass; /** * Clear module IR from Scene maps and the module's filesMap so the next load rebuilds * from disk. Used by {@link unload} and when upgrading out of hollow INDEX. */ private resetModuleIrForRebuild; /** * Get the number of source files in a module. If the module's filesMap is empty * (not yet loaded), scans the module directory. */ private getFileCount; /** * Compute the hard and soft protected sets for eviction. * - Hard: the current module and its direct dependencies (never evicted). * - Soft: direct dependencies of not-yet-loaded modules, plus already-cached modules * at later topo positions (to avoid reload overhead). */ private computeProtectedSets; /** * Build module ArkFile IR up to SIGNATURES or BODIES. * {@link ModuleDepthLevel.INDEX} is rejected ({@link assertDirectLoadLevel}). * * 1. Ensure ArkFile shells exist (create if filesMap is empty, reuse otherwise). * 2. Build import/export info (if not already done) + analyze intra-module file dependencies. * 3. Upgrade each file to the target level in topological order. * * Method bodies are built later by {@link loadModule} when the target is BODIES. * * @param module - The module to build. * @param level - Must be SIGNATURES or BODIES. */ buildModuleToLevel(module: ArkModule, level: ModuleDepthLevel): void; /** * Analyze file-to-file dependencies within a module using the import/export `from` specifiers * populated during the import/export parse step of {@link buildModuleToLevel}. * * Builds a {@link FileDepGraph}, resolves relative `from` specifiers to file paths within the * same module, adds dependency edges, computes a topological order via SCC detection, and * stores the graph on the {@link ArkModule}. The topological order is retained inside the * FileDepGraph and queried via {@link ArkModule.hasFileTopoOrder}. * * - Only relative `from` specifiers (`./`, `../`) are resolved; bare specifiers are ignored. * - Resolution matches the specifier against already-existing ArkFile paths in the module's filesMap. * - Only files within the module (present in the module's filesMap) get edges; external files are ignored. * * @param module - The module whose files are analyzed. */ analyzeFileDependencies(module: ArkModule): void; /** * Collect all `from` specifiers from an ArkFile's import and export infos. * Duplicate specifiers are deduplicated. */ private collectFromSpecifiers; /** * Resolve a relative `from` specifier (e.g. `./b`, `../utils/helper`) to an absolute file path * that exists as an already-built ArkFile in the module. * * Resolution matches against the keys of {@link pathToFile} (the module's already-generated * ArkFile paths) instead of probing the filesystem: * * 1. The specifier as-is (may already include an extension). * 2. The specifier with each unique extension found among existing ArkFile paths appended. * 3. The specifier as a directory: look for an index file whose path is in {@link pathToFile}. * * @param from - The relative from specifier (starts with `./` or `../`). * @param arkFile - The file containing the import/export (base for relative resolution). * @param pathToFile - Map of already-built ArkFile paths in the module. * @returns The resolved absolute file path, or undefined if not found. */ private resolveFromSpecifier; } //# sourceMappingURL=ModuleBuilder.d.ts.map