@bamboocss/parser 1.45.4 → 1.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -877,6 +877,8 @@ var ParserResult = class {
877
877
  pattern = /* @__PURE__ */ new Map();
878
878
  filePath;
879
879
  encoder;
880
+ /** Resolver targets crossed while extracting values which contributed CSS. */
881
+ dependencies = /* @__PURE__ */ new Set();
880
882
  /**
881
883
  * `css()` calls whose styles the build could not fully see.
882
884
  *
@@ -1042,6 +1044,33 @@ var ParserResult = class {
1042
1044
  this.filePath = filePath;
1043
1045
  return this;
1044
1046
  }
1047
+ /** @internal Called only by the extractor-facing resolver, not by import classification. */
1048
+ addDependency(filePath) {
1049
+ this.dependencies.add(filePath.replaceAll("\\", "/"));
1050
+ }
1051
+ /**
1052
+ * Local source paths crossed while resolving values this extraction actually encoded.
1053
+ *
1054
+ * The Project ledger deliberately records every local import, including ordinary runtime
1055
+ * bindings. Box nodes retain the declaration node followed while resolving a style value,
1056
+ * so this is the narrow semantic target set consumers can feed back to that ledger to recover
1057
+ * re-export/barrel paths without watching unrelated imports.
1058
+ */
1059
+ getDependencies() {
1060
+ const own = this.filePath?.replaceAll("\\", "/");
1061
+ const paths = new Set(this.dependencies);
1062
+ const seen = /* @__PURE__ */ new Set();
1063
+ const visit = (node) => {
1064
+ if (!node || seen.has(node)) return;
1065
+ seen.add(node);
1066
+ const path = node.getNode?.()?.getSourceFile().getFilePath().replaceAll("\\", "/");
1067
+ if (path && path !== own) paths.add(path);
1068
+ if (_bamboocss_extractor.box.isMap(node)) for (const child of node.value.values()) visit(child);
1069
+ else if (_bamboocss_extractor.box.isArray(node)) for (const child of node.value) visit(child);
1070
+ };
1071
+ for (const item of this.all) if (item.type !== "cva-call") visit(item.box);
1072
+ return [...paths].sort();
1073
+ }
1045
1074
  merge(result) {
1046
1075
  result.css.forEach((item) => this.css.add(this.append(item)));
1047
1076
  result.cva.forEach((item) => this.cva.add(this.append(item)));
@@ -1058,6 +1087,7 @@ var ParserResult = class {
1058
1087
  items.forEach((item) => set.add(this.append(item)));
1059
1088
  });
1060
1089
  if (result.unresolved.length) this.unresolved.push(...result.unresolved);
1090
+ for (const dependency of result.dependencies) this.dependencies.add(dependency);
1061
1091
  return this;
1062
1092
  }
1063
1093
  toArray() {
@@ -1142,6 +1172,7 @@ function createParser(context) {
1142
1172
  reason: "unresolved-raw"
1143
1173
  });
1144
1174
  };
1175
+ const recordDependency = (filePath) => parserResult.addDependency(filePath);
1145
1176
  (0, _bamboocss_extractor.extract)({
1146
1177
  ast: sourceFile,
1147
1178
  tokens: context.tokens ? {
@@ -1190,7 +1221,9 @@ function createParser(context) {
1190
1221
  if (file.isValidRecipe(name) || file.isValidPattern(name)) reportUnresolvedRaw(name, node);
1191
1222
  return { environment: Object.assign({}, defaultEnv, { extra: { [name]: { raw: (v) => v } } }) };
1192
1223
  },
1193
- flags: { skipTraverseFiles: false }
1224
+ flags: { skipTraverseFiles: false },
1225
+ recordDependency,
1226
+ resolveModule
1194
1227
  }).forEach((result, alias) => {
1195
1228
  const name = file.getName(file.normalizeFnName(alias));
1196
1229
  _bamboocss_logger.logger.debug(`ast:${name}`, name !== alias ? {
@@ -1298,23 +1331,61 @@ const invalidateResolutions = () => {
1298
1331
  (0, _bamboocss_extractor.clearBoxNodeCache)();
1299
1332
  clearImportedRecipeCache();
1300
1333
  };
1301
- const normalizeCompilerOptions = (raw) => {
1334
+ const normalizeCompilerOptions = (raw, basePath = process.cwd()) => {
1302
1335
  if (!raw) return {};
1303
- const { options } = ts_morph.ts.convertCompilerOptionsFromJson(raw, process.cwd());
1336
+ const { options } = ts_morph.ts.convertCompilerOptionsFromJson(raw, basePath);
1304
1337
  return options;
1305
1338
  };
1306
- const createTsProject = (options) => new ts_morph.Project({
1307
- skipAddingFilesFromTsConfig: true,
1308
- skipFileDependencyResolution: true,
1309
- skipLoadingLibFiles: true,
1310
- ...options,
1311
- compilerOptions: {
1312
- allowJs: true,
1313
- strictNullChecks: false,
1314
- skipLibCheck: true,
1315
- ...normalizeCompilerOptions(options.compilerOptions)
1339
+ /** Snapshot the JSON-shaped ts-morph options while retaining opaque hosts and callbacks. */
1340
+ const snapshotProjectOption = (value) => {
1341
+ if (Array.isArray(value)) return value.map(snapshotProjectOption);
1342
+ if (!value || typeof value !== "object") return value;
1343
+ if (Object.getPrototypeOf(value) !== Object.prototype) return value;
1344
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, snapshotProjectOption(entry)]));
1345
+ };
1346
+ const prepareTsProjectOptions = (options, snapshotNested = false, compilerOptionsBasePath = process.cwd()) => {
1347
+ const snapshot = { ...options };
1348
+ if (snapshotNested) {
1349
+ for (const key of [
1350
+ "compilerOptions",
1351
+ "defaultCompilerOptions",
1352
+ "manipulationSettings"
1353
+ ]) if (key in snapshot) snapshot[key] = snapshotProjectOption(snapshot[key]);
1316
1354
  }
1317
- });
1355
+ return {
1356
+ skipAddingFilesFromTsConfig: true,
1357
+ skipFileDependencyResolution: true,
1358
+ skipLoadingLibFiles: true,
1359
+ ...snapshot,
1360
+ compilerOptions: {
1361
+ allowJs: true,
1362
+ strictNullChecks: false,
1363
+ skipLibCheck: true,
1364
+ ...normalizeCompilerOptions(snapshot.compilerOptions, compilerOptionsBasePath)
1365
+ }
1366
+ };
1367
+ };
1368
+ const createTsProject = (options) => new ts_morph.Project(options);
1369
+ /** Filesystem errors proving a lexical candidate cannot name a resolvable file. */
1370
+ const isMissingPathShapeError = (error) => {
1371
+ const code = error?.code;
1372
+ return code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP" || code === "ENAMETOOLONG";
1373
+ };
1374
+ /** Match the successful receiver cases of an ordinary writable data-property assignment. */
1375
+ const setProjectOnReceiver = (receiver, project) => {
1376
+ if (typeof receiver !== "object" && typeof receiver !== "function" || receiver === null) return;
1377
+ const descriptor = Reflect.getOwnPropertyDescriptor(receiver, "project");
1378
+ if (descriptor) {
1379
+ if ("value" in descriptor && descriptor.writable) Reflect.defineProperty(receiver, "project", { value: project });
1380
+ return;
1381
+ }
1382
+ Reflect.defineProperty(receiver, "project", {
1383
+ configurable: true,
1384
+ enumerable: true,
1385
+ value: project,
1386
+ writable: true
1387
+ });
1388
+ };
1318
1389
  /**
1319
1390
  * How to parse a file, decided by its extension.
1320
1391
  *
@@ -1341,35 +1412,188 @@ const scriptKindFor = (filePath) => {
1341
1412
  return extension === ".ts" || extension === ".mts" || extension === ".cts" ? ts_morph.ScriptKind.TS : ts_morph.ScriptKind.TSX;
1342
1413
  };
1343
1414
  var Project = class {
1344
- options;
1345
- project;
1415
+ /**
1416
+ * Source-loading contract for the opt-in deferred mode:
1417
+ *
1418
+ * - materializing: `project`, `getSourceFile`, `getDependents`, every create/add/remove/reload
1419
+ * API, and non-JSON `parseSourceFile`;
1420
+ * - graph-independent: `files`, `parser`, `parserOptions`, `readFile`, `getFiles`, the
1421
+ * resolution-ledger/work getters, `getUnresolvedImporters`, `parseJson`/JSON
1422
+ * `parseSourceFile`, `transformFile`, `classify`.
1423
+ *
1424
+ * Graph-independent means outside the atomic preload. While it is `loading`, every public
1425
+ * wrapper entry rejects reentrancy before returning live state or invoking a callback.
1426
+ *
1427
+ * Private resolution and dependency helpers are reachable only after a materializing parse.
1428
+ */
1346
1429
  parser;
1430
+ project;
1431
+ #guardedParser;
1432
+ #parser;
1433
+ #sourceFiles;
1434
+ options;
1347
1435
  get parserOptions() {
1436
+ this.#assertNotLoading();
1348
1437
  return this.options.parserOptions;
1349
1438
  }
1350
1439
  constructor(options) {
1440
+ const { deferInitialSourceFiles, getFiles, parserOptions } = options;
1441
+ const tsProjectOptions = { ...options };
1442
+ delete tsProjectOptions.deferInitialSourceFiles;
1443
+ delete tsProjectOptions.resolutionConfigFiles;
1351
1444
  this.options = options;
1352
- const { parserOptions } = options;
1353
- this.project = createTsProject(options);
1354
- this.parser = createParser(parserOptions);
1355
- this.createSourceFiles();
1445
+ this.#sourceFiles = {
1446
+ accessor: void 0,
1447
+ initialFiles: [],
1448
+ phase: deferInitialSourceFiles ? "pending" : "ready",
1449
+ project: void 0,
1450
+ projectOptions: prepareTsProjectOptions(tsProjectOptions, Boolean(deferInitialSourceFiles), parserOptions.config.cwd || process.cwd()),
1451
+ revision: 0
1452
+ };
1453
+ if (!deferInitialSourceFiles) {
1454
+ this.project = createTsProject(this.#sourceFiles.projectOptions);
1455
+ this.#sourceFiles.project = this.project;
1456
+ }
1457
+ const parser = createParser(parserOptions);
1458
+ this.#parser = parser;
1459
+ if (deferInitialSourceFiles) {
1460
+ this.#guardedParser = (...args) => {
1461
+ this.#assertNotLoading();
1462
+ return this.#parser(...args);
1463
+ };
1464
+ Object.defineProperty(this, "parser", {
1465
+ configurable: false,
1466
+ enumerable: true,
1467
+ get: () => {
1468
+ this.#assertNotLoading();
1469
+ return this.#guardedParser;
1470
+ },
1471
+ set: (next) => {
1472
+ this.#assertNotLoading();
1473
+ if (Object.isFrozen(this)) throw new TypeError("Cannot assign to read only property 'parser' of Project");
1474
+ this.#parser = next;
1475
+ }
1476
+ });
1477
+ this.#sourceFiles.initialFiles = Object.freeze([...getFiles()]);
1478
+ const getProject = () => {
1479
+ this.#ensureSourceFiles();
1480
+ return this.#sourceFiles.project;
1481
+ };
1482
+ const isOwner = (receiver) => receiver === this;
1483
+ const isFrozen = () => Object.isFrozen(this);
1484
+ const setOwnerProject = (project) => {
1485
+ this.#sourceFiles.revision++;
1486
+ if (Object.isFrozen(this)) throw new TypeError("Cannot assign to read only property 'project' of Project");
1487
+ this.#sourceFiles.project = project;
1488
+ this.#sourceFiles.phase = "ready";
1489
+ this.resetResolutionState();
1490
+ };
1491
+ const assertNotLoading = () => this.#assertNotLoading();
1492
+ const setProject = function(project) {
1493
+ assertNotLoading();
1494
+ if (!isOwner(this)) {
1495
+ if (isFrozen()) return;
1496
+ setProjectOnReceiver(this, project);
1497
+ return;
1498
+ }
1499
+ setOwnerProject(project);
1500
+ };
1501
+ this.#sourceFiles.accessor = {
1502
+ get: getProject,
1503
+ set: setProject
1504
+ };
1505
+ Object.defineProperty(this, "project", {
1506
+ configurable: false,
1507
+ enumerable: true,
1508
+ get: getProject,
1509
+ set: setProject
1510
+ });
1511
+ } else {
1512
+ this.parser = parser;
1513
+ this.createSourceFiles();
1514
+ }
1356
1515
  }
1357
1516
  get files() {
1517
+ this.#assertNotLoading();
1358
1518
  return this.options.getFiles();
1359
1519
  }
1360
1520
  /**
1361
- * Reverse dependency graph: imported file -> files importing it, both keyed on
1362
- * the source file's own normalized path so lookups match regardless of whether
1363
- * the caller passed a relative, aliased or platform-specific path.
1521
+ * Atomically preload the inventory captured when this wrapper was constructed.
1364
1522
  *
1365
- * Populated while parsing. Cross-file extraction folds imported values into the
1366
- * importer's output, so editing a shared style file has to re-parse everyone who
1367
- * imports it re-parsing only the changed file leaves consumers stale.
1523
+ * The candidate ts-morph Project stays local until every non-ENOENT read and AST creation
1524
+ * succeeds. A failure discards it, and the next operation retries with a fresh candidate
1525
+ * over the same frozen membership. A materializing callback that re-enters this wrapper
1526
+ * sees no candidate and fails explicitly rather than observing a partial module graph.
1368
1527
  */
1528
+ #hasSourceFilesAccessor = () => {
1529
+ const expected = this.#sourceFiles.accessor;
1530
+ const descriptor = Object.getOwnPropertyDescriptor(this, "project");
1531
+ return Boolean(expected && descriptor?.get === expected.get && descriptor.set === expected.set);
1532
+ };
1533
+ #assertSourceFilesAccessor = () => {
1534
+ if (!this.#hasSourceFilesAccessor()) throw new Error("Project property changed during source initialization");
1535
+ };
1536
+ #assertNotLoading = () => {
1537
+ if (this.#sourceFiles.phase !== "loading") return;
1538
+ this.#sourceFiles.revision++;
1539
+ throw new Error("Project source files are already being initialized");
1540
+ };
1541
+ #assertSourceFilesTransaction = (revision) => {
1542
+ this.#assertSourceFilesAccessor();
1543
+ if (this.#sourceFiles.revision !== revision) throw new Error("Project source files are already being initialized; transaction was re-entered or mutated");
1544
+ };
1545
+ #materializeSourceFiles = (read, skipMissing) => {
1546
+ this.#assertSourceFilesAccessor();
1547
+ const revision = this.#sourceFiles.revision;
1548
+ this.#sourceFiles.phase = "loading";
1549
+ try {
1550
+ const candidate = createTsProject(this.#sourceFiles.projectOptions);
1551
+ this.#assertSourceFilesTransaction(revision);
1552
+ let loaded = 0;
1553
+ for (const [index, file] of this.#sourceFiles.initialFiles.entries()) try {
1554
+ const content = read(file, index);
1555
+ this.#assertSourceFilesTransaction(revision);
1556
+ candidate.createSourceFile(file, content, {
1557
+ overwrite: true,
1558
+ scriptKind: scriptKindFor(file)
1559
+ });
1560
+ this.#assertSourceFilesTransaction(revision);
1561
+ loaded++;
1562
+ } catch (error) {
1563
+ this.#assertSourceFilesTransaction(revision);
1564
+ if (!skipMissing || error?.code !== "ENOENT") throw error;
1565
+ }
1566
+ this.#assertSourceFilesTransaction(revision);
1567
+ if (loaded > 0) invalidateResolutions();
1568
+ this.#moduleResolutionCache = void 0;
1569
+ this.#fileTreeRevision++;
1570
+ this.#assertSourceFilesTransaction(revision);
1571
+ this.#sourceFiles.project = candidate;
1572
+ this.#sourceFiles.phase = "ready";
1573
+ return;
1574
+ } catch (error) {
1575
+ this.#sourceFiles.project = void 0;
1576
+ this.#sourceFiles.phase = "pending";
1577
+ throw error;
1578
+ }
1579
+ };
1580
+ #ensureSourceFiles = () => {
1581
+ if (this.#sourceFiles.phase === "ready") return;
1582
+ this.#assertNotLoading();
1583
+ this.#materializeSourceFiles((file) => this.options.readFile(file), true);
1584
+ };
1585
+ /** Reverse dependency graph: resolved target -> importers. */
1369
1586
  dependents = /* @__PURE__ */ new Map();
1370
1587
  /** Forward edges, so a re-parse can retract exactly the previous ones. */
1371
1588
  dependencies = /* @__PURE__ */ new Map();
1372
1589
  /**
1590
+ * Deleted targets retain their last importers until those importers are reparsed.
1591
+ *
1592
+ * A watcher commonly removes first and asks second. The ledger itself must say the target
1593
+ * is now unresolved, while this one-turn tombstone keeps that unlink query answerable.
1594
+ */
1595
+ removedDependents = /* @__PURE__ */ new Map();
1596
+ /**
1373
1597
  * Path as a caller spells it -> the source file's own path.
1374
1598
  *
1375
1599
  * The graph is keyed on the latter, but callers pass whatever the watcher gave
@@ -1380,32 +1604,74 @@ var Project = class {
1380
1604
  */
1381
1605
  canonicalPaths = /* @__PURE__ */ new Map();
1382
1606
  /**
1383
- * Files holding at least one import whose specifier resolved to nothing.
1607
+ * Files holding at least one import whose local resolution is not final.
1384
1608
  *
1385
- * A broken or not-yet-created import produces no edge, so when the file it wants
1386
- * finally appears there is nothing in the graph connecting them. These importers
1387
- * are the only candidates for that, and the set is normally empty.
1609
+ * A broken or not-yet-created import produces no edge. A successful fallback edge
1610
+ * likewise cannot point at the missing higher-priority candidate that may replace it.
1611
+ * These importers are the only candidates for either add-event transition, and the
1612
+ * set is normally empty.
1388
1613
  */
1389
1614
  unresolvedImporters = /* @__PURE__ */ new Set();
1390
- /** Files whose imports did not all resolve when they were last parsed. */
1391
- getUnresolvedImporters = () => [...this.unresolvedImporters];
1615
+ /** Exact post-transform resolution facts, one immutable list per importer. */
1616
+ resolutionsByImporter = /* @__PURE__ */ new Map();
1617
+ /** One hook/transform transaction for each source revision Bamboo can semantically read. */
1618
+ sourcePreparations = /* @__PURE__ */ new Map();
1619
+ /** Paths explicitly removed through this wrapper, until an add/create observes them again. */
1620
+ removedSourcePaths = /* @__PURE__ */ new Set();
1621
+ /** File-tree changes invalidate even successful resolutions (extension precedence can move). */
1622
+ #fileTreeRevision = 0;
1623
+ resolutionWork = {
1624
+ moduleResolutionsAttempted: 0,
1625
+ sourceFilesAdded: 0,
1626
+ sourceFilesRead: 0
1627
+ };
1628
+ resetResolutionState = () => {
1629
+ this.#moduleResolutionCache = void 0;
1630
+ this.#fileTreeRevision++;
1631
+ this.dependents = /* @__PURE__ */ new Map();
1632
+ this.dependencies = /* @__PURE__ */ new Map();
1633
+ this.removedDependents = /* @__PURE__ */ new Map();
1634
+ this.canonicalPaths = /* @__PURE__ */ new Map();
1635
+ this.unresolvedImporters = /* @__PURE__ */ new Set();
1636
+ this.resolutionsByImporter = /* @__PURE__ */ new Map();
1637
+ this.sourcePreparations = /* @__PURE__ */ new Map();
1638
+ this.removedSourcePaths = /* @__PURE__ */ new Set();
1639
+ this.resolutionWork = {
1640
+ moduleResolutionsAttempted: 0,
1641
+ sourceFilesAdded: 0,
1642
+ sourceFilesRead: 0
1643
+ };
1644
+ };
1645
+ /** Files whose imports may resolve differently after a local file appears. */
1646
+ getUnresolvedImporters = () => {
1647
+ this.#assertNotLoading();
1648
+ return [...this.unresolvedImporters].sort();
1649
+ };
1650
+ /** @internal Immutable resolution facts in importer/AST order. */
1651
+ getResolutionLedger = () => {
1652
+ this.#assertNotLoading();
1653
+ return Object.freeze([...this.resolutionsByImporter.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).flatMap(([, entry]) => entry.facts));
1654
+ };
1655
+ /** @internal Every distinct local target represented by the current ledger. */
1656
+ getResolvedSourceFiles = () => {
1657
+ this.#assertNotLoading();
1658
+ const paths = /* @__PURE__ */ new Set();
1659
+ for (const entry of this.resolutionsByImporter.values()) for (const fact of entry.facts) if (fact.target) paths.add(fact.target);
1660
+ return Object.freeze([...paths].sort());
1661
+ };
1662
+ /** @internal Deterministic resolver/add/read work, for regression assertions. */
1663
+ getResolutionWork = () => {
1664
+ this.#assertNotLoading();
1665
+ return Object.freeze({ ...this.resolutionWork });
1666
+ };
1392
1667
  getSourceFile = (filePath) => {
1668
+ this.#assertNotLoading();
1393
1669
  return this.project.getSourceFile(filePath);
1394
1670
  };
1395
1671
  /** ts-morph reports forward slashes; normalize callers' paths to match on Windows. */
1396
1672
  normalizePath = (filePath) => filePath.replaceAll("\\", "/");
1397
- /**
1398
- * Resolves a module specifier to a file already in the project.
1399
- *
1400
- * Deliberately not `decl.getModuleSpecifierSourceFile()`: that goes through the
1401
- * symbol table, which forces `initializeTypeChecker` on first use and costs
1402
- * hundreds of ms on a cold build. `ts.resolveModuleName` is purely a filesystem
1403
- * lookup, and a shared cache keeps repeat specifiers off the disk.
1404
- *
1405
- * Looks the result up rather than adding it, so resolving `react` cannot pull a
1406
- * `.d.ts` into the project. The graph only tracks files bamboo already scans.
1407
- */
1408
- moduleResolutionCache;
1673
+ /** Shared filesystem-only resolution cache; no type checker is constructed. */
1674
+ #moduleResolutionCache;
1409
1675
  /**
1410
1676
  * Everything memoized against the shape of the file tree, including the negative half.
1411
1677
  *
@@ -1415,56 +1681,383 @@ var Project = class {
1415
1681
  * importing it, since resolution is what finds one.
1416
1682
  */
1417
1683
  invalidate = (fileTreeChanged = true) => {
1684
+ this.#assertNotLoading();
1418
1685
  invalidateResolutions();
1419
- if (fileTreeChanged) this.moduleResolutionCache = void 0;
1420
- };
1421
- resolveImport = (decl) => {
1422
- const moduleName = decl.getModuleSpecifierValue();
1423
- if (!moduleName) return;
1424
- const compilerOptions = this.project.getCompilerOptions();
1425
- this.moduleResolutionCache ??= ts_morph.ts.createModuleResolutionCache(this.project.getFileSystem().getCurrentDirectory(), (f) => f, compilerOptions);
1426
- const name = ts_morph.ts.resolveModuleName(moduleName, decl.getSourceFile().getFilePath(), compilerOptions, this.project.getModuleResolutionHost(), this.moduleResolutionCache).resolvedModule?.resolvedFileName;
1427
- return name ? this.project.getSourceFile(name) : void 0;
1686
+ if (fileTreeChanged) {
1687
+ this.#moduleResolutionCache = void 0;
1688
+ this.#fileTreeRevision++;
1689
+ }
1428
1690
  };
1429
1691
  /**
1430
- * `resolveImport` in the shape a caller can use, for a specifier read off any declaration.
1692
+ * Invalidate module/evaluator state after a resolver configuration byte changes.
1693
+ *
1694
+ * Unlike `resetResolutionState`, this keeps the published dependency graph long enough for
1695
+ * an incremental consumer to take its old dependent closure. Each reparsed importer replaces
1696
+ * its own facts under the bumped revision; unrelated graph entries remain available until
1697
+ * they are next queried instead of forcing a whole-project rebuild.
1431
1698
  *
1432
- * Shares the module resolution cache above, so a barrel resolved while tracking
1433
- * dependencies is not resolved again while looking for recipes.
1699
+ * @internal
1434
1700
  */
1435
- resolveModule = (specifier, from) => this.resolveImport({
1436
- getModuleSpecifierValue: () => specifier,
1437
- getSourceFile: () => from
1438
- });
1701
+ refreshResolutionConfiguration = (compilerOptions, resolutionConfigFiles, replaceCompilerOptions) => {
1702
+ this.#assertNotLoading();
1703
+ this.options.resolutionConfigFiles = Object.freeze([...new Set(resolutionConfigFiles)].sort());
1704
+ if (replaceCompilerOptions) {
1705
+ const next = prepareTsProjectOptions({ compilerOptions }, false, this.options.parserOptions.config.cwd || process.cwd()).compilerOptions ?? {};
1706
+ this.#sourceFiles.projectOptions = {
1707
+ ...this.#sourceFiles.projectOptions,
1708
+ compilerOptions: snapshotProjectOption(next)
1709
+ };
1710
+ if (this.#sourceFiles.phase === "ready") {
1711
+ const current = this.project.getCompilerOptions();
1712
+ const cleared = Object.fromEntries(Object.keys(current).map((key) => [key, void 0]));
1713
+ this.project.compilerOptions.set({
1714
+ ...cleared,
1715
+ ...next
1716
+ });
1717
+ }
1718
+ }
1719
+ invalidateResolutions();
1720
+ this.#moduleResolutionCache = void 0;
1721
+ this.#fileTreeRevision++;
1722
+ this.sourcePreparations.clear();
1723
+ };
1724
+ isLocalAlias = (specifier) => {
1725
+ const paths = this.project.getCompilerOptions().paths;
1726
+ if (!paths) return false;
1727
+ return Object.keys(paths).some((pattern) => {
1728
+ const wildcard = pattern.indexOf("*");
1729
+ if (wildcard === -1) return pattern === specifier;
1730
+ return specifier.startsWith(pattern.slice(0, wildcard)) && specifier.endsWith(pattern.slice(wildcard + 1));
1731
+ });
1732
+ };
1733
+ getLocalFailedLookupCandidates = (failedLookupLocations) => {
1734
+ const seen = /* @__PURE__ */ new Set();
1735
+ const candidates = [];
1736
+ for (const filePath of failedLookupLocations ?? []) {
1737
+ const normalized = this.normalizePath(filePath);
1738
+ if (normalized.includes("/node_modules/") || normalized.endsWith("/package.json")) continue;
1739
+ if (!seen.has(normalized) && this.isInCheckout(normalized)) {
1740
+ seen.add(normalized);
1741
+ candidates.push(normalized);
1742
+ }
1743
+ }
1744
+ return candidates;
1745
+ };
1746
+ isUnresolvedLocalSpecifier = (specifier, pendingCandidates) => specifier.startsWith(".") || specifier.startsWith("/") || this.isLocalAlias(specifier) || pendingCandidates.length > 0;
1747
+ isInCheckout = (filePath) => {
1748
+ if (this.#sourceFiles.projectOptions.useInMemoryFileSystem) return true;
1749
+ const fileSystem = this.project.getFileSystem();
1750
+ const configured = this.options.parserOptions.config.cwd;
1751
+ const spelledRoot = this.normalizePath(configured || fileSystem.getCurrentDirectory()).replace(/\/$/, "");
1752
+ const root = this.normalizePath(fileSystem.realpathSync(spelledRoot)).replace(/\/$/, "");
1753
+ let target;
1754
+ try {
1755
+ target = this.normalizePath(fileSystem.realpathSync(filePath));
1756
+ } catch (error) {
1757
+ if (!isMissingPathShapeError(error)) throw error;
1758
+ target = this.normalizePath(filePath);
1759
+ }
1760
+ return [root, spelledRoot].some((boundary) => target === boundary || target.startsWith(`${boundary}/`));
1761
+ };
1762
+ resolveSpecifier = (moduleName, from) => {
1763
+ this.#assertNotLoading();
1764
+ const project = this.project;
1765
+ const compilerOptions = project.getCompilerOptions();
1766
+ this.#moduleResolutionCache ??= ts_morph.ts.createModuleResolutionCache(project.getFileSystem().getCurrentDirectory(), (f) => f, compilerOptions);
1767
+ const configurationFiles = /* @__PURE__ */ new Set();
1768
+ const resolutionHost = project.getModuleResolutionHost();
1769
+ const recordConfigurationFile = (filePath) => {
1770
+ const normalized = this.normalizePath(filePath);
1771
+ if (!normalized.endsWith("/package.json") || normalized.includes("/node_modules/")) return;
1772
+ if (!this.isInCheckout(normalized)) return;
1773
+ configurationFiles.add(normalized);
1774
+ };
1775
+ const host = {
1776
+ ...resolutionHost,
1777
+ fileExists: (filePath) => {
1778
+ recordConfigurationFile(filePath);
1779
+ return resolutionHost.fileExists(filePath);
1780
+ },
1781
+ readFile: (filePath) => {
1782
+ recordConfigurationFile(filePath);
1783
+ return resolutionHost.readFile?.(filePath);
1784
+ }
1785
+ };
1786
+ this.resolutionWork.moduleResolutionsAttempted++;
1787
+ const resolved = ts_morph.ts.resolveModuleName(moduleName, from.getFilePath(), compilerOptions, host, this.#moduleResolutionCache);
1788
+ const failedLookupLocations = resolved.failedLookupLocations;
1789
+ const affectingLocations = resolved.affectingLocations;
1790
+ for (const filePath of affectingLocations ?? []) recordConfigurationFile(filePath);
1791
+ const pendingCandidates = this.getLocalFailedLookupCandidates(failedLookupLocations);
1792
+ const module = resolved.resolvedModule;
1793
+ if (!module) return {
1794
+ configurationFiles: [...configurationFiles].sort(),
1795
+ local: this.isUnresolvedLocalSpecifier(moduleName, pendingCandidates) || moduleName.startsWith("#"),
1796
+ pendingCandidates
1797
+ };
1798
+ const name = this.normalizePath(module.resolvedFileName);
1799
+ if (module.isExternalLibraryImport || name.includes("/node_modules/")) return {
1800
+ configurationFiles: [],
1801
+ local: false,
1802
+ pendingCandidates: []
1803
+ };
1804
+ if (!this.isInCheckout(name)) return {
1805
+ configurationFiles: [...configurationFiles].sort(),
1806
+ local: this.isUnresolvedLocalSpecifier(moduleName, pendingCandidates),
1807
+ pendingCandidates
1808
+ };
1809
+ const existing = project.getSourceFile(name);
1810
+ if (existing) {
1811
+ this.removedSourcePaths.delete(name);
1812
+ return {
1813
+ configurationFiles: [...configurationFiles].sort(),
1814
+ local: true,
1815
+ pendingCandidates,
1816
+ sourceFile: existing
1817
+ };
1818
+ }
1819
+ const withResolvedTarget = pendingCandidates.includes(name) ? pendingCandidates : [...pendingCandidates, name];
1820
+ if (this.removedSourcePaths.has(name)) return {
1821
+ configurationFiles: [...configurationFiles].sort(),
1822
+ local: true,
1823
+ pendingCandidates: withResolvedTarget
1824
+ };
1825
+ try {
1826
+ this.resolutionWork.sourceFilesRead++;
1827
+ const content = project.getFileSystem().readFileSync(name);
1828
+ const sourceFile = project.createSourceFile(name, content, {
1829
+ overwrite: true,
1830
+ scriptKind: scriptKindFor(name)
1831
+ });
1832
+ this.resolutionWork.sourceFilesAdded++;
1833
+ this.canonicalPaths.set(name, this.normalizePath(sourceFile.getFilePath()));
1834
+ return {
1835
+ configurationFiles: [...configurationFiles].sort(),
1836
+ local: true,
1837
+ pendingCandidates,
1838
+ sourceFile
1839
+ };
1840
+ } catch (error) {
1841
+ if (error?.code !== "ENOENT") throw error;
1842
+ return {
1843
+ configurationFiles: [...configurationFiles].sort(),
1844
+ local: true,
1845
+ pendingCandidates: withResolvedTarget
1846
+ };
1847
+ }
1848
+ };
1849
+ retractImporter = (importer) => {
1850
+ for (const previous of this.dependencies.get(importer) ?? []) {
1851
+ const reverse = this.dependents.get(previous);
1852
+ reverse?.delete(importer);
1853
+ if (reverse?.size === 0) this.dependents.delete(previous);
1854
+ }
1855
+ this.dependencies.delete(importer);
1856
+ this.unresolvedImporters.delete(importer);
1857
+ this.resolutionsByImporter.delete(importer);
1858
+ for (const [removed, importers] of this.removedDependents) {
1859
+ importers.delete(importer);
1860
+ if (importers.size === 0) this.removedDependents.delete(removed);
1861
+ }
1862
+ };
1863
+ publishResolutionFacts = (importer, sourceFile, text, facts, pendingCandidates, configurationFiles) => {
1864
+ this.retractImporter(importer);
1865
+ const current = /* @__PURE__ */ new Set();
1866
+ for (const fact of facts) {
1867
+ if (!fact.target || fact.target === importer) continue;
1868
+ const importers = this.dependents.get(fact.target) ?? /* @__PURE__ */ new Set();
1869
+ importers.add(importer);
1870
+ this.dependents.set(fact.target, importers);
1871
+ current.add(fact.target);
1872
+ }
1873
+ this.dependencies.set(importer, current);
1874
+ if (pendingCandidates.length > 0 || facts.some((fact) => fact.target === null)) this.unresolvedImporters.add(importer);
1875
+ this.resolutionsByImporter.set(importer, {
1876
+ configurationFiles: Object.freeze([...configurationFiles]),
1877
+ facts: Object.freeze([...facts]),
1878
+ hasPendingLocalCandidate: pendingCandidates.length > 0,
1879
+ pendingCandidates: Object.freeze([...pendingCandidates]),
1880
+ sourceFile,
1881
+ text,
1882
+ treeRevision: this.#fileTreeRevision
1883
+ });
1884
+ };
1885
+ ensureResolutionFacts = (sourceFile) => {
1886
+ this.#assertNotLoading();
1887
+ const importer = this.normalizePath(sourceFile.getFilePath());
1888
+ const text = sourceFile.getFullText();
1889
+ const cached = this.resolutionsByImporter.get(importer);
1890
+ if (cached?.sourceFile === sourceFile && cached.text === text && cached.treeRevision === this.#fileTreeRevision) return cached;
1891
+ const declarations = [...sourceFile.getImportDeclarations().map((declaration) => ({
1892
+ declaration,
1893
+ kind: "import"
1894
+ })), ...sourceFile.getExportDeclarations().map((declaration) => ({
1895
+ declaration,
1896
+ kind: "export"
1897
+ }))].filter(({ declaration }) => declaration.getModuleSpecifierValue() !== void 0).sort((left, right) => left.declaration.getStart() - right.declaration.getStart());
1898
+ const facts = [];
1899
+ const pendingCandidates = [];
1900
+ const configurationFiles = [];
1901
+ for (const [ordinal, { declaration, kind }] of declarations.entries()) {
1902
+ const specifier = declaration.getModuleSpecifierValue();
1903
+ if (!specifier) continue;
1904
+ const resolved = this.resolveSpecifier(specifier, sourceFile);
1905
+ if (!resolved.local) continue;
1906
+ facts.push(Object.freeze({
1907
+ importer,
1908
+ target: resolved.sourceFile ? this.normalizePath(resolved.sourceFile.getFilePath()) : null,
1909
+ specifier,
1910
+ kind,
1911
+ ordinal
1912
+ }));
1913
+ for (const target of resolved.pendingCandidates) pendingCandidates.push(Object.freeze({
1914
+ importer,
1915
+ target,
1916
+ specifier,
1917
+ kind,
1918
+ ordinal
1919
+ }));
1920
+ for (const target of resolved.configurationFiles) configurationFiles.push(Object.freeze({
1921
+ importer,
1922
+ target,
1923
+ specifier,
1924
+ kind,
1925
+ ordinal
1926
+ }));
1927
+ }
1928
+ this.publishResolutionFacts(importer, sourceFile, text, facts, pendingCandidates, configurationFiles);
1929
+ return this.resolutionsByImporter.get(importer);
1930
+ };
1931
+ /** The sole cross-file source resolver supplied to parser and extractor. */
1932
+ resolveModule = (specifier, from) => {
1933
+ const target = this.ensureResolutionFacts(from).facts.find((fact) => fact.specifier === specifier)?.target;
1934
+ if (!target) return;
1935
+ const sourceFile = this.project.getSourceFile(target);
1936
+ if (!sourceFile) return;
1937
+ this.prepareEffectiveSource(sourceFile.getFilePath(), sourceFile);
1938
+ return sourceFile;
1939
+ };
1439
1940
  trackDependencies = (filePath, sourceFile) => {
1941
+ this.#assertNotLoading();
1440
1942
  const importer = this.normalizePath(sourceFile.getFilePath());
1441
1943
  this.canonicalPaths.set(this.normalizePath(filePath), importer);
1442
- for (const previous of this.dependencies.get(importer) ?? []) this.dependents.get(previous)?.delete(importer);
1443
- const current = /* @__PURE__ */ new Set();
1444
- const declarations = [...sourceFile.getImportDeclarations(), ...sourceFile.getExportDeclarations()];
1445
- let unresolved = false;
1446
- for (const decl of declarations) {
1447
- const imported = this.resolveImport(decl);
1448
- if (!imported) {
1449
- if (decl.getModuleSpecifierValue()?.startsWith(".")) unresolved = true;
1450
- continue;
1944
+ this.canonicalPaths.set(importer, importer);
1945
+ this.ensureResolutionFacts(sourceFile);
1946
+ };
1947
+ invalidateSourcePreparation = (filePath, sourceFile) => {
1948
+ const sourcePath = this.normalizePath(sourceFile?.getFilePath() ?? filePath);
1949
+ const preparation = this.sourcePreparations.get(sourcePath);
1950
+ if (preparation?.state === "preparing") preparation.invalidated = true;
1951
+ this.sourcePreparations.delete(sourcePath);
1952
+ this.retractImporter(sourcePath);
1953
+ };
1954
+ /**
1955
+ * Apply the parser hook and built-in transform once to one source revision.
1956
+ *
1957
+ * Resolver traversal is a semantic read just as surely as an explicit parse: returning a
1958
+ * raw dependency here would let parse order decide both the value extraction sees and the
1959
+ * downstream edges the ledger records. The transaction is published only after the AST and
1960
+ * its facts agree. A failed or re-entered attempt restores the input AST and can be retried.
1961
+ */
1962
+ prepareEffectiveSource = (filePath, sourceFile) => {
1963
+ this.#assertNotLoading();
1964
+ const sourcePath = this.normalizePath(sourceFile.getFilePath());
1965
+ const currentText = sourceFile.getFullText();
1966
+ const current = this.sourcePreparations.get(sourcePath);
1967
+ if (current?.state === "ready" && current.sourceFile === sourceFile && current.effectiveText === currentText) {
1968
+ this.trackDependencies(filePath, sourceFile);
1969
+ return current;
1970
+ }
1971
+ if (current?.state === "preparing") {
1972
+ current.invalidated = true;
1973
+ throw new Error(`Project source is already being prepared: ${sourcePath}`);
1974
+ }
1975
+ this.sourcePreparations.delete(sourcePath);
1976
+ this.retractImporter(sourcePath);
1977
+ const transaction = {
1978
+ state: "preparing",
1979
+ sourceFile,
1980
+ inputText: currentText,
1981
+ invalidated: false
1982
+ };
1983
+ this.sourcePreparations.set(sourcePath, transaction);
1984
+ const assertTransaction = (expectedText) => {
1985
+ let actualText;
1986
+ try {
1987
+ actualText = sourceFile.getFullText();
1988
+ } catch {
1989
+ transaction.invalidated = true;
1990
+ actualText = "";
1451
1991
  }
1452
- const importedPath = this.normalizePath(imported.getFilePath());
1453
- if (importedPath === importer) continue;
1454
- const importers = this.dependents.get(importedPath) ?? /* @__PURE__ */ new Set();
1455
- importers.add(importer);
1456
- this.dependents.set(importedPath, importers);
1457
- current.add(importedPath);
1992
+ if (transaction.invalidated || this.sourcePreparations.get(sourcePath) !== transaction || actualText !== expectedText) throw new Error(`Project source changed while it was being prepared: ${sourcePath}`);
1993
+ };
1994
+ const options = {};
1995
+ try {
1996
+ const custom = this.options.hooks["parser:before"]?.({
1997
+ filePath,
1998
+ content: currentText,
1999
+ configure(next) {
2000
+ const { matchTag, matchTagMode, matchTagProp } = next;
2001
+ if (matchTag) options.matchTag = matchTag;
2002
+ if (matchTagMode) options.matchTagMode = matchTagMode;
2003
+ if (matchTagProp) options.matchTagProp = matchTagProp;
2004
+ }
2005
+ });
2006
+ assertTransaction(currentText);
2007
+ const transformed = custom ?? this.transformFile(filePath, currentText);
2008
+ assertTransaction(currentText);
2009
+ if (currentText !== transformed) sourceFile.replaceWithText(transformed);
2010
+ assertTransaction(transformed);
2011
+ this.trackDependencies(filePath, sourceFile);
2012
+ assertTransaction(transformed);
2013
+ const ready = Object.freeze({
2014
+ state: "ready",
2015
+ sourceFile,
2016
+ inputText: currentText,
2017
+ effectiveText: transformed,
2018
+ options: Object.freeze({ ...options })
2019
+ });
2020
+ this.sourcePreparations.set(sourcePath, ready);
2021
+ return ready;
2022
+ } catch (error) {
2023
+ if (this.sourcePreparations.get(sourcePath) === transaction) this.sourcePreparations.delete(sourcePath);
2024
+ this.retractImporter(sourcePath);
2025
+ try {
2026
+ if (sourceFile.getFullText() !== currentText) sourceFile.replaceWithText(currentText);
2027
+ } catch {}
2028
+ throw error;
1458
2029
  }
1459
- this.dependencies.set(importer, current);
1460
- if (unresolved) this.unresolvedImporters.add(importer);
1461
- else this.unresolvedImporters.delete(importer);
2030
+ };
2031
+ markTargetRemoved = (target, sourceFile) => {
2032
+ const importers = new Set(this.dependents.get(target) ?? []);
2033
+ if (importers.size > 0) this.removedDependents.set(target, importers);
2034
+ for (const importer of importers) {
2035
+ const entry = this.resolutionsByImporter.get(importer);
2036
+ if (!entry) continue;
2037
+ const facts = entry.facts.map((fact) => fact.target === target ? Object.freeze({
2038
+ ...fact,
2039
+ target: null
2040
+ }) : fact);
2041
+ this.dependencies.get(importer)?.delete(target);
2042
+ if (facts.some((fact) => fact.target === null)) this.unresolvedImporters.add(importer);
2043
+ this.resolutionsByImporter.set(importer, {
2044
+ ...entry,
2045
+ facts: Object.freeze(facts),
2046
+ hasPendingLocalCandidate: true
2047
+ });
2048
+ }
2049
+ this.dependents.delete(target);
2050
+ this.retractImporter(target);
2051
+ this.removedSourcePaths.add(target);
2052
+ this.canonicalPaths.set(target, target);
2053
+ this.canonicalPaths.set(this.normalizePath(sourceFile.getFilePath()), target);
1462
2054
  };
1463
2055
  /**
1464
2056
  * Every file that transitively imports `filePath`, so a watcher can re-parse the
1465
2057
  * consumers of an edited file. Excludes `filePath` itself.
1466
2058
  */
1467
2059
  getDependents = (filePath) => {
2060
+ this.#assertNotLoading();
1468
2061
  const given = this.normalizePath(filePath);
1469
2062
  const resolved = this.project.getSourceFile(filePath)?.getFilePath();
1470
2063
  const start = resolved ? this.normalizePath(resolved) : this.canonicalPaths.get(given) ?? given;
@@ -1472,23 +2065,139 @@ var Project = class {
1472
2065
  const queue = [start];
1473
2066
  while (queue.length) {
1474
2067
  const current = queue.shift();
1475
- for (const importer of this.dependents.get(current) ?? []) {
2068
+ const importers = new Set([...this.dependents.get(current) ?? [], ...this.removedDependents.get(current) ?? []]);
2069
+ for (const importer of [...importers].sort()) {
1476
2070
  if (importer === start || seen.has(importer)) continue;
1477
2071
  seen.add(importer);
1478
2072
  queue.push(importer);
1479
2073
  }
1480
2074
  }
1481
- return [...seen];
2075
+ return [...seen].sort();
2076
+ };
2077
+ /**
2078
+ * Every local source transitively read by `filePath`, excluding the file itself. When
2079
+ * `targets` are supplied, retain only paths leading to one of those semantic reads.
2080
+ *
2081
+ * This is the forward half of `getDependents`, exposed for bundler consumers which must
2082
+ * register the complete semantic read-set as watch files. Walking the indexed ledger closure
2083
+ * avoids rescanning every resolution fact once per transformed module.
2084
+ */
2085
+ getDependencies = (filePath, targets) => {
2086
+ this.#assertNotLoading();
2087
+ const given = this.normalizePath(filePath);
2088
+ const resolved = this.project.getSourceFile(filePath)?.getFilePath();
2089
+ const start = resolved ? this.normalizePath(resolved) : this.canonicalPaths.get(given) ?? given;
2090
+ const seen = /* @__PURE__ */ new Set();
2091
+ const importersByDependency = /* @__PURE__ */ new Map();
2092
+ const queue = [start];
2093
+ while (queue.length) {
2094
+ const current = queue.shift();
2095
+ for (const dependency of [...this.dependencies.get(current) ?? []].sort()) {
2096
+ const importers = importersByDependency.get(dependency) ?? /* @__PURE__ */ new Set();
2097
+ importers.add(current);
2098
+ importersByDependency.set(dependency, importers);
2099
+ if (dependency === start || seen.has(dependency)) continue;
2100
+ seen.add(dependency);
2101
+ queue.push(dependency);
2102
+ }
2103
+ }
2104
+ if (targets === void 0) return [...seen].sort();
2105
+ const selected = /* @__PURE__ */ new Set();
2106
+ const reverse = Array.from(targets, (target) => {
2107
+ const normalized = this.normalizePath(target);
2108
+ const source = this.project.getSourceFile(target)?.getFilePath();
2109
+ return source ? this.normalizePath(source) : this.canonicalPaths.get(normalized) ?? normalized;
2110
+ }).filter((target) => seen.has(target));
2111
+ while (reverse.length) {
2112
+ const dependency = reverse.shift();
2113
+ if (selected.has(dependency)) continue;
2114
+ selected.add(dependency);
2115
+ for (const importer of importersByDependency.get(dependency) ?? []) if (importer !== start && !selected.has(importer)) reverse.push(importer);
2116
+ }
2117
+ return [...selected].sort();
2118
+ };
2119
+ /**
2120
+ * Exact semantic dependencies plus missing local resolver candidates which can supersede
2121
+ * one of those dependencies.
2122
+ *
2123
+ * Candidate provenance stays attached to its import/export fact. Filtering those facts
2124
+ * through the selected dependency closure keeps an unrelated runtime import—even a local
2125
+ * alias with its own fallback—out of the watch set without asking consumers to reinterpret
2126
+ * specifiers or scan the checkout.
2127
+ */
2128
+ getResolutionReadSet = (filePath, targets, previous) => {
2129
+ const dependencies = this.getDependencies(filePath, targets);
2130
+ const selected = new Set(dependencies);
2131
+ const retained = new Set([...previous?.dependencies ?? [], ...previous?.pendingCandidates ?? []]);
2132
+ const given = this.normalizePath(filePath);
2133
+ const resolved = this.project.getSourceFile(filePath)?.getFilePath();
2134
+ const start = resolved ? this.normalizePath(resolved) : this.canonicalPaths.get(given) ?? given;
2135
+ const pendingCandidates = /* @__PURE__ */ new Set();
2136
+ for (const importer of [start, ...dependencies]) {
2137
+ const resolution = this.resolutionsByImporter.get(importer);
2138
+ if (!resolution) continue;
2139
+ const semanticOrdinals = new Set(resolution.facts.filter((fact) => fact.target !== null && selected.has(fact.target)).map((fact) => fact.ordinal));
2140
+ for (const fact of resolution.facts) {
2141
+ if (fact.target !== null) continue;
2142
+ if (resolution.pendingCandidates.some((candidate) => candidate.ordinal === fact.ordinal && retained.has(candidate.target))) semanticOrdinals.add(fact.ordinal);
2143
+ }
2144
+ for (const candidate of resolution.pendingCandidates) if (semanticOrdinals.has(candidate.ordinal)) pendingCandidates.add(candidate.target);
2145
+ }
2146
+ return Object.freeze({
2147
+ dependencies: Object.freeze([...dependencies]),
2148
+ pendingCandidates: Object.freeze([...pendingCandidates].sort())
2149
+ });
2150
+ };
2151
+ /**
2152
+ * Exact resolution configuration files read by the semantic closure selected by `targets`.
2153
+ *
2154
+ * Package manifests stay attached to their import/export ordinal, so a runtime-only branch
2155
+ * cannot turn an arbitrary package.json into a Builder dependency. Tsconfig files are global
2156
+ * inputs to those same selected facts and are added only when the owner has a semantic
2157
+ * cross-file read.
2158
+ *
2159
+ * @internal
2160
+ */
2161
+ getResolutionConfigurationFiles = (filePath, targets, previous = []) => {
2162
+ const dependencies = this.getDependencies(filePath, targets);
2163
+ const selected = new Set(dependencies);
2164
+ const retained = new Set(Array.from(previous, (file) => this.normalizePath(file)));
2165
+ const given = this.normalizePath(filePath);
2166
+ const resolved = this.project.getSourceFile(filePath)?.getFilePath();
2167
+ const start = resolved ? this.normalizePath(resolved) : this.canonicalPaths.get(given) ?? given;
2168
+ const files = /* @__PURE__ */ new Set();
2169
+ let hasSemanticFact = false;
2170
+ for (const importer of [start, ...dependencies]) {
2171
+ const resolution = this.resolutionsByImporter.get(importer);
2172
+ if (!resolution) continue;
2173
+ const semanticOrdinals = new Set(resolution.facts.filter((fact) => fact.target !== null && selected.has(fact.target)).map((fact) => fact.ordinal));
2174
+ for (const fact of resolution.facts) {
2175
+ if (fact.target !== null) continue;
2176
+ if (resolution.configurationFiles.some((configuration) => configuration.ordinal === fact.ordinal && retained.has(configuration.target))) semanticOrdinals.add(fact.ordinal);
2177
+ }
2178
+ if (semanticOrdinals.size > 0) hasSemanticFact = true;
2179
+ for (const configuration of resolution.configurationFiles) if (semanticOrdinals.has(configuration.ordinal)) files.add(configuration.target);
2180
+ }
2181
+ if (hasSemanticFact) for (const file of this.options.resolutionConfigFiles ?? []) files.add(this.normalizePath(file));
2182
+ return Object.freeze([...files].sort());
1482
2183
  };
1483
2184
  createSourceFile = (filePath) => {
2185
+ this.#assertNotLoading();
2186
+ this.#ensureSourceFiles();
1484
2187
  const { readFile } = this.options;
2188
+ const content = readFile(filePath);
2189
+ const existing = this.project.getSourceFile(filePath);
2190
+ this.invalidateSourcePreparation(filePath, existing);
2191
+ this.removedSourcePaths.delete(this.normalizePath(filePath));
1485
2192
  this.invalidate();
1486
- return this.project.createSourceFile(filePath, readFile(filePath), {
2193
+ return this.project.createSourceFile(filePath, content, {
1487
2194
  overwrite: true,
1488
2195
  scriptKind: scriptKindFor(filePath)
1489
2196
  });
1490
2197
  };
1491
2198
  createSourceFiles = () => {
2199
+ this.#assertNotLoading();
2200
+ this.#ensureSourceFiles();
1492
2201
  const files = this.getFiles();
1493
2202
  for (const file of files) try {
1494
2203
  this.createSourceFile(file);
@@ -1497,6 +2206,8 @@ var Project = class {
1497
2206
  }
1498
2207
  };
1499
2208
  addSourceFile = (filePath, content) => {
2209
+ this.#assertNotLoading();
2210
+ this.#ensureSourceFiles();
1500
2211
  const existing = filePath.includes("/") ? this.project.getSourceFile(filePath) : void 0;
1501
2212
  /**
1502
2213
  * Re-adding a file the text it already holds is a no-op, and saying so is what makes the
@@ -1521,6 +2232,8 @@ var Project = class {
1521
2232
  * matches its own source, falls through, and is overwritten exactly as before.
1522
2233
  */
1523
2234
  if (existing && existing.getFullText() === content) return existing;
2235
+ this.invalidateSourcePreparation(filePath, existing);
2236
+ this.removedSourcePaths.delete(this.normalizePath(filePath));
1524
2237
  this.invalidate(!existing);
1525
2238
  return this.project.createSourceFile(filePath, content, {
1526
2239
  overwrite: true,
@@ -1528,54 +2241,69 @@ var Project = class {
1528
2241
  });
1529
2242
  };
1530
2243
  removeSourceFile = (filePath) => {
2244
+ this.#assertNotLoading();
2245
+ this.#ensureSourceFiles();
1531
2246
  const sourceFile = this.project.getSourceFile(filePath);
1532
2247
  if (sourceFile) {
1533
2248
  this.invalidate();
2249
+ this.invalidateSourcePreparation(filePath, sourceFile);
2250
+ this.markTargetRemoved(this.normalizePath(sourceFile.getFilePath()), sourceFile);
1534
2251
  this.options.parserOptions.encoder.releaseFile(sourceFile.getFilePath());
1535
2252
  return this.project.removeSourceFile(sourceFile);
1536
2253
  }
1537
2254
  return false;
1538
2255
  };
1539
2256
  reloadSourceFile = (filePath) => {
2257
+ this.#assertNotLoading();
2258
+ this.#ensureSourceFiles();
1540
2259
  this.invalidate(false);
1541
- return this.getSourceFile(filePath)?.refreshFromFileSystemSync();
2260
+ const sourceFile = this.getSourceFile(filePath);
2261
+ if (!sourceFile) return;
2262
+ this.invalidateSourcePreparation(filePath, sourceFile);
2263
+ return sourceFile.refreshFromFileSystemSync();
1542
2264
  };
1543
2265
  reloadSourceFiles = () => {
2266
+ this.#assertNotLoading();
2267
+ this.#ensureSourceFiles();
1544
2268
  const files = this.getFiles();
1545
2269
  this.invalidate();
1546
- for (const file of files) this.getSourceFile(file)?.refreshFromFileSystemSync() ?? this.project.addSourceFileAtPath(file);
2270
+ for (const file of files) {
2271
+ const source = this.getSourceFile(file);
2272
+ if (source) {
2273
+ this.invalidateSourcePreparation(file, source);
2274
+ source.refreshFromFileSystemSync();
2275
+ } else {
2276
+ this.invalidateSourcePreparation(file);
2277
+ this.removedSourcePaths.delete(this.normalizePath(file));
2278
+ this.project.createSourceFile(file, this.options.readFile(file), {
2279
+ overwrite: true,
2280
+ scriptKind: scriptKindFor(file)
2281
+ });
2282
+ }
2283
+ }
1547
2284
  };
1548
2285
  get readFile() {
2286
+ this.#assertNotLoading();
1549
2287
  return this.options.readFile;
1550
2288
  }
1551
2289
  get getFiles() {
2290
+ this.#assertNotLoading();
1552
2291
  return this.options.getFiles;
1553
2292
  }
1554
2293
  parseJson = (filePath) => {
2294
+ this.#assertNotLoading();
1555
2295
  const { readFile, parserOptions } = this.options;
1556
2296
  const content = readFile(filePath);
1557
2297
  parserOptions.encoder.fromJSON(JSON.parse(content));
1558
2298
  return new ParserResult(parserOptions).setFilePath(filePath);
1559
2299
  };
1560
2300
  parseSourceFile = (filePath, encoder) => {
2301
+ this.#assertNotLoading();
1561
2302
  const { hooks } = this.options;
1562
2303
  if (filePath.endsWith(".json")) return this.parseJson(filePath);
1563
2304
  const sourceFile = this.project.getSourceFile(filePath);
1564
2305
  if (!sourceFile) return;
1565
- this.trackDependencies(filePath, sourceFile);
1566
- const original = sourceFile.getText();
1567
- const options = {};
1568
- const transformed = hooks["parser:before"]?.({
1569
- filePath,
1570
- content: original,
1571
- configure(opts) {
1572
- const { matchTag, matchTagMode, matchTagProp } = opts;
1573
- if (matchTag) options.matchTag = matchTag;
1574
- if (matchTagMode) options.matchTagMode = matchTagMode;
1575
- if (matchTagProp) options.matchTagProp = matchTagProp;
1576
- }
1577
- }) ?? this.transformFile(filePath, original);
1578
- if (original !== transformed) sourceFile.replaceWithText(transformed);
2306
+ const { options } = this.prepareEffectiveSource(filePath, sourceFile);
1579
2307
  const result = (encoder ?? this.options.parserOptions.encoder).withOwner("parse", sourceFile.getFilePath(), () => this.parser(sourceFile, encoder, options, this.resolveModule))?.setFilePath(filePath);
1580
2308
  hooks["parser:after"]?.({
1581
2309
  filePath,
@@ -1584,9 +2312,11 @@ var Project = class {
1584
2312
  return result;
1585
2313
  };
1586
2314
  transformFile = (_filePath, content) => {
2315
+ this.#assertNotLoading();
1587
2316
  return content;
1588
2317
  };
1589
2318
  classify = (fileMap) => {
2319
+ this.#assertNotLoading();
1590
2320
  const { parserOptions } = this.options;
1591
2321
  return classifyProject(parserOptions, fileMap);
1592
2322
  };