@abreen/tada 1.13.7 → 1.14.1
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/README.md +46 -0
- package/build/generate-content-assets.ts +39 -185
- package/build/pipeline.ts +1 -1
- package/build/source-model.ts +2 -14
- package/build/types.d.ts +18 -18
- package/build/utils/html-comments.ts +3 -0
- package/build/utils/jdi-runner/TraceRunner.java +227 -38
- package/build/utils/markdown-partials.ts +235 -0
- package/build/utils/markdown.ts +12 -1
- package/build/utils/python-runner/trace_runner.py +58 -13
- package/build/utils/render.ts +14 -12
- package/build/utils/trace-core.ts +136 -14
- package/build/utils/trace-heap.ts +34 -0
- package/build/utils/trace-java.ts +60 -7
- package/build/utils/trace-layout.ts +58 -13
- package/build/utils/trace-python.ts +6 -2
- package/build/utils/trace-svg.ts +54 -60
- package/build/utils/trace.ts +193 -78
- package/build/watch/build-full.ts +26 -52
- package/build/watch/build-helpers.ts +66 -0
- package/build/watch/build-incremental.ts +32 -60
- package/build/watch/build-result.ts +19 -0
- package/build/watch/compiler-types.d.ts +2 -2
- package/build/watch/compiler.ts +30 -62
- package/build/watch/config-paths.ts +56 -0
- package/build/watch/engine.ts +199 -0
- package/{watch → build/watch}/fs-commit.ts +1 -3
- package/build/watch/index.ts +1 -1
- package/build/watch/mutations.ts +23 -25
- package/build/watch/planner.ts +14 -78
- package/build/watch/runtime.ts +26 -38
- package/build/watch/snapshot.ts +26 -47
- package/build/watch/types.d.ts +84 -0
- package/build/watch/validation.ts +1 -1
- package/init/content/lectures/02/_pr1.md +1 -1
- package/init/content/lectures/02/index.md +2 -2
- package/init/content/lectures/02/subdir/_foobar.md +3 -0
- package/init/content/lectures/02/subdir/_pr2.md +3 -3
- package/package.json +1 -2
- package/src/slides/index.ts +6 -2
- package/src/trace/index.ts +49 -12
- package/src/trace/style.scss +10 -0
- package/src/trace/types.d.ts +16 -4
- package/build/utils/include.ts +0 -57
- package/build/watch-reachability-state.ts +0 -305
- package/init/content/lectures/02/subdir/_foobar.html +0 -3
- package/watch/engine.ts +0 -296
- package/watch/events.ts +0 -10
- package/watch/types.d.ts +0 -103
package/src/trace/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TraceManifest, TraceChunkEntry } from './types';
|
|
1
|
+
import type { TraceManifest, TraceChunkEntry, TraceOutputEvent } from './types';
|
|
2
2
|
import { globals } from '../globals';
|
|
3
3
|
|
|
4
4
|
interface WidgetState {
|
|
@@ -9,7 +9,7 @@ interface WidgetState {
|
|
|
9
9
|
|
|
10
10
|
interface WidgetElements {
|
|
11
11
|
root: HTMLElement;
|
|
12
|
-
|
|
12
|
+
sourceWrapper: HTMLElement;
|
|
13
13
|
controls: HTMLElement;
|
|
14
14
|
content: HTMLElement;
|
|
15
15
|
diagram: HTMLElement;
|
|
@@ -77,6 +77,22 @@ function updateSourceHighlight(panel: HTMLElement, currentLine: number): void {
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
function updateVisibleSourcePanel(
|
|
81
|
+
sourceWrapper: HTMLElement,
|
|
82
|
+
currentFile: string,
|
|
83
|
+
): HTMLElement | null {
|
|
84
|
+
let activePanel: HTMLElement | null = null;
|
|
85
|
+
sourceWrapper.querySelectorAll('.trace-source').forEach(panel => {
|
|
86
|
+
const sourcePanel = panel as HTMLElement;
|
|
87
|
+
const matches = sourcePanel.dataset.traceSourceFile === currentFile;
|
|
88
|
+
sourcePanel.hidden = !matches;
|
|
89
|
+
if (matches) {
|
|
90
|
+
activePanel = sourcePanel;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
return activePanel;
|
|
94
|
+
}
|
|
95
|
+
|
|
80
96
|
function updateStepControls(
|
|
81
97
|
container: HTMLElement,
|
|
82
98
|
currentStep: number,
|
|
@@ -202,13 +218,13 @@ function scaleDiagramSvg(diagram: HTMLElement, doc: Document): void {
|
|
|
202
218
|
|
|
203
219
|
function updateOutput(
|
|
204
220
|
content: HTMLElement,
|
|
205
|
-
output:
|
|
221
|
+
output: TraceOutputEvent[],
|
|
206
222
|
doc: Document,
|
|
207
223
|
): void {
|
|
208
224
|
const existing = content.querySelector(
|
|
209
225
|
':scope > .trace-output',
|
|
210
226
|
) as HTMLPreElement | null;
|
|
211
|
-
if (
|
|
227
|
+
if (output.length === 0) {
|
|
212
228
|
existing?.remove();
|
|
213
229
|
return;
|
|
214
230
|
}
|
|
@@ -218,7 +234,16 @@ function updateOutput(
|
|
|
218
234
|
outputElement.className = 'trace-output';
|
|
219
235
|
content.append(outputElement);
|
|
220
236
|
}
|
|
221
|
-
outputElement.
|
|
237
|
+
outputElement.replaceChildren(
|
|
238
|
+
...output.map(event => {
|
|
239
|
+
const span = doc.createElement('span');
|
|
240
|
+
if (event.stream === 'stderr') {
|
|
241
|
+
span.className = 'trace-output-stderr';
|
|
242
|
+
}
|
|
243
|
+
span.textContent = event.text;
|
|
244
|
+
return span;
|
|
245
|
+
}),
|
|
246
|
+
);
|
|
222
247
|
}
|
|
223
248
|
|
|
224
249
|
function renderWidgetState(
|
|
@@ -227,21 +252,25 @@ function renderWidgetState(
|
|
|
227
252
|
doc: Document,
|
|
228
253
|
): void {
|
|
229
254
|
const entry = getStep(state);
|
|
230
|
-
|
|
255
|
+
const sourcePanel = updateVisibleSourcePanel(
|
|
256
|
+
elements.sourceWrapper,
|
|
257
|
+
entry.file,
|
|
258
|
+
);
|
|
259
|
+
if (sourcePanel) {
|
|
260
|
+
updateSourceHighlight(sourcePanel, entry.line);
|
|
261
|
+
}
|
|
231
262
|
updateStepControls(
|
|
232
263
|
elements.controls,
|
|
233
264
|
state.currentStep,
|
|
234
265
|
state.manifest.totalSteps,
|
|
235
266
|
doc,
|
|
236
267
|
);
|
|
237
|
-
|
|
268
|
+
const output: TraceOutputEvent[] = [];
|
|
238
269
|
for (let i = 0; i <= state.currentStep; i++) {
|
|
239
270
|
const ci = Math.floor(i / state.manifest.chunkSize);
|
|
240
271
|
const off = i % state.manifest.chunkSize;
|
|
241
272
|
const s = state.chunks.get(ci)![off];
|
|
242
|
-
|
|
243
|
-
output += s.stdout;
|
|
244
|
-
}
|
|
273
|
+
output.push(...(s.output ?? []));
|
|
245
274
|
}
|
|
246
275
|
updateOutput(elements.content, output, doc);
|
|
247
276
|
|
|
@@ -277,7 +306,9 @@ async function initWidget(root: HTMLElement, doc: Document): Promise<void> {
|
|
|
277
306
|
|
|
278
307
|
await loadChunk(state, manifestUrl, 0);
|
|
279
308
|
|
|
280
|
-
const
|
|
309
|
+
const sourceWrapper = root.querySelector(
|
|
310
|
+
'.trace-source-wrapper',
|
|
311
|
+
) as HTMLElement;
|
|
281
312
|
const content = root.querySelector('.trace-content') as HTMLElement;
|
|
282
313
|
const diagram = root.querySelector('.trace-diagram') as HTMLElement;
|
|
283
314
|
|
|
@@ -321,7 +352,13 @@ async function initWidget(root: HTMLElement, doc: Document): Promise<void> {
|
|
|
321
352
|
next.focus();
|
|
322
353
|
});
|
|
323
354
|
|
|
324
|
-
const elements: WidgetElements = {
|
|
355
|
+
const elements: WidgetElements = {
|
|
356
|
+
root,
|
|
357
|
+
sourceWrapper,
|
|
358
|
+
controls,
|
|
359
|
+
content,
|
|
360
|
+
diagram,
|
|
361
|
+
};
|
|
325
362
|
|
|
326
363
|
renderWidgetState(state, elements, doc);
|
|
327
364
|
}
|
package/src/trace/style.scss
CHANGED
|
@@ -141,6 +141,16 @@
|
|
|
141
141
|
content: 'Output';
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
.trace-output-stderr {
|
|
145
|
+
color: hsl(0deg 70% 45%);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@media (prefers-color-scheme: dark) {
|
|
149
|
+
.trace-output-stderr {
|
|
150
|
+
color: hsl(0deg 85% 72%);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
144
154
|
.trace-memory {
|
|
145
155
|
max-width: 100%;
|
|
146
156
|
height: auto;
|
package/src/trace/types.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
export interface TraceManifest {
|
|
2
2
|
totalSteps: number;
|
|
3
3
|
chunkSize: number;
|
|
4
|
-
|
|
4
|
+
primaryFile: string;
|
|
5
|
+
sources: TraceSource[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface TraceSource {
|
|
9
|
+
file: string;
|
|
5
10
|
source: string;
|
|
6
11
|
lineToSteps: Record<number, number[]>;
|
|
7
12
|
}
|
|
@@ -11,7 +16,13 @@ export interface TraceStep {
|
|
|
11
16
|
file: string;
|
|
12
17
|
stack: TraceStackFrame[];
|
|
13
18
|
heap: Record<string, TraceHeapObject>;
|
|
14
|
-
|
|
19
|
+
output?: TraceOutputEvent[];
|
|
20
|
+
transientHeapRoots?: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TraceOutputEvent {
|
|
24
|
+
stream: 'stdout' | 'stderr';
|
|
25
|
+
text: string;
|
|
15
26
|
}
|
|
16
27
|
|
|
17
28
|
export interface TraceStackFrame {
|
|
@@ -37,11 +48,12 @@ export type TraceValue =
|
|
|
37
48
|
export type TraceHeapObject =
|
|
38
49
|
| { type: string; elements: TraceValue[] }
|
|
39
50
|
| { type: string; fields: Record<string, TraceValue> }
|
|
40
|
-
| { type: string; value: string };
|
|
51
|
+
| { type: string; value: string | number | boolean };
|
|
41
52
|
|
|
42
53
|
/** A single entry in a chunk file (new format with precomputed SVG). */
|
|
43
54
|
export interface TraceChunkEntry {
|
|
55
|
+
file: string;
|
|
44
56
|
line: number;
|
|
45
|
-
|
|
57
|
+
output?: TraceOutputEvent[];
|
|
46
58
|
svg: string;
|
|
47
59
|
}
|
package/build/utils/include.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import _ from 'lodash';
|
|
4
|
-
import { isPartial } from './file-types';
|
|
5
|
-
import { stripHtmlComments } from './render';
|
|
6
|
-
import type { RenderDependencyCollector } from '../types';
|
|
7
|
-
|
|
8
|
-
const MAX_INCLUDE_DEPTH = 10;
|
|
9
|
-
|
|
10
|
-
export function createIncludeFunction(
|
|
11
|
-
callerFilePath: string,
|
|
12
|
-
templateParams: Record<string, unknown>,
|
|
13
|
-
collector?: RenderDependencyCollector,
|
|
14
|
-
depth: number = 0,
|
|
15
|
-
): (relativePath: string) => string {
|
|
16
|
-
return (relativePath: string): string => {
|
|
17
|
-
const callerDir = path.dirname(callerFilePath);
|
|
18
|
-
const resolved = path.resolve(callerDir, relativePath);
|
|
19
|
-
|
|
20
|
-
if (!fs.existsSync(resolved)) {
|
|
21
|
-
throw new Error(
|
|
22
|
-
`${callerFilePath}: partial not found: ${relativePath} (resolved to ${resolved})`,
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
if (!isPartial(resolved)) {
|
|
26
|
-
throw new Error(
|
|
27
|
-
`${callerFilePath}: include target must start with "_": ${relativePath}`,
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
collector?.partials?.add(resolved);
|
|
31
|
-
if (depth >= MAX_INCLUDE_DEPTH) {
|
|
32
|
-
throw new Error(
|
|
33
|
-
`${callerFilePath}: maximum include depth (${MAX_INCLUDE_DEPTH}) exceeded`,
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const raw = fs.readFileSync(resolved, 'utf-8');
|
|
38
|
-
const content = stripHtmlComments(raw);
|
|
39
|
-
|
|
40
|
-
const nestedInclude = createIncludeFunction(
|
|
41
|
-
resolved,
|
|
42
|
-
templateParams,
|
|
43
|
-
collector,
|
|
44
|
-
depth + 1,
|
|
45
|
-
);
|
|
46
|
-
const nestedParams = { ...templateParams, include: nestedInclude };
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
return _.template(content)(nestedParams);
|
|
50
|
-
} catch (err) {
|
|
51
|
-
throw new Error(
|
|
52
|
-
`${resolved}: Lodash template error in partial: ${(err as Error).message}`,
|
|
53
|
-
{ cause: err },
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
}
|
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
import { collectOutgoingHtmlAssetPaths } from './reachability';
|
|
2
|
-
import type { HtmlOutputAnalysis } from './types';
|
|
3
|
-
|
|
4
|
-
type ReadAnalysisForAsset = (assetPath: string) => HtmlOutputAnalysis;
|
|
5
|
-
|
|
6
|
-
function cloneSet(values: Iterable<string> | undefined): Set<string> {
|
|
7
|
-
return new Set(values || []);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
interface IncrementalOptions {
|
|
11
|
-
changedAssetPaths?: Iterable<string>;
|
|
12
|
-
removedAssetPaths?: Iterable<string>;
|
|
13
|
-
readAnalysisForAsset: ReadAnalysisForAsset;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
class WatchReachabilityState {
|
|
17
|
-
private rootPath: string;
|
|
18
|
-
private knownAssets: Set<string>;
|
|
19
|
-
private outgoingBySource: Map<string, Set<string>>;
|
|
20
|
-
private incomingByTarget: Map<string, Set<string>>;
|
|
21
|
-
private reachable: Set<string>;
|
|
22
|
-
private initialized: boolean;
|
|
23
|
-
|
|
24
|
-
constructor({ rootPath = 'index.html' }: { rootPath?: string } = {}) {
|
|
25
|
-
this.rootPath = rootPath;
|
|
26
|
-
this.knownAssets = new Set();
|
|
27
|
-
this.outgoingBySource = new Map();
|
|
28
|
-
this.incomingByTarget = new Map();
|
|
29
|
-
this.reachable = new Set();
|
|
30
|
-
this.initialized = false;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
reset(): void {
|
|
34
|
-
this.knownAssets = new Set();
|
|
35
|
-
this.outgoingBySource = new Map();
|
|
36
|
-
this.incomingByTarget = new Map();
|
|
37
|
-
this.reachable = new Set();
|
|
38
|
-
this.initialized = false;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
setKnownAssets(assetPaths: Iterable<string>): void {
|
|
42
|
-
this.knownAssets = cloneSet(assetPaths);
|
|
43
|
-
this.reachable = new Set(
|
|
44
|
-
[...this.reachable].filter(assetPath => this.knownAssets.has(assetPath)),
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
const nextOutgoingBySource = new Map<string, Set<string>>();
|
|
48
|
-
for (const [sourcePath, targets] of this.outgoingBySource) {
|
|
49
|
-
if (!this.knownAssets.has(sourcePath)) {
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
nextOutgoingBySource.set(
|
|
54
|
-
sourcePath,
|
|
55
|
-
new Set(
|
|
56
|
-
[...targets].filter(targetPath => this.knownAssets.has(targetPath)),
|
|
57
|
-
),
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
this.outgoingBySource = nextOutgoingBySource;
|
|
62
|
-
this.rebuildIncomingByTarget();
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
rebuild(readAnalysisForAsset: ReadAnalysisForAsset): void {
|
|
66
|
-
if (!this.knownAssets.has(this.rootPath)) {
|
|
67
|
-
throw new Error(`Pagefind reachability root not found: ${this.rootPath}`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
this.outgoingBySource = new Map();
|
|
71
|
-
this.incomingByTarget = new Map();
|
|
72
|
-
this.reachable = new Set();
|
|
73
|
-
|
|
74
|
-
const knownAssetPaths = [...this.knownAssets].sort();
|
|
75
|
-
for (const sourcePath of knownAssetPaths) {
|
|
76
|
-
const outgoingPaths = this.readOutgoingPaths(
|
|
77
|
-
sourcePath,
|
|
78
|
-
readAnalysisForAsset,
|
|
79
|
-
);
|
|
80
|
-
this.outgoingBySource.set(sourcePath, outgoingPaths);
|
|
81
|
-
this.addIncomingEdges(sourcePath, outgoingPaths);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
this.reachable = this.collectClosure(new Set([this.rootPath]));
|
|
85
|
-
this.initialized = true;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
applyIncremental({
|
|
89
|
-
changedAssetPaths,
|
|
90
|
-
removedAssetPaths,
|
|
91
|
-
readAnalysisForAsset,
|
|
92
|
-
}: IncrementalOptions): void {
|
|
93
|
-
if (!this.initialized) {
|
|
94
|
-
this.rebuild(readAnalysisForAsset);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const normalizedChanged = new Set<string>();
|
|
99
|
-
for (const assetPath of changedAssetPaths || []) {
|
|
100
|
-
if (this.knownAssets.has(assetPath)) {
|
|
101
|
-
normalizedChanged.add(assetPath);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const normalizedRemoved = new Set<string>();
|
|
106
|
-
for (const assetPath of removedAssetPaths || []) {
|
|
107
|
-
if (
|
|
108
|
-
!this.knownAssets.has(assetPath) &&
|
|
109
|
-
!normalizedChanged.has(assetPath)
|
|
110
|
-
) {
|
|
111
|
-
normalizedRemoved.add(assetPath);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const previouslyReachableRoots = new Set<string>();
|
|
116
|
-
for (const assetPath of normalizedChanged) {
|
|
117
|
-
if (this.reachable.has(assetPath)) {
|
|
118
|
-
previouslyReachableRoots.add(assetPath);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
for (const assetPath of normalizedRemoved) {
|
|
122
|
-
if (this.reachable.has(assetPath)) {
|
|
123
|
-
previouslyReachableRoots.add(assetPath);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const oldAffectedClosure = this.collectClosure(previouslyReachableRoots);
|
|
128
|
-
|
|
129
|
-
for (const assetPath of normalizedRemoved) {
|
|
130
|
-
this.removeAsset(assetPath);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const changedAssetList = [...normalizedChanged].sort();
|
|
134
|
-
for (const assetPath of changedAssetList) {
|
|
135
|
-
const outgoingPaths = this.readOutgoingPaths(
|
|
136
|
-
assetPath,
|
|
137
|
-
readAnalysisForAsset,
|
|
138
|
-
);
|
|
139
|
-
this.replaceOutgoing(assetPath, outgoingPaths);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const stillKnownRoots = new Set(
|
|
143
|
-
[...previouslyReachableRoots].filter(assetPath =>
|
|
144
|
-
this.knownAssets.has(assetPath),
|
|
145
|
-
),
|
|
146
|
-
);
|
|
147
|
-
const newAffectedClosure = this.collectClosure(stillKnownRoots);
|
|
148
|
-
const affected = new Set([...oldAffectedClosure, ...newAffectedClosure]);
|
|
149
|
-
|
|
150
|
-
if (affected.size === 0) {
|
|
151
|
-
this.initialized = true;
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
for (const assetPath of affected) {
|
|
156
|
-
this.reachable.delete(assetPath);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const seedPaths = new Set<string>();
|
|
160
|
-
for (const assetPath of stillKnownRoots) {
|
|
161
|
-
if (affected.has(assetPath)) {
|
|
162
|
-
seedPaths.add(assetPath);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (this.knownAssets.has(this.rootPath) && affected.has(this.rootPath)) {
|
|
167
|
-
seedPaths.add(this.rootPath);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
for (const assetPath of affected) {
|
|
171
|
-
const incomingPaths = this.incomingByTarget.get(assetPath);
|
|
172
|
-
if (!incomingPaths) {
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
for (const sourcePath of incomingPaths) {
|
|
177
|
-
if (this.reachable.has(sourcePath) && !affected.has(sourcePath)) {
|
|
178
|
-
seedPaths.add(assetPath);
|
|
179
|
-
break;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const restoredReachable = this.collectClosure(seedPaths, affected);
|
|
185
|
-
for (const assetPath of restoredReachable) {
|
|
186
|
-
this.reachable.add(assetPath);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
this.initialized = true;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
getReachablePaths(): string[] {
|
|
193
|
-
return [...this.reachable].sort();
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
private readOutgoingPaths(
|
|
197
|
-
sourcePath: string,
|
|
198
|
-
readAnalysisForAsset: ReadAnalysisForAsset,
|
|
199
|
-
): Set<string> {
|
|
200
|
-
if (!this.knownAssets.has(sourcePath)) {
|
|
201
|
-
return new Set();
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const analysis = readAnalysisForAsset(sourcePath);
|
|
205
|
-
return new Set(collectOutgoingHtmlAssetPaths(analysis, this.knownAssets));
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
private addIncomingEdges(
|
|
209
|
-
sourcePath: string,
|
|
210
|
-
outgoingPaths: Set<string>,
|
|
211
|
-
): void {
|
|
212
|
-
for (const targetPath of outgoingPaths) {
|
|
213
|
-
let incomingPaths = this.incomingByTarget.get(targetPath);
|
|
214
|
-
if (!incomingPaths) {
|
|
215
|
-
incomingPaths = new Set();
|
|
216
|
-
this.incomingByTarget.set(targetPath, incomingPaths);
|
|
217
|
-
}
|
|
218
|
-
incomingPaths.add(sourcePath);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
private rebuildIncomingByTarget(): void {
|
|
223
|
-
this.incomingByTarget = new Map();
|
|
224
|
-
for (const [sourcePath, outgoingPaths] of this.outgoingBySource) {
|
|
225
|
-
this.addIncomingEdges(sourcePath, outgoingPaths);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
private removeOutgoingEdges(sourcePath: string): void {
|
|
230
|
-
const outgoingPaths = this.outgoingBySource.get(sourcePath);
|
|
231
|
-
if (!outgoingPaths) {
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
for (const targetPath of outgoingPaths) {
|
|
236
|
-
const incomingPaths = this.incomingByTarget.get(targetPath);
|
|
237
|
-
if (!incomingPaths) {
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
incomingPaths.delete(sourcePath);
|
|
242
|
-
if (incomingPaths.size === 0) {
|
|
243
|
-
this.incomingByTarget.delete(targetPath);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
private replaceOutgoing(
|
|
249
|
-
sourcePath: string,
|
|
250
|
-
outgoingPaths: Set<string>,
|
|
251
|
-
): void {
|
|
252
|
-
this.removeOutgoingEdges(sourcePath);
|
|
253
|
-
if (!this.knownAssets.has(sourcePath)) {
|
|
254
|
-
this.outgoingBySource.delete(sourcePath);
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
this.outgoingBySource.set(sourcePath, outgoingPaths);
|
|
259
|
-
this.addIncomingEdges(sourcePath, outgoingPaths);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
private removeAsset(assetPath: string): void {
|
|
263
|
-
this.removeOutgoingEdges(assetPath);
|
|
264
|
-
this.outgoingBySource.delete(assetPath);
|
|
265
|
-
this.incomingByTarget.delete(assetPath);
|
|
266
|
-
this.reachable.delete(assetPath);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
private collectClosure(
|
|
270
|
-
rootPaths: Set<string>,
|
|
271
|
-
allowedPaths: Set<string> | null = null,
|
|
272
|
-
): Set<string> {
|
|
273
|
-
const pending = [...rootPaths];
|
|
274
|
-
const visited = new Set<string>();
|
|
275
|
-
|
|
276
|
-
while (pending.length > 0) {
|
|
277
|
-
const currentPath = pending.pop()!;
|
|
278
|
-
if (visited.has(currentPath)) {
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
if (allowedPaths && !allowedPaths.has(currentPath)) {
|
|
282
|
-
continue;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
visited.add(currentPath);
|
|
286
|
-
const outgoingPaths = this.outgoingBySource.get(currentPath);
|
|
287
|
-
if (!outgoingPaths) {
|
|
288
|
-
continue;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
for (const targetPath of outgoingPaths) {
|
|
292
|
-
if (allowedPaths && !allowedPaths.has(targetPath)) {
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
|
-
if (!visited.has(targetPath)) {
|
|
296
|
-
pending.push(targetPath);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return visited;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
export default WatchReachabilityState;
|