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/vite.js
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite plugin for svelte-docinfo.
|
|
3
|
+
*
|
|
4
|
+
* Drives a long-lived `AnalysisSession` against a debounce-batched watcher.
|
|
5
|
+
* Every file change funnels into a `pendingChanges` Map and flushes via one
|
|
6
|
+
* `setFiles` + one `query` per debounce window — multi-file editor saves
|
|
7
|
+
* collapse to a single re-analysis instead of N serialized round-trips.
|
|
8
|
+
*
|
|
9
|
+
* The plugin no longer maintains a `fileCache.content` mirror — `setFile`
|
|
10
|
+
* returns `{changed: boolean}` so HMR invalidation reacts to that flag
|
|
11
|
+
* directly. `session.list()` / `session.has()` cover owned-set introspection.
|
|
12
|
+
*
|
|
13
|
+
* Consumers import the analysis result:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* import {modules, diagnostics} from 'virtual:svelte-docinfo';
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* For TypeScript support, add to your `app.d.ts`:
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* /// <reference types="svelte-docinfo/virtual-svelte-docinfo.js" />
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* // vite.config.ts
|
|
28
|
+
* import svelteDocinfo from './vite.js';
|
|
29
|
+
*
|
|
30
|
+
* export default defineConfig({
|
|
31
|
+
* plugins: [
|
|
32
|
+
* sveltekit(),
|
|
33
|
+
* svelteDocinfo(),
|
|
34
|
+
* ],
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @module
|
|
39
|
+
*/
|
|
40
|
+
import { readFile } from 'node:fs/promises';
|
|
41
|
+
import { createAnalysisSession } from './session.js';
|
|
42
|
+
import { discoverSourceFiles } from './discovery.js';
|
|
43
|
+
import { compactReplacer } from './declaration-helpers.js';
|
|
44
|
+
import { createSourceOptions, isSource, } from './source-config.js';
|
|
45
|
+
import { noDepsResolver } from './dep-resolver.js';
|
|
46
|
+
import { toPosixPath } from './paths.js';
|
|
47
|
+
const VIRTUAL_ID = 'virtual:svelte-docinfo';
|
|
48
|
+
const RESOLVED_VIRTUAL_ID = '\0virtual:svelte-docinfo';
|
|
49
|
+
/** Stable resolver identity for Vite's plugin-container resolver in dev mode. */
|
|
50
|
+
const VITE_DEV_IDENTITY = 'vite-plugin-container';
|
|
51
|
+
/** Stable resolver identity for the Rollup `this.resolve` resolver in build mode. */
|
|
52
|
+
const VITE_BUILD_IDENTITY = 'vite-rollup-build';
|
|
53
|
+
/**
|
|
54
|
+
* Vite plugin: analyzes TypeScript and Svelte source files via a persistent
|
|
55
|
+
* `AnalysisSession` and serves the result as `virtual:svelte-docinfo`.
|
|
56
|
+
*
|
|
57
|
+
* In dev mode, watches source files and triggers debounced HMR updates on
|
|
58
|
+
* changes via `setFiles` batches drained from a pending-changes Map.
|
|
59
|
+
* In build mode, runs analysis once during `buildStart`.
|
|
60
|
+
*/
|
|
61
|
+
const svelteDocinfo = (options = {}) => {
|
|
62
|
+
const { include, exclude, resolveDependencies = true, discovery, distDir, sourceOptions, onDuplicates, hmrDebounceMs = 100, } = options;
|
|
63
|
+
let projectRoot;
|
|
64
|
+
let resolvedSourceOptions;
|
|
65
|
+
let logger = null;
|
|
66
|
+
let isDev = false;
|
|
67
|
+
let modulesJson = '[]';
|
|
68
|
+
let diagnosticsJson = '[]';
|
|
69
|
+
let cachedModuleCode = null;
|
|
70
|
+
let server = null;
|
|
71
|
+
let session = null;
|
|
72
|
+
// Discovery diagnostics survive across HMR cycles (the discovery pass only
|
|
73
|
+
// runs at cold start). Tracked here because the session has no concept of
|
|
74
|
+
// "discovery" — it ingests files the plugin hands to it.
|
|
75
|
+
let discoveryDiagnostics = [];
|
|
76
|
+
// ── Resolver wiring ──────────────────────────────────────────────────────
|
|
77
|
+
// Dev resolver: server.pluginContainer.resolveId. Stable identity across
|
|
78
|
+
// the dev session — cache hits work across HMR cycles.
|
|
79
|
+
const viteDevResolver = {
|
|
80
|
+
resolve: async (specifier, fromFile) => {
|
|
81
|
+
try {
|
|
82
|
+
const r = await server.pluginContainer.resolveId(specifier, fromFile);
|
|
83
|
+
return r?.id ?? null;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
identity: VITE_DEV_IDENTITY,
|
|
90
|
+
};
|
|
91
|
+
// Build resolver: Rollup `this.resolve` is per-build; we re-bind in
|
|
92
|
+
// buildStart while keeping a stable identity so cache hits survive
|
|
93
|
+
// `vite build --watch` rebuilds.
|
|
94
|
+
let buildResolveFn = null;
|
|
95
|
+
const buildResolver = {
|
|
96
|
+
resolve: (specifier, fromFile) => buildResolveFn ? buildResolveFn(specifier, fromFile) : Promise.resolve(null),
|
|
97
|
+
identity: VITE_BUILD_IDENTITY,
|
|
98
|
+
};
|
|
99
|
+
// ── In-flight analysis tracking ─────────────────────────────────────────
|
|
100
|
+
// Initial analysis runs once in buildStart; HMR flushes run on debounce.
|
|
101
|
+
// `load()` awaits both so we never serve stale JSON to a freshly-importing
|
|
102
|
+
// module.
|
|
103
|
+
let initialPromise = null;
|
|
104
|
+
let hmrFlushPromise = null;
|
|
105
|
+
const waitInflight = async () => {
|
|
106
|
+
if (initialPromise)
|
|
107
|
+
await initialPromise;
|
|
108
|
+
if (hmrFlushPromise)
|
|
109
|
+
await hmrFlushPromise;
|
|
110
|
+
};
|
|
111
|
+
// Pending-changes Map: path → newContent (string) or unlink marker (null).
|
|
112
|
+
// Drained by `flushBatch`; survives across multiple flush cycles when new
|
|
113
|
+
// events arrive mid-flight.
|
|
114
|
+
const pendingChanges = new Map();
|
|
115
|
+
// Per-path event sequence. Watcher events (change/add/unlink) bump the
|
|
116
|
+
// counter and stamp the path with the new value; async handlers (the
|
|
117
|
+
// `change` handler awaits readFile before enqueueing) capture the seq at
|
|
118
|
+
// dispatch and drop their result if a newer event has stamped the path
|
|
119
|
+
// in between. Without this, an `unlink` arriving during a `change`'s
|
|
120
|
+
// in-flight readFile gets clobbered when the read resolves with stale
|
|
121
|
+
// content for a now-deleted file.
|
|
122
|
+
let eventSeqCounter = 0;
|
|
123
|
+
const eventSeqs = new Map();
|
|
124
|
+
const stampEvent = (path) => {
|
|
125
|
+
const seq = ++eventSeqCounter;
|
|
126
|
+
eventSeqs.set(path, seq);
|
|
127
|
+
return seq;
|
|
128
|
+
};
|
|
129
|
+
const isLatestEvent = (path, seq) => eventSeqs.get(path) === seq;
|
|
130
|
+
const buildModuleCode = () => {
|
|
131
|
+
if (cachedModuleCode !== null)
|
|
132
|
+
return cachedModuleCode;
|
|
133
|
+
const lines = [
|
|
134
|
+
`export const modules = ${modulesJson};`,
|
|
135
|
+
`export const diagnostics = ${diagnosticsJson};`,
|
|
136
|
+
`export default {modules, diagnostics};`,
|
|
137
|
+
];
|
|
138
|
+
if (isDev) {
|
|
139
|
+
lines.push(`if (import.meta.hot) { import.meta.hot.accept(); }`);
|
|
140
|
+
}
|
|
141
|
+
cachedModuleCode = lines.join('\n');
|
|
142
|
+
return cachedModuleCode;
|
|
143
|
+
};
|
|
144
|
+
const updateOutputFromQuery = (modules, queryDiagnostics) => {
|
|
145
|
+
// `JSON.stringify([], compactReplacer)` returns the JS `undefined` because
|
|
146
|
+
// the replacer strips empty arrays — we embed the JSON into a template
|
|
147
|
+
// literal where that would interpolate as the literal text `"undefined"`,
|
|
148
|
+
// so handle the empty-modules case explicitly.
|
|
149
|
+
const newModulesJson = modules.length === 0 ? '[]' : JSON.stringify(modules, compactReplacer);
|
|
150
|
+
// Pull cumulative ingest diagnostics from the session — every owned
|
|
151
|
+
// entry's `ingestDiagnostics` are walked there. Discovery diagnostics
|
|
152
|
+
// don't pass through the session (file discovery happens before ingest),
|
|
153
|
+
// so we maintain those separately and merge at publish time.
|
|
154
|
+
const ingest = session ? session.allIngestDiagnostics() : [];
|
|
155
|
+
const merged = [...ingest, ...discoveryDiagnostics, ...queryDiagnostics];
|
|
156
|
+
const newDiagnosticsJson = JSON.stringify(merged);
|
|
157
|
+
if (newModulesJson === modulesJson && newDiagnosticsJson === diagnosticsJson)
|
|
158
|
+
return false;
|
|
159
|
+
modulesJson = newModulesJson;
|
|
160
|
+
diagnosticsJson = newDiagnosticsJson;
|
|
161
|
+
cachedModuleCode = null;
|
|
162
|
+
return true;
|
|
163
|
+
};
|
|
164
|
+
const sendHmrInvalidation = () => {
|
|
165
|
+
if (!server)
|
|
166
|
+
return;
|
|
167
|
+
const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_ID);
|
|
168
|
+
if (!mod)
|
|
169
|
+
return;
|
|
170
|
+
server.moduleGraph.invalidateModule(mod);
|
|
171
|
+
// Hardcoded Vite URL encoding for \0 prefix on virtual modules.
|
|
172
|
+
// Not a documented Vite public API — if Vite changes the encoding in a
|
|
173
|
+
// future release, HMR silently stops firing (initial load still works
|
|
174
|
+
// through the public resolveId/load hooks). Same pattern as
|
|
175
|
+
// vite_plugin_fuz_css; update both if Vite exposes a proper API.
|
|
176
|
+
const hmrPath = '/@id/__x00__virtual:svelte-docinfo';
|
|
177
|
+
server.hot.send({
|
|
178
|
+
type: 'update',
|
|
179
|
+
updates: [
|
|
180
|
+
{
|
|
181
|
+
type: 'js-update',
|
|
182
|
+
path: hmrPath,
|
|
183
|
+
acceptedPath: hmrPath,
|
|
184
|
+
timestamp: Date.now(),
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
});
|
|
188
|
+
};
|
|
189
|
+
const errorLog = (err) => {
|
|
190
|
+
const log = logger ?? { error: (msg) => console.error(msg) };
|
|
191
|
+
const message = err instanceof Error ? (err.stack ?? err.message) : String(err);
|
|
192
|
+
log.error(`[svelte-docinfo] ${message}`);
|
|
193
|
+
};
|
|
194
|
+
const isWatchedFile = (file) => isSource(file, resolvedSourceOptions);
|
|
195
|
+
// ── Initial analysis (cold start in buildStart) ──────────────────────────
|
|
196
|
+
const runInitialAnalysis = async () => {
|
|
197
|
+
if (!session)
|
|
198
|
+
return;
|
|
199
|
+
const { files, diagnostics } = await discoverSourceFiles({
|
|
200
|
+
sourceOptions: resolvedSourceOptions,
|
|
201
|
+
include,
|
|
202
|
+
discovery,
|
|
203
|
+
distDir,
|
|
204
|
+
log: logger ?? undefined,
|
|
205
|
+
});
|
|
206
|
+
discoveryDiagnostics = diagnostics;
|
|
207
|
+
await session.setFiles(files);
|
|
208
|
+
const result = session.query({ onDuplicates, log: logger ?? undefined });
|
|
209
|
+
updateOutputFromQuery(result.modules, result.diagnostics);
|
|
210
|
+
};
|
|
211
|
+
// ── HMR flush: drain pendingChanges via setFiles + query ─────────────────
|
|
212
|
+
const flushBatch = async () => {
|
|
213
|
+
if (initialPromise)
|
|
214
|
+
await initialPromise;
|
|
215
|
+
if (!session)
|
|
216
|
+
return;
|
|
217
|
+
if (pendingChanges.size === 0)
|
|
218
|
+
return;
|
|
219
|
+
// Drain. New events arriving during this flush land in pendingChanges
|
|
220
|
+
// and are picked up by the surrounding while-loop in `scheduleFlush`.
|
|
221
|
+
const drained = [...pendingChanges];
|
|
222
|
+
pendingChanges.clear();
|
|
223
|
+
const toAdd = [];
|
|
224
|
+
const toDelete = [];
|
|
225
|
+
for (const [path, content] of drained) {
|
|
226
|
+
if (content === null)
|
|
227
|
+
toDelete.push(path);
|
|
228
|
+
else
|
|
229
|
+
toAdd.push({ id: path, content });
|
|
230
|
+
}
|
|
231
|
+
// Apply deletions first so re-adds (rare but possible: unlink+add same
|
|
232
|
+
// path within one debounce window) work correctly.
|
|
233
|
+
for (const path of toDelete)
|
|
234
|
+
await session.deleteFile(path);
|
|
235
|
+
let anyChanged = toDelete.length > 0;
|
|
236
|
+
if (toAdd.length > 0) {
|
|
237
|
+
const ingest = await session.setFiles(toAdd);
|
|
238
|
+
if (ingest.changedIds.size > 0)
|
|
239
|
+
anyChanged = true;
|
|
240
|
+
}
|
|
241
|
+
if (!anyChanged)
|
|
242
|
+
return;
|
|
243
|
+
// `updateOutputFromQuery` pulls the cumulative ingest diagnostics from
|
|
244
|
+
// `session.allIngestDiagnostics()` itself — no per-batch survival
|
|
245
|
+
// tracking needed at the plugin layer.
|
|
246
|
+
const result = session.query({ onDuplicates, log: logger ?? undefined });
|
|
247
|
+
const updated = updateOutputFromQuery(result.modules, result.diagnostics);
|
|
248
|
+
if (updated)
|
|
249
|
+
sendHmrInvalidation();
|
|
250
|
+
};
|
|
251
|
+
const scheduleFlush = () => {
|
|
252
|
+
if (hmrFlushPromise)
|
|
253
|
+
return hmrFlushPromise;
|
|
254
|
+
hmrFlushPromise = (async () => {
|
|
255
|
+
await new Promise((r) => setTimeout(r, hmrDebounceMs));
|
|
256
|
+
try {
|
|
257
|
+
while (pendingChanges.size > 0) {
|
|
258
|
+
await flushBatch();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch (err) {
|
|
262
|
+
errorLog(err);
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
hmrFlushPromise = null;
|
|
266
|
+
}
|
|
267
|
+
})();
|
|
268
|
+
return hmrFlushPromise;
|
|
269
|
+
};
|
|
270
|
+
const enqueueChange = (path, content) => {
|
|
271
|
+
pendingChanges.set(path, content);
|
|
272
|
+
// `eventSeqs[path]` is no longer needed once the latest event has been
|
|
273
|
+
// enqueued: any older in-flight handler will fail `isLatestEvent`
|
|
274
|
+
// against `undefined`, and any newer event will re-stamp on arrival.
|
|
275
|
+
// Bounds the Map's growth in long-running dev sessions with churn.
|
|
276
|
+
eventSeqs.delete(path);
|
|
277
|
+
void scheduleFlush();
|
|
278
|
+
};
|
|
279
|
+
return {
|
|
280
|
+
name: 'vite-plugin-svelte-docinfo',
|
|
281
|
+
configResolved(config) {
|
|
282
|
+
projectRoot = options.projectRoot ?? config.root;
|
|
283
|
+
// `exclude` shortcut overrides `sourceOptions.exclude` (same precedence
|
|
284
|
+
// as `analyzeFromFiles`).
|
|
285
|
+
const mergedSourceOptions = exclude !== undefined ? { ...sourceOptions, exclude } : sourceOptions;
|
|
286
|
+
resolvedSourceOptions = createSourceOptions(projectRoot, mergedSourceOptions);
|
|
287
|
+
// Reject contradictory discovery config upfront so failures are at
|
|
288
|
+
// config-load time rather than first analysis.
|
|
289
|
+
if (discovery === 'exports' && include) {
|
|
290
|
+
throw new Error("svelte-docinfo: discovery: 'exports' is incompatible with `include`. " +
|
|
291
|
+
"Use discovery: 'glob' (with include) or remove include for strict exports mode.");
|
|
292
|
+
}
|
|
293
|
+
logger = config.logger;
|
|
294
|
+
isDev = config.command === 'serve';
|
|
295
|
+
},
|
|
296
|
+
async buildStart() {
|
|
297
|
+
// Choose resolver based on mode; create the session here (after
|
|
298
|
+
// configResolved set sourceOptions; for dev, after configureServer
|
|
299
|
+
// set `server`). Identity stable across rebuilds.
|
|
300
|
+
let sessionResolver;
|
|
301
|
+
if (!resolveDependencies) {
|
|
302
|
+
sessionResolver = noDepsResolver;
|
|
303
|
+
}
|
|
304
|
+
else if (server) {
|
|
305
|
+
sessionResolver = viteDevResolver;
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
const ctxResolve = this.resolve.bind(this);
|
|
309
|
+
buildResolveFn = async (specifier, fromFile) => {
|
|
310
|
+
try {
|
|
311
|
+
const r = await ctxResolve(specifier, fromFile);
|
|
312
|
+
return r?.id ?? null;
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
sessionResolver = buildResolver;
|
|
319
|
+
}
|
|
320
|
+
// Recreate session on each buildStart so `vite build --watch` cycles
|
|
321
|
+
// get a fresh LS — the document registry across builds isn't a
|
|
322
|
+
// guaranteed-stable contract from Vite's side.
|
|
323
|
+
session?.dispose();
|
|
324
|
+
session = createAnalysisSession({
|
|
325
|
+
sourceOptions: resolvedSourceOptions,
|
|
326
|
+
resolveImport: sessionResolver,
|
|
327
|
+
log: logger ?? undefined,
|
|
328
|
+
});
|
|
329
|
+
initialPromise = runInitialAnalysis().finally(() => {
|
|
330
|
+
initialPromise = null;
|
|
331
|
+
});
|
|
332
|
+
await initialPromise;
|
|
333
|
+
},
|
|
334
|
+
resolveId(id) {
|
|
335
|
+
if (id === VIRTUAL_ID)
|
|
336
|
+
return RESOLVED_VIRTUAL_ID;
|
|
337
|
+
return undefined;
|
|
338
|
+
},
|
|
339
|
+
async load(id) {
|
|
340
|
+
if (id !== RESOLVED_VIRTUAL_ID)
|
|
341
|
+
return undefined;
|
|
342
|
+
// Wait for any in-flight analysis (initial or HMR-triggered) so we
|
|
343
|
+
// never serve stale JSON to a freshly-importing module.
|
|
344
|
+
await waitInflight();
|
|
345
|
+
return buildModuleCode();
|
|
346
|
+
},
|
|
347
|
+
configureServer(devServer) {
|
|
348
|
+
server = devServer;
|
|
349
|
+
devServer.watcher.on('change', (rawFile) => {
|
|
350
|
+
// Watcher emits native-separator paths on Windows. Posixify at
|
|
351
|
+
// the boundary so eventSeqs/pendingChanges keys, isWatchedFile
|
|
352
|
+
// checks, and downstream session calls all share one canonical id.
|
|
353
|
+
const file = toPosixPath(rawFile);
|
|
354
|
+
if (!isWatchedFile(file))
|
|
355
|
+
return;
|
|
356
|
+
// Stamp the event before awaiting readFile so a later unlink/add
|
|
357
|
+
// for the same path bumps the seq and our resolution drops below.
|
|
358
|
+
const seq = stampEvent(file);
|
|
359
|
+
// `.catch(errorLog)` — a sync throw before the first `await` would
|
|
360
|
+
// otherwise become an unhandled rejection.
|
|
361
|
+
void (async () => {
|
|
362
|
+
// Re-read the file. Editors that touch mtime without changing
|
|
363
|
+
// content (formatters saving idempotently) trigger spurious
|
|
364
|
+
// events; the session's content-equality check in setFile
|
|
365
|
+
// handles that case — `changed: false` short-circuits the
|
|
366
|
+
// HMR invalidation.
|
|
367
|
+
let newContent;
|
|
368
|
+
try {
|
|
369
|
+
newContent = await readFile(file, 'utf-8');
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
// File became unreadable between event and read — treat as unlink.
|
|
373
|
+
if (isLatestEvent(file, seq))
|
|
374
|
+
enqueueChange(file, null);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
// A newer event (unlink/add/change) stamped this path while we
|
|
378
|
+
// were reading — discard our result so we don't overwrite it
|
|
379
|
+
// with stale content (e.g. ingesting a since-deleted file).
|
|
380
|
+
if (!isLatestEvent(file, seq))
|
|
381
|
+
return;
|
|
382
|
+
enqueueChange(file, newContent);
|
|
383
|
+
})().catch(errorLog);
|
|
384
|
+
});
|
|
385
|
+
devServer.watcher.on('add', (rawFile) => {
|
|
386
|
+
const file = toPosixPath(rawFile);
|
|
387
|
+
if (!isWatchedFile(file))
|
|
388
|
+
return;
|
|
389
|
+
const seq = stampEvent(file);
|
|
390
|
+
void (async () => {
|
|
391
|
+
try {
|
|
392
|
+
const content = await readFile(file, 'utf-8');
|
|
393
|
+
if (!isLatestEvent(file, seq))
|
|
394
|
+
return;
|
|
395
|
+
enqueueChange(file, content);
|
|
396
|
+
}
|
|
397
|
+
catch (err) {
|
|
398
|
+
errorLog(err);
|
|
399
|
+
}
|
|
400
|
+
})().catch(errorLog);
|
|
401
|
+
});
|
|
402
|
+
devServer.watcher.on('unlink', (rawFile) => {
|
|
403
|
+
const file = toPosixPath(rawFile);
|
|
404
|
+
// Use session.has() as the gate: paths outside source paths were
|
|
405
|
+
// never owned, so nothing to do.
|
|
406
|
+
if (!session?.has(file))
|
|
407
|
+
return;
|
|
408
|
+
stampEvent(file);
|
|
409
|
+
enqueueChange(file, null);
|
|
410
|
+
});
|
|
411
|
+
devServer.httpServer?.on('close', () => {
|
|
412
|
+
session?.dispose();
|
|
413
|
+
session = null;
|
|
414
|
+
});
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
};
|
|
418
|
+
export default svelteDocinfo;
|
package/package.json
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "svelte-docinfo",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "static analysis for TypeScript and Svelte",
|
|
5
|
+
"glyph": "📜",
|
|
6
|
+
"logo": "logo.svg",
|
|
7
|
+
"logo_alt": "a cyan scroll with curling ends",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"homepage": "https://svelte-docinfo.fuz.dev/",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/fuzdev/svelte-docinfo.git"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"svelte",
|
|
16
|
+
"typescript",
|
|
17
|
+
"documentation",
|
|
18
|
+
"static-analysis",
|
|
19
|
+
"api-docs",
|
|
20
|
+
"component",
|
|
21
|
+
"jsdoc",
|
|
22
|
+
"tsdoc",
|
|
23
|
+
"vite-plugin"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"start": "gro dev",
|
|
27
|
+
"dev": "gro dev",
|
|
28
|
+
"build": "gro build",
|
|
29
|
+
"check": "gro check",
|
|
30
|
+
"test": "gro test",
|
|
31
|
+
"setup-examples": "npm install --prefix examples/api && npm install --prefix examples/vite",
|
|
32
|
+
"preview": "vite preview",
|
|
33
|
+
"deploy": "gro deploy"
|
|
34
|
+
},
|
|
35
|
+
"type": "module",
|
|
36
|
+
"bin": {
|
|
37
|
+
"svelte-docinfo": "dist/main.js"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@jridgewell/trace-mapping": "^0.3.25",
|
|
41
|
+
"commander": "^14.0.3",
|
|
42
|
+
"es-module-lexer": "^2.0.0",
|
|
43
|
+
"picomatch": "^4.0.4",
|
|
44
|
+
"tinyglobby": "^0.2.15",
|
|
45
|
+
"typescript": "^5.9.3",
|
|
46
|
+
"zod": "^4.3.6"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"svelte": "^5.0.0",
|
|
50
|
+
"svelte2tsx": "^0.7.30"
|
|
51
|
+
},
|
|
52
|
+
"peerDependenciesMeta": {
|
|
53
|
+
"svelte": {
|
|
54
|
+
"optional": true
|
|
55
|
+
},
|
|
56
|
+
"svelte2tsx": {
|
|
57
|
+
"optional": true
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@fuzdev/fuz_code": "^0.45.1",
|
|
62
|
+
"@fuzdev/fuz_css": "^0.60.0",
|
|
63
|
+
"@fuzdev/fuz_ui": "^0.194.0",
|
|
64
|
+
"@fuzdev/fuz_util": "^0.61.1",
|
|
65
|
+
"@fuzdev/gro": "^0.199.1",
|
|
66
|
+
"@ryanatkn/eslint-config": "^0.12.1",
|
|
67
|
+
"@sveltejs/acorn-typescript": "^1.0.9",
|
|
68
|
+
"@sveltejs/adapter-static": "^3.0.10",
|
|
69
|
+
"@sveltejs/kit": "^2.61.1",
|
|
70
|
+
"@sveltejs/package": "^2.5.7",
|
|
71
|
+
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
72
|
+
"@types/estree": "^1.0.8",
|
|
73
|
+
"@types/node": "^24.10.4",
|
|
74
|
+
"@types/picomatch": "^4.0.2",
|
|
75
|
+
"@webref/css": "^8.2.0",
|
|
76
|
+
"eslint": "^9.39.1",
|
|
77
|
+
"eslint-plugin-svelte": "^3.13.1",
|
|
78
|
+
"esm-env": "^1.2.2",
|
|
79
|
+
"magic-string": "^0.30.21",
|
|
80
|
+
"prettier": "^3.7.4",
|
|
81
|
+
"prettier-plugin-svelte": "^3.4.1",
|
|
82
|
+
"svelte": "^5.55.10",
|
|
83
|
+
"svelte-check": "^4.3.5",
|
|
84
|
+
"svelte2tsx": "^0.7.52",
|
|
85
|
+
"tslib": "^2.8.1",
|
|
86
|
+
"typescript-eslint": "^8.48.1",
|
|
87
|
+
"vite": "^7.3.1",
|
|
88
|
+
"vitest": "^4.0.15",
|
|
89
|
+
"zimmerframe": "^1.1.4"
|
|
90
|
+
},
|
|
91
|
+
"prettier": {
|
|
92
|
+
"plugins": [
|
|
93
|
+
"prettier-plugin-svelte"
|
|
94
|
+
],
|
|
95
|
+
"useTabs": true,
|
|
96
|
+
"printWidth": 100,
|
|
97
|
+
"singleQuote": true,
|
|
98
|
+
"bracketSpacing": false,
|
|
99
|
+
"overrides": [
|
|
100
|
+
{
|
|
101
|
+
"files": "package.json",
|
|
102
|
+
"options": {
|
|
103
|
+
"useTabs": false
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
},
|
|
108
|
+
"sideEffects": [],
|
|
109
|
+
"files": [
|
|
110
|
+
"dist",
|
|
111
|
+
"virtual-svelte-docinfo.d.ts"
|
|
112
|
+
],
|
|
113
|
+
"exports": {
|
|
114
|
+
"./package.json": "./package.json",
|
|
115
|
+
".": {
|
|
116
|
+
"types": "./dist/index.d.ts",
|
|
117
|
+
"default": "./dist/index.js"
|
|
118
|
+
},
|
|
119
|
+
"./virtual-svelte-docinfo.js": {
|
|
120
|
+
"types": "./virtual-svelte-docinfo.d.ts"
|
|
121
|
+
},
|
|
122
|
+
"./*.js": {
|
|
123
|
+
"types": "./dist/*.d.ts",
|
|
124
|
+
"default": "./dist/*.js"
|
|
125
|
+
},
|
|
126
|
+
"./*.ts": {
|
|
127
|
+
"types": "./dist/*.d.ts",
|
|
128
|
+
"default": "./dist/*.js"
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declaration for the `virtual:svelte-docinfo` virtual module
|
|
3
|
+
* provided by the Vite plugin (`vite.ts`).
|
|
4
|
+
*
|
|
5
|
+
* Reference in your `app.d.ts`:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* /// <reference types="svelte-docinfo/virtual-svelte-docinfo.js" />
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* ## Wire shape vs Zod output shape
|
|
12
|
+
*
|
|
13
|
+
* The plugin serializes `modules` through `compactReplacer`, which strips
|
|
14
|
+
* empty arrays and `false` values to keep the bundle small. So fields like
|
|
15
|
+
* `declarations`, `members`, `props` that are `.default([])` in the schema
|
|
16
|
+
* arrive as `undefined` on any module that has none, and `.default(false)`
|
|
17
|
+
* booleans arrive as `undefined` when false.
|
|
18
|
+
*
|
|
19
|
+
* `ModuleJsonInput` (the `z.input` of `ModuleJson`) reflects this — its
|
|
20
|
+
* default-bearing fields are optional. Consumers either use optional
|
|
21
|
+
* chaining (`mod.declarations?.length`) or run the value through
|
|
22
|
+
* `AnalyzeResultJson.parse({modules, diagnostics})` to restore defaults.
|
|
23
|
+
*
|
|
24
|
+
* `diagnostics` is serialized without the replacer, so the array is always
|
|
25
|
+
* present and `Diagnostic` itself has no defaults to strip — the static
|
|
26
|
+
* type matches the runtime shape exactly.
|
|
27
|
+
*
|
|
28
|
+
* ## Why this file lives at the package root, not `src/lib/`
|
|
29
|
+
*
|
|
30
|
+
* `svelte.config.js` aliases `'svelte-docinfo' → 'src/lib'` (load-bearing
|
|
31
|
+
* for fuz_ui's `library_gen`, a circular dev-dep). `@sveltejs/package`
|
|
32
|
+
* applies that alias to every file under `src/lib/`, including hand-written
|
|
33
|
+
* `.d.ts`, rewriting the `'svelte-docinfo/...'` imports below to relative
|
|
34
|
+
* paths. TypeScript doesn't resolve relative imports inside ambient
|
|
35
|
+
* `declare module` blocks in consumer projects — types silently collapse
|
|
36
|
+
* to `any`.
|
|
37
|
+
*
|
|
38
|
+
* Living at the root keeps the file out of `svelte-package`'s reach;
|
|
39
|
+
* `gro.config.ts` and `package.json` `files` re-register it for npm.
|
|
40
|
+
* Do not "fix" this by moving the file into `src/lib/`.
|
|
41
|
+
*/
|
|
42
|
+
declare module 'virtual:svelte-docinfo' {
|
|
43
|
+
import type {ModuleJsonInput} from 'svelte-docinfo/types.js';
|
|
44
|
+
import type {Diagnostic} from 'svelte-docinfo/diagnostics.js';
|
|
45
|
+
export const modules: Array<ModuleJsonInput>;
|
|
46
|
+
export const diagnostics: Array<Diagnostic>;
|
|
47
|
+
const data: {modules: Array<ModuleJsonInput>; diagnostics: Array<Diagnostic>};
|
|
48
|
+
export default data;
|
|
49
|
+
}
|