@bamboocss/parser 1.46.0 → 1.46.2
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 +107 -3
- package/dist/index.d.cts +21 -0
- package/dist/index.d.mts +21 -0
- package/dist/index.mjs +108 -4
- package/package.json +10 -10
package/dist/index.cjs
CHANGED
|
@@ -2,6 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
let ts_morph = require("ts-morph");
|
|
3
3
|
let _bamboocss_extractor = require("@bamboocss/extractor");
|
|
4
4
|
let _bamboocss_shared = require("@bamboocss/shared");
|
|
5
|
+
let node_crypto = require("node:crypto");
|
|
5
6
|
let _bamboocss_logger = require("@bamboocss/logger");
|
|
6
7
|
let ts_pattern = require("ts-pattern");
|
|
7
8
|
let _bamboocss_config_ts_path = require("@bamboocss/config/ts-path");
|
|
@@ -612,6 +613,52 @@ const walkImports = (sourceFile, imports, resolveModule, seen) => {
|
|
|
612
613
|
*/
|
|
613
614
|
const importedRecipeBindings = (sourceFile, imports, resolveModule) => walkImports(sourceFile, imports, resolveModule, new Set([sourceFile])).bindings;
|
|
614
615
|
//#endregion
|
|
616
|
+
//#region src/export-read-digest.ts
|
|
617
|
+
/**
|
|
618
|
+
* A digest of what one exported name resolves to right now, or `undefined` when the answer
|
|
619
|
+
* cannot be pinned down.
|
|
620
|
+
*
|
|
621
|
+
* This is the witness the read-verification protocol compares: a dependent records the digest
|
|
622
|
+
* of every cross-file value it folded, and an edit to the exporting file is verified by
|
|
623
|
+
* recomputing the same digest through the same resolution path. The two sides must therefore
|
|
624
|
+
* be the *same function* -- recorded at parse time, recomputed at verification time -- or
|
|
625
|
+
* equal values would compare unequal and every edit would fall back to a full re-fold.
|
|
626
|
+
*
|
|
627
|
+
* `undefined` is the conservative verdict, and everything unusual maps to it: a missing
|
|
628
|
+
* initializer, an evaluation the bare context cannot resolve (a value built through the
|
|
629
|
+
* parser's evaluation environment, say), a conditional value, a non-serializable result, a
|
|
630
|
+
* throw. An export that stopped existing is *not* unknown -- it digests to a distinct
|
|
631
|
+
* sentinel, so its consumers read as changed rather than unverifiable.
|
|
632
|
+
*
|
|
633
|
+
* Serialized with insertion order preserved: property order is part of a style value's
|
|
634
|
+
* meaning, so reordering keys must read as a change. `undefined` leaves inside the value are
|
|
635
|
+
* kept visible through a sentinel, since `JSON.stringify` would otherwise erase the
|
|
636
|
+
* difference between an unresolvable member and an absent one.
|
|
637
|
+
*/
|
|
638
|
+
const digestExportValue = (sourceFile, exportedName, resolveModule, onCrossing) => {
|
|
639
|
+
if (!sourceFile) return "bamboo:module-missing";
|
|
640
|
+
const boxCtx = {
|
|
641
|
+
flags: { skipTraverseFiles: false },
|
|
642
|
+
resolveModule,
|
|
643
|
+
recordDependency: onCrossing
|
|
644
|
+
};
|
|
645
|
+
try {
|
|
646
|
+
const declaration = (0, _bamboocss_extractor.getExportedVarDeclarationWithName)(exportedName, sourceFile, [], boxCtx);
|
|
647
|
+
if (!declaration) return "bamboo:export-missing";
|
|
648
|
+
const initializer = declaration.getInitializer?.();
|
|
649
|
+
if (!initializer) return void 0;
|
|
650
|
+
const box = (0, _bamboocss_extractor.maybeBoxNode)(initializer, [], boxCtx);
|
|
651
|
+
if (!box || Array.isArray(box)) return void 0;
|
|
652
|
+
const unboxed = (0, _bamboocss_extractor.unbox)(box);
|
|
653
|
+
if (unboxed.conditions.length || unboxed.spreadConditions.length) return void 0;
|
|
654
|
+
const json = JSON.stringify(unboxed.raw, (_key, value) => value === void 0 ? "bamboo:undefined" : value);
|
|
655
|
+
if (json === void 0) return void 0;
|
|
656
|
+
return (0, node_crypto.createHash)("sha256").update(json).digest("base64");
|
|
657
|
+
} catch {
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
//#endregion
|
|
615
662
|
//#region src/get-import-declarations.ts
|
|
616
663
|
function getImportDeclarations(context, sourceFile) {
|
|
617
664
|
const { imports, tsOptions } = context;
|
|
@@ -1048,6 +1095,16 @@ var ParserResult = class {
|
|
|
1048
1095
|
addDependency(filePath) {
|
|
1049
1096
|
this.dependencies.add(filePath.replaceAll("\\", "/"));
|
|
1050
1097
|
}
|
|
1098
|
+
/** Cross-file value reads this extraction performed, with the digests read at parse time. */
|
|
1099
|
+
exportReads = [];
|
|
1100
|
+
/** @internal Set once by the parser after extraction, digests computed on warm caches. */
|
|
1101
|
+
setExportReads(reads) {
|
|
1102
|
+
this.exportReads = reads;
|
|
1103
|
+
}
|
|
1104
|
+
/** @internal The reads a consumer can verify instead of re-folding; see the Vite plugin. */
|
|
1105
|
+
getExportReads() {
|
|
1106
|
+
return this.exportReads;
|
|
1107
|
+
}
|
|
1051
1108
|
/**
|
|
1052
1109
|
* Local source paths crossed while resolving values this extraction actually encoded.
|
|
1053
1110
|
*
|
|
@@ -1173,6 +1230,8 @@ function createParser(context) {
|
|
|
1173
1230
|
});
|
|
1174
1231
|
};
|
|
1175
1232
|
const recordDependency = (filePath) => parserResult.addDependency(filePath);
|
|
1233
|
+
const exportReadPairs = /* @__PURE__ */ new Set();
|
|
1234
|
+
const recordExportRead = (filePath, exportedName) => exportReadPairs.add(`${filePath.replaceAll("\\", "/")}\u0000${exportedName}`);
|
|
1176
1235
|
(0, _bamboocss_extractor.extract)({
|
|
1177
1236
|
ast: sourceFile,
|
|
1178
1237
|
tokens: context.tokens ? {
|
|
@@ -1223,6 +1282,7 @@ function createParser(context) {
|
|
|
1223
1282
|
},
|
|
1224
1283
|
flags: { skipTraverseFiles: false },
|
|
1225
1284
|
recordDependency,
|
|
1285
|
+
recordExportRead,
|
|
1226
1286
|
resolveModule
|
|
1227
1287
|
}).forEach((result, alias) => {
|
|
1228
1288
|
const name = file.getName(file.normalizeFnName(alias));
|
|
@@ -1315,6 +1375,19 @@ function createParser(context) {
|
|
|
1315
1375
|
});
|
|
1316
1376
|
});
|
|
1317
1377
|
parserResult.deadCalls = file.getDeadCalls();
|
|
1378
|
+
if (exportReadPairs.size) {
|
|
1379
|
+
const project = sourceFile.getProject();
|
|
1380
|
+
parserResult.setExportReads([...exportReadPairs].map((pair) => {
|
|
1381
|
+
const at = pair.indexOf("\0");
|
|
1382
|
+
const file = pair.slice(0, at);
|
|
1383
|
+
const name = pair.slice(at + 1);
|
|
1384
|
+
return {
|
|
1385
|
+
file,
|
|
1386
|
+
name,
|
|
1387
|
+
digest: digestExportValue(project.getSourceFile(file), name, resolveModule)
|
|
1388
|
+
};
|
|
1389
|
+
}));
|
|
1390
|
+
}
|
|
1318
1391
|
return parserResult;
|
|
1319
1392
|
};
|
|
1320
1393
|
}
|
|
@@ -1680,8 +1753,28 @@ var Project = class {
|
|
|
1680
1753
|
* dependency edge; now it would also leave a recipe permanently invisible to the module
|
|
1681
1754
|
* importing it, since resolution is what finds one.
|
|
1682
1755
|
*/
|
|
1683
|
-
invalidate = (fileTreeChanged = true) => {
|
|
1756
|
+
invalidate = (fileTreeChanged = true, changedPath) => {
|
|
1684
1757
|
this.#assertNotLoading();
|
|
1758
|
+
/**
|
|
1759
|
+
* A content edit to a file the project already holds invalidates *by path* rather than
|
|
1760
|
+
* dropping every memoized value. Each cache entry records the module paths its
|
|
1761
|
+
* computation read — the same record the watch system replays into fold dependencies —
|
|
1762
|
+
* so marking the edited path stale rejects exactly the entries whose value could have
|
|
1763
|
+
* moved, while a value resolved without reading this file keeps its cache. The edited
|
|
1764
|
+
* file's own entries need no marking: its re-parse retires their key nodes.
|
|
1765
|
+
*
|
|
1766
|
+
* That narrowing holds only for content: a file appearing or disappearing can change
|
|
1767
|
+
* what a specifier resolves to without any recorded path's bytes moving — extension
|
|
1768
|
+
* precedence, a new local shadowing a package — so tree changes keep the full clear.
|
|
1769
|
+
* The recipe-origin memo records no read-set, so it is cleared on every event either
|
|
1770
|
+
* way; per edit that is one walk rebuilt lazily, not the per-transform storm the
|
|
1771
|
+
* identical-text no-op in `addSourceFile` exists to prevent.
|
|
1772
|
+
*/
|
|
1773
|
+
if (!fileTreeChanged && changedPath) {
|
|
1774
|
+
(0, _bamboocss_extractor.invalidateDependencyPath)(this.normalizePath(changedPath));
|
|
1775
|
+
clearImportedRecipeCache();
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1685
1778
|
invalidateResolutions();
|
|
1686
1779
|
if (fileTreeChanged) {
|
|
1687
1780
|
this.#moduleResolutionCache = void 0;
|
|
@@ -2234,7 +2327,7 @@ var Project = class {
|
|
|
2234
2327
|
if (existing && existing.getFullText() === content) return existing;
|
|
2235
2328
|
this.invalidateSourcePreparation(filePath, existing);
|
|
2236
2329
|
this.removedSourcePaths.delete(this.normalizePath(filePath));
|
|
2237
|
-
this.invalidate(!existing);
|
|
2330
|
+
this.invalidate(!existing, existing?.getFilePath());
|
|
2238
2331
|
return this.project.createSourceFile(filePath, content, {
|
|
2239
2332
|
overwrite: true,
|
|
2240
2333
|
scriptKind: scriptKindFor(filePath)
|
|
@@ -2253,11 +2346,22 @@ var Project = class {
|
|
|
2253
2346
|
}
|
|
2254
2347
|
return false;
|
|
2255
2348
|
};
|
|
2349
|
+
/**
|
|
2350
|
+
* The current digest of one exported value, for read verification.
|
|
2351
|
+
*
|
|
2352
|
+
* Same function and same resolver identity as the parse-time recording, which is what makes
|
|
2353
|
+
* the comparison meaningful; see `digestExportValue`.
|
|
2354
|
+
*/
|
|
2355
|
+
digestExportRead = (filePath, exportedName, onCrossing) => {
|
|
2356
|
+
this.#assertNotLoading();
|
|
2357
|
+
this.#ensureSourceFiles();
|
|
2358
|
+
return digestExportValue(this.getSourceFile(filePath), exportedName, this.resolveModule, onCrossing);
|
|
2359
|
+
};
|
|
2256
2360
|
reloadSourceFile = (filePath) => {
|
|
2257
2361
|
this.#assertNotLoading();
|
|
2258
2362
|
this.#ensureSourceFiles();
|
|
2259
|
-
this.invalidate(false);
|
|
2260
2363
|
const sourceFile = this.getSourceFile(filePath);
|
|
2364
|
+
this.invalidate(false, sourceFile?.getFilePath());
|
|
2261
2365
|
if (!sourceFile) return;
|
|
2262
2366
|
this.invalidateSourcePreparation(filePath, sourceFile);
|
|
2263
2367
|
return sourceFile.refreshFromFileSystemSync();
|
package/dist/index.d.cts
CHANGED
|
@@ -357,6 +357,20 @@ declare class ParserResult implements ParserResultInterface {
|
|
|
357
357
|
setFilePath(filePath: string): this;
|
|
358
358
|
/** @internal Called only by the extractor-facing resolver, not by import classification. */
|
|
359
359
|
addDependency(filePath: string): void;
|
|
360
|
+
/** Cross-file value reads this extraction performed, with the digests read at parse time. */
|
|
361
|
+
private exportReads;
|
|
362
|
+
/** @internal Set once by the parser after extraction, digests computed on warm caches. */
|
|
363
|
+
setExportReads(reads: Array<{
|
|
364
|
+
file: string;
|
|
365
|
+
name: string;
|
|
366
|
+
digest: string | undefined;
|
|
367
|
+
}>): void;
|
|
368
|
+
/** @internal The reads a consumer can verify instead of re-folding; see the Vite plugin. */
|
|
369
|
+
getExportReads(): ReadonlyArray<{
|
|
370
|
+
file: string;
|
|
371
|
+
name: string;
|
|
372
|
+
digest: string | undefined;
|
|
373
|
+
}>;
|
|
360
374
|
/**
|
|
361
375
|
* Local source paths crossed while resolving values this extraction actually encoded.
|
|
362
376
|
*
|
|
@@ -584,6 +598,13 @@ declare class Project {
|
|
|
584
598
|
createSourceFiles: () => void;
|
|
585
599
|
addSourceFile: (filePath: string, content: string) => SourceFile;
|
|
586
600
|
removeSourceFile: (filePath: string) => boolean;
|
|
601
|
+
/**
|
|
602
|
+
* The current digest of one exported value, for read verification.
|
|
603
|
+
*
|
|
604
|
+
* Same function and same resolver identity as the parse-time recording, which is what makes
|
|
605
|
+
* the comparison meaningful; see `digestExportValue`.
|
|
606
|
+
*/
|
|
607
|
+
digestExportRead: (filePath: string, exportedName: string, onCrossing?: (crossedPath: string) => void) => string | undefined;
|
|
587
608
|
reloadSourceFile: (filePath: string) => FileSystemRefreshResult | undefined;
|
|
588
609
|
reloadSourceFiles: () => void;
|
|
589
610
|
get readFile(): (filePath: string) => string;
|
package/dist/index.d.mts
CHANGED
|
@@ -357,6 +357,20 @@ declare class ParserResult implements ParserResultInterface {
|
|
|
357
357
|
setFilePath(filePath: string): this;
|
|
358
358
|
/** @internal Called only by the extractor-facing resolver, not by import classification. */
|
|
359
359
|
addDependency(filePath: string): void;
|
|
360
|
+
/** Cross-file value reads this extraction performed, with the digests read at parse time. */
|
|
361
|
+
private exportReads;
|
|
362
|
+
/** @internal Set once by the parser after extraction, digests computed on warm caches. */
|
|
363
|
+
setExportReads(reads: Array<{
|
|
364
|
+
file: string;
|
|
365
|
+
name: string;
|
|
366
|
+
digest: string | undefined;
|
|
367
|
+
}>): void;
|
|
368
|
+
/** @internal The reads a consumer can verify instead of re-folding; see the Vite plugin. */
|
|
369
|
+
getExportReads(): ReadonlyArray<{
|
|
370
|
+
file: string;
|
|
371
|
+
name: string;
|
|
372
|
+
digest: string | undefined;
|
|
373
|
+
}>;
|
|
360
374
|
/**
|
|
361
375
|
* Local source paths crossed while resolving values this extraction actually encoded.
|
|
362
376
|
*
|
|
@@ -584,6 +598,13 @@ declare class Project {
|
|
|
584
598
|
createSourceFiles: () => void;
|
|
585
599
|
addSourceFile: (filePath: string, content: string) => SourceFile;
|
|
586
600
|
removeSourceFile: (filePath: string) => boolean;
|
|
601
|
+
/**
|
|
602
|
+
* The current digest of one exported value, for read verification.
|
|
603
|
+
*
|
|
604
|
+
* Same function and same resolver identity as the parse-time recording, which is what makes
|
|
605
|
+
* the comparison meaningful; see `digestExportValue`.
|
|
606
|
+
*/
|
|
607
|
+
digestExportRead: (filePath: string, exportedName: string, onCrossing?: (crossedPath: string) => void) => string | undefined;
|
|
587
608
|
reloadSourceFile: (filePath: string) => FileSystemRefreshResult | undefined;
|
|
588
609
|
reloadSourceFiles: () => void;
|
|
589
610
|
get readFile(): (filePath: string) => string;
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Node, Project as Project$1, ScriptKind, ts } from "ts-morph";
|
|
2
|
-
import { box, clearBoxNodeCache, extract, unbox, unwrapExpression } from "@bamboocss/extractor";
|
|
2
|
+
import { box, clearBoxNodeCache, extract, getExportedVarDeclarationWithName, invalidateDependencyPath, maybeBoxNode, unbox, unwrapExpression } from "@bamboocss/extractor";
|
|
3
3
|
import { BambooError, compact, createPatternFns, getOrCreateSet } from "@bamboocss/shared";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
4
5
|
import { logger } from "@bamboocss/logger";
|
|
5
6
|
import { match } from "ts-pattern";
|
|
6
7
|
import { resolveTsPathPattern } from "@bamboocss/config/ts-path";
|
|
@@ -611,6 +612,52 @@ const walkImports = (sourceFile, imports, resolveModule, seen) => {
|
|
|
611
612
|
*/
|
|
612
613
|
const importedRecipeBindings = (sourceFile, imports, resolveModule) => walkImports(sourceFile, imports, resolveModule, new Set([sourceFile])).bindings;
|
|
613
614
|
//#endregion
|
|
615
|
+
//#region src/export-read-digest.ts
|
|
616
|
+
/**
|
|
617
|
+
* A digest of what one exported name resolves to right now, or `undefined` when the answer
|
|
618
|
+
* cannot be pinned down.
|
|
619
|
+
*
|
|
620
|
+
* This is the witness the read-verification protocol compares: a dependent records the digest
|
|
621
|
+
* of every cross-file value it folded, and an edit to the exporting file is verified by
|
|
622
|
+
* recomputing the same digest through the same resolution path. The two sides must therefore
|
|
623
|
+
* be the *same function* -- recorded at parse time, recomputed at verification time -- or
|
|
624
|
+
* equal values would compare unequal and every edit would fall back to a full re-fold.
|
|
625
|
+
*
|
|
626
|
+
* `undefined` is the conservative verdict, and everything unusual maps to it: a missing
|
|
627
|
+
* initializer, an evaluation the bare context cannot resolve (a value built through the
|
|
628
|
+
* parser's evaluation environment, say), a conditional value, a non-serializable result, a
|
|
629
|
+
* throw. An export that stopped existing is *not* unknown -- it digests to a distinct
|
|
630
|
+
* sentinel, so its consumers read as changed rather than unverifiable.
|
|
631
|
+
*
|
|
632
|
+
* Serialized with insertion order preserved: property order is part of a style value's
|
|
633
|
+
* meaning, so reordering keys must read as a change. `undefined` leaves inside the value are
|
|
634
|
+
* kept visible through a sentinel, since `JSON.stringify` would otherwise erase the
|
|
635
|
+
* difference between an unresolvable member and an absent one.
|
|
636
|
+
*/
|
|
637
|
+
const digestExportValue = (sourceFile, exportedName, resolveModule, onCrossing) => {
|
|
638
|
+
if (!sourceFile) return "bamboo:module-missing";
|
|
639
|
+
const boxCtx = {
|
|
640
|
+
flags: { skipTraverseFiles: false },
|
|
641
|
+
resolveModule,
|
|
642
|
+
recordDependency: onCrossing
|
|
643
|
+
};
|
|
644
|
+
try {
|
|
645
|
+
const declaration = getExportedVarDeclarationWithName(exportedName, sourceFile, [], boxCtx);
|
|
646
|
+
if (!declaration) return "bamboo:export-missing";
|
|
647
|
+
const initializer = declaration.getInitializer?.();
|
|
648
|
+
if (!initializer) return void 0;
|
|
649
|
+
const box = maybeBoxNode(initializer, [], boxCtx);
|
|
650
|
+
if (!box || Array.isArray(box)) return void 0;
|
|
651
|
+
const unboxed = unbox(box);
|
|
652
|
+
if (unboxed.conditions.length || unboxed.spreadConditions.length) return void 0;
|
|
653
|
+
const json = JSON.stringify(unboxed.raw, (_key, value) => value === void 0 ? "bamboo:undefined" : value);
|
|
654
|
+
if (json === void 0) return void 0;
|
|
655
|
+
return createHash("sha256").update(json).digest("base64");
|
|
656
|
+
} catch {
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
//#endregion
|
|
614
661
|
//#region src/get-import-declarations.ts
|
|
615
662
|
function getImportDeclarations(context, sourceFile) {
|
|
616
663
|
const { imports, tsOptions } = context;
|
|
@@ -1047,6 +1094,16 @@ var ParserResult = class {
|
|
|
1047
1094
|
addDependency(filePath) {
|
|
1048
1095
|
this.dependencies.add(filePath.replaceAll("\\", "/"));
|
|
1049
1096
|
}
|
|
1097
|
+
/** Cross-file value reads this extraction performed, with the digests read at parse time. */
|
|
1098
|
+
exportReads = [];
|
|
1099
|
+
/** @internal Set once by the parser after extraction, digests computed on warm caches. */
|
|
1100
|
+
setExportReads(reads) {
|
|
1101
|
+
this.exportReads = reads;
|
|
1102
|
+
}
|
|
1103
|
+
/** @internal The reads a consumer can verify instead of re-folding; see the Vite plugin. */
|
|
1104
|
+
getExportReads() {
|
|
1105
|
+
return this.exportReads;
|
|
1106
|
+
}
|
|
1050
1107
|
/**
|
|
1051
1108
|
* Local source paths crossed while resolving values this extraction actually encoded.
|
|
1052
1109
|
*
|
|
@@ -1172,6 +1229,8 @@ function createParser(context) {
|
|
|
1172
1229
|
});
|
|
1173
1230
|
};
|
|
1174
1231
|
const recordDependency = (filePath) => parserResult.addDependency(filePath);
|
|
1232
|
+
const exportReadPairs = /* @__PURE__ */ new Set();
|
|
1233
|
+
const recordExportRead = (filePath, exportedName) => exportReadPairs.add(`${filePath.replaceAll("\\", "/")}\u0000${exportedName}`);
|
|
1175
1234
|
extract({
|
|
1176
1235
|
ast: sourceFile,
|
|
1177
1236
|
tokens: context.tokens ? {
|
|
@@ -1222,6 +1281,7 @@ function createParser(context) {
|
|
|
1222
1281
|
},
|
|
1223
1282
|
flags: { skipTraverseFiles: false },
|
|
1224
1283
|
recordDependency,
|
|
1284
|
+
recordExportRead,
|
|
1225
1285
|
resolveModule
|
|
1226
1286
|
}).forEach((result, alias) => {
|
|
1227
1287
|
const name = file.getName(file.normalizeFnName(alias));
|
|
@@ -1314,6 +1374,19 @@ function createParser(context) {
|
|
|
1314
1374
|
});
|
|
1315
1375
|
});
|
|
1316
1376
|
parserResult.deadCalls = file.getDeadCalls();
|
|
1377
|
+
if (exportReadPairs.size) {
|
|
1378
|
+
const project = sourceFile.getProject();
|
|
1379
|
+
parserResult.setExportReads([...exportReadPairs].map((pair) => {
|
|
1380
|
+
const at = pair.indexOf("\0");
|
|
1381
|
+
const file = pair.slice(0, at);
|
|
1382
|
+
const name = pair.slice(at + 1);
|
|
1383
|
+
return {
|
|
1384
|
+
file,
|
|
1385
|
+
name,
|
|
1386
|
+
digest: digestExportValue(project.getSourceFile(file), name, resolveModule)
|
|
1387
|
+
};
|
|
1388
|
+
}));
|
|
1389
|
+
}
|
|
1317
1390
|
return parserResult;
|
|
1318
1391
|
};
|
|
1319
1392
|
}
|
|
@@ -1679,8 +1752,28 @@ var Project = class {
|
|
|
1679
1752
|
* dependency edge; now it would also leave a recipe permanently invisible to the module
|
|
1680
1753
|
* importing it, since resolution is what finds one.
|
|
1681
1754
|
*/
|
|
1682
|
-
invalidate = (fileTreeChanged = true) => {
|
|
1755
|
+
invalidate = (fileTreeChanged = true, changedPath) => {
|
|
1683
1756
|
this.#assertNotLoading();
|
|
1757
|
+
/**
|
|
1758
|
+
* A content edit to a file the project already holds invalidates *by path* rather than
|
|
1759
|
+
* dropping every memoized value. Each cache entry records the module paths its
|
|
1760
|
+
* computation read — the same record the watch system replays into fold dependencies —
|
|
1761
|
+
* so marking the edited path stale rejects exactly the entries whose value could have
|
|
1762
|
+
* moved, while a value resolved without reading this file keeps its cache. The edited
|
|
1763
|
+
* file's own entries need no marking: its re-parse retires their key nodes.
|
|
1764
|
+
*
|
|
1765
|
+
* That narrowing holds only for content: a file appearing or disappearing can change
|
|
1766
|
+
* what a specifier resolves to without any recorded path's bytes moving — extension
|
|
1767
|
+
* precedence, a new local shadowing a package — so tree changes keep the full clear.
|
|
1768
|
+
* The recipe-origin memo records no read-set, so it is cleared on every event either
|
|
1769
|
+
* way; per edit that is one walk rebuilt lazily, not the per-transform storm the
|
|
1770
|
+
* identical-text no-op in `addSourceFile` exists to prevent.
|
|
1771
|
+
*/
|
|
1772
|
+
if (!fileTreeChanged && changedPath) {
|
|
1773
|
+
invalidateDependencyPath(this.normalizePath(changedPath));
|
|
1774
|
+
clearImportedRecipeCache();
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1684
1777
|
invalidateResolutions();
|
|
1685
1778
|
if (fileTreeChanged) {
|
|
1686
1779
|
this.#moduleResolutionCache = void 0;
|
|
@@ -2233,7 +2326,7 @@ var Project = class {
|
|
|
2233
2326
|
if (existing && existing.getFullText() === content) return existing;
|
|
2234
2327
|
this.invalidateSourcePreparation(filePath, existing);
|
|
2235
2328
|
this.removedSourcePaths.delete(this.normalizePath(filePath));
|
|
2236
|
-
this.invalidate(!existing);
|
|
2329
|
+
this.invalidate(!existing, existing?.getFilePath());
|
|
2237
2330
|
return this.project.createSourceFile(filePath, content, {
|
|
2238
2331
|
overwrite: true,
|
|
2239
2332
|
scriptKind: scriptKindFor(filePath)
|
|
@@ -2252,11 +2345,22 @@ var Project = class {
|
|
|
2252
2345
|
}
|
|
2253
2346
|
return false;
|
|
2254
2347
|
};
|
|
2348
|
+
/**
|
|
2349
|
+
* The current digest of one exported value, for read verification.
|
|
2350
|
+
*
|
|
2351
|
+
* Same function and same resolver identity as the parse-time recording, which is what makes
|
|
2352
|
+
* the comparison meaningful; see `digestExportValue`.
|
|
2353
|
+
*/
|
|
2354
|
+
digestExportRead = (filePath, exportedName, onCrossing) => {
|
|
2355
|
+
this.#assertNotLoading();
|
|
2356
|
+
this.#ensureSourceFiles();
|
|
2357
|
+
return digestExportValue(this.getSourceFile(filePath), exportedName, this.resolveModule, onCrossing);
|
|
2358
|
+
};
|
|
2255
2359
|
reloadSourceFile = (filePath) => {
|
|
2256
2360
|
this.#assertNotLoading();
|
|
2257
2361
|
this.#ensureSourceFiles();
|
|
2258
|
-
this.invalidate(false);
|
|
2259
2362
|
const sourceFile = this.getSourceFile(filePath);
|
|
2363
|
+
this.invalidate(false, sourceFile?.getFilePath());
|
|
2260
2364
|
if (!sourceFile) return;
|
|
2261
2365
|
this.invalidateSourcePreparation(filePath, sourceFile);
|
|
2262
2366
|
return sourceFile.refreshFromFileSystemSync();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bamboocss/parser",
|
|
3
|
-
"version": "1.46.
|
|
3
|
+
"version": "1.46.2",
|
|
4
4
|
"description": "The static parser for bamboo css",
|
|
5
5
|
"homepage": "https://bamboocss.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,18 +34,18 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"ts-morph": "28.0.0",
|
|
36
36
|
"ts-pattern": "5.9.0",
|
|
37
|
-
"@bamboocss/config": "^1.46.
|
|
38
|
-
"@bamboocss/core": "^1.46.
|
|
39
|
-
"@bamboocss/extractor": "1.46.
|
|
40
|
-
"@bamboocss/logger": "1.46.
|
|
41
|
-
"@bamboocss/shared": "1.46.
|
|
42
|
-
"@bamboocss/types": "1.46.
|
|
37
|
+
"@bamboocss/config": "^1.46.2",
|
|
38
|
+
"@bamboocss/core": "^1.46.2",
|
|
39
|
+
"@bamboocss/extractor": "1.46.2",
|
|
40
|
+
"@bamboocss/logger": "1.46.2",
|
|
41
|
+
"@bamboocss/shared": "1.46.2",
|
|
42
|
+
"@bamboocss/types": "1.46.2"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@typescript/api": "npm:typescript@7.1.0-dev.20260819.1",
|
|
46
|
-
"@bamboocss/generator": "1.46.
|
|
47
|
-
"@bamboocss/plugin-svelte": "1.46.
|
|
48
|
-
"@bamboocss/plugin-vue": "1.46.
|
|
46
|
+
"@bamboocss/generator": "1.46.2",
|
|
47
|
+
"@bamboocss/plugin-svelte": "1.46.2",
|
|
48
|
+
"@bamboocss/plugin-vue": "1.46.2"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"build": "tsdown src/index.ts --format=esm,cjs --dts",
|