@archora/core 1.1.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/LICENSE +201 -0
- package/README.md +62 -0
- package/package.json +36 -0
- package/src/README.md +4 -0
- package/src/analyzer/__tests__/__snapshots__/referenceSnapshot.test.ts.snap +145 -0
- package/src/analyzer/__tests__/_paths.ts +8 -0
- package/src/analyzer/__tests__/analyze.test.ts +522 -0
- package/src/analyzer/__tests__/archDebt.test.ts +111 -0
- package/src/analyzer/__tests__/asyncLifecycleRisk.test.ts +122 -0
- package/src/analyzer/__tests__/browserFsAccessFileSource.test.ts +97 -0
- package/src/analyzer/__tests__/bundle.test.ts +191 -0
- package/src/analyzer/__tests__/classify.test.ts +99 -0
- package/src/analyzer/__tests__/contracts.test.ts +372 -0
- package/src/analyzer/__tests__/crossSourceConsistency.test.ts +317 -0
- package/src/analyzer/__tests__/cyclePatterns.test.ts +132 -0
- package/src/analyzer/__tests__/cycles.test.ts +74 -0
- package/src/analyzer/__tests__/detect.test.ts +62 -0
- package/src/analyzer/__tests__/discover.test.ts +68 -0
- package/src/analyzer/__tests__/displayId.test.ts +30 -0
- package/src/analyzer/__tests__/feedbackArcSet.test.ts +168 -0
- package/src/analyzer/__tests__/inMemoryFileSource.test.ts +34 -0
- package/src/analyzer/__tests__/incremental.test.ts +154 -0
- package/src/analyzer/__tests__/layers.test.ts +87 -0
- package/src/analyzer/__tests__/layersOverrides.test.ts +120 -0
- package/src/analyzer/__tests__/memoryRisk.test.ts +132 -0
- package/src/analyzer/__tests__/metrics.test.ts +59 -0
- package/src/analyzer/__tests__/parserRegistry.test.ts +54 -0
- package/src/analyzer/__tests__/parsers.test.ts +187 -0
- package/src/analyzer/__tests__/reactParser.test.ts +93 -0
- package/src/analyzer/__tests__/recommendations.test.ts +171 -0
- package/src/analyzer/__tests__/referenceSnapshot.test.ts +63 -0
- package/src/analyzer/__tests__/resolve.test.ts +294 -0
- package/src/analyzer/__tests__/rsc.test.ts +130 -0
- package/src/analyzer/__tests__/signals.test.ts +316 -0
- package/src/analyzer/__tests__/suggestContracts.test.ts +108 -0
- package/src/analyzer/__tests__/svelteParser.test.ts +108 -0
- package/src/analyzer/__tests__/typeOnlyCandidates.test.ts +163 -0
- package/src/analyzer/__tests__/vueAutoImport.test.ts +177 -0
- package/src/analyzer/archDebt.ts +68 -0
- package/src/analyzer/asyncLifecycleRisk.ts +234 -0
- package/src/analyzer/buildGraph.ts +683 -0
- package/src/analyzer/bundle/analyzeBundle.ts +147 -0
- package/src/analyzer/bundle/index.ts +12 -0
- package/src/analyzer/bundle/parseStats.ts +152 -0
- package/src/analyzer/bundle/types.ts +85 -0
- package/src/analyzer/classify.ts +54 -0
- package/src/analyzer/contracts.ts +265 -0
- package/src/analyzer/cyclePatterns.ts +138 -0
- package/src/analyzer/cycles.ts +98 -0
- package/src/analyzer/detect.ts +34 -0
- package/src/analyzer/discover.ts +131 -0
- package/src/analyzer/displayId.ts +21 -0
- package/src/analyzer/entryPoints.ts +136 -0
- package/src/analyzer/feedbackArcSet.ts +332 -0
- package/src/analyzer/fileSource.ts +8 -0
- package/src/analyzer/hotZones.ts +17 -0
- package/src/analyzer/incremental.ts +455 -0
- package/src/analyzer/index.ts +444 -0
- package/src/analyzer/layers.ts +183 -0
- package/src/analyzer/loadAliases.ts +288 -0
- package/src/analyzer/memoryRisk.ts +345 -0
- package/src/analyzer/metrics.ts +156 -0
- package/src/analyzer/parsers/index.ts +62 -0
- package/src/analyzer/parsers/reactParser.ts +24 -0
- package/src/analyzer/parsers/svelteParser.ts +46 -0
- package/src/analyzer/parsers/tsParser.ts +364 -0
- package/src/analyzer/parsers/vueParser.ts +109 -0
- package/src/analyzer/recommendations.ts +432 -0
- package/src/analyzer/resolve.ts +315 -0
- package/src/analyzer/rsc.ts +120 -0
- package/src/analyzer/signals.ts +684 -0
- package/src/analyzer/sources/browserFsAccessFileSource.ts +132 -0
- package/src/analyzer/sources/inMemoryFileSource.ts +24 -0
- package/src/analyzer/sources/nodeFsFileSource.ts +93 -0
- package/src/analyzer/sources/tauriFileSource.ts +68 -0
- package/src/analyzer/suggestContracts.ts +214 -0
- package/src/analyzer/typeOnlyCandidates.ts +233 -0
- package/src/analyzer/types.ts +537 -0
- package/src/cache/__tests__/cache.test.ts +316 -0
- package/src/cache/index.ts +432 -0
- package/src/codegen/__tests__/applyTypeOnlyFix.integration.test.ts +62 -0
- package/src/codegen/__tests__/applyTypeOnlyFix.test.ts +176 -0
- package/src/codegen/__tests__/configSnippets.test.ts +230 -0
- package/src/codegen/applyTypeOnlyFix.ts +344 -0
- package/src/codegen/configSnippets.ts +172 -0
- package/src/codegen/initConfig.ts +223 -0
- package/src/config/__tests__/frontScopeConfig.test.ts +187 -0
- package/src/config/frontScopeConfig.ts +830 -0
- package/src/diff/__tests__/diffScans.test.ts +103 -0
- package/src/diff/diffScans.ts +61 -0
- package/src/diff/index.ts +2 -0
- package/src/diff/types.ts +39 -0
- package/src/git/__tests__/computeChurn.test.ts +113 -0
- package/src/git/__tests__/computeTemporalCoupling.test.ts +125 -0
- package/src/git/__tests__/parseGitLog.test.ts +120 -0
- package/src/git/computeChurn.ts +111 -0
- package/src/git/computeTemporalCoupling.ts +114 -0
- package/src/git/index.ts +24 -0
- package/src/git/parseGitLog.ts +124 -0
- package/src/git/readGitHistory.ts +130 -0
- package/src/git/types.ts +119 -0
- package/src/index.ts +137 -0
- package/src/report/__tests__/buildFixPlan.test.ts +357 -0
- package/src/report/__tests__/buildJsonReport.test.ts +34 -0
- package/src/report/buildFixPlan.ts +481 -0
- package/src/report/buildJsonReport.ts +27 -0
- package/src/search/__tests__/parseQuery.test.ts +67 -0
- package/src/search/__tests__/search.test.ts +172 -0
- package/src/search/index.ts +281 -0
- package/src/search/parseQuery.ts +75 -0
- package/src/views/__tests__/analyzerViews.test.ts +558 -0
- package/src/views/analyzerViews.ts +1294 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
// End-to-end cache tests against the real Node FS. We use `mkdtemp` to
|
|
2
|
+
// give every test its own throw-away project and assert the
|
|
3
|
+
// load → diff → save cycle on top of an actual `analyze()`/`analyzeWithCache()` run.
|
|
4
|
+
|
|
5
|
+
import { promises as fs } from 'node:fs';
|
|
6
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
7
|
+
import { tmpdir } from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
10
|
+
|
|
11
|
+
import { analyze } from '../../analyzer/index';
|
|
12
|
+
import { createNodeFsFileSource } from '../../analyzer/sources/nodeFsFileSource';
|
|
13
|
+
import {
|
|
14
|
+
analyzeWithCache,
|
|
15
|
+
CACHE_FORMAT_VERSION,
|
|
16
|
+
computeCacheKey,
|
|
17
|
+
diffAgainstManifest,
|
|
18
|
+
loadCache,
|
|
19
|
+
resolveCacheLocation,
|
|
20
|
+
saveCache,
|
|
21
|
+
statFiles,
|
|
22
|
+
type CacheManifest,
|
|
23
|
+
} from '../index';
|
|
24
|
+
|
|
25
|
+
interface Sandbox {
|
|
26
|
+
root: string;
|
|
27
|
+
cleanup: () => Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function makeSandbox(files: Record<string, string>): Promise<Sandbox> {
|
|
31
|
+
const root = await mkdtemp(path.join(tmpdir(), 'archora-cache-test-'));
|
|
32
|
+
await fs.mkdir(path.join(root, 'src'), { recursive: true });
|
|
33
|
+
await fs.writeFile(path.join(root, 'tsconfig.json'), '{}');
|
|
34
|
+
await fs.writeFile(path.join(root, 'package.json'), '{"name":"test"}');
|
|
35
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
36
|
+
const abs = path.join(root, rel);
|
|
37
|
+
await fs.mkdir(path.dirname(abs), { recursive: true });
|
|
38
|
+
await fs.writeFile(abs, content);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
root,
|
|
42
|
+
cleanup: () => rm(root, { recursive: true, force: true }),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let sandbox: Sandbox;
|
|
47
|
+
afterEach(async () => {
|
|
48
|
+
if (sandbox) await sandbox.cleanup();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('computeCacheKey', () => {
|
|
52
|
+
it('is stable for identical inputs', () => {
|
|
53
|
+
const a = computeCacheKey({ rootPath: '/p', toolVersion: '1.0', tsconfigText: '{}' });
|
|
54
|
+
const b = computeCacheKey({ rootPath: '/p', toolVersion: '1.0', tsconfigText: '{}' });
|
|
55
|
+
expect(a).toBe(b);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('changes on tool version bump', () => {
|
|
59
|
+
const a = computeCacheKey({ rootPath: '/p', toolVersion: '1.0' });
|
|
60
|
+
const b = computeCacheKey({ rootPath: '/p', toolVersion: '1.1' });
|
|
61
|
+
expect(a).not.toBe(b);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('changes on tsconfig text edit', () => {
|
|
65
|
+
const a = computeCacheKey({ rootPath: '/p', toolVersion: '1.0', tsconfigText: '{}' });
|
|
66
|
+
const b = computeCacheKey({ rootPath: '/p', toolVersion: '1.0', tsconfigText: '{ }' });
|
|
67
|
+
expect(a).not.toBe(b);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("changes on root path change (different projects don't share cache)", () => {
|
|
71
|
+
const a = computeCacheKey({ rootPath: '/p1', toolVersion: '1.0' });
|
|
72
|
+
const b = computeCacheKey({ rootPath: '/p2', toolVersion: '1.0' });
|
|
73
|
+
expect(a).not.toBe(b);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('diffAgainstManifest', () => {
|
|
78
|
+
const manifest: CacheManifest = {
|
|
79
|
+
files: {
|
|
80
|
+
'a.ts': { mtimeMs: 100, size: 10 },
|
|
81
|
+
'b.ts': { mtimeMs: 200, size: 20 },
|
|
82
|
+
'c.ts': { mtimeMs: 300, size: 30 },
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
it('detects added/removed/changed correctly', () => {
|
|
87
|
+
const current = {
|
|
88
|
+
'a.ts': { mtimeMs: 100, size: 10 }, // unchanged
|
|
89
|
+
'b.ts': { mtimeMs: 250, size: 20 }, // mtime changed
|
|
90
|
+
// c.ts removed
|
|
91
|
+
'd.ts': { mtimeMs: 400, size: 40 }, // added
|
|
92
|
+
};
|
|
93
|
+
const r = diffAgainstManifest(manifest, current);
|
|
94
|
+
expect(r.added).toEqual(['d.ts']);
|
|
95
|
+
expect(r.removed).toEqual(['c.ts']);
|
|
96
|
+
expect(r.changed).toEqual(['b.ts']);
|
|
97
|
+
expect(r.unchanged).toBe(1);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('size change alone counts as changed', () => {
|
|
101
|
+
const current = { 'a.ts': { mtimeMs: 100, size: 999 } };
|
|
102
|
+
const r = diffAgainstManifest({ files: { 'a.ts': { mtimeMs: 100, size: 10 } } }, current);
|
|
103
|
+
expect(r.changed).toEqual(['a.ts']);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('returns all unchanged when filesystem matches manifest exactly', () => {
|
|
107
|
+
const r = diffAgainstManifest(manifest, manifest.files);
|
|
108
|
+
expect(r.added).toEqual([]);
|
|
109
|
+
expect(r.removed).toEqual([]);
|
|
110
|
+
expect(r.changed).toEqual([]);
|
|
111
|
+
expect(r.unchanged).toBe(3);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('saveCache / loadCache roundtrip', () => {
|
|
116
|
+
beforeEach(async () => {
|
|
117
|
+
sandbox = await makeSandbox({
|
|
118
|
+
'src/a.ts': 'export const a = 1;\n',
|
|
119
|
+
'src/b.ts': "import { a } from './a';\nexport const b = a;\n",
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('roundtrips a real ScanResult through v8.serialize', async () => {
|
|
124
|
+
const source = await createNodeFsFileSource({ rootPath: sandbox.root });
|
|
125
|
+
const scan = await analyze(source);
|
|
126
|
+
const location = await resolveCacheLocation({ rootPath: sandbox.root, toolVersion: '1.0' });
|
|
127
|
+
|
|
128
|
+
const files = await statFiles(
|
|
129
|
+
sandbox.root,
|
|
130
|
+
scan.modules.map((m) => m.id),
|
|
131
|
+
);
|
|
132
|
+
await saveCache(location, scan, { files }, '1.0');
|
|
133
|
+
|
|
134
|
+
const loaded = await loadCache(location);
|
|
135
|
+
expect(loaded).not.toBeNull();
|
|
136
|
+
expect(loaded?.scan.modules.length).toBe(scan.modules.length);
|
|
137
|
+
expect(loaded?.scan.edges).toEqual(scan.edges);
|
|
138
|
+
expect(loaded?.scan.cycles).toEqual(scan.cycles);
|
|
139
|
+
expect(Object.keys(loaded?.manifest.files ?? {})).toEqual(Object.keys(files));
|
|
140
|
+
expect(loaded?.meta.version).toBe(CACHE_FORMAT_VERSION);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('returns null for missing cache directory', async () => {
|
|
144
|
+
const location = await resolveCacheLocation({ rootPath: sandbox.root, toolVersion: '1.0' });
|
|
145
|
+
expect(await loadCache(location)).toBeNull();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('returns null on version skew', async () => {
|
|
149
|
+
const source = await createNodeFsFileSource({ rootPath: sandbox.root });
|
|
150
|
+
const scan = await analyze(source);
|
|
151
|
+
const location = await resolveCacheLocation({ rootPath: sandbox.root, toolVersion: '1.0' });
|
|
152
|
+
const files = await statFiles(
|
|
153
|
+
sandbox.root,
|
|
154
|
+
scan.modules.map((m) => m.id),
|
|
155
|
+
);
|
|
156
|
+
await saveCache(location, scan, { files }, '1.0');
|
|
157
|
+
|
|
158
|
+
// Tamper with meta.json to simulate an old format.
|
|
159
|
+
const metaPath = path.join(location.dir, 'meta.json');
|
|
160
|
+
const meta = JSON.parse(await fs.readFile(metaPath, 'utf8')) as { version: number };
|
|
161
|
+
meta.version = -1;
|
|
162
|
+
await fs.writeFile(metaPath, JSON.stringify(meta));
|
|
163
|
+
|
|
164
|
+
expect(await loadCache(location)).toBeNull();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('returns null on cache key mismatch (e.g. tsconfig changed)', async () => {
|
|
168
|
+
const source = await createNodeFsFileSource({ rootPath: sandbox.root });
|
|
169
|
+
const scan = await analyze(source);
|
|
170
|
+
const locA = await resolveCacheLocation({ rootPath: sandbox.root, toolVersion: '1.0' });
|
|
171
|
+
const files = await statFiles(
|
|
172
|
+
sandbox.root,
|
|
173
|
+
scan.modules.map((m) => m.id),
|
|
174
|
+
);
|
|
175
|
+
await saveCache(locA, scan, { files }, '1.0');
|
|
176
|
+
|
|
177
|
+
// Read it back with a *different* key - emulates someone constructing a
|
|
178
|
+
// location object pointing at the same dir while the meta records the
|
|
179
|
+
// original key. loadCache should refuse rather than serve stale data.
|
|
180
|
+
const wrong = { ...locA, cacheKey: 'wrong-key' };
|
|
181
|
+
expect(await loadCache(wrong)).toBeNull();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('returns null on corrupt manifest', async () => {
|
|
185
|
+
const source = await createNodeFsFileSource({ rootPath: sandbox.root });
|
|
186
|
+
const scan = await analyze(source);
|
|
187
|
+
const location = await resolveCacheLocation({ rootPath: sandbox.root, toolVersion: '1.0' });
|
|
188
|
+
const files = await statFiles(
|
|
189
|
+
sandbox.root,
|
|
190
|
+
scan.modules.map((m) => m.id),
|
|
191
|
+
);
|
|
192
|
+
await saveCache(location, scan, { files }, '1.0');
|
|
193
|
+
await fs.writeFile(path.join(location.dir, 'manifest.bin'), 'not a v8 buffer');
|
|
194
|
+
expect(await loadCache(location)).toBeNull();
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe('analyzeWithCache', () => {
|
|
199
|
+
beforeEach(async () => {
|
|
200
|
+
sandbox = await makeSandbox({
|
|
201
|
+
'src/a.ts': 'export const a = 1;\n',
|
|
202
|
+
'src/b.ts': "import { a } from './a';\nexport const b = a;\n",
|
|
203
|
+
'src/c.ts': 'export const c = 3;\n',
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
async function run(): Promise<{
|
|
208
|
+
scan: import('../../analyzer/types').ScanResult;
|
|
209
|
+
outcome: import('../index').CacheOutcome;
|
|
210
|
+
}> {
|
|
211
|
+
const source = await createNodeFsFileSource({ rootPath: sandbox.root });
|
|
212
|
+
const r = await analyzeWithCache(source, {
|
|
213
|
+
rootPath: sandbox.root,
|
|
214
|
+
toolVersion: 'test',
|
|
215
|
+
});
|
|
216
|
+
return { scan: r.scan, outcome: r.outcome };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
it('first run is a miss and writes the cache', async () => {
|
|
220
|
+
const r = await run();
|
|
221
|
+
expect(r.outcome.kind).toBe('miss');
|
|
222
|
+
const location = await resolveCacheLocation({ rootPath: sandbox.root, toolVersion: 'test' });
|
|
223
|
+
expect(await loadCache(location)).not.toBeNull();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('second run with no changes is a fresh cache hit', async () => {
|
|
227
|
+
await run();
|
|
228
|
+
const r = await run();
|
|
229
|
+
expect(r.outcome.kind).toBe('fresh');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('modifying a file triggers an incremental rerun and matches a cold scan', async () => {
|
|
233
|
+
await run();
|
|
234
|
+
// Bump mtime + content of one file.
|
|
235
|
+
const target = path.join(sandbox.root, 'src/b.ts');
|
|
236
|
+
await fs.writeFile(target, "import { c } from './c';\nexport const b = c;\n");
|
|
237
|
+
const r = await run();
|
|
238
|
+
expect(r.outcome.kind).toBe('incremental');
|
|
239
|
+
if (r.outcome.kind === 'incremental') {
|
|
240
|
+
expect(r.outcome.changed).toBe(1);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const cold = await analyze(await createNodeFsFileSource({ rootPath: sandbox.root }));
|
|
244
|
+
// Same edge set (sort to ignore order)
|
|
245
|
+
const sortKey = (e: { from: string; to: string }): string => `${e.from}→${e.to}`;
|
|
246
|
+
const sorted = (es: readonly { from: string; to: string }[]) =>
|
|
247
|
+
[...es].sort((a, b) => sortKey(a).localeCompare(sortKey(b)));
|
|
248
|
+
expect(sorted(r.scan.edges)).toEqual(sorted(cold.edges));
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('removing a file is handled on the fast path', async () => {
|
|
252
|
+
await run();
|
|
253
|
+
await fs.unlink(path.join(sandbox.root, 'src/c.ts'));
|
|
254
|
+
const r = await run();
|
|
255
|
+
expect(r.outcome.kind).toBe('incremental');
|
|
256
|
+
expect(r.scan.modules.find((m) => m.id === 'src/c.ts')).toBeUndefined();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('adding a file forces a full scan (added-files invalidation)', async () => {
|
|
260
|
+
await run();
|
|
261
|
+
await fs.writeFile(path.join(sandbox.root, 'src/d.ts'), 'export const d = 4;\n');
|
|
262
|
+
const r = await run();
|
|
263
|
+
expect(r.outcome.kind).toBe('invalidated');
|
|
264
|
+
if (r.outcome.kind === 'invalidated') {
|
|
265
|
+
expect(r.outcome.reason).toBe('added-files');
|
|
266
|
+
}
|
|
267
|
+
expect(r.scan.modules.find((m) => m.id === 'src/d.ts')).toBeDefined();
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('cache key change (e.g. tsconfig edit) forces a fresh cache write', async () => {
|
|
271
|
+
await run();
|
|
272
|
+
await fs.writeFile(path.join(sandbox.root, 'tsconfig.json'), '{"compilerOptions":{}}');
|
|
273
|
+
// Simulate the CLI passing the new tsconfig text.
|
|
274
|
+
const source = await createNodeFsFileSource({ rootPath: sandbox.root });
|
|
275
|
+
const r = await analyzeWithCache(source, {
|
|
276
|
+
rootPath: sandbox.root,
|
|
277
|
+
toolVersion: 'test',
|
|
278
|
+
tsconfigText: '{"compilerOptions":{}}',
|
|
279
|
+
});
|
|
280
|
+
expect(r.outcome.kind).toBe('miss');
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
describe('incrementalAnalyze - removed files (fast path)', () => {
|
|
285
|
+
beforeEach(async () => {
|
|
286
|
+
sandbox = await makeSandbox({
|
|
287
|
+
'src/a.ts': "import { b } from './b';\nexport const a = b;\n",
|
|
288
|
+
'src/b.ts': 'export const b = 1;\n',
|
|
289
|
+
'src/c.ts': 'export const c = 2;\n',
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('drops module + incident edges and emits resolve-failed for orphaned imports', async () => {
|
|
294
|
+
const source = await createNodeFsFileSource({ rootPath: sandbox.root });
|
|
295
|
+
const prev = await analyze(source);
|
|
296
|
+
expect(prev.modules.find((m) => m.id === 'src/b.ts')).toBeDefined();
|
|
297
|
+
expect(prev.edges.find((e) => e.from === 'src/a.ts' && e.to === 'src/b.ts')).toBeDefined();
|
|
298
|
+
|
|
299
|
+
// Remove b.ts
|
|
300
|
+
await fs.unlink(path.join(sandbox.root, 'src/b.ts'));
|
|
301
|
+
const source2 = await createNodeFsFileSource({ rootPath: sandbox.root });
|
|
302
|
+
const { incrementalAnalyze } = await import('../../analyzer/incremental');
|
|
303
|
+
const next = await incrementalAnalyze({
|
|
304
|
+
prev,
|
|
305
|
+
source: source2,
|
|
306
|
+
changedFiles: [],
|
|
307
|
+
removedFiles: ['src/b.ts'],
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
expect(next.modules.find((m) => m.id === 'src/b.ts')).toBeUndefined();
|
|
311
|
+
expect(next.edges.find((e) => e.to === 'src/b.ts')).toBeUndefined();
|
|
312
|
+
expect(
|
|
313
|
+
next.warnings.find((w) => w.code === 'resolve-failed' && w.file === 'src/a.ts'),
|
|
314
|
+
).toBeDefined();
|
|
315
|
+
});
|
|
316
|
+
});
|