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