svelte-docinfo 0.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 +21 -0
- package/README.md +349 -0
- package/dist/analyze-core.d.ts +1386 -0
- package/dist/analyze-core.d.ts.map +1 -0
- package/dist/analyze-core.js +324 -0
- package/dist/analyze.d.ts +137 -0
- package/dist/analyze.d.ts.map +1 -0
- package/dist/analyze.js +148 -0
- package/dist/cli.d.ts +102 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +206 -0
- package/dist/concurrency.d.ts +38 -0
- package/dist/concurrency.d.ts.map +1 -0
- package/dist/concurrency.js +57 -0
- package/dist/declaration-build.d.ts +164 -0
- package/dist/declaration-build.d.ts.map +1 -0
- package/dist/declaration-build.js +15 -0
- package/dist/declaration-helpers.d.ts +169 -0
- package/dist/declaration-helpers.d.ts.map +1 -0
- package/dist/declaration-helpers.js +239 -0
- package/dist/dep-resolver.d.ts +166 -0
- package/dist/dep-resolver.d.ts.map +1 -0
- package/dist/dep-resolver.js +144 -0
- package/dist/diagnostics.d.ts +650 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +377 -0
- package/dist/discovery.d.ts +115 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +126 -0
- package/dist/exports.d.ts +105 -0
- package/dist/exports.d.ts.map +1 -0
- package/dist/exports.js +274 -0
- package/dist/files.d.ts +77 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +95 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/log.d.ts +32 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +11 -0
- package/dist/logo.d.ts +8 -0
- package/dist/logo.d.ts.map +1 -0
- package/dist/logo.js +9 -0
- package/dist/main.d.ts +11 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +18 -0
- package/dist/paths.d.ts +35 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +34 -0
- package/dist/postprocess.d.ts +145 -0
- package/dist/postprocess.d.ts.map +1 -0
- package/dist/postprocess.js +305 -0
- package/dist/session.d.ts +248 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +496 -0
- package/dist/source-config.d.ts +330 -0
- package/dist/source-config.d.ts.map +1 -0
- package/dist/source-config.js +356 -0
- package/dist/source.d.ts +126 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +68 -0
- package/dist/svelte.d.ts +179 -0
- package/dist/svelte.d.ts.map +1 -0
- package/dist/svelte.js +898 -0
- package/dist/tsdoc.d.ts +145 -0
- package/dist/tsdoc.d.ts.map +1 -0
- package/dist/tsdoc.js +262 -0
- package/dist/types.d.ts +3381 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +588 -0
- package/dist/typescript-exports.d.ts +91 -0
- package/dist/typescript-exports.d.ts.map +1 -0
- package/dist/typescript-exports.js +656 -0
- package/dist/typescript-extract-class.d.ts +29 -0
- package/dist/typescript-extract-class.d.ts.map +1 -0
- package/dist/typescript-extract-class.js +251 -0
- package/dist/typescript-extract-function.d.ts +47 -0
- package/dist/typescript-extract-function.d.ts.map +1 -0
- package/dist/typescript-extract-function.js +108 -0
- package/dist/typescript-extract-shared.d.ts +166 -0
- package/dist/typescript-extract-shared.d.ts.map +1 -0
- package/dist/typescript-extract-shared.js +528 -0
- package/dist/typescript-extract-type-properties.d.ts +34 -0
- package/dist/typescript-extract-type-properties.d.ts.map +1 -0
- package/dist/typescript-extract-type-properties.js +241 -0
- package/dist/typescript-extract-type.d.ts +48 -0
- package/dist/typescript-extract-type.d.ts.map +1 -0
- package/dist/typescript-extract-type.js +238 -0
- package/dist/typescript-program.d.ts +211 -0
- package/dist/typescript-program.d.ts.map +1 -0
- package/dist/typescript-program.js +290 -0
- package/dist/vite.d.ts +116 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +418 -0
- package/package.json +131 -0
- package/virtual-svelte-docinfo.d.ts +49 -0
package/dist/session.js
ADDED
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent analysis session — δ-shaped API over a `ts.LanguageService`.
|
|
3
|
+
*
|
|
4
|
+
* Maps cleanly onto LSP and Vite/HMR consumers:
|
|
5
|
+
*
|
|
6
|
+
* - `setFile` / `setFiles` — additive ingest; transform-if-Svelte, lex
|
|
7
|
+
* specifiers, resolve imports (parallel), push content/virtual to the LS.
|
|
8
|
+
* Returns ingest-time diagnostics + a `changed` flag. Cache-hit no-op
|
|
9
|
+
* when content matches AND the mode-specific cache key matches (resolver
|
|
10
|
+
* identity for lex+resolve, dependency-snapshot equality for pre-resolved).
|
|
11
|
+
* - `deleteFile` — drop owned entry, evict from LS.
|
|
12
|
+
* - `has` / `list` — owned-set introspection (covers what consumers used to
|
|
13
|
+
* get from their own mirror caches).
|
|
14
|
+
* - `query` — sync analysis pass against the current owned set; returns
|
|
15
|
+
* analysis-pass diagnostics only (ingest diagnostics surface via the
|
|
16
|
+
* `setFile`/`setFiles` returns).
|
|
17
|
+
* - `dispose` — release LS resources.
|
|
18
|
+
*
|
|
19
|
+
* The session owns a single `Map<id, OwnedEntry>` covering content, svelte
|
|
20
|
+
* virtuals, unfiltered deps, the mode-specific cache key (resolver identity
|
|
21
|
+
* or pre-resolved snapshot), and ingest-time diagnostics. svelte2tsx runs
|
|
22
|
+
* at most once per content change. Resolver work parallelizes across the
|
|
23
|
+
* batch in phase 2 of the three-phase setFiles pipeline; fully pre-resolved
|
|
24
|
+
* batches skip phase 2 (and the default-resolver construction) entirely.
|
|
25
|
+
*
|
|
26
|
+
* @see `analyze-core.ts` for the two-phase analysis orchestrator
|
|
27
|
+
* @see `dep-resolver.ts` for the `ImportResolver` token contract
|
|
28
|
+
*
|
|
29
|
+
* @module
|
|
30
|
+
*/
|
|
31
|
+
import { createAnalysisLanguageService, loadTsconfig, } from './typescript-program.js';
|
|
32
|
+
import { transformSvelteSource } from './svelte.js';
|
|
33
|
+
import { createDefaultResolver, ensureLexerReady, lexImports, normalizeResolveImport, } from './dep-resolver.js';
|
|
34
|
+
import { isSource, normalizeSourceOptions } from './source-config.js';
|
|
35
|
+
import { toPosixPath } from './paths.js';
|
|
36
|
+
import { MAX_RESOLVE_CONCURRENCY, map_concurrent } from './concurrency.js';
|
|
37
|
+
import { analyzeCore, normalizeDiagnosticPaths, AnalyzeResultJson, } from './analyze-core.js';
|
|
38
|
+
import { computeDependents } from './postprocess.js';
|
|
39
|
+
/**
|
|
40
|
+
* Element-wise equality on two readonly string arrays. Used for the
|
|
41
|
+
* pre-resolved-deps cache key: a fresh array with identical contents
|
|
42
|
+
* cache-hits (matches Gro filer's `[...Map.keys()]`-per-call pattern), while
|
|
43
|
+
* any length, element, or order difference cache-misses. Order-sensitive
|
|
44
|
+
* because `unfilteredDeps` ordering reflects the caller's declared graph
|
|
45
|
+
* shape — reordering without a content change is a caller-driven intent
|
|
46
|
+
* signal worth honoring.
|
|
47
|
+
*/
|
|
48
|
+
const arraysShallowEqual = (a, b) => {
|
|
49
|
+
if (a === b)
|
|
50
|
+
return true;
|
|
51
|
+
if (a.length !== b.length)
|
|
52
|
+
return false;
|
|
53
|
+
for (let i = 0; i < a.length; i++) {
|
|
54
|
+
if (a[i] !== b[i])
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Create a persistent analysis session.
|
|
61
|
+
*
|
|
62
|
+
* @example Vite plugin integration
|
|
63
|
+
* ```ts
|
|
64
|
+
* const session = createAnalysisSession({sourceOptions, resolveImport, log});
|
|
65
|
+
* await session.setFiles(initialFiles);
|
|
66
|
+
* const result = session.query();
|
|
67
|
+
* // on watcher events:
|
|
68
|
+
* await session.setFile({id, content});
|
|
69
|
+
* await session.deleteFile(removedId);
|
|
70
|
+
* const next = session.query();
|
|
71
|
+
* // on shutdown:
|
|
72
|
+
* session.dispose();
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* @example One-shot via the public wrapper
|
|
76
|
+
* ```ts
|
|
77
|
+
* // Equivalent to `analyze(...)` — the wrapper goes through a session internally.
|
|
78
|
+
* const session = createAnalysisSession({sourceOptions});
|
|
79
|
+
* try {
|
|
80
|
+
* await session.setFiles(sourceFiles);
|
|
81
|
+
* return session.query({onDuplicates: 'throw'});
|
|
82
|
+
* } finally {
|
|
83
|
+
* session.dispose();
|
|
84
|
+
* }
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export const createAnalysisSession = (options) => {
|
|
88
|
+
const sourceOptions = normalizeSourceOptions(options.sourceOptions);
|
|
89
|
+
const ls = createAnalysisLanguageService({
|
|
90
|
+
projectRoot: sourceOptions.projectRoot,
|
|
91
|
+
tsconfig: options.tsconfig,
|
|
92
|
+
compilerOptions: options.compilerOptions,
|
|
93
|
+
documentRegistry: options.documentRegistry,
|
|
94
|
+
}, options.log);
|
|
95
|
+
const owned = new Map();
|
|
96
|
+
// TODO: the owned-entry cache is in-memory only, so cold one-shot runs
|
|
97
|
+
// (`analyzeFromFiles` from the CLI, `vite build`) re-transform and re-analyze
|
|
98
|
+
// every file from scratch — the expensive case given svelte2tsx + the TS
|
|
99
|
+
// checker. A schema-versioned disk cache (cf. fuz_css `css_cache.ts`: mirror
|
|
100
|
+
// the source tree, key each file's extracted JSON by content hash, stamp an
|
|
101
|
+
// integer cache-version bumped on output-shape changes, self-heal on
|
|
102
|
+
// hash/version/parse mismatch, atomic temp-file+rename writes, skip on CI)
|
|
103
|
+
// would make those cold paths incremental across process restarts. The
|
|
104
|
+
// existing per-entry content-equality check is the in-memory analog; the
|
|
105
|
+
// cache key would need the same `(content, mode-specific key)` discipline
|
|
106
|
+
// the session already uses, plus the version stamp.
|
|
107
|
+
// Lazy default resolver — only constructed when needed: a `setFiles` batch
|
|
108
|
+
// has at least one file lacking `dependencies`, the call doesn't supply a
|
|
109
|
+
// per-call override, and no session-default resolver was configured. Fully
|
|
110
|
+
// pre-resolved batches skip the construction entirely (see `needsResolver`
|
|
111
|
+
// gating below). The TS module-resolution cache is kept on the resolver so
|
|
112
|
+
// consecutive resolves share state.
|
|
113
|
+
let lazyDefault;
|
|
114
|
+
const getDefaultResolver = () => {
|
|
115
|
+
if (lazyDefault)
|
|
116
|
+
return lazyDefault;
|
|
117
|
+
const { compilerOptions } = loadTsconfig({
|
|
118
|
+
projectRoot: sourceOptions.projectRoot,
|
|
119
|
+
tsconfig: options.tsconfig,
|
|
120
|
+
compilerOptions: options.compilerOptions,
|
|
121
|
+
}, options.log);
|
|
122
|
+
lazyDefault = createDefaultResolver(compilerOptions, sourceOptions.projectRoot);
|
|
123
|
+
return lazyDefault;
|
|
124
|
+
};
|
|
125
|
+
// Normalize the session default once at construction. A bare function gets a
|
|
126
|
+
// single synthesized identity here, stable for the session's lifetime — so
|
|
127
|
+
// repeated ingests of byte-identical content cache-hit on identity.
|
|
128
|
+
const sessionDefaultResolver = normalizeResolveImport(options.resolveImport);
|
|
129
|
+
const pickResolver = (override) => {
|
|
130
|
+
if (override)
|
|
131
|
+
return override;
|
|
132
|
+
if (sessionDefaultResolver)
|
|
133
|
+
return sessionDefaultResolver;
|
|
134
|
+
return getDefaultResolver();
|
|
135
|
+
};
|
|
136
|
+
// ── Phase 1: sync per-file transform/lex ─────────────────────────────────
|
|
137
|
+
// `resolver` is `null` when the entire batch is pre-resolved — phase 1
|
|
138
|
+
// needs no resolver identity in that case, and skipping `pickResolver`
|
|
139
|
+
// avoids constructing the lazy default for fully pre-resolved consumers.
|
|
140
|
+
const phase1 = (file, resolver) => {
|
|
141
|
+
const existing = owned.get(file.id);
|
|
142
|
+
// Pre-resolved deps mode: caller asserted `file.dependencies` is the
|
|
143
|
+
// authoritative absolute-path list. Skip lex+resolve; Svelte still gets
|
|
144
|
+
// transformed because the LS needs the virtual.
|
|
145
|
+
const preResolvedDeps = file.dependencies;
|
|
146
|
+
const usePreResolved = preResolvedDeps !== undefined;
|
|
147
|
+
// Cache hit shape depends on mode:
|
|
148
|
+
// pre-resolved → match content + element-wise deps equality (a fresh
|
|
149
|
+
// array with the same contents still hits — Gro's
|
|
150
|
+
// `[...Map.keys()]` per-call pattern cache-reuses
|
|
151
|
+
// cleanly across persistent-session calls)
|
|
152
|
+
// lex+resolve → match content + same resolver identity AND prior
|
|
153
|
+
// entry was lex+resolve mode (mode flip invalidates)
|
|
154
|
+
// All-or-nothing per the spec (no half-runs).
|
|
155
|
+
const cacheHit = existing !== undefined &&
|
|
156
|
+
existing.content === file.content &&
|
|
157
|
+
(usePreResolved
|
|
158
|
+
? existing.mode === 'pre-resolved' &&
|
|
159
|
+
arraysShallowEqual(existing.preResolvedDepsSnapshot, preResolvedDeps)
|
|
160
|
+
: existing.mode === 'lex+resolve' &&
|
|
161
|
+
resolver !== null &&
|
|
162
|
+
existing.resolverIdentity === resolver.identity);
|
|
163
|
+
if (cacheHit) {
|
|
164
|
+
return {
|
|
165
|
+
file,
|
|
166
|
+
virtual: existing.virtual,
|
|
167
|
+
transformFailed: existing.transformFailed === true,
|
|
168
|
+
ingestDiagnostics: existing.ingestDiagnostics,
|
|
169
|
+
specifiers: [],
|
|
170
|
+
// Phase 3 short-circuits on `cacheHit: true` before reading this
|
|
171
|
+
// field — leave it undefined to document that it's unused on
|
|
172
|
+
// the cache-hit path (the entry in `owned` already carries the
|
|
173
|
+
// authoritative resolver identity / deps snapshot).
|
|
174
|
+
resolverIdentity: undefined,
|
|
175
|
+
cacheHit: true,
|
|
176
|
+
previousVirtualPath: undefined,
|
|
177
|
+
preResolvedDeps: usePreResolved ? preResolvedDeps : undefined,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
// Capture the prior virtual path before we replace the entry. If the new
|
|
181
|
+
// transform fails, phase 3 evicts the stale virtual from the LS.
|
|
182
|
+
const previousVirtualPath = existing?.virtual?.virtualPath;
|
|
183
|
+
const ingestDiagnostics = [];
|
|
184
|
+
const analyzer = sourceOptions.getAnalyzerType(file.id);
|
|
185
|
+
// Transform if Svelte. transformSvelteSource returns ingest diagnostics
|
|
186
|
+
// directly (transform_failed on throw, source_map_failed on map error).
|
|
187
|
+
// Runs regardless of mode — the LS host needs the virtual for checker
|
|
188
|
+
// state, independent of how we obtain dependency edges.
|
|
189
|
+
let virtual;
|
|
190
|
+
let transformFailed = false;
|
|
191
|
+
if (analyzer === 'svelte') {
|
|
192
|
+
const tres = transformSvelteSource(file);
|
|
193
|
+
for (const d of tres.diagnostics)
|
|
194
|
+
ingestDiagnostics.push(d);
|
|
195
|
+
if (tres.virtual) {
|
|
196
|
+
virtual = tres.virtual;
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
transformFailed = true;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// Lex import specifiers — skipped when caller supplied pre-resolved deps.
|
|
203
|
+
// Sync — caller awaited ensureLexerReady. CSS/JSON files have nothing to
|
|
204
|
+
// lex; transform_failed Svelte has no virtual content to lex.
|
|
205
|
+
let specifiers = [];
|
|
206
|
+
if (!usePreResolved &&
|
|
207
|
+
!transformFailed &&
|
|
208
|
+
(analyzer === 'typescript' || analyzer === 'svelte')) {
|
|
209
|
+
try {
|
|
210
|
+
const contentToLex = virtual ? virtual.content : file.content;
|
|
211
|
+
specifiers = lexImports(contentToLex, file.id);
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
ingestDiagnostics.push({
|
|
215
|
+
kind: 'import_parse_failed',
|
|
216
|
+
file: file.id,
|
|
217
|
+
message: `Failed to parse imports: ${err instanceof Error ? err.message : String(err)}`,
|
|
218
|
+
severity: 'warning',
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
file,
|
|
224
|
+
virtual,
|
|
225
|
+
transformFailed,
|
|
226
|
+
ingestDiagnostics,
|
|
227
|
+
specifiers,
|
|
228
|
+
resolverIdentity: resolver?.identity,
|
|
229
|
+
cacheHit: false,
|
|
230
|
+
previousVirtualPath,
|
|
231
|
+
preResolvedDeps: usePreResolved ? preResolvedDeps : undefined,
|
|
232
|
+
};
|
|
233
|
+
};
|
|
234
|
+
// ── Phase 3: serial per-file LS push + entry write ───────────────────────
|
|
235
|
+
const phase3 = (pending, resolved) => {
|
|
236
|
+
if (pending.cacheHit) {
|
|
237
|
+
// Cached SetFileResult: same diagnostics array reference, changed: false.
|
|
238
|
+
// `[...]` clone keeps the caller from mutating the entry's stored array.
|
|
239
|
+
// Cached entry's diagnostics were normalized at the time the entry was stored.
|
|
240
|
+
return { changed: false, diagnostics: [...pending.ingestDiagnostics] };
|
|
241
|
+
}
|
|
242
|
+
// Normalize ingest diagnostics to project-root-relative paths before
|
|
243
|
+
// the entry is stored. After this point, `entry.ingestDiagnostics` is
|
|
244
|
+
// normalized at rest — `allIngestDiagnostics()` can read it directly
|
|
245
|
+
// without depending on a later in-place mutation via the aggregate.
|
|
246
|
+
normalizeDiagnosticPaths(pending.ingestDiagnostics, sourceOptions.projectRoot);
|
|
247
|
+
// Build `unfilteredDeps` from one of two sources:
|
|
248
|
+
// pre-resolved → caller's `file.dependencies`, posixified + filtered
|
|
249
|
+
// lex+resolve → resolver task outputs, already posixified, filtered
|
|
250
|
+
const unfilteredDeps = [];
|
|
251
|
+
if (pending.preResolvedDeps !== undefined) {
|
|
252
|
+
for (const raw of pending.preResolvedDeps) {
|
|
253
|
+
const posix = toPosixPath(raw);
|
|
254
|
+
if (!isSource(posix, sourceOptions))
|
|
255
|
+
continue;
|
|
256
|
+
unfilteredDeps.push(posix);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
for (const r of resolved) {
|
|
261
|
+
if (r === null)
|
|
262
|
+
continue;
|
|
263
|
+
if (!isSource(r, sourceOptions))
|
|
264
|
+
continue;
|
|
265
|
+
unfilteredDeps.push(r);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// Build a mode-specific entry. The discriminator (`mode`) tags which
|
|
269
|
+
// cache-key field to read on the next ingest: `resolverIdentity` for
|
|
270
|
+
// lex+resolve, `preResolvedDepsSnapshot` for pre-resolved. Snapshot
|
|
271
|
+
// the caller's array on the pre-resolved branch — owning a copy means
|
|
272
|
+
// subsequent caller-side mutation doesn't produce false cache hits.
|
|
273
|
+
const entry = pending.preResolvedDeps !== undefined
|
|
274
|
+
? {
|
|
275
|
+
mode: 'pre-resolved',
|
|
276
|
+
content: pending.file.content,
|
|
277
|
+
virtual: pending.virtual,
|
|
278
|
+
unfilteredDeps,
|
|
279
|
+
preResolvedDepsSnapshot: pending.preResolvedDeps.slice(),
|
|
280
|
+
ingestDiagnostics: pending.ingestDiagnostics,
|
|
281
|
+
}
|
|
282
|
+
: {
|
|
283
|
+
mode: 'lex+resolve',
|
|
284
|
+
content: pending.file.content,
|
|
285
|
+
virtual: pending.virtual,
|
|
286
|
+
unfilteredDeps,
|
|
287
|
+
// Non-null assert: lex+resolve branch implies the batch picked a
|
|
288
|
+
// resolver, so phase 1 stored its identity on the pending entry.
|
|
289
|
+
resolverIdentity: pending.resolverIdentity,
|
|
290
|
+
ingestDiagnostics: pending.ingestDiagnostics,
|
|
291
|
+
};
|
|
292
|
+
if (pending.transformFailed)
|
|
293
|
+
entry.transformFailed = true;
|
|
294
|
+
owned.set(pending.file.id, entry);
|
|
295
|
+
// LS push — virtual path for successful Svelte transforms, real path
|
|
296
|
+
// for TS/JS. CSS/JSON aren't TypeScript-resolvable; skip the push.
|
|
297
|
+
// Transform-failed Svelte: no virtual; skip.
|
|
298
|
+
const analyzer = sourceOptions.getAnalyzerType(pending.file.id);
|
|
299
|
+
if (pending.virtual) {
|
|
300
|
+
ls.setFile(pending.virtual.virtualPath, pending.virtual.content);
|
|
301
|
+
}
|
|
302
|
+
else if (analyzer === 'typescript') {
|
|
303
|
+
ls.setFile(pending.file.id, pending.file.content);
|
|
304
|
+
}
|
|
305
|
+
else if (pending.previousVirtualPath) {
|
|
306
|
+
// Transform regressed (had virtual → now transform_failed). Evict the
|
|
307
|
+
// stale virtual from the LS so other files importing this `.svelte`
|
|
308
|
+
// don't see the prior svelte2tsx output via the checker.
|
|
309
|
+
ls.deleteFile(pending.previousVirtualPath);
|
|
310
|
+
}
|
|
311
|
+
return { changed: true, diagnostics: [...pending.ingestDiagnostics] };
|
|
312
|
+
};
|
|
313
|
+
// ── setFiles: orchestrate three phases ───────────────────────────────────
|
|
314
|
+
const setFiles = async (files, opts) => {
|
|
315
|
+
await ensureLexerReady();
|
|
316
|
+
// Resolver gating: pick one only if any file in the batch lacks
|
|
317
|
+
// `dependencies` (i.e., needs lex+resolve). For fully pre-resolved
|
|
318
|
+
// batches the resolver is never consulted, so we skip `pickResolver`
|
|
319
|
+
// entirely — that avoids constructing the lazy default
|
|
320
|
+
// (`loadTsconfig` + `createDefaultResolver`) for consumers like the
|
|
321
|
+
// Gro filer that always hand over pre-resolved deps.
|
|
322
|
+
const needsResolver = files.some((f) => f.dependencies === undefined);
|
|
323
|
+
// Normalize the per-call override (bare fn → fresh identity each call).
|
|
324
|
+
const resolver = needsResolver
|
|
325
|
+
? pickResolver(normalizeResolveImport(opts?.resolveImport))
|
|
326
|
+
: null;
|
|
327
|
+
// Posixify ids at ingest — the internal contract is forward-slash
|
|
328
|
+
// everywhere. Skip the clone when the input is already POSIX (common
|
|
329
|
+
// path on Linux/macOS).
|
|
330
|
+
const normalizedFiles = files.map((f) => {
|
|
331
|
+
const posixId = toPosixPath(f.id);
|
|
332
|
+
return posixId === f.id ? f : { ...f, id: posixId };
|
|
333
|
+
});
|
|
334
|
+
// Phase 1: sync per-file transform + lex.
|
|
335
|
+
const pendings = [];
|
|
336
|
+
for (const file of normalizedFiles) {
|
|
337
|
+
pendings.push(phase1(file, resolver));
|
|
338
|
+
}
|
|
339
|
+
const tasks = [];
|
|
340
|
+
for (let pi = 0; pi < pendings.length; pi++) {
|
|
341
|
+
const p = pendings[pi];
|
|
342
|
+
if (p.cacheHit)
|
|
343
|
+
continue;
|
|
344
|
+
for (let si = 0; si < p.specifiers.length; si++) {
|
|
345
|
+
tasks.push({ pendingIdx: pi, specIdx: si, specifier: p.specifiers[si] });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// Resolver invariant: `tasks` non-empty implies `resolver !== null`.
|
|
349
|
+
// A task is only enqueued for a non-cache-hit pending with at least
|
|
350
|
+
// one specifier, which by phase 1's logic implies the file went
|
|
351
|
+
// through the lex+resolve branch, which only runs when at least one
|
|
352
|
+
// file in the batch lacks `dependencies` — i.e., `needsResolver` was
|
|
353
|
+
// `true` and a resolver was picked. Convert this unreachable-by-
|
|
354
|
+
// invariant into unreachable-by-throw so the `resolver!` below has
|
|
355
|
+
// an explicit runtime defense rather than relying on the chain.
|
|
356
|
+
if (tasks.length > 0 && resolver === null) {
|
|
357
|
+
throw new Error('svelte-docinfo: phase-2 invariant violated — tasks pending without a resolver');
|
|
358
|
+
}
|
|
359
|
+
const taskResults = await map_concurrent(tasks, MAX_RESOLVE_CONCURRENCY, async (t) => {
|
|
360
|
+
const pending = pendings[t.pendingIdx];
|
|
361
|
+
try {
|
|
362
|
+
const resolved = await resolver.resolve(t.specifier, pending.file.id);
|
|
363
|
+
// Posixify resolver output — custom resolvers (Vite/Rollup,
|
|
364
|
+
// user-supplied) may emit native paths on Windows. The TS
|
|
365
|
+
// default resolver already returns POSIX, so this is a no-op
|
|
366
|
+
// there. Storing POSIX keeps unfilteredDeps consistent with
|
|
367
|
+
// owned-set keys in `query()`'s ownedIds filter.
|
|
368
|
+
return {
|
|
369
|
+
...t,
|
|
370
|
+
resolved: resolved === null ? null : toPosixPath(resolved),
|
|
371
|
+
error: undefined,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
catch (err) {
|
|
375
|
+
return {
|
|
376
|
+
...t,
|
|
377
|
+
resolved: null,
|
|
378
|
+
error: err instanceof Error ? err.message : String(err),
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
// Group resolved results by pending index for phase 3. Resolver errors
|
|
383
|
+
// land on the importing file's ingest diagnostics so per-file grouping
|
|
384
|
+
// (LSP publish, etc.) keeps them attached to the right source.
|
|
385
|
+
// Dedup on (pendingIdx, specifier) — duplicate imports of the same path
|
|
386
|
+
// throw N times, but the diagnostic carries no per-import-site info, so
|
|
387
|
+
// emitting once per specifier keeps the output non-redundant.
|
|
388
|
+
const resolvedByPending = pendings.map((p) => p.cacheHit ? [] : new Array(p.specifiers.length).fill(null));
|
|
389
|
+
const seenFailures = new Map();
|
|
390
|
+
for (const r of taskResults) {
|
|
391
|
+
resolvedByPending[r.pendingIdx][r.specIdx] = r.resolved;
|
|
392
|
+
if (r.error === undefined)
|
|
393
|
+
continue;
|
|
394
|
+
let seen = seenFailures.get(r.pendingIdx);
|
|
395
|
+
if (!seen) {
|
|
396
|
+
seen = new Set();
|
|
397
|
+
seenFailures.set(r.pendingIdx, seen);
|
|
398
|
+
}
|
|
399
|
+
if (seen.has(r.specifier))
|
|
400
|
+
continue;
|
|
401
|
+
seen.add(r.specifier);
|
|
402
|
+
const pending = pendings[r.pendingIdx];
|
|
403
|
+
pending.ingestDiagnostics.push({
|
|
404
|
+
kind: 'resolver_failed',
|
|
405
|
+
file: pending.file.id,
|
|
406
|
+
message: `Import resolver threw for "${r.specifier}": ${r.error}`,
|
|
407
|
+
severity: 'warning',
|
|
408
|
+
specifier: r.specifier,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
// Phase 3: serial per-file LS push + entry write. Single LS mutator
|
|
412
|
+
// across the batch — no interleaved updates from concurrent tasks.
|
|
413
|
+
const perFile = new Map();
|
|
414
|
+
const changedIds = new Set();
|
|
415
|
+
const aggregateDiagnostics = [];
|
|
416
|
+
for (let i = 0; i < pendings.length; i++) {
|
|
417
|
+
const pending = pendings[i];
|
|
418
|
+
const result = phase3(pending, resolvedByPending[i]);
|
|
419
|
+
perFile.set(pending.file.id, result);
|
|
420
|
+
if (result.changed)
|
|
421
|
+
changedIds.add(pending.file.id);
|
|
422
|
+
for (const d of result.diagnostics)
|
|
423
|
+
aggregateDiagnostics.push(d);
|
|
424
|
+
}
|
|
425
|
+
return { changedIds, diagnostics: aggregateDiagnostics, perFile };
|
|
426
|
+
};
|
|
427
|
+
const setFile = async (file, opts) => {
|
|
428
|
+
const batch = await setFiles([file], opts);
|
|
429
|
+
return batch.perFile.get(toPosixPath(file.id));
|
|
430
|
+
};
|
|
431
|
+
// `deleteFile` returns `Promise<void>` for symmetry with `setFile`/`setFiles`
|
|
432
|
+
// (the spec calls this "async-by-convention"). The body is purely sync —
|
|
433
|
+
// `ls.deleteFile` and `Map.delete` are sync — so we wrap the return rather
|
|
434
|
+
// than declaring `async` (which would trip eslint's require-await).
|
|
435
|
+
const deleteFile = (id) => {
|
|
436
|
+
const posixId = toPosixPath(id);
|
|
437
|
+
const entry = owned.get(posixId);
|
|
438
|
+
if (!entry)
|
|
439
|
+
return Promise.resolve();
|
|
440
|
+
if (entry.virtual) {
|
|
441
|
+
ls.deleteFile(entry.virtual.virtualPath);
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
ls.deleteFile(posixId);
|
|
445
|
+
}
|
|
446
|
+
owned.delete(posixId);
|
|
447
|
+
return Promise.resolve();
|
|
448
|
+
};
|
|
449
|
+
const has = (id) => owned.has(toPosixPath(id));
|
|
450
|
+
const list = () => [...owned.keys()];
|
|
451
|
+
const allIngestDiagnostics = () => {
|
|
452
|
+
const out = [];
|
|
453
|
+
for (const entry of owned.values()) {
|
|
454
|
+
for (const d of entry.ingestDiagnostics)
|
|
455
|
+
out.push(d);
|
|
456
|
+
}
|
|
457
|
+
return out;
|
|
458
|
+
};
|
|
459
|
+
const query = (opts) => {
|
|
460
|
+
// Build query inputs from owned entries. Filter unfilteredDeps to the
|
|
461
|
+
// current owned set per cache strategy A.
|
|
462
|
+
const ownedIds = new Set(owned.keys());
|
|
463
|
+
const sourceFiles = [];
|
|
464
|
+
const svelteVirtualFiles = new Map();
|
|
465
|
+
const transformFailedIds = new Set();
|
|
466
|
+
for (const [id, entry] of owned) {
|
|
467
|
+
const filteredDeps = entry.unfilteredDeps.filter((d) => ownedIds.has(d));
|
|
468
|
+
sourceFiles.push({ id, content: entry.content, dependencies: filteredDeps });
|
|
469
|
+
if (entry.virtual)
|
|
470
|
+
svelteVirtualFiles.set(id, entry.virtual);
|
|
471
|
+
if (entry.transformFailed)
|
|
472
|
+
transformFailedIds.add(id);
|
|
473
|
+
}
|
|
474
|
+
// Compute bidirectional dependents from the filtered forward edges.
|
|
475
|
+
const filesWithDeps = computeDependents(sourceFiles);
|
|
476
|
+
// `getProgram()` returns the same `ts.Program` reference as the prior
|
|
477
|
+
// call when no version bumped, or a fresh program reusing unchanged
|
|
478
|
+
// ASTs via the document registry.
|
|
479
|
+
const program = ls.getProgram();
|
|
480
|
+
const result = analyzeCore({
|
|
481
|
+
sourceFiles: filesWithDeps,
|
|
482
|
+
sourceOptions,
|
|
483
|
+
program,
|
|
484
|
+
svelteVirtualFiles,
|
|
485
|
+
transformFailedIds,
|
|
486
|
+
onDuplicates: opts?.onDuplicates,
|
|
487
|
+
log: opts?.log ?? options.log,
|
|
488
|
+
});
|
|
489
|
+
return result;
|
|
490
|
+
};
|
|
491
|
+
const dispose = () => {
|
|
492
|
+
ls.dispose();
|
|
493
|
+
owned.clear();
|
|
494
|
+
};
|
|
495
|
+
return { setFile, setFiles, deleteFile, has, list, query, allIngestDiagnostics, dispose };
|
|
496
|
+
};
|