svelte-docinfo 0.2.1 → 0.4.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 +9 -7
- package/dist/analyze-core.d.ts +2 -2
- package/dist/analyze-core.js +2 -2
- package/dist/svelte.d.ts +1 -1
- package/dist/svelte.js +12 -12
- package/dist/typescript-extract-shared.d.ts +15 -11
- package/dist/typescript-extract-shared.d.ts.map +1 -1
- package/dist/typescript-extract-shared.js +80 -26
- package/dist/typescript-extract-type-properties.d.ts.map +1 -1
- package/dist/typescript-extract-type-properties.js +12 -9
- package/package.json +7 -14
package/README.md
CHANGED
|
@@ -37,15 +37,14 @@ but instead of AST-only inspection it uses the TypeScript compiler API for riche
|
|
|
37
37
|
and also analyzes TypeScript modules.
|
|
38
38
|
The docs [compare](https://svelte-docinfo.fuz.dev/docs/introduction#Compared-to-sveld) them.
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
but for Svelte 5 it's mostly complete and used in production websites.
|
|
43
|
-
Please open issues for bugs
|
|
40
|
+
The library is mostly complete for Svelte 5 and used in production websites,
|
|
41
|
+
but you may find gaps and flaws -- please open issues for bugs
|
|
44
42
|
and [discussions](https://github.com/fuzdev/svelte-docinfo/discussions)
|
|
45
43
|
for everything else!
|
|
46
44
|
|
|
47
|
-
**AI disclosure:** the code and docs beyond the intro
|
|
48
|
-
|
|
45
|
+
**AI disclosure:** the code and docs beyond the intro
|
|
46
|
+
were mostly written by Claude Code with uneven human guidance.
|
|
47
|
+
The first release took 5 months of intermittent work and ~500 manual commits.
|
|
49
48
|
|
|
50
49
|
## Quick start
|
|
51
50
|
|
|
@@ -175,6 +174,8 @@ All three produce the same JSON shape:
|
|
|
175
174
|
}
|
|
176
175
|
```
|
|
177
176
|
|
|
177
|
+
For many more worked examples — source input paired with the exact JSON `svelte-docinfo` emits — browse the [test fixtures](https://github.com/fuzdev/svelte-docinfo/tree/main/src/test/fixtures), where each case sits beside its `expected.json`.
|
|
178
|
+
|
|
178
179
|
## Vite plugin
|
|
179
180
|
|
|
180
181
|
1. Add the plugin to `vite.config.ts`:
|
|
@@ -337,7 +338,8 @@ Known gaps: standalone `namespace Foo {}` declarations and decorators are not ye
|
|
|
337
338
|
was this project's main inspiration.
|
|
338
339
|
|
|
339
340
|
Extracted from [fuz_ui](https://github.com/fuzdev/fuz_ui),
|
|
340
|
-
which has example components using the data for docs websites
|
|
341
|
+
which has example components using the data for docs websites like
|
|
342
|
+
[`DeclarationDetail`](https://github.com/fuzdev/fuz_ui/blob/main/src/lib/DeclarationDetail.svelte).
|
|
341
343
|
|
|
342
344
|
`svelte-docinfo` is more featureful than it would be
|
|
343
345
|
otherwise thanks to LLM assistance, mostly Claude Code.
|
package/dist/analyze-core.d.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Pure two-phase analysis orchestrator.
|
|
3
3
|
*
|
|
4
4
|
* Extracted from `analyze.ts` to break what would otherwise be a circular
|
|
5
|
-
* import: `session.ts → analyze.ts (for the orchestrator) → session.ts
|
|
6
|
-
* the wrappers)
|
|
5
|
+
* import: `session.ts` → `analyze.ts` (for the orchestrator) → `session.ts`
|
|
6
|
+
* (for the wrappers). With the orchestrator here, both `session.ts` and
|
|
7
7
|
* `analyze.ts` import downward into this module without depending on each
|
|
8
8
|
* other.
|
|
9
9
|
*
|
package/dist/analyze-core.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Pure two-phase analysis orchestrator.
|
|
3
3
|
*
|
|
4
4
|
* Extracted from `analyze.ts` to break what would otherwise be a circular
|
|
5
|
-
* import: `session.ts → analyze.ts (for the orchestrator) → session.ts
|
|
6
|
-
* the wrappers)
|
|
5
|
+
* import: `session.ts` → `analyze.ts` (for the orchestrator) → `session.ts`
|
|
6
|
+
* (for the wrappers). With the orchestrator here, both `session.ts` and
|
|
7
7
|
* `analyze.ts` import downward into this module without depending on each
|
|
8
8
|
* other.
|
|
9
9
|
*
|
package/dist/svelte.d.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* There is no Svelte 4 compatibility layer.
|
|
16
16
|
*
|
|
17
17
|
* @see `typescript-exports.ts` for `analyzeExports`, `extractModuleComment`
|
|
18
|
-
* @see `typescript-extract-shared.ts` for `parseGenericParam`, `
|
|
18
|
+
* @see `typescript-extract-shared.ts` for `parseGenericParam`, `filterExternalProperties`
|
|
19
19
|
* @see `typescript-program.ts` for `IsExternalFile`, `createIsExternalFile`
|
|
20
20
|
* @see `tsdoc.ts` for `parseComment`, `applyToDeclaration`
|
|
21
21
|
* @see `source.ts` for `SourceFileInfo`, `getComponentName`
|
package/dist/svelte.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* There is no Svelte 4 compatibility layer.
|
|
16
16
|
*
|
|
17
17
|
* @see `typescript-exports.ts` for `analyzeExports`, `extractModuleComment`
|
|
18
|
-
* @see `typescript-extract-shared.ts` for `parseGenericParam`, `
|
|
18
|
+
* @see `typescript-extract-shared.ts` for `parseGenericParam`, `filterExternalProperties`
|
|
19
19
|
* @see `typescript-program.ts` for `IsExternalFile`, `createIsExternalFile`
|
|
20
20
|
* @see `tsdoc.ts` for `parseComment`, `applyToDeclaration`
|
|
21
21
|
* @see `source.ts` for `SourceFileInfo`, `getComponentName`
|
|
@@ -28,7 +28,7 @@ import { svelte2tsx } from 'svelte2tsx';
|
|
|
28
28
|
import ts from 'typescript';
|
|
29
29
|
import { parseComment, applyToDeclaration, hasModuleTag } from './tsdoc.js';
|
|
30
30
|
import { createIsExternalFile } from './typescript-program.js';
|
|
31
|
-
import { parseGenericParam,
|
|
31
|
+
import { parseGenericParam, filterExternalProperties } from './typescript-extract-shared.js';
|
|
32
32
|
import { extractModuleComment, analyzeExports } from './typescript-exports.js';
|
|
33
33
|
import { getComponentName, SVELTE_VIRTUAL_SUFFIX } from './source.js';
|
|
34
34
|
import { extractDependencies } from './source-config.js';
|
|
@@ -638,14 +638,14 @@ const extractPropsViaChecker = (virtualSource, checker, componentName, filePath,
|
|
|
638
638
|
// Detect `acceptsChildren` via type inference: `children` must resolve to a
|
|
639
639
|
// `Snippet<...>` type. Checking the symbol name alone (the previous approach)
|
|
640
640
|
// misreports a non-Snippet `children` (e.g. `children: string`) as accepting
|
|
641
|
-
// children. The lookup runs on the unfiltered
|
|
641
|
+
// children. The lookup runs on the unfiltered props type so inherited
|
|
642
642
|
// `children` from `SvelteHTMLElements`/`DOMAttributes` (declared as `Snippet`)
|
|
643
643
|
// is honored even when its declaration lives in node_modules.
|
|
644
|
-
const allProperties = propsType.getProperties();
|
|
645
644
|
const acceptsChildren = detectChildrenSnippet(propsType, propsTypeNode, checker, diagnostics, componentName, filePath);
|
|
646
|
-
//
|
|
647
|
-
|
|
648
|
-
|
|
645
|
+
// Drop properties contributed by external types (node_modules / svelte's
|
|
646
|
+
// element-attribute bags like `SvelteHTMLElements['li']`); those external
|
|
647
|
+
// types are summarized in `intersects` rather than enumerated as props.
|
|
648
|
+
const { properties, externalTypes } = filterExternalProperties(propsType, propsTypeNode, checker, isExternalFile);
|
|
649
649
|
const props = [];
|
|
650
650
|
for (const prop of properties) {
|
|
651
651
|
const propDecl = prop.valueDeclaration || prop.declarations?.[0];
|
|
@@ -693,7 +693,7 @@ const extractPropsViaChecker = (virtualSource, checker, componentName, filePath,
|
|
|
693
693
|
const propSourceFile = propDecl?.getSourceFile();
|
|
694
694
|
props.push(assemblePropInfo(prop.name, typeString, optional, propDecl, propSourceFile, propsDefaults, bindableProps, snippetParams));
|
|
695
695
|
}
|
|
696
|
-
return { props,
|
|
696
|
+
return { props, externalTypes, acceptsChildren };
|
|
697
697
|
};
|
|
698
698
|
/**
|
|
699
699
|
* Analyze a Svelte module using checker-backed analysis.
|
|
@@ -813,15 +813,15 @@ export const analyzeSvelteModule = (sourceFile, modulePath, checker, options, di
|
|
|
813
813
|
const isExternalFile = createIsExternalFile(options);
|
|
814
814
|
// Extract props via checker (resolves imported types)
|
|
815
815
|
const { bindableProps, propsDefaults } = extractPropsMetadata(virtualTsSource);
|
|
816
|
-
const { props,
|
|
816
|
+
const { props, externalTypes, acceptsChildren: propsAcceptsChildren, } = extractPropsViaChecker(virtualTsSource, checker, componentName, modulePath, virtualFile.sourceMap, diagnostics, propsDefaults, bindableProps, isExternalFile);
|
|
817
817
|
if (props.length > 0) {
|
|
818
818
|
componentDecl.props = props;
|
|
819
819
|
}
|
|
820
|
-
if (
|
|
821
|
-
componentDecl.intersects =
|
|
820
|
+
if (externalTypes?.length) {
|
|
821
|
+
componentDecl.intersects = externalTypes;
|
|
822
822
|
}
|
|
823
823
|
// Determine acceptsChildren via two paths:
|
|
824
|
-
// Path A: children found in props type (from extractPropsViaChecker, before
|
|
824
|
+
// Path A: children found in props type (from extractPropsViaChecker, before external-property filtering)
|
|
825
825
|
// Path B: implicit children usage in template (for components without $props() children declaration)
|
|
826
826
|
let acceptsChildren = propsAcceptsChildren;
|
|
827
827
|
if (!acceptsChildren) {
|
|
@@ -86,19 +86,23 @@ export declare const isExternalIntersectionBranch: (branchNode: ts.TypeNode, che
|
|
|
86
86
|
*/
|
|
87
87
|
export declare const resolveIntersectionTypeNode: (type: ts.Type, typeNode: ts.Node) => ts.IntersectionTypeNode | undefined;
|
|
88
88
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
89
|
+
* Partition a type's properties into local (kept) and external (dropped), and
|
|
90
|
+
* collect the external type references that contributed the dropped ones.
|
|
91
|
+
*
|
|
92
|
+
* Applies to any composition shape — intersection, union, bare reference,
|
|
93
|
+
* indexed-access — not only intersections. Membership is decided per property
|
|
94
|
+
* by declaration origin (`isExternalProperty`), which is structure-agnostic:
|
|
95
|
+
* TypeScript preserves original declaration sources on derived properties, so
|
|
96
|
+
* the test gives the right answer through utility-type wrappers (Partial, Pick,
|
|
97
|
+
* `OmitStrict`) too. A property with no declarations (synthesized) is treated as
|
|
98
|
+
* local and kept. The external-type labels for `intersects` come from an AST
|
|
99
|
+
* walk (`collectExternalTypeRefs`) — the authoritative source for the
|
|
100
|
+
* `&`/`|`/index-access shape inference would otherwise erase.
|
|
97
101
|
*/
|
|
98
|
-
export declare const
|
|
102
|
+
export declare const filterExternalProperties: (type: ts.Type, typeNode: ts.Node, checker: ts.TypeChecker, isExternalFile: IsExternalFile) => {
|
|
99
103
|
properties: Array<ts.Symbol>;
|
|
100
|
-
|
|
101
|
-
}
|
|
104
|
+
externalTypes: Array<string>;
|
|
105
|
+
};
|
|
102
106
|
/**
|
|
103
107
|
* Detect a Svelte 5 reactivity rune from a variable or property initializer.
|
|
104
108
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typescript-extract-shared.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/typescript-extract-shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,OAAO,KAAK,EACX,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EAEnB,aAAa,EAEb,UAAU,EACV,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAC,oBAAoB,EAAE,eAAe,EAAC,MAAM,wBAAwB,CAAC;AAClF,OAAO,EAAC,KAAK,UAAU,EAA8B,MAAM,kBAAkB,CAAC;AAC9E,OAAO,EAAmC,KAAK,kBAAkB,EAAC,MAAM,YAAY,CAAC;AACrF,OAAO,EAAC,KAAK,cAAc,EAAC,MAAM,yBAAyB,CAAC;AAE5D;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,IAAI,KAAG,eA0BvE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,0BAA0B,GACtC,KAAK,EAAE,CAAC,SAAS,EACjB,SAAS,EAAE,CAAC,WAAW,EACvB,aAAa,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,KAC7C,KAAK,CAAC,aAAa,CAoDrB,CAAC;AA8JF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,sBAAsB,GAClC,QAAQ,oBAAoB,GAAG,eAAe,EAC9C,YAAY,aAAa,CAAC,EAAE,CAAC,SAAS,CAAC,EACvC,SAAS,EAAE,CAAC,WAAW,EACvB,OAAO,kBAAkB,GAAG,SAAS,EACrC,qBAAqB,EAAE,CAAC,IAAI,EAC5B,MAAM,MAAM,EACZ,aAAa,KAAK,CAAC,UAAU,CAAC,EAC9B,uBAAoB,KAClB,IAiBF,CAAC;AAYF;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,GACxC,YAAY,EAAE,CAAC,QAAQ,EACvB,SAAS,EAAE,CAAC,WAAW,EACvB,gBAAgB,cAAc,KAC5B,OASF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,EAAE,CAAC,IAAI,EACb,UAAU,EAAE,CAAC,IAAI,KACf,EAAE,CAAC,oBAAoB,GAAG,SAW5B,CAAC;
|
|
1
|
+
{"version":3,"file":"typescript-extract-shared.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/typescript-extract-shared.ts"],"names":[],"mappings":"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"}
|
|
@@ -350,38 +350,92 @@ export const resolveIntersectionTypeNode = (type, typeNode) => {
|
|
|
350
350
|
return undefined;
|
|
351
351
|
};
|
|
352
352
|
/**
|
|
353
|
-
*
|
|
354
|
-
*
|
|
353
|
+
* Determine whether a type-reference / indexed-access node names a type whose
|
|
354
|
+
* properties all come from external files (e.g. `SvelteHTMLElements['li']`,
|
|
355
|
+
* `HTMLAttributes<HTMLDivElement>`). Such a node is an external attribute "bag"
|
|
356
|
+
* that should be summarized in `intersects` rather than enumerated as members.
|
|
355
357
|
*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
+
* Mirrors the per-property origin test used for membership: external only when
|
|
359
|
+
* the node has at least one property and every property is external. A
|
|
360
|
+
* zero-property branch (e.g. a pure index signature) is not surfaced — there is
|
|
361
|
+
* no named member to attribute to it.
|
|
362
|
+
*/
|
|
363
|
+
const isExternalTypeRefNode = (node, checker, isExternalFile) => {
|
|
364
|
+
const props = checker.getTypeAtLocation(node).getProperties();
|
|
365
|
+
return props.length > 0 && props.every((p) => isExternalProperty(p, isExternalFile));
|
|
366
|
+
};
|
|
367
|
+
/**
|
|
368
|
+
* Walk a written type node and collect, in source order, the verbatim text of
|
|
369
|
+
* every external type reference it composes.
|
|
370
|
+
*
|
|
371
|
+
* Structure is read from the AST rather than the inferred type because
|
|
372
|
+
* inference erases it: `(A | B) & C` normalizes to a union and `X['k']`
|
|
373
|
+
* flattens to a property bag, both losing the `&`/`|`/index-access shape the
|
|
374
|
+
* author wrote. Composition nodes (intersection, union, parenthesized) recurse;
|
|
375
|
+
* leaf references (`TypeReference`, `IndexedAccessType`) are tested with
|
|
376
|
+
* `isExternalTypeRefNode` and, when external, emitted via `getText()`. Inline
|
|
377
|
+
* object literals and other local shapes contribute no entry.
|
|
378
|
+
*
|
|
379
|
+
* @mutates out - appends each external reference's source text
|
|
380
|
+
*/
|
|
381
|
+
const collectExternalTypeRefs = (node, checker, isExternalFile, out) => {
|
|
382
|
+
if (ts.isParenthesizedTypeNode(node)) {
|
|
383
|
+
collectExternalTypeRefs(node.type, checker, isExternalFile, out);
|
|
384
|
+
}
|
|
385
|
+
else if (ts.isIntersectionTypeNode(node) || ts.isUnionTypeNode(node)) {
|
|
386
|
+
for (const branch of node.types)
|
|
387
|
+
collectExternalTypeRefs(branch, checker, isExternalFile, out);
|
|
388
|
+
}
|
|
389
|
+
else if (ts.isTypeReferenceNode(node) || ts.isIndexedAccessTypeNode(node)) {
|
|
390
|
+
if (isExternalTypeRefNode(node, checker, isExternalFile))
|
|
391
|
+
out.push(node.getText());
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
/**
|
|
395
|
+
* Resolve a props/type-alias annotation node to the written type node whose
|
|
396
|
+
* structure drives `intersects` extraction.
|
|
397
|
+
*
|
|
398
|
+
* The svelte2tsx props annotation is a reference to a generated `$$ComponentProps`
|
|
399
|
+
* alias; unwrap one level of *local* type-alias reference so the underlying
|
|
400
|
+
* composition (`SvelteHTMLElements['li']`, `(A | B) & C`, …) is visible. External
|
|
401
|
+
* alias references are left intact so they read as a single named bag rather than
|
|
402
|
+
* leaking their node_modules-internal definition. Type-alias callers pass the
|
|
403
|
+
* written node directly, so for them this is a no-op.
|
|
404
|
+
*/
|
|
405
|
+
const resolveAnnotationTypeNode = (typeNode, checker, isExternalFile) => {
|
|
406
|
+
if (ts.isTypeReferenceNode(typeNode)) {
|
|
407
|
+
const aliasDecl = checker
|
|
408
|
+
.getSymbolAtLocation(typeNode.typeName)
|
|
409
|
+
?.getDeclarations()
|
|
410
|
+
?.find(ts.isTypeAliasDeclaration);
|
|
411
|
+
if (aliasDecl && !isExternalFile(aliasDecl.getSourceFile()))
|
|
412
|
+
return aliasDecl.type;
|
|
413
|
+
}
|
|
414
|
+
return ts.isTypeNode(typeNode) ? typeNode : undefined;
|
|
415
|
+
};
|
|
416
|
+
/**
|
|
417
|
+
* Partition a type's properties into local (kept) and external (dropped), and
|
|
418
|
+
* collect the external type references that contributed the dropped ones.
|
|
358
419
|
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
420
|
+
* Applies to any composition shape — intersection, union, bare reference,
|
|
421
|
+
* indexed-access — not only intersections. Membership is decided per property
|
|
422
|
+
* by declaration origin (`isExternalProperty`), which is structure-agnostic:
|
|
423
|
+
* TypeScript preserves original declaration sources on derived properties, so
|
|
424
|
+
* the test gives the right answer through utility-type wrappers (Partial, Pick,
|
|
425
|
+
* `OmitStrict`) too. A property with no declarations (synthesized) is treated as
|
|
426
|
+
* local and kept. The external-type labels for `intersects` come from an AST
|
|
427
|
+
* walk (`collectExternalTypeRefs`) — the authoritative source for the
|
|
428
|
+
* `&`/`|`/index-access shape inference would otherwise erase.
|
|
361
429
|
*/
|
|
362
|
-
export const
|
|
363
|
-
if (!type.isIntersection())
|
|
364
|
-
return null;
|
|
430
|
+
export const filterExternalProperties = (type, typeNode, checker, isExternalFile) => {
|
|
365
431
|
const properties = type
|
|
366
432
|
.getProperties()
|
|
367
433
|
.filter((prop) => !isExternalProperty(prop, isExternalFile));
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
for (const branch of intersectionNode.types) {
|
|
374
|
-
if (ts.isTypeReferenceNode(branch) || ts.isIndexedAccessTypeNode(branch)) {
|
|
375
|
-
const branchType = checker.getTypeAtLocation(branch);
|
|
376
|
-
const branchProps = branchType.getProperties();
|
|
377
|
-
if (branchProps.length > 0 &&
|
|
378
|
-
branchProps.every((p) => isExternalProperty(p, isExternalFile))) {
|
|
379
|
-
intersectionTypes.push(branch.getText());
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
return { properties, intersectionTypes };
|
|
434
|
+
const externalTypes = [];
|
|
435
|
+
const annotation = resolveAnnotationTypeNode(typeNode, checker, isExternalFile);
|
|
436
|
+
if (annotation)
|
|
437
|
+
collectExternalTypeRefs(annotation, checker, isExternalFile, externalTypes);
|
|
438
|
+
return { properties, externalTypes };
|
|
385
439
|
};
|
|
386
440
|
/**
|
|
387
441
|
* Detect a Svelte 5 reactivity rune from a variable or property initializer.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typescript-extract-type-properties.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/typescript-extract-type-properties.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAG5B,OAAO,KAAK,EAAC,oBAAoB,EAAkB,MAAM,wBAAwB,CAAC;AAClF,OAAO,EAAC,KAAK,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAC,KAAK,cAAc,EAAC,MAAM,yBAAyB,CAAC;AA+J5D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GACtC,MAAM,EAAE,CAAC,oBAAoB,EAC7B,UAAU,EAAE,CAAC,IAAI,EACjB,SAAS,EAAE,CAAC,WAAW,EACvB,aAAa,oBAAoB,EACjC,aAAa,KAAK,CAAC,UAAU,CAAC,EAC9B,gBAAgB,cAAc,KAC5B,
|
|
1
|
+
{"version":3,"file":"typescript-extract-type-properties.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/typescript-extract-type-properties.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAG5B,OAAO,KAAK,EAAC,oBAAoB,EAAkB,MAAM,wBAAwB,CAAC;AAClF,OAAO,EAAC,KAAK,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAC,KAAK,cAAc,EAAC,MAAM,yBAAyB,CAAC;AA+J5D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GACtC,MAAM,EAAE,CAAC,oBAAoB,EAC7B,UAAU,EAAE,CAAC,IAAI,EACjB,SAAS,EAAE,CAAC,WAAW,EACvB,aAAa,oBAAoB,EACjC,aAAa,KAAK,CAAC,UAAU,CAAC,EAC9B,gBAAgB,cAAc,KAC5B,IA8IF,CAAC"}
|
|
@@ -18,7 +18,7 @@ import ts from 'typescript';
|
|
|
18
18
|
import {} from './diagnostics.js';
|
|
19
19
|
import { parseComment, applyToDeclaration } from './tsdoc.js';
|
|
20
20
|
import {} from './typescript-program.js';
|
|
21
|
-
import { emitCallOrConstructSignature,
|
|
21
|
+
import { emitCallOrConstructSignature, filterExternalProperties, getNodeLocation, isExternalIntersectionBranch, populateCallableMember, resolveIntersectionTypeNode, } from './typescript-extract-shared.js';
|
|
22
22
|
/**
|
|
23
23
|
* Check whether a resolved type has properties worth extracting for documentation.
|
|
24
24
|
*
|
|
@@ -160,11 +160,15 @@ const isReadonlyProperty = (prop, mappedReadonly) => {
|
|
|
160
160
|
export const extractTypeAliasProperties = (node, nodeType, checker, declaration, diagnostics, isExternalFile) => {
|
|
161
161
|
if (!hasExtractableProperties(nodeType))
|
|
162
162
|
return;
|
|
163
|
-
//
|
|
164
|
-
// and
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
163
|
+
// Drop properties contributed by external types (node_modules / declaration
|
|
164
|
+
// files) and surface those external types in the `intersects` field. Applies
|
|
165
|
+
// to the property-bearing shapes that pass `hasExtractableProperties` above —
|
|
166
|
+
// intersections, bare references, indexed-access. Unions are gated out here
|
|
167
|
+
// (the Svelte prop path calls `filterExternalProperties` directly, so unions
|
|
168
|
+
// still surface `intersects` there, just not for plain type aliases).
|
|
169
|
+
const { properties: filteredProperties, externalTypes } = filterExternalProperties(nodeType, node.type, checker, isExternalFile);
|
|
170
|
+
if (externalTypes.length) {
|
|
171
|
+
declaration.intersects = externalTypes;
|
|
168
172
|
}
|
|
169
173
|
// Detect mapped-type-level readonly (e.g., Readonly<T>, { readonly [K in ...]: ... })
|
|
170
174
|
let mappedReadonly = false;
|
|
@@ -177,9 +181,8 @@ export const extractTypeAliasProperties = (node, nodeType, checker, declaration,
|
|
|
177
181
|
mappedReadonly = true;
|
|
178
182
|
}
|
|
179
183
|
}
|
|
180
|
-
// Extract named properties (
|
|
181
|
-
const
|
|
182
|
-
for (const prop of properties) {
|
|
184
|
+
// Extract named properties (external contributions already filtered out)
|
|
185
|
+
for (const prop of filteredProperties) {
|
|
183
186
|
// Skip internal TypeScript symbols
|
|
184
187
|
if (prop.getName().startsWith('__@'))
|
|
185
188
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-docinfo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "static analysis for TypeScript and Svelte",
|
|
5
5
|
"glyph": "📜",
|
|
6
6
|
"logo": "logo.svg",
|
|
@@ -42,27 +42,19 @@
|
|
|
42
42
|
"es-module-lexer": "^2.0.0",
|
|
43
43
|
"picomatch": "^4.0.4",
|
|
44
44
|
"tinyglobby": "^0.2.15",
|
|
45
|
-
"typescript": "^5.9.3"
|
|
46
|
-
"zod": "^4.3.6"
|
|
45
|
+
"typescript": "^5.9.3"
|
|
47
46
|
},
|
|
48
47
|
"peerDependencies": {
|
|
49
48
|
"svelte": "^5.0.0",
|
|
50
|
-
"svelte2tsx": "^0.7.30"
|
|
51
|
-
|
|
52
|
-
"peerDependenciesMeta": {
|
|
53
|
-
"svelte": {
|
|
54
|
-
"optional": true
|
|
55
|
-
},
|
|
56
|
-
"svelte2tsx": {
|
|
57
|
-
"optional": true
|
|
58
|
-
}
|
|
49
|
+
"svelte2tsx": "^0.7.30",
|
|
50
|
+
"zod": "^4"
|
|
59
51
|
},
|
|
60
52
|
"devDependencies": {
|
|
61
53
|
"@fuzdev/blake3_wasm": "^0.1.1",
|
|
62
54
|
"@fuzdev/fuz_code": "^0.45.1",
|
|
63
55
|
"@fuzdev/fuz_css": "^0.61.1",
|
|
64
56
|
"@fuzdev/fuz_ui": "^0.197.0",
|
|
65
|
-
"@fuzdev/fuz_util": "^0.63.
|
|
57
|
+
"@fuzdev/fuz_util": "^0.63.1",
|
|
66
58
|
"@fuzdev/gro": "^0.200.0",
|
|
67
59
|
"@ryanatkn/eslint-config": "^0.12.1",
|
|
68
60
|
"@sveltejs/acorn-typescript": "^1.0.9",
|
|
@@ -87,7 +79,8 @@
|
|
|
87
79
|
"typescript-eslint": "^8.48.1",
|
|
88
80
|
"vite": "^7.3.1",
|
|
89
81
|
"vitest": "^4.0.15",
|
|
90
|
-
"zimmerframe": "^1.1.4"
|
|
82
|
+
"zimmerframe": "^1.1.4",
|
|
83
|
+
"zod": "^4.3.6"
|
|
91
84
|
},
|
|
92
85
|
"prettier": {
|
|
93
86
|
"plugins": [
|