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
|
@@ -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
|
+
};
|