svelte-docinfo 0.4.1 → 0.5.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/README.md +21 -12
- package/dist/analyze-core.d.ts +19 -12
- package/dist/analyze-core.d.ts.map +1 -1
- package/dist/analyze-core.js +40 -17
- package/dist/declaration-build.d.ts +16 -19
- package/dist/declaration-build.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/postprocess.d.ts +118 -28
- package/dist/postprocess.d.ts.map +1 -1
- package/dist/postprocess.js +279 -31
- package/dist/svelte.d.ts.map +1 -1
- package/dist/svelte.js +47 -3
- package/dist/types.d.ts +145 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +115 -1
- package/dist/typescript-exports.d.ts +3 -1
- package/dist/typescript-exports.d.ts.map +1 -1
- package/dist/typescript-exports.js +237 -158
- package/package.json +9 -9
|
@@ -39,7 +39,7 @@ import { extractClassInfo } from './typescript-extract-class.js';
|
|
|
39
39
|
*/
|
|
40
40
|
export const analyzeTypescriptModule = (sourceFileInfo, tsSourceFile, modulePath, checker, options, diagnostics) => {
|
|
41
41
|
// Use the mid-level helper for core analysis
|
|
42
|
-
const { moduleComment, declarations, reExports, starExports } = analyzeExports(tsSourceFile, checker, options, diagnostics);
|
|
42
|
+
const { moduleComment, declarations, reExports, starExports, externalReExports, externalStarExports, } = analyzeExports(tsSourceFile, checker, options, diagnostics);
|
|
43
43
|
// Extract dependencies and dependents if provided
|
|
44
44
|
const { dependencies, dependents } = extractDependencies(sourceFileInfo, options);
|
|
45
45
|
return {
|
|
@@ -50,6 +50,8 @@ export const analyzeTypescriptModule = (sourceFileInfo, tsSourceFile, modulePath
|
|
|
50
50
|
dependents,
|
|
51
51
|
starExports,
|
|
52
52
|
reExports,
|
|
53
|
+
externalReExports,
|
|
54
|
+
externalStarExports,
|
|
53
55
|
};
|
|
54
56
|
};
|
|
55
57
|
/**
|
|
@@ -93,29 +95,19 @@ const classifyNamespaceReExport = (exportSymbol, checker, currentFileName, optio
|
|
|
93
95
|
if ((deeplyAliased.flags & ts.SymbolFlags.ValueModule) === 0)
|
|
94
96
|
return null;
|
|
95
97
|
// Source module = where the deeply-resolved module symbol lives.
|
|
96
|
-
const
|
|
97
|
-
if (!
|
|
98
|
+
const sourceModuleFile = getPrimaryDeclarationFile(deeplyAliased);
|
|
99
|
+
if (!sourceModuleFile || !isSource(sourceModuleFile, options))
|
|
98
100
|
return null;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
//
|
|
103
|
-
// NamespaceExport. TypeScript shares the same NamespaceExport node across
|
|
104
|
-
// star-projecting modules, so the defining file may not equal currentFile.
|
|
101
|
+
// Origination: export's first declaration is itself a NamespaceExport in
|
|
102
|
+
// this file. The caller's locality skip filters star-projected bindings
|
|
103
|
+
// before classification, but merged symbols could put a foreign
|
|
104
|
+
// declaration first — bail rather than misclassify.
|
|
105
105
|
const exportDecl = exportSymbol.declarations?.[0];
|
|
106
106
|
if (exportDecl && ts.isNamespaceExport(exportDecl)) {
|
|
107
107
|
const definingFile = stripVirtualSuffix(exportDecl.getSourceFile().fileName);
|
|
108
|
-
if (definingFile
|
|
109
|
-
return
|
|
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;
|
|
108
|
+
if (definingFile !== currentFileName)
|
|
109
|
+
return null;
|
|
110
|
+
return { kind: 'origination', sourceModule: extractPath(sourceModuleFile, options) };
|
|
119
111
|
}
|
|
120
112
|
// Re-export specifier (`export {ns ...} from`). Use immediate-alias name
|
|
121
113
|
// comparison for rename detection — this matches the existing non-namespace
|
|
@@ -169,6 +161,108 @@ const classifyNamespaceReExport = (exportSymbol, checker, currentFileName, optio
|
|
|
169
161
|
sourceModule: extractPath(sourceModuleFile, options),
|
|
170
162
|
};
|
|
171
163
|
};
|
|
164
|
+
/**
|
|
165
|
+
* Whether any of the symbol's declarations lives in `fileName`
|
|
166
|
+
* (virtual-suffix-normalized).
|
|
167
|
+
*
|
|
168
|
+
* Merged symbols (module augmentation, declaration merging) can have
|
|
169
|
+
* declarations in several files — a symbol counts as declared in the file
|
|
170
|
+
* when at least one declaration is, so checking a single declaration node
|
|
171
|
+
* would drop locally-declared exports depending on bind order. Symbols
|
|
172
|
+
* without declarations are treated as declared in the file (permissive).
|
|
173
|
+
*/
|
|
174
|
+
const isDeclaredInFile = (symbol, fileName) => {
|
|
175
|
+
const decls = symbol.declarations;
|
|
176
|
+
if (!decls?.length)
|
|
177
|
+
return true;
|
|
178
|
+
return decls.some((d) => stripVirtualSuffix(d.getSourceFile().fileName) === fileName);
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* The source file of a symbol's primary declaration (`valueDeclaration`,
|
|
182
|
+
* else the first declaration), or `undefined` for declaration-less symbols.
|
|
183
|
+
*
|
|
184
|
+
* Resolves which file "owns" a symbol for canonical-module attribution.
|
|
185
|
+
* Distinct from `isDeclaredInFile`, which asks whether *any* declaration
|
|
186
|
+
* lives in a given file — the right question for ownership tests on
|
|
187
|
+
* potentially-merged symbols.
|
|
188
|
+
*/
|
|
189
|
+
const getPrimaryDeclarationSourceFile = (symbol) => (symbol.valueDeclaration ?? symbol.declarations?.[0])?.getSourceFile();
|
|
190
|
+
/**
|
|
191
|
+
* The virtual-suffix-normalized file name of a symbol's primary declaration,
|
|
192
|
+
* or `undefined` for declaration-less symbols. String form of
|
|
193
|
+
* `getPrimaryDeclarationSourceFile` for callers that only compare paths.
|
|
194
|
+
*/
|
|
195
|
+
const getPrimaryDeclarationFile = (symbol) => {
|
|
196
|
+
const source = getPrimaryDeclarationSourceFile(symbol);
|
|
197
|
+
return source && stripVirtualSuffix(source.fileName);
|
|
198
|
+
};
|
|
199
|
+
/**
|
|
200
|
+
* The local export statement and binding node for an alias export symbol —
|
|
201
|
+
* `{node, statement}` where `node` is the `ExportSpecifier` (or, for
|
|
202
|
+
* `export * as ns`, the `NamespaceExport`) and `statement` its
|
|
203
|
+
* `ExportDeclaration`.
|
|
204
|
+
*
|
|
205
|
+
* Returns `undefined` when the statement isn't in `sourceFile`: merged
|
|
206
|
+
* symbols can put a foreign declaration first, and parsing JSDoc or
|
|
207
|
+
* positions there would attribute another module's content here.
|
|
208
|
+
*/
|
|
209
|
+
const getLocalExportStatement = (exportSymbol, sourceFile) => {
|
|
210
|
+
const node = exportSymbol.declarations?.[0];
|
|
211
|
+
if (!node)
|
|
212
|
+
return undefined;
|
|
213
|
+
if (ts.isExportSpecifier(node)) {
|
|
214
|
+
const statement = node.parent.parent;
|
|
215
|
+
if (statement.getSourceFile() !== sourceFile)
|
|
216
|
+
return undefined;
|
|
217
|
+
return { node, statement };
|
|
218
|
+
}
|
|
219
|
+
if (ts.isNamespaceExport(node)) {
|
|
220
|
+
const statement = node.parent;
|
|
221
|
+
if (statement.getSourceFile() !== sourceFile)
|
|
222
|
+
return undefined;
|
|
223
|
+
return { node, statement };
|
|
224
|
+
}
|
|
225
|
+
return undefined;
|
|
226
|
+
};
|
|
227
|
+
/**
|
|
228
|
+
* Whether a local export statement/specifier pair is type-only — either
|
|
229
|
+
* statement-level (`export type {…} from`) or specifier-level
|
|
230
|
+
* (`export {type A} from`). Type-only names are erased at runtime.
|
|
231
|
+
*/
|
|
232
|
+
const isTypeOnlyLocalExport = (local) => local.statement.isTypeOnly || (ts.isExportSpecifier(local.node) && local.node.isTypeOnly);
|
|
233
|
+
/**
|
|
234
|
+
* Synthesize a cross-file alias declaration for a renamed or documented
|
|
235
|
+
* same-name re-export.
|
|
236
|
+
*
|
|
237
|
+
* Svelte canonicals get a `kind: 'component'` placeholder — running
|
|
238
|
+
* `analyzeDeclaration` on svelte2tsx's `__SvelteComponent_` type alias would
|
|
239
|
+
* leak internal names; phase-2 `resolveComponentAliases` copies
|
|
240
|
+
* props/acceptsChildren/lang/etc. from the canonical (fill-gaps-only, so
|
|
241
|
+
* local JSDoc applied by the caller sticks). Everything else is analyzed in
|
|
242
|
+
* its own source file so the alias inherits `typeSignature`, `reactivity`,
|
|
243
|
+
* `docComment`, `parameters`, etc.
|
|
244
|
+
*
|
|
245
|
+
* `aliasOf.name` is the canonical's own symbol name — `'default'` for
|
|
246
|
+
* default-slot canonicals (renames into and out of the slot flow through
|
|
247
|
+
* uniformly), the filename-derived component name for Svelte. `sourceLine`
|
|
248
|
+
* is the local export specifier's line, not the canonical's location.
|
|
249
|
+
*/
|
|
250
|
+
const synthesizeCrossFileAlias = (publicName, aliasedSymbol, originalSource, originalModule, specifierLine, checker, diagnostics, isExternalFile) => {
|
|
251
|
+
if (originalSource.fileName.endsWith(SVELTE_VIRTUAL_SUFFIX)) {
|
|
252
|
+
return {
|
|
253
|
+
name: publicName,
|
|
254
|
+
kind: 'component',
|
|
255
|
+
aliasOf: { module: originalModule, name: getComponentName(originalModule) },
|
|
256
|
+
sourceLine: specifierLine,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
const { declaration: analyzed } = analyzeDeclaration(aliasedSymbol, originalSource, checker, diagnostics, isExternalFile);
|
|
260
|
+
const canonicalName = analyzed.name;
|
|
261
|
+
analyzed.name = publicName;
|
|
262
|
+
analyzed.aliasOf = { module: originalModule, name: canonicalName };
|
|
263
|
+
analyzed.sourceLine = specifierLine;
|
|
264
|
+
return analyzed;
|
|
265
|
+
};
|
|
172
266
|
/**
|
|
173
267
|
* Analyze all exports from a TypeScript source file.
|
|
174
268
|
*
|
|
@@ -178,6 +272,8 @@ const classifyNamespaceReExport = (exportSymbol, checker, currentFileName, optio
|
|
|
178
272
|
* - Same-name re-exports: tracked in `reExports` for `alsoExportedFrom` building
|
|
179
273
|
* - Renamed re-exports: included as new declarations with `aliasOf` metadata
|
|
180
274
|
* - Star exports (`export * from`): tracked in `starExports` for namespace-level info
|
|
275
|
+
* - Direct external re-exports: tracked in `externalReExports`/`externalStarExports`
|
|
276
|
+
* (specifier as written; import-then-export and source-chained forms stay silent)
|
|
181
277
|
*
|
|
182
278
|
* This is a mid-level function (above the individual `extract*` helpers, below `analyze`)
|
|
183
279
|
* suitable for building documentation, API explorers, or analysis tools.
|
|
@@ -187,19 +283,23 @@ const classifyNamespaceReExport = (exportSymbol, checker, currentFileName, optio
|
|
|
187
283
|
* @param checker - the TypeScript type checker
|
|
188
284
|
* @param options - module source options for path extraction in re-exports
|
|
189
285
|
* @param diagnostics - diagnostics collector for non-fatal issues
|
|
190
|
-
* @returns module comment, declarations, re-exports, and star exports
|
|
286
|
+
* @returns module comment, declarations, re-exports (source + external), and star exports (source + external)
|
|
191
287
|
*/
|
|
192
288
|
export const analyzeExports = (sourceFile, checker, options, diagnostics) => {
|
|
193
289
|
const declarations = [];
|
|
194
290
|
const reExports = [];
|
|
291
|
+
const externalReExports = [];
|
|
195
292
|
const isExternalFile = createIsExternalFile(options);
|
|
196
293
|
// Extract module-level comment
|
|
197
294
|
const moduleComment = extractModuleComment(sourceFile);
|
|
198
|
-
// Extract star exports (export * from './module')
|
|
199
|
-
const starExports = extractStarExports(sourceFile, checker, options);
|
|
295
|
+
// Extract star exports (export * from './module' / 'pkg')
|
|
296
|
+
const { starExports, externalStarExports } = extractStarExports(sourceFile, checker, options);
|
|
200
297
|
// Normalize virtual paths once (e.g., Foo.svelte.__svelte2tsx__.ts → Foo.svelte)
|
|
201
298
|
// so re-export tracking matches real module paths
|
|
202
299
|
const currentFileName = stripVirtualSuffix(sourceFile.fileName);
|
|
300
|
+
// 1-based line of a node in this file. Virtual coordinates for Svelte
|
|
301
|
+
// `<script module>` sources — remapped in `analyzeSvelteModule`.
|
|
302
|
+
const lineOf = (node) => sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
203
303
|
// Get all exported symbols
|
|
204
304
|
const symbol = checker.getSymbolAtLocation(sourceFile);
|
|
205
305
|
if (symbol) {
|
|
@@ -208,6 +308,19 @@ export const analyzeExports = (sourceFile, checker, options, diagnostics) => {
|
|
|
208
308
|
// Check if this is an alias (potential re-export) using the Alias flag
|
|
209
309
|
const isAlias = (exportSymbol.flags & ts.SymbolFlags.Alias) !== 0;
|
|
210
310
|
if (isAlias) {
|
|
311
|
+
// Star-projected alias bindings: `export * from './b'` projects
|
|
312
|
+
// b.ts's own re-export bindings into this module's export table,
|
|
313
|
+
// sharing the foreign declaration node (an `ExportSpecifier` or,
|
|
314
|
+
// for `export * as ns`, a `NamespaceExport`). Same encoding rule
|
|
315
|
+
// as star-projected value symbols below — `starExports` is the
|
|
316
|
+
// sole encoding; processing the binding here would publish
|
|
317
|
+
// re-export edges for statements this module's source doesn't
|
|
318
|
+
// contain and read the foreign statement's JSDoc as if local
|
|
319
|
+
// (synthesizing duplicate declarations with mis-attributed docs).
|
|
320
|
+
// Runs before namespace classification so star-projected
|
|
321
|
+
// namespace bindings are silenced uniformly.
|
|
322
|
+
if (!isDeclaredInFile(exportSymbol, currentFileName))
|
|
323
|
+
continue;
|
|
211
324
|
// Namespace re-exports (`export * as ns from './x'` and re-exports
|
|
212
325
|
// of such bindings) need special handling: their deeply-resolved
|
|
213
326
|
// canonical is a module symbol, and `analyzeDeclaration` would fall
|
|
@@ -219,35 +332,21 @@ export const analyzeExports = (sourceFile, checker, options, diagnostics) => {
|
|
|
219
332
|
// not `NamespaceExport`).
|
|
220
333
|
const nsClass = classifyNamespaceReExport(exportSymbol, checker, currentFileName, options);
|
|
221
334
|
if (nsClass) {
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const
|
|
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;
|
|
335
|
+
// The locality skip above filters star-projected bindings before
|
|
336
|
+
// classification, so origination/renamed/same-name statements are
|
|
337
|
+
// local; `getLocalExportStatement`'s identity check stays as
|
|
338
|
+
// defense against merged symbols whose first declaration could be
|
|
339
|
+
// a foreign node.
|
|
340
|
+
const local = getLocalExportStatement(exportSymbol, sourceFile);
|
|
341
|
+
const localTsdoc = local ? parseComment(local.statement, sourceFile) : undefined;
|
|
342
|
+
const nsSpecifierLine = local ? lineOf(local.node) : undefined;
|
|
241
343
|
if (nsClass.kind === 'origination') {
|
|
242
344
|
const decl = {
|
|
243
345
|
name: exportSymbol.name,
|
|
244
346
|
kind: 'namespace',
|
|
245
347
|
module: nsClass.sourceModule,
|
|
348
|
+
sourceLine: nsSpecifierLine,
|
|
246
349
|
};
|
|
247
|
-
if (localExportStatement) {
|
|
248
|
-
const start = localExportStatement.getStart(sourceFile);
|
|
249
|
-
decl.sourceLine = sourceFile.getLineAndCharacterOfPosition(start).line + 1;
|
|
250
|
-
}
|
|
251
350
|
if (localTsdoc) {
|
|
252
351
|
applyToDeclaration(decl, localTsdoc);
|
|
253
352
|
}
|
|
@@ -262,8 +361,10 @@ export const analyzeExports = (sourceFile, checker, options, diagnostics) => {
|
|
|
262
361
|
module: nsClass.namespaceDefiningFile,
|
|
263
362
|
name: nsClass.canonicalName,
|
|
264
363
|
},
|
|
364
|
+
// Synthesized alias — the local export specifier's line,
|
|
365
|
+
// not the canonical's location
|
|
366
|
+
sourceLine: nsSpecifierLine,
|
|
265
367
|
};
|
|
266
|
-
decl.sourceLine = undefined;
|
|
267
368
|
if (localTsdoc) {
|
|
268
369
|
applyToDeclaration(decl, localTsdoc);
|
|
269
370
|
}
|
|
@@ -286,15 +387,17 @@ export const analyzeExports = (sourceFile, checker, options, diagnostics) => {
|
|
|
286
387
|
module: nsClass.canonicalModule,
|
|
287
388
|
name: exportSymbol.name,
|
|
288
389
|
},
|
|
390
|
+
sourceLine: nsSpecifierLine,
|
|
289
391
|
};
|
|
290
|
-
decl.sourceLine = undefined;
|
|
291
392
|
applyToDeclaration(decl, localTsdoc);
|
|
292
393
|
declarations.push({ declaration: decl, nodocs: !!localTsdoc.nodocs });
|
|
293
394
|
}
|
|
294
395
|
if (!localTsdoc?.nodocs) {
|
|
295
396
|
reExports.push({
|
|
296
397
|
name: exportSymbol.name,
|
|
297
|
-
|
|
398
|
+
module: nsClass.canonicalModule,
|
|
399
|
+
...(local && isTypeOnlyLocalExport(local) ? { typeOnly: true } : {}),
|
|
400
|
+
...(nsSpecifierLine !== undefined ? { sourceLine: nsSpecifierLine } : {}),
|
|
298
401
|
});
|
|
299
402
|
}
|
|
300
403
|
}
|
|
@@ -302,12 +405,18 @@ export const analyzeExports = (sourceFile, checker, options, diagnostics) => {
|
|
|
302
405
|
}
|
|
303
406
|
// This might be a re-export - use getAliasedSymbol to find the original
|
|
304
407
|
const aliasedSymbol = checker.getAliasedSymbol(exportSymbol);
|
|
305
|
-
const
|
|
306
|
-
if (
|
|
307
|
-
const originalSource = aliasedDecl.getSourceFile();
|
|
408
|
+
const originalSource = getPrimaryDeclarationSourceFile(aliasedSymbol);
|
|
409
|
+
if (originalSource) {
|
|
308
410
|
const originalFileName = stripVirtualSuffix(originalSource.fileName);
|
|
309
411
|
// Check if this is a CROSS-FILE re-export (original in different file)
|
|
310
412
|
if (originalFileName !== currentFileName) {
|
|
413
|
+
// The local export statement, shared by the source and external
|
|
414
|
+
// arms. JSDoc on `/** Doc */ export {...} from './x'` lives on
|
|
415
|
+
// the ExportDeclaration, not on the canonical's declaration in
|
|
416
|
+
// the foreign file.
|
|
417
|
+
const local = getLocalExportStatement(exportSymbol, sourceFile);
|
|
418
|
+
const specifierTypeOnly = local ? isTypeOnlyLocalExport(local) : false;
|
|
419
|
+
const specifierLine = local ? lineOf(local.node) : undefined;
|
|
311
420
|
// Only track if the original is from a source module (not node_modules)
|
|
312
421
|
if (isSource(originalFileName, options)) {
|
|
313
422
|
const originalModule = extractPath(originalFileName, options);
|
|
@@ -318,58 +427,11 @@ export const analyzeExports = (sourceFile, checker, options, diagnostics) => {
|
|
|
318
427
|
const immediateAlias = checker.getImmediateAliasedSymbol(exportSymbol);
|
|
319
428
|
const immediateName = immediateAlias?.name ?? aliasedSymbol.name;
|
|
320
429
|
const isRenamed = exportSymbol.name !== immediateName;
|
|
321
|
-
|
|
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;
|
|
430
|
+
const localTsdoc = local ? parseComment(local.statement, sourceFile) : undefined;
|
|
330
431
|
if (isRenamed) {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
}
|
|
432
|
+
// Renamed re-export (`export {foo as bar}`, `export {default as
|
|
433
|
+
// Foo} from './X.svelte'`) — synthesize the alias declaration.
|
|
434
|
+
const decl = synthesizeCrossFileAlias(exportSymbol.name, aliasedSymbol, originalSource, originalModule, specifierLine, checker, diagnostics, isExternalFile);
|
|
373
435
|
// Local JSDoc on the export statement overrides the canonical's
|
|
374
436
|
// (mirrors within-file branch semantics). `applyToDeclaration` only
|
|
375
437
|
// overwrites fields the local tsdoc actually populates, so canonical
|
|
@@ -387,8 +449,7 @@ export const analyzeExports = (sourceFile, checker, options, diagnostics) => {
|
|
|
387
449
|
// canonical (whose declaration uses the pre-rename name and
|
|
388
450
|
// wouldn't match in `mergeReExports`).
|
|
389
451
|
const canonical = walkSameNameCanonical(exportSymbol, immediateAlias, checker);
|
|
390
|
-
const
|
|
391
|
-
const canonicalSource = canonicalDecl?.getSourceFile();
|
|
452
|
+
const canonicalSource = getPrimaryDeclarationSourceFile(canonical);
|
|
392
453
|
const canonicalFile = canonicalSource
|
|
393
454
|
? stripVirtualSuffix(canonicalSource.fileName)
|
|
394
455
|
: originalFileName;
|
|
@@ -408,32 +469,7 @@ export const analyzeExports = (sourceFile, checker, options, diagnostics) => {
|
|
|
408
469
|
// in the re-exporting module so the local content has a place to live.
|
|
409
470
|
// Without local content, fall through to the alsoExportedFrom link only.
|
|
410
471
|
if (localTsdoc) {
|
|
411
|
-
|
|
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
|
-
}
|
|
472
|
+
const decl = synthesizeCrossFileAlias(reExportName, aliasedSymbol, originalSource, originalModule, specifierLine, checker, diagnostics, isExternalFile);
|
|
437
473
|
applyToDeclaration(decl, localTsdoc);
|
|
438
474
|
declarations.push({ declaration: decl, nodocs: !!localTsdoc.nodocs });
|
|
439
475
|
}
|
|
@@ -446,18 +482,56 @@ export const analyzeExports = (sourceFile, checker, options, diagnostics) => {
|
|
|
446
482
|
isSource(canonicalFile, options)) {
|
|
447
483
|
reExports.push({
|
|
448
484
|
name: reExportName,
|
|
449
|
-
|
|
485
|
+
module: extractPath(canonicalFile, options),
|
|
486
|
+
...(specifierTypeOnly ? { typeOnly: true } : {}),
|
|
487
|
+
...(specifierLine !== undefined ? { sourceLine: specifierLine } : {}),
|
|
450
488
|
});
|
|
451
489
|
}
|
|
452
490
|
}
|
|
453
491
|
continue;
|
|
454
492
|
}
|
|
455
|
-
// Re-export from external module
|
|
493
|
+
// Re-export from an external module. Direct forms
|
|
494
|
+
// (`export {x} from 'pkg'`, `export * as ns from 'pkg'`) are
|
|
495
|
+
// captured as externalReExports — but only when the statement's
|
|
496
|
+
// *immediate* target is itself external: chains that reach a
|
|
497
|
+
// package through another source module stay silent (that module
|
|
498
|
+
// owns the entry), as do import-then-export forms (their
|
|
499
|
+
// specifier lives on an import statement, and their immediate
|
|
500
|
+
// alias is the local import binding).
|
|
501
|
+
const immediateExternal = checker.getImmediateAliasedSymbol(exportSymbol);
|
|
502
|
+
const immediateExternalFile = immediateExternal && getPrimaryDeclarationFile(immediateExternal);
|
|
503
|
+
if (!immediateExternalFile || isSource(immediateExternalFile, options))
|
|
504
|
+
continue;
|
|
505
|
+
if (!local?.statement.moduleSpecifier)
|
|
506
|
+
continue;
|
|
507
|
+
if (!ts.isStringLiteral(local.statement.moduleSpecifier))
|
|
508
|
+
continue;
|
|
509
|
+
if (parseComment(local.statement, sourceFile)?.nodocs)
|
|
510
|
+
continue;
|
|
511
|
+
const originalName = ts.isExportSpecifier(local.node)
|
|
512
|
+
? local.node.propertyName?.text
|
|
513
|
+
: undefined;
|
|
514
|
+
externalReExports.push({
|
|
515
|
+
name: exportSymbol.name,
|
|
516
|
+
specifier: local.statement.moduleSpecifier.text,
|
|
517
|
+
...(originalName !== undefined ? { originalName } : {}),
|
|
518
|
+
...(specifierTypeOnly ? { typeOnly: true } : {}),
|
|
519
|
+
sourceLine: specifierLine,
|
|
520
|
+
});
|
|
456
521
|
continue;
|
|
457
522
|
}
|
|
458
523
|
// Within-file alias (export { x as y }) - fall through to normal analysis
|
|
459
524
|
}
|
|
460
525
|
}
|
|
526
|
+
// Star-projected exports surface as the target module's own symbols —
|
|
527
|
+
// no Alias flag, declarations in a foreign file (`export * from './a'`
|
|
528
|
+
// merges a.ts's export table; there is no per-name alias node). Their
|
|
529
|
+
// encoding is `starExports`; analyzing them here would duplicate the
|
|
530
|
+
// canonical declaration into this module (triggering spurious
|
|
531
|
+
// duplicate_declaration diagnostics, with sourceLine pointing into
|
|
532
|
+
// the foreign file).
|
|
533
|
+
if (!isAlias && !isDeclaredInFile(exportSymbol, currentFileName))
|
|
534
|
+
continue;
|
|
461
535
|
// Normal export or within-file alias - declared in this file.
|
|
462
536
|
// For within-file aliases (export { x } or export { x as y }), resolve to
|
|
463
537
|
// the aliased symbol so that inferDeclarationKind sees the actual declaration
|
|
@@ -478,15 +552,12 @@ export const analyzeExports = (sourceFile, checker, options, diagnostics) => {
|
|
|
478
552
|
// The aliased symbol's declaration (e.g., svelte2tsx-generated const) may lack JSDoc,
|
|
479
553
|
// but the export statement (e.g., /** Doc */ export { greet }) may have it.
|
|
480
554
|
if (isAlias) {
|
|
481
|
-
const
|
|
482
|
-
const
|
|
483
|
-
if (
|
|
484
|
-
|
|
485
|
-
if (exportTsdoc) {
|
|
486
|
-
|
|
487
|
-
if (exportTsdoc.nodocs) {
|
|
488
|
-
nodocs = true;
|
|
489
|
-
}
|
|
555
|
+
const local = getLocalExportStatement(exportSymbol, sourceFile);
|
|
556
|
+
const exportTsdoc = local ? parseComment(local.statement, sourceFile) : undefined;
|
|
557
|
+
if (exportTsdoc) {
|
|
558
|
+
applyToDeclaration(declaration, exportTsdoc);
|
|
559
|
+
if (exportTsdoc.nodocs) {
|
|
560
|
+
nodocs = true;
|
|
490
561
|
}
|
|
491
562
|
}
|
|
492
563
|
}
|
|
@@ -499,6 +570,8 @@ export const analyzeExports = (sourceFile, checker, options, diagnostics) => {
|
|
|
499
570
|
declarations,
|
|
500
571
|
reExports,
|
|
501
572
|
starExports,
|
|
573
|
+
externalReExports,
|
|
574
|
+
externalStarExports,
|
|
502
575
|
};
|
|
503
576
|
};
|
|
504
577
|
/**
|
|
@@ -621,36 +694,42 @@ const stripModuleTag = (text) => {
|
|
|
621
694
|
return filtered.join('\n').trim();
|
|
622
695
|
};
|
|
623
696
|
/**
|
|
624
|
-
* Extract star exports (`export * from './module'`) from a source file.
|
|
697
|
+
* Extract star exports (`export * from './module'` / `'pkg'`) from a source file.
|
|
698
|
+
*
|
|
699
|
+
* Uses the type checker to resolve module specifiers: source modules land in
|
|
700
|
+
* `starExports` (as `sourceRoot`-relative paths), external modules in
|
|
701
|
+
* `externalStarExports` (specifier as written). Unresolvable specifiers
|
|
702
|
+
* (missing package, typo) are silently skipped.
|
|
625
703
|
*
|
|
626
|
-
*
|
|
627
|
-
*
|
|
704
|
+
* Statement-level `@nodocs` suppresses the entry — the same rule as the other
|
|
705
|
+
* re-export encodings (same-name edges and renamed aliases).
|
|
628
706
|
*/
|
|
629
707
|
const extractStarExports = (sourceFile, checker, options) => {
|
|
630
708
|
const starExports = [];
|
|
709
|
+
const externalStarExports = [];
|
|
631
710
|
for (const statement of sourceFile.statements) {
|
|
632
711
|
if (ts.isExportDeclaration(statement) &&
|
|
633
712
|
!statement.exportClause && // No exportClause means `export *`
|
|
634
713
|
statement.moduleSpecifier &&
|
|
635
714
|
ts.isStringLiteral(statement.moduleSpecifier)) {
|
|
715
|
+
if (parseComment(statement, sourceFile)?.nodocs)
|
|
716
|
+
continue;
|
|
636
717
|
// Use the type checker to resolve the module - it has already resolved all imports
|
|
637
718
|
// during program creation, so this leverages TypeScript's full module resolution
|
|
638
719
|
const moduleSymbol = checker.getSymbolAtLocation(statement.moduleSpecifier);
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
if (
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
//
|
|
647
|
-
|
|
648
|
-
starExports.push(extractPath(resolvedPath, options));
|
|
649
|
-
}
|
|
720
|
+
// Virtual paths for Svelte files are normalized by getPrimaryDeclarationFile
|
|
721
|
+
const resolvedPath = moduleSymbol && getPrimaryDeclarationFile(moduleSymbol);
|
|
722
|
+
if (resolvedPath) {
|
|
723
|
+
if (isSource(resolvedPath, options)) {
|
|
724
|
+
starExports.push(extractPath(resolvedPath, options));
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
// External package — record the specifier as written
|
|
728
|
+
externalStarExports.push(statement.moduleSpecifier.text);
|
|
650
729
|
}
|
|
651
730
|
}
|
|
652
|
-
// If module couldn't be resolved (
|
|
731
|
+
// If the module couldn't be resolved (missing package, typo), skip it
|
|
653
732
|
}
|
|
654
733
|
}
|
|
655
|
-
return starExports;
|
|
734
|
+
return { starExports, externalStarExports };
|
|
656
735
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-docinfo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "static analysis for TypeScript and Svelte",
|
|
5
5
|
"glyph": "📜",
|
|
6
6
|
"logo": "logo.svg",
|
|
@@ -51,15 +51,15 @@
|
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@fuzdev/blake3_wasm": "^0.1.1",
|
|
54
|
-
"@fuzdev/fuz_code": "^0.
|
|
55
|
-
"@fuzdev/fuz_css": "^0.
|
|
56
|
-
"@fuzdev/fuz_ui": "^0.
|
|
57
|
-
"@fuzdev/fuz_util": "^0.
|
|
58
|
-
"@fuzdev/gro": "^0.
|
|
54
|
+
"@fuzdev/fuz_code": "^0.46.0",
|
|
55
|
+
"@fuzdev/fuz_css": "^0.62.0",
|
|
56
|
+
"@fuzdev/fuz_ui": "^0.202.0",
|
|
57
|
+
"@fuzdev/fuz_util": "^0.65.1",
|
|
58
|
+
"@fuzdev/gro": "^0.204.0",
|
|
59
59
|
"@ryanatkn/eslint-config": "^0.12.1",
|
|
60
60
|
"@sveltejs/acorn-typescript": "^1.0.9",
|
|
61
61
|
"@sveltejs/adapter-static": "^3.0.10",
|
|
62
|
-
"@sveltejs/kit": "^2.
|
|
62
|
+
"@sveltejs/kit": "^2.64.0",
|
|
63
63
|
"@sveltejs/package": "^2.5.7",
|
|
64
64
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
65
65
|
"@types/estree": "^1.0.8",
|
|
@@ -72,8 +72,8 @@
|
|
|
72
72
|
"magic-string": "^0.30.21",
|
|
73
73
|
"prettier": "^3.7.4",
|
|
74
74
|
"prettier-plugin-svelte": "^3.4.1",
|
|
75
|
-
"svelte": "^5.56.
|
|
76
|
-
"svelte-check": "^4.
|
|
75
|
+
"svelte": "^5.56.3",
|
|
76
|
+
"svelte-check": "^4.6.0",
|
|
77
77
|
"svelte2tsx": "^0.7.55",
|
|
78
78
|
"tslib": "^2.8.1",
|
|
79
79
|
"typescript-eslint": "^8.48.1",
|