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.
Files changed (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +349 -0
  3. package/dist/analyze-core.d.ts +1386 -0
  4. package/dist/analyze-core.d.ts.map +1 -0
  5. package/dist/analyze-core.js +324 -0
  6. package/dist/analyze.d.ts +137 -0
  7. package/dist/analyze.d.ts.map +1 -0
  8. package/dist/analyze.js +148 -0
  9. package/dist/cli.d.ts +102 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +206 -0
  12. package/dist/concurrency.d.ts +38 -0
  13. package/dist/concurrency.d.ts.map +1 -0
  14. package/dist/concurrency.js +57 -0
  15. package/dist/declaration-build.d.ts +164 -0
  16. package/dist/declaration-build.d.ts.map +1 -0
  17. package/dist/declaration-build.js +15 -0
  18. package/dist/declaration-helpers.d.ts +169 -0
  19. package/dist/declaration-helpers.d.ts.map +1 -0
  20. package/dist/declaration-helpers.js +239 -0
  21. package/dist/dep-resolver.d.ts +166 -0
  22. package/dist/dep-resolver.d.ts.map +1 -0
  23. package/dist/dep-resolver.js +144 -0
  24. package/dist/diagnostics.d.ts +650 -0
  25. package/dist/diagnostics.d.ts.map +1 -0
  26. package/dist/diagnostics.js +377 -0
  27. package/dist/discovery.d.ts +115 -0
  28. package/dist/discovery.d.ts.map +1 -0
  29. package/dist/discovery.js +126 -0
  30. package/dist/exports.d.ts +105 -0
  31. package/dist/exports.d.ts.map +1 -0
  32. package/dist/exports.js +274 -0
  33. package/dist/files.d.ts +77 -0
  34. package/dist/files.d.ts.map +1 -0
  35. package/dist/files.js +95 -0
  36. package/dist/index.d.ts +32 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +30 -0
  39. package/dist/log.d.ts +32 -0
  40. package/dist/log.d.ts.map +1 -0
  41. package/dist/log.js +11 -0
  42. package/dist/logo.d.ts +8 -0
  43. package/dist/logo.d.ts.map +1 -0
  44. package/dist/logo.js +9 -0
  45. package/dist/main.d.ts +11 -0
  46. package/dist/main.d.ts.map +1 -0
  47. package/dist/main.js +18 -0
  48. package/dist/paths.d.ts +35 -0
  49. package/dist/paths.d.ts.map +1 -0
  50. package/dist/paths.js +34 -0
  51. package/dist/postprocess.d.ts +145 -0
  52. package/dist/postprocess.d.ts.map +1 -0
  53. package/dist/postprocess.js +305 -0
  54. package/dist/session.d.ts +248 -0
  55. package/dist/session.d.ts.map +1 -0
  56. package/dist/session.js +496 -0
  57. package/dist/source-config.d.ts +330 -0
  58. package/dist/source-config.d.ts.map +1 -0
  59. package/dist/source-config.js +356 -0
  60. package/dist/source.d.ts +126 -0
  61. package/dist/source.d.ts.map +1 -0
  62. package/dist/source.js +68 -0
  63. package/dist/svelte.d.ts +179 -0
  64. package/dist/svelte.d.ts.map +1 -0
  65. package/dist/svelte.js +898 -0
  66. package/dist/tsdoc.d.ts +145 -0
  67. package/dist/tsdoc.d.ts.map +1 -0
  68. package/dist/tsdoc.js +262 -0
  69. package/dist/types.d.ts +3381 -0
  70. package/dist/types.d.ts.map +1 -0
  71. package/dist/types.js +588 -0
  72. package/dist/typescript-exports.d.ts +91 -0
  73. package/dist/typescript-exports.d.ts.map +1 -0
  74. package/dist/typescript-exports.js +656 -0
  75. package/dist/typescript-extract-class.d.ts +29 -0
  76. package/dist/typescript-extract-class.d.ts.map +1 -0
  77. package/dist/typescript-extract-class.js +251 -0
  78. package/dist/typescript-extract-function.d.ts +47 -0
  79. package/dist/typescript-extract-function.d.ts.map +1 -0
  80. package/dist/typescript-extract-function.js +108 -0
  81. package/dist/typescript-extract-shared.d.ts +166 -0
  82. package/dist/typescript-extract-shared.d.ts.map +1 -0
  83. package/dist/typescript-extract-shared.js +528 -0
  84. package/dist/typescript-extract-type-properties.d.ts +34 -0
  85. package/dist/typescript-extract-type-properties.d.ts.map +1 -0
  86. package/dist/typescript-extract-type-properties.js +241 -0
  87. package/dist/typescript-extract-type.d.ts +48 -0
  88. package/dist/typescript-extract-type.d.ts.map +1 -0
  89. package/dist/typescript-extract-type.js +238 -0
  90. package/dist/typescript-program.d.ts +211 -0
  91. package/dist/typescript-program.d.ts.map +1 -0
  92. package/dist/typescript-program.js +290 -0
  93. package/dist/vite.d.ts +116 -0
  94. package/dist/vite.d.ts.map +1 -0
  95. package/dist/vite.js +418 -0
  96. package/package.json +131 -0
  97. package/virtual-svelte-docinfo.d.ts +49 -0
@@ -0,0 +1,656 @@
1
+ /**
2
+ * Module-level export analysis: resolve every export of a TypeScript source
3
+ * file into a `DeclarationAnalysis`, including re-export classification.
4
+ *
5
+ * Builds on the per-declaration extractors in `typescript-extract-*.ts` by
6
+ * adding the orchestration layer — alias chain walking, namespace
7
+ * classification, JSDoc routing for re-exports, default-slot handling.
8
+ *
9
+ * @see `typescript-program.ts` for `IsExternalFile` and program construction
10
+ * @see `typescript-extract-*.ts` for the per-declaration extractors
11
+ *
12
+ * @module
13
+ */
14
+ import ts from 'typescript';
15
+ import { parseComment, applyToDeclaration, cleanComment, hasModuleTag } from './tsdoc.js';
16
+ import { stripVirtualSuffix, SVELTE_VIRTUAL_SUFFIX, getComponentName, } from './source.js';
17
+ import { extractDependencies, extractPath, isSource, } from './source-config.js';
18
+ import { createIsExternalFile } from './typescript-program.js';
19
+ import { inferDeclarationKind } from './typescript-extract-shared.js';
20
+ import { extractFunctionInfo, extractVariableInfo } from './typescript-extract-function.js';
21
+ import { extractTypeInfo, extractEnumInfo } from './typescript-extract-type.js';
22
+ import { extractClassInfo } from './typescript-extract-class.js';
23
+ /**
24
+ * Analyze a TypeScript file and extract module metadata.
25
+ *
26
+ * Wraps `analyzeExports` and adds dependency information via `extractDependencies`
27
+ * from the source file info if available.
28
+ *
29
+ * This is a high-level function suitable for building documentation or library metadata.
30
+ * For lower-level analysis, use `analyzeExports` directly.
31
+ *
32
+ * @param sourceFileInfo - the source file info (from file system, build pipeline, or other source)
33
+ * @param tsSourceFile - TypeScript source file from the program
34
+ * @param modulePath - the module path (relative to source root)
35
+ * @param checker - TypeScript type checker
36
+ * @param options - module source options for path extraction
37
+ * @param diagnostics - diagnostics collector for non-fatal issues
38
+ * @returns module metadata and re-export information
39
+ */
40
+ export const analyzeTypescriptModule = (sourceFileInfo, tsSourceFile, modulePath, checker, options, diagnostics) => {
41
+ // Use the mid-level helper for core analysis
42
+ const { moduleComment, declarations, reExports, starExports } = analyzeExports(tsSourceFile, checker, options, diagnostics);
43
+ // Extract dependencies and dependents if provided
44
+ const { dependencies, dependents } = extractDependencies(sourceFileInfo, options);
45
+ return {
46
+ path: modulePath,
47
+ moduleComment,
48
+ declarations,
49
+ dependencies,
50
+ dependents,
51
+ starExports,
52
+ reExports,
53
+ };
54
+ };
55
+ /**
56
+ * Walk the immediate-alias chain while names match, returning the deepest
57
+ * symbol whose name still equals `exportSymbol.name`. Used by both the
58
+ * namespace classifier and the standard alias path in `analyzeExports` to
59
+ * locate the canonical-for-this-name (which may be an upstream alias
60
+ * declaration, not the deeply-resolved root — relevant when a chain renames
61
+ * partway through).
62
+ *
63
+ * `getImmediateAliasedSymbol` asserts on non-alias symbols, so the walk also
64
+ * stops when the chain reaches a real declaration.
65
+ */
66
+ const walkSameNameCanonical = (exportSymbol, immediateAlias, checker) => {
67
+ let canonical = exportSymbol;
68
+ let next = immediateAlias;
69
+ while (next && next.name === exportSymbol.name) {
70
+ canonical = next;
71
+ next =
72
+ (next.flags & ts.SymbolFlags.Alias) !== 0
73
+ ? checker.getImmediateAliasedSymbol(next)
74
+ : undefined;
75
+ }
76
+ return canonical;
77
+ };
78
+ /**
79
+ * Classify a namespace re-export, robust to arbitrary alias-chain depth.
80
+ *
81
+ * Detection uses the `ValueModule` flag on the deeply-resolved alias —
82
+ * `getImmediateAliasedSymbol` is fragile because intermediate hops are
83
+ * `ExportSpecifier` nodes, not `NamespaceExport`, so a chain like
84
+ * `c.ts: export {ns as foo} from './b'` → `b.ts: export {ns} from './a'` →
85
+ * `a.ts: export * as ns from './x'` defeats immediate-alias detection.
86
+ *
87
+ * Returns `null` for non-namespace re-exports (regular declarations and
88
+ * external-module re-exports), letting the caller fall through to the
89
+ * standard alias-handling path.
90
+ */
91
+ const classifyNamespaceReExport = (exportSymbol, checker, currentFileName, options) => {
92
+ const deeplyAliased = checker.getAliasedSymbol(exportSymbol);
93
+ if ((deeplyAliased.flags & ts.SymbolFlags.ValueModule) === 0)
94
+ return null;
95
+ // Source module = where the deeply-resolved module symbol lives.
96
+ const sourceModuleDecl = deeplyAliased.valueDeclaration ?? deeplyAliased.declarations?.[0];
97
+ if (!sourceModuleDecl)
98
+ return null;
99
+ const sourceModuleFile = stripVirtualSuffix(sourceModuleDecl.getSourceFile().fileName);
100
+ if (!isSource(sourceModuleFile, options))
101
+ return null;
102
+ // Origination + star-projection: export's first declaration is itself a
103
+ // NamespaceExport. TypeScript shares the same NamespaceExport node across
104
+ // star-projecting modules, so the defining file may not equal currentFile.
105
+ const exportDecl = exportSymbol.declarations?.[0];
106
+ if (exportDecl && ts.isNamespaceExport(exportDecl)) {
107
+ const definingFile = stripVirtualSuffix(exportDecl.getSourceFile().fileName);
108
+ if (definingFile === currentFileName) {
109
+ return { kind: 'origination', sourceModule: extractPath(sourceModuleFile, options) };
110
+ }
111
+ if (isSource(definingFile, options)) {
112
+ return {
113
+ kind: 'same-name',
114
+ canonicalModule: extractPath(definingFile, options),
115
+ sourceModule: extractPath(sourceModuleFile, options),
116
+ };
117
+ }
118
+ return null;
119
+ }
120
+ // Re-export specifier (`export {ns ...} from`). Use immediate-alias name
121
+ // comparison for rename detection — this matches the existing non-namespace
122
+ // rename semantics and stays correct for chains.
123
+ const immediateAlias = checker.getImmediateAliasedSymbol(exportSymbol);
124
+ if (!immediateAlias)
125
+ return null;
126
+ if (exportSymbol.name !== immediateAlias.name) {
127
+ // Renamed: walk forward until we hit the canonical NamespaceExport.
128
+ // That's the namespace-defining file (where the binding originates).
129
+ let cursor = immediateAlias;
130
+ let namespaceDefiningFile;
131
+ let canonicalName;
132
+ while (cursor) {
133
+ const decl = cursor.declarations?.[0];
134
+ if (decl && ts.isNamespaceExport(decl)) {
135
+ namespaceDefiningFile = stripVirtualSuffix(decl.getSourceFile().fileName);
136
+ canonicalName = cursor.name;
137
+ break;
138
+ }
139
+ cursor =
140
+ (cursor.flags & ts.SymbolFlags.Alias) !== 0
141
+ ? checker.getImmediateAliasedSymbol(cursor)
142
+ : undefined;
143
+ }
144
+ if (!namespaceDefiningFile || !canonicalName)
145
+ return null;
146
+ if (!isSource(namespaceDefiningFile, options))
147
+ return null;
148
+ return {
149
+ kind: 'renamed',
150
+ namespaceDefiningFile: extractPath(namespaceDefiningFile, options),
151
+ sourceModule: extractPath(sourceModuleFile, options),
152
+ canonicalName,
153
+ };
154
+ }
155
+ // Same-name: the canonical-for-this-name may be an upstream renamed alias
156
+ // declaration, not the original NamespaceExport.
157
+ const canonical = walkSameNameCanonical(exportSymbol, immediateAlias, checker);
158
+ const canonicalDecl = canonical.declarations?.[0];
159
+ if (!canonicalDecl)
160
+ return null;
161
+ const canonicalFile = stripVirtualSuffix(canonicalDecl.getSourceFile().fileName);
162
+ if (canonicalFile === currentFileName)
163
+ return null;
164
+ if (!isSource(canonicalFile, options))
165
+ return null;
166
+ return {
167
+ kind: 'same-name',
168
+ canonicalModule: extractPath(canonicalFile, options),
169
+ sourceModule: extractPath(sourceModuleFile, options),
170
+ };
171
+ };
172
+ /**
173
+ * Analyze all exports from a TypeScript source file.
174
+ *
175
+ * Extracts the module-level comment via `extractModuleComment`, star exports via
176
+ * `extractStarExports`, and all exported declarations with complete metadata.
177
+ * Handles re-exports by:
178
+ * - Same-name re-exports: tracked in `reExports` for `alsoExportedFrom` building
179
+ * - Renamed re-exports: included as new declarations with `aliasOf` metadata
180
+ * - Star exports (`export * from`): tracked in `starExports` for namespace-level info
181
+ *
182
+ * This is a mid-level function (above the individual `extract*` helpers, below `analyze`)
183
+ * suitable for building documentation, API explorers, or analysis tools.
184
+ * For standard SvelteKit library layouts, use `createSourceOptions(process.cwd())`.
185
+ *
186
+ * @param sourceFile - the TypeScript source file to analyze
187
+ * @param checker - the TypeScript type checker
188
+ * @param options - module source options for path extraction in re-exports
189
+ * @param diagnostics - diagnostics collector for non-fatal issues
190
+ * @returns module comment, declarations, re-exports, and star exports
191
+ */
192
+ export const analyzeExports = (sourceFile, checker, options, diagnostics) => {
193
+ const declarations = [];
194
+ const reExports = [];
195
+ const isExternalFile = createIsExternalFile(options);
196
+ // Extract module-level comment
197
+ const moduleComment = extractModuleComment(sourceFile);
198
+ // Extract star exports (export * from './module')
199
+ const starExports = extractStarExports(sourceFile, checker, options);
200
+ // Normalize virtual paths once (e.g., Foo.svelte.__svelte2tsx__.ts → Foo.svelte)
201
+ // so re-export tracking matches real module paths
202
+ const currentFileName = stripVirtualSuffix(sourceFile.fileName);
203
+ // Get all exported symbols
204
+ const symbol = checker.getSymbolAtLocation(sourceFile);
205
+ if (symbol) {
206
+ const exports = checker.getExportsOfModule(symbol);
207
+ for (const exportSymbol of exports) {
208
+ // Check if this is an alias (potential re-export) using the Alias flag
209
+ const isAlias = (exportSymbol.flags & ts.SymbolFlags.Alias) !== 0;
210
+ if (isAlias) {
211
+ // Namespace re-exports (`export * as ns from './x'` and re-exports
212
+ // of such bindings) need special handling: their deeply-resolved
213
+ // canonical is a module symbol, and `analyzeDeclaration` would fall
214
+ // through to `kind: 'variable'` and produce a `typeof import("/abs/path")`
215
+ // signature that leaks the publisher's filesystem path. Detection
216
+ // uses the `ValueModule` flag on the deeply-resolved alias, which
217
+ // is robust to arbitrary re-export chain depth (the immediate-alias
218
+ // shape is fragile — intermediate hops are `ExportSpecifier` nodes,
219
+ // not `NamespaceExport`).
220
+ const nsClass = classifyNamespaceReExport(exportSymbol, checker, currentFileName, options);
221
+ if (nsClass) {
222
+ // Locate the local export statement, if it lives in the current
223
+ // source file. For origination/renamed/named-same-name, the
224
+ // statement IS local. For star-projection, `exportSymbol.declarations[0]`
225
+ // is the shared `NamespaceExport` node from the *defining* file —
226
+ // its parent is in the defining file too, so parsing JSDoc there
227
+ // would attribute another module's docs to the local re-export.
228
+ // Identity-check on the source file rules star-projection out.
229
+ const exportDeclNode = exportSymbol.declarations?.[0];
230
+ const candidateStatement = exportDeclNode && ts.isNamespaceExport(exportDeclNode)
231
+ ? exportDeclNode.parent
232
+ : exportDeclNode?.parent.parent;
233
+ const localExportStatement = candidateStatement &&
234
+ ts.isExportDeclaration(candidateStatement) &&
235
+ candidateStatement.getSourceFile() === sourceFile
236
+ ? candidateStatement
237
+ : undefined;
238
+ const localTsdoc = localExportStatement
239
+ ? parseComment(localExportStatement, sourceFile)
240
+ : undefined;
241
+ if (nsClass.kind === 'origination') {
242
+ const decl = {
243
+ name: exportSymbol.name,
244
+ kind: 'namespace',
245
+ module: nsClass.sourceModule,
246
+ };
247
+ if (localExportStatement) {
248
+ const start = localExportStatement.getStart(sourceFile);
249
+ decl.sourceLine = sourceFile.getLineAndCharacterOfPosition(start).line + 1;
250
+ }
251
+ if (localTsdoc) {
252
+ applyToDeclaration(decl, localTsdoc);
253
+ }
254
+ declarations.push({ declaration: decl, nodocs: !!localTsdoc?.nodocs });
255
+ }
256
+ else if (nsClass.kind === 'renamed') {
257
+ const decl = {
258
+ name: exportSymbol.name,
259
+ kind: 'namespace',
260
+ module: nsClass.sourceModule,
261
+ aliasOf: {
262
+ module: nsClass.namespaceDefiningFile,
263
+ name: nsClass.canonicalName,
264
+ },
265
+ };
266
+ decl.sourceLine = undefined;
267
+ if (localTsdoc) {
268
+ applyToDeclaration(decl, localTsdoc);
269
+ }
270
+ declarations.push({ declaration: decl, nodocs: !!localTsdoc?.nodocs });
271
+ }
272
+ else {
273
+ // Same-name re-export — link via alsoExportedFrom on the canonical.
274
+ // Position 3 (content-conditional synthesis): when the local
275
+ // statement carries JSDoc or `@nodocs`, also synthesize a
276
+ // `kind: 'namespace'` alias declaration so the local content
277
+ // has somewhere to live (mirrors non-namespace same-name semantics
278
+ // at the standard alias path). `@nodocs` suppresses both the
279
+ // alias and the link.
280
+ if (localTsdoc) {
281
+ const decl = {
282
+ name: exportSymbol.name,
283
+ kind: 'namespace',
284
+ module: nsClass.sourceModule,
285
+ aliasOf: {
286
+ module: nsClass.canonicalModule,
287
+ name: exportSymbol.name,
288
+ },
289
+ };
290
+ decl.sourceLine = undefined;
291
+ applyToDeclaration(decl, localTsdoc);
292
+ declarations.push({ declaration: decl, nodocs: !!localTsdoc.nodocs });
293
+ }
294
+ if (!localTsdoc?.nodocs) {
295
+ reExports.push({
296
+ name: exportSymbol.name,
297
+ originalModule: nsClass.canonicalModule,
298
+ });
299
+ }
300
+ }
301
+ continue;
302
+ }
303
+ // This might be a re-export - use getAliasedSymbol to find the original
304
+ const aliasedSymbol = checker.getAliasedSymbol(exportSymbol);
305
+ const aliasedDecl = aliasedSymbol.valueDeclaration || aliasedSymbol.declarations?.[0];
306
+ if (aliasedDecl) {
307
+ const originalSource = aliasedDecl.getSourceFile();
308
+ const originalFileName = stripVirtualSuffix(originalSource.fileName);
309
+ // Check if this is a CROSS-FILE re-export (original in different file)
310
+ if (originalFileName !== currentFileName) {
311
+ // Only track if the original is from a source module (not node_modules)
312
+ if (isSource(originalFileName, options)) {
313
+ const originalModule = extractPath(originalFileName, options);
314
+ // Use the IMMEDIATE alias (one hop) for rename detection so a
315
+ // same-name re-export of an intermediate alias is not mistaken
316
+ // for a rename relative to the deeply-resolved canonical (whose
317
+ // name may differ from this hop's name).
318
+ const immediateAlias = checker.getImmediateAliasedSymbol(exportSymbol);
319
+ const immediateName = immediateAlias?.name ?? aliasedSymbol.name;
320
+ const isRenamed = exportSymbol.name !== immediateName;
321
+ // Look up JSDoc on the local re-export statement once for both
322
+ // renamed and same-name branches. JSDoc on `/** Doc */ export {...}
323
+ // from './x'` lives on the ExportDeclaration, not on the canonical's
324
+ // declaration in the foreign file.
325
+ const exportDecl = exportSymbol.declarations?.[0];
326
+ const exportStatement = exportDecl?.parent.parent;
327
+ const localTsdoc = exportStatement && ts.isExportDeclaration(exportStatement)
328
+ ? parseComment(exportStatement, sourceFile)
329
+ : undefined;
330
+ if (isRenamed) {
331
+ let decl;
332
+ if (originalSource.fileName.endsWith(SVELTE_VIRTUAL_SUFFIX)) {
333
+ // Svelte component re-export (export {default as Foo} from './X.svelte').
334
+ // `analyzeDeclaration` on the svelte2tsx-generated `__SvelteComponent_`
335
+ // type would produce a `kind: 'type'` declaration with the internal name
336
+ // leaking. Instead synthesize a `kind: 'component'` placeholder; phase 2
337
+ // fixup in `mergeReExports` copies props/acceptsChildren/lang/etc. from
338
+ // the canonical component declaration in `analyzeSvelteModule`'s output
339
+ // (fill-gaps-only — local JSDoc applied below sticks).
340
+ const componentName = getComponentName(originalModule);
341
+ decl = {
342
+ name: exportSymbol.name,
343
+ kind: 'component',
344
+ aliasOf: {
345
+ module: originalModule,
346
+ name: componentName,
347
+ },
348
+ };
349
+ }
350
+ else {
351
+ // Renamed re-export (export {foo as bar}) — analyze the canonical
352
+ // declaration in its own source file so the alias inherits
353
+ // typeSignature, reactivity, docComment, parameters, etc.
354
+ const { declaration: analyzed } = analyzeDeclaration(aliasedSymbol, originalSource, checker, diagnostics, isExternalFile);
355
+ // `analyzed.name` is the canonical's symbol name — `'default'`
356
+ // when the canonical was reached via `export default ...`. Both
357
+ // branches (named canonical, default canonical) flow through
358
+ // uniformly: `aliasOf.name` is whatever the canonical's name is.
359
+ // Renames *into* the default slot (`export {foo as default}`)
360
+ // likewise just take `exportSymbol.name === 'default'`.
361
+ const canonicalName = analyzed.name;
362
+ analyzed.name = exportSymbol.name;
363
+ analyzed.aliasOf = {
364
+ module: originalModule,
365
+ name: canonicalName,
366
+ };
367
+ // The alias declaration is synthesized — it doesn't live at any
368
+ // line in the re-exporting file, so clear sourceLine (which would
369
+ // otherwise refer to the canonical's location).
370
+ analyzed.sourceLine = undefined;
371
+ decl = analyzed;
372
+ }
373
+ // Local JSDoc on the export statement overrides the canonical's
374
+ // (mirrors within-file branch semantics). `applyToDeclaration` only
375
+ // overwrites fields the local tsdoc actually populates, so canonical
376
+ // fields without a local override are preserved.
377
+ if (localTsdoc) {
378
+ applyToDeclaration(decl, localTsdoc);
379
+ }
380
+ declarations.push({ declaration: decl, nodocs: !!localTsdoc?.nodocs });
381
+ }
382
+ else {
383
+ // Same-name re-export — track for alsoExportedFrom on the
384
+ // canonical-for-this-name. The walk lands on the deepest
385
+ // same-named symbol so a same-name re-export of an intermediate
386
+ // alias points at that alias's module, not the deeply-resolved
387
+ // canonical (whose declaration uses the pre-rename name and
388
+ // wouldn't match in `mergeReExports`).
389
+ const canonical = walkSameNameCanonical(exportSymbol, immediateAlias, checker);
390
+ const canonicalDecl = canonical.valueDeclaration ?? canonical.declarations?.[0];
391
+ const canonicalSource = canonicalDecl?.getSourceFile();
392
+ const canonicalFile = canonicalSource
393
+ ? stripVirtualSuffix(canonicalSource.fileName)
394
+ : originalFileName;
395
+ // `export {default} from './x'` is a same-name re-export of the
396
+ // default slot. For Svelte components the canonical declaration
397
+ // in `analyzeSvelteModule` is named after the file, not `'default'`
398
+ // — so re-key the link by component name to match. For non-Svelte
399
+ // defaults the canonical's name is `'default'` (the actual symbol
400
+ // name); pass it through. Other same-name re-exports
401
+ // (`export {foo}`) match the canonical by name as before.
402
+ const isSvelteCanonical = canonicalSource?.fileName.endsWith(SVELTE_VIRTUAL_SUFFIX) ?? false;
403
+ const reExportName = exportSymbol.name === 'default' && isSvelteCanonical
404
+ ? getComponentName(canonicalFile)
405
+ : exportSymbol.name;
406
+ // Position 3 (content-conditional synthesis): if the local export
407
+ // statement carries JSDoc or @nodocs, synthesize an alias declaration
408
+ // in the re-exporting module so the local content has a place to live.
409
+ // Without local content, fall through to the alsoExportedFrom link only.
410
+ if (localTsdoc) {
411
+ let decl;
412
+ if (originalSource.fileName.endsWith(SVELTE_VIRTUAL_SUFFIX)) {
413
+ const componentName = getComponentName(originalModule);
414
+ decl = {
415
+ name: reExportName,
416
+ kind: 'component',
417
+ aliasOf: {
418
+ module: originalModule,
419
+ name: componentName,
420
+ },
421
+ };
422
+ }
423
+ else {
424
+ const { declaration: analyzed } = analyzeDeclaration(aliasedSymbol, originalSource, checker, diagnostics, isExternalFile);
425
+ // Same-name Position 3 alias: `aliasOf.name` is the canonical's
426
+ // own symbol name (`'default'` for default-slot canonicals,
427
+ // the original identifier otherwise).
428
+ const canonicalName = analyzed.name;
429
+ analyzed.name = reExportName;
430
+ analyzed.aliasOf = {
431
+ module: originalModule,
432
+ name: canonicalName,
433
+ };
434
+ analyzed.sourceLine = undefined;
435
+ decl = analyzed;
436
+ }
437
+ applyToDeclaration(decl, localTsdoc);
438
+ declarations.push({ declaration: decl, nodocs: !!localTsdoc.nodocs });
439
+ }
440
+ // `@nodocs` on a same-name re-export suppresses both the synthesized
441
+ // alias (filtered via nodocs flag) and the alsoExportedFrom link.
442
+ // Without `@nodocs`, the link is preserved so canonical declarations
443
+ // continue to surface every module that re-exports them.
444
+ if (!localTsdoc?.nodocs &&
445
+ canonicalFile !== currentFileName &&
446
+ isSource(canonicalFile, options)) {
447
+ reExports.push({
448
+ name: reExportName,
449
+ originalModule: extractPath(canonicalFile, options),
450
+ });
451
+ }
452
+ }
453
+ continue;
454
+ }
455
+ // Re-export from external module (node_modules) - skip entirely
456
+ continue;
457
+ }
458
+ // Within-file alias (export { x as y }) - fall through to normal analysis
459
+ }
460
+ }
461
+ // Normal export or within-file alias - declared in this file.
462
+ // For within-file aliases (export { x } or export { x as y }), resolve to
463
+ // the aliased symbol so that inferDeclarationKind sees the actual declaration
464
+ // node (e.g., VariableDeclaration with ArrowFunction) instead of the ExportSpecifier.
465
+ const symbolToAnalyze = isAlias ? checker.getAliasedSymbol(exportSymbol) : exportSymbol;
466
+ const analysisResult = analyzeDeclaration(symbolToAnalyze, sourceFile, checker, diagnostics, isExternalFile);
467
+ const { declaration } = analysisResult;
468
+ let { nodocs } = analysisResult;
469
+ // Preserve the export name for within-file renames (export { x as y }).
470
+ // Renaming TO `default` (`export {x as default}`) lands in the default
471
+ // slot — `exportSymbol.name === 'default'` and the assignment carries
472
+ // it through. The default slot is just another name in the export
473
+ // object; no special-casing needed.
474
+ if (isAlias && declaration.name !== exportSymbol.name) {
475
+ declaration.name = exportSymbol.name;
476
+ }
477
+ // For within-file aliases, check the export statement for JSDoc.
478
+ // The aliased symbol's declaration (e.g., svelte2tsx-generated const) may lack JSDoc,
479
+ // but the export statement (e.g., /** Doc */ export { greet }) may have it.
480
+ if (isAlias) {
481
+ const exportDecl = exportSymbol.declarations?.[0];
482
+ const exportStatement = exportDecl?.parent.parent; // ExportSpecifier → NamedExports → ExportDeclaration
483
+ if (exportStatement && ts.isExportDeclaration(exportStatement)) {
484
+ const exportTsdoc = parseComment(exportStatement, sourceFile);
485
+ if (exportTsdoc) {
486
+ applyToDeclaration(declaration, exportTsdoc);
487
+ if (exportTsdoc.nodocs) {
488
+ nodocs = true;
489
+ }
490
+ }
491
+ }
492
+ }
493
+ // Include all declarations with nodocs flag - consumer decides filtering policy
494
+ declarations.push({ declaration, nodocs });
495
+ }
496
+ }
497
+ return {
498
+ moduleComment,
499
+ declarations,
500
+ reExports,
501
+ starExports,
502
+ };
503
+ };
504
+ /**
505
+ * Analyze a TypeScript symbol and extract rich metadata.
506
+ *
507
+ * This is a high-level function that combines TSDoc parsing with TypeScript
508
+ * type analysis to produce complete declaration metadata. Suitable for use
509
+ * in documentation generators, IDE integrations, and other tooling.
510
+ *
511
+ * @param symbol - the TypeScript symbol to analyze
512
+ * @param sourceFile - the source file containing the symbol
513
+ * @param checker - the TypeScript type checker
514
+ * @param diagnostics - diagnostics collector for non-fatal issues
515
+ * @param isExternalFile - predicate for determining whether a source file is external to the project
516
+ * @returns complete declaration metadata including docs, types, and parameters, plus nodocs flag
517
+ */
518
+ export const analyzeDeclaration = (symbol, sourceFile, checker, diagnostics, isExternalFile) => {
519
+ const declNode = symbol.valueDeclaration || symbol.declarations?.[0];
520
+ // Pass the symbol's name through verbatim. Default-slot symbols
521
+ // (`export default ...`, `export {x as default}`) carry `symbol.name === 'default'`
522
+ // — that's the actual export-object key in JS (`ns.default`,
523
+ // `import {default as X}`), not a sentinel for "no name." Consumers that need
524
+ // to render `import X from 'mod'` form branch on `name === 'default'`.
525
+ const name = symbol.name;
526
+ // Determine kind (fallback to 'variable' if no declaration node)
527
+ const kind = declNode ? inferDeclarationKind(symbol, declNode) : 'variable';
528
+ const result = {
529
+ name,
530
+ kind,
531
+ };
532
+ if (!declNode) {
533
+ return { declaration: result, nodocs: false };
534
+ }
535
+ // Extract TSDoc — skip @module comments (handled by extractModuleComment)
536
+ const tsdoc = parseComment(declNode, sourceFile);
537
+ const nodocs = tsdoc?.nodocs ?? false;
538
+ if (!hasModuleTag(declNode)) {
539
+ applyToDeclaration(result, tsdoc);
540
+ }
541
+ // Extract source line
542
+ const start = declNode.getStart(sourceFile);
543
+ const startPos = sourceFile.getLineAndCharacterOfPosition(start);
544
+ result.sourceLine = startPos.line + 1;
545
+ // Extract type-specific info
546
+ if (result.kind === 'function') {
547
+ extractFunctionInfo(declNode, symbol, checker, result, tsdoc, diagnostics);
548
+ }
549
+ else if (result.kind === 'type' || result.kind === 'interface') {
550
+ extractTypeInfo(declNode, checker, result, diagnostics, isExternalFile);
551
+ }
552
+ else if (result.kind === 'enum') {
553
+ extractEnumInfo(declNode, checker, result, diagnostics);
554
+ }
555
+ else if (result.kind === 'class') {
556
+ extractClassInfo(declNode, checker, result, diagnostics);
557
+ }
558
+ else if (result.kind === 'variable') {
559
+ extractVariableInfo(declNode, symbol, checker, result, diagnostics);
560
+ }
561
+ return { declaration: result, nodocs };
562
+ };
563
+ /**
564
+ * Extract module-level comment.
565
+ *
566
+ * @internal Used by `analyzeTypescriptModule` and `analyzeSvelteModule`'s
567
+ * `<script module>` handling. Exposed via the `svelte-docinfo/typescript-exports.js`
568
+ * subpath so the Svelte analyzer can reuse it without circular imports, but
569
+ * **not part of the stable barrel export**.
570
+ *
571
+ * Requires `@module` tag to identify module comments. The tag line is stripped
572
+ * from the output. Supports optional module renaming: `@module custom-name`.
573
+ *
574
+ * @returns cleaned module comment text (with `@module` line removed), or `undefined` if no `@module` comment found
575
+ * @see {@link https://typedoc.org/documents/Tags._module.html|TypeDoc @module documentation}
576
+ */
577
+ export const extractModuleComment = (sourceFile) => {
578
+ const fullText = sourceFile.getFullText();
579
+ // Collect all JSDoc comments in the file
580
+ const allComments = [];
581
+ // Check for comments at the start of the file (before any statements)
582
+ const leadingComments = ts.getLeadingCommentRanges(fullText, 0);
583
+ if (leadingComments?.length) {
584
+ allComments.push(...leadingComments);
585
+ }
586
+ // Check for comments before each statement
587
+ for (const statement of sourceFile.statements) {
588
+ const comments = ts.getLeadingCommentRanges(fullText, statement.getFullStart());
589
+ if (comments?.length) {
590
+ allComments.push(...comments);
591
+ }
592
+ }
593
+ // Find the first comment with `@module` tag
594
+ for (const comment of allComments) {
595
+ const commentText = fullText.substring(comment.pos, comment.end);
596
+ if (!commentText.trimStart().startsWith('/**'))
597
+ continue;
598
+ // Clean the comment first, then check for tag at start of line
599
+ const cleaned = cleanComment(commentText);
600
+ if (!cleaned)
601
+ continue;
602
+ // Check for `@module` as a proper tag (at start of line, not mentioned in prose)
603
+ if (/(?:^|\n)@module\b/.test(cleaned)) {
604
+ const stripped = stripModuleTag(cleaned);
605
+ return stripped || undefined;
606
+ }
607
+ }
608
+ return undefined;
609
+ };
610
+ /**
611
+ * Strip `@module` tag line from comment text.
612
+ *
613
+ * Handles formats:
614
+ * - `@module` (standalone)
615
+ * - `@module module-name` (with rename)
616
+ */
617
+ const stripModuleTag = (text) => {
618
+ // Remove lines that START with `@module` (not mentioned in prose)
619
+ const lines = text.split('\n');
620
+ const filtered = lines.filter((line) => !/^\s*@module\b/.test(line));
621
+ return filtered.join('\n').trim();
622
+ };
623
+ /**
624
+ * Extract star exports (`export * from './module'`) from a source file.
625
+ *
626
+ * Uses the type checker to resolve module specifiers and filters to source modules only
627
+ * (excludes node_modules). Unresolvable modules (external packages) are silently skipped.
628
+ */
629
+ const extractStarExports = (sourceFile, checker, options) => {
630
+ const starExports = [];
631
+ for (const statement of sourceFile.statements) {
632
+ if (ts.isExportDeclaration(statement) &&
633
+ !statement.exportClause && // No exportClause means `export *`
634
+ statement.moduleSpecifier &&
635
+ ts.isStringLiteral(statement.moduleSpecifier)) {
636
+ // Use the type checker to resolve the module - it has already resolved all imports
637
+ // during program creation, so this leverages TypeScript's full module resolution
638
+ const moduleSymbol = checker.getSymbolAtLocation(statement.moduleSpecifier);
639
+ if (moduleSymbol) {
640
+ // Get the source file from the module symbol's declarations
641
+ const moduleDecl = moduleSymbol.valueDeclaration ?? moduleSymbol.declarations?.[0];
642
+ if (moduleDecl) {
643
+ const resolvedSource = moduleDecl.getSourceFile();
644
+ // Normalize virtual paths for Svelte files
645
+ const resolvedPath = stripVirtualSuffix(resolvedSource.fileName);
646
+ // Only include star exports from source modules (not node_modules)
647
+ if (isSource(resolvedPath, options)) {
648
+ starExports.push(extractPath(resolvedPath, options));
649
+ }
650
+ }
651
+ }
652
+ // If module couldn't be resolved (external package, etc.), skip it
653
+ }
654
+ }
655
+ return starExports;
656
+ };