svelte-docsmith 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/buildtime/paths.d.ts +5 -0
  2. package/dist/buildtime/paths.js +7 -0
  3. package/dist/buildtime/props/extract.d.ts +19 -0
  4. package/dist/buildtime/props/extract.js +158 -0
  5. package/dist/buildtime/props/parse.d.ts +17 -0
  6. package/dist/buildtime/props/parse.js +108 -0
  7. package/dist/buildtime/props/typescript.d.ts +54 -0
  8. package/dist/buildtime/props/typescript.js +345 -0
  9. package/dist/buildtime/vite/collect.js +22 -42
  10. package/dist/buildtime/vite/fields.d.ts +49 -0
  11. package/dist/buildtime/vite/fields.js +72 -0
  12. package/dist/buildtime/vite/frontmatter-schema.d.ts +38 -0
  13. package/dist/buildtime/vite/frontmatter-schema.js +96 -0
  14. package/dist/buildtime/vite/index.d.ts +16 -0
  15. package/dist/buildtime/vite/index.js +65 -2
  16. package/dist/buildtime/vite/pages.d.ts +5 -0
  17. package/dist/buildtime/vite/pages.js +10 -5
  18. package/dist/client.d.ts +24 -0
  19. package/dist/components/docs/accordion-item.svelte +2 -1
  20. package/dist/components/docs/accordion-item.svelte.d.ts +2 -1
  21. package/dist/components/docs/accordion.svelte +2 -1
  22. package/dist/components/docs/accordion.svelte.d.ts +2 -1
  23. package/dist/components/docs/badge.svelte +4 -3
  24. package/dist/components/docs/badge.svelte.d.ts +4 -3
  25. package/dist/components/docs/callout.svelte +6 -2
  26. package/dist/components/docs/callout.svelte.d.ts +6 -2
  27. package/dist/components/docs/card-grid.svelte +6 -1
  28. package/dist/components/docs/card-grid.svelte.d.ts +1 -0
  29. package/dist/components/docs/card.svelte +4 -2
  30. package/dist/components/docs/card.svelte.d.ts +4 -2
  31. package/dist/components/docs/file-tree-item.svelte +5 -2
  32. package/dist/components/docs/file-tree-item.svelte.d.ts +5 -2
  33. package/dist/components/docs/file-tree.svelte +6 -1
  34. package/dist/components/docs/file-tree.svelte.d.ts +1 -0
  35. package/dist/components/docs/kbd.svelte +6 -1
  36. package/dist/components/docs/kbd.svelte.d.ts +1 -0
  37. package/dist/components/docs/live-example.svelte +5 -2
  38. package/dist/components/docs/live-example.svelte.d.ts +5 -2
  39. package/dist/components/docs/prop.svelte +43 -12
  40. package/dist/components/docs/prop.svelte.d.ts +4 -0
  41. package/dist/components/docs/props-table.svelte +75 -6
  42. package/dist/components/docs/props-table.svelte.d.ts +9 -2
  43. package/dist/components/docs/step.svelte +1 -0
  44. package/dist/components/docs/step.svelte.d.ts +1 -0
  45. package/dist/components/docs/steps.svelte +6 -1
  46. package/dist/components/docs/steps.svelte.d.ts +1 -0
  47. package/dist/components/docs/tab-item.svelte +6 -2
  48. package/dist/components/docs/tab-item.svelte.d.ts +6 -2
  49. package/dist/components/docs/tabs.svelte +8 -4
  50. package/dist/components/docs/tabs.svelte.d.ts +8 -4
  51. package/dist/components/landing/action.svelte +5 -2
  52. package/dist/components/landing/action.svelte.d.ts +5 -2
  53. package/dist/components/landing/cta.svelte +4 -3
  54. package/dist/components/landing/cta.svelte.d.ts +4 -3
  55. package/dist/components/landing/feature-grid.svelte +7 -4
  56. package/dist/components/landing/feature-grid.svelte.d.ts +7 -4
  57. package/dist/components/landing/feature.svelte +1 -0
  58. package/dist/components/landing/feature.svelte.d.ts +1 -0
  59. package/dist/components/landing/hero.svelte +5 -5
  60. package/dist/components/landing/hero.svelte.d.ts +5 -5
  61. package/dist/core/content.d.ts +27 -0
  62. package/dist/core/extracted-props.d.ts +39 -0
  63. package/dist/core/extracted-props.js +1 -0
  64. package/dist/core/index.d.ts +1 -1
  65. package/dist/index.d.ts +2 -1
  66. package/dist/utils/type-text.d.ts +13 -0
  67. package/dist/utils/type-text.js +49 -0
  68. package/package.json +14 -2
@@ -10,3 +10,8 @@ export declare function urlFor(routesDir: string, dir: string): string;
10
10
  * base of `/docs`.
11
11
  */
12
12
  export declare function docsBaseFrom(routesDir: string, contentDir: string): string;
13
+ /**
14
+ * A file's path relative to the app's working directory, with `/` separators:
15
+ * the path an author would type, for "Edit this page" links and error messages.
16
+ */
17
+ export declare function relativeToCwd(file: string): string;
@@ -21,3 +21,10 @@ export function urlFor(routesDir, dir) {
21
21
  export function docsBaseFrom(routesDir, contentDir) {
22
22
  return urlFor(routesDir, contentDir);
23
23
  }
24
+ /**
25
+ * A file's path relative to the app's working directory, with `/` separators:
26
+ * the path an author would type, for "Edit this page" links and error messages.
27
+ */
28
+ export function relativeToCwd(file) {
29
+ return path.relative(process.cwd(), file).split(path.sep).join('/');
30
+ }
@@ -0,0 +1,19 @@
1
+ import type { ExtractedProps } from '../../core/extracted-props.js';
2
+ export interface ExtractResult {
3
+ props: ExtractedProps;
4
+ warnings: string[];
5
+ /** Files whose changes should re-extract this component. */
6
+ watchFiles: string[];
7
+ }
8
+ export interface PropsExtractorOptions {
9
+ /** Project root: where `tsconfig.json` is looked up. */
10
+ root: string;
11
+ /** tsconfig used to read prop types. Default: the root's `tsconfig.json`. */
12
+ tsconfig?: string;
13
+ }
14
+ export interface PropsExtractor {
15
+ extract(file: string): Promise<ExtractResult>;
16
+ }
17
+ export declare function createPropsExtractor(options: PropsExtractorOptions): PropsExtractor;
18
+ /** `file-tree-item.svelte` → `FileTreeItem`. */
19
+ export declare function componentName(file: string): string;
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Props extraction for props imports (`Component.svelte?props`): merges what
3
+ * the source says (./parse.ts: destructured names, defaults, `$bindable()`)
4
+ * with what TypeScript says (./typescript.ts: types, optionality, JSDoc) into
5
+ * the `ExtractedProps` that `<PropsTable of={…}>` renders.
6
+ *
7
+ * Extraction never throws over one component. Anything it can't read becomes a
8
+ * warning naming the file, and the table shows what was found: untyped props
9
+ * by name only, a missing TypeScript install as names and defaults.
10
+ */
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ import { marked } from 'marked';
14
+ import { VERSION } from 'svelte/compiler';
15
+ import { parseComponent } from './parse.js';
16
+ import { createPropsTypeChecker, inNodeModules, loadPeers, mtimeOf, MISSING_PEERS } from './typescript.js';
17
+ export function createPropsExtractor(options) {
18
+ let checker;
19
+ const getChecker = () => (checker ??= loadPeers().then((peers) => peers &&
20
+ createPropsTypeChecker(peers, {
21
+ ...options,
22
+ svelteVersion: VERSION
23
+ })));
24
+ // Props imports load concurrently (a page with five tables asks for five at
25
+ // once). Collect the requests made in the same tick and read them against
26
+ // one TypeScript program instead of rebuilding it per component.
27
+ let batch = [];
28
+ const flush = async () => {
29
+ const items = batch;
30
+ batch = [];
31
+ let results;
32
+ try {
33
+ const tsChecker = await getChecker();
34
+ results = tsChecker
35
+ ? tsChecker.read(items.map((item) => item.file))
36
+ : items.map(() => 'missing-peers');
37
+ }
38
+ catch (error) {
39
+ // Setting TypeScript up failed (a broken tsconfig, say). Report it on
40
+ // every waiting import rather than leaving them pending, and try again
41
+ // from scratch next time.
42
+ checker = undefined;
43
+ const failure = error instanceof Error ? error : new Error(String(error));
44
+ results = items.map(() => failure);
45
+ }
46
+ items.forEach((item, i) => item.done(results[i]));
47
+ };
48
+ const readTyped = (file) => new Promise((done) => {
49
+ if (batch.push({ file, done }) === 1)
50
+ setTimeout(flush, 0);
51
+ });
52
+ // Results stay valid until one of the files they were read from changes.
53
+ // The client and server builds both import every props module, so the
54
+ // second build is served from here.
55
+ const cache = new Map();
56
+ const stamp = mtimeOf;
57
+ return {
58
+ async extract(file) {
59
+ const cached = cache.get(file);
60
+ if (cached && cached.stamps.every(([f, time]) => stamp(f) === time))
61
+ return cached.result;
62
+ const result = await extractFresh(file);
63
+ cache.set(file, { stamps: result.watchFiles.map((f) => [f, stamp(f)]), result });
64
+ return result;
65
+ }
66
+ };
67
+ async function extractFresh(file) {
68
+ const warnings = [];
69
+ const rel = path.relative(options.root, file) || file;
70
+ const warn = (message) => warnings.push(`${rel}: ${message}`);
71
+ const extracted = {
72
+ name: componentName(file),
73
+ typeParameters: [],
74
+ props: [],
75
+ inherited: []
76
+ };
77
+ if (inNodeModules(file)) {
78
+ warn('reading a published package file. Published components usually have their ' +
79
+ 'types stripped, so point the props import at the source instead.');
80
+ }
81
+ let parsed;
82
+ try {
83
+ parsed = parseComponent(fs.readFileSync(file, 'utf-8'), file);
84
+ }
85
+ catch (error) {
86
+ warn(`could not parse the component (${message(error)}).`);
87
+ return { props: extracted, warnings, watchFiles: [file] };
88
+ }
89
+ extracted.typeParameters = parsed.typeParameters;
90
+ if (!parsed.hasProps) {
91
+ warn('no `$props()` found, so the props table is empty.');
92
+ return { props: extracted, warnings, watchFiles: [file] };
93
+ }
94
+ let typed;
95
+ if (parsed.typed) {
96
+ const result = await readTyped(file);
97
+ if (result === 'missing-peers')
98
+ warn(MISSING_PEERS);
99
+ else if (result instanceof Error) {
100
+ warn(`could not read prop types (${result.message}). Showing names only.`);
101
+ }
102
+ else
103
+ typed = result;
104
+ }
105
+ else {
106
+ warn('`$props()` has no type annotation, so props show without types.');
107
+ }
108
+ const defaults = new Map(parsed.destructured.map((d) => [d.name, d]));
109
+ if (typed) {
110
+ for (const prop of typed.props) {
111
+ if (prop.internal)
112
+ continue;
113
+ const destructured = defaults.get(prop.name);
114
+ const fallback = prop.defaultTag ?? destructured?.default;
115
+ extracted.props.push(clean({
116
+ name: prop.name,
117
+ type: prop.type,
118
+ default: fallback,
119
+ required: !prop.optional && fallback === undefined,
120
+ bindable: destructured?.bindable ?? false,
121
+ deprecated: prop.deprecated === undefined ? undefined : renderMarkdown(prop.deprecated),
122
+ description: prop.description ? renderMarkdown(prop.description) : undefined
123
+ }));
124
+ }
125
+ extracted.inherited = typed.inherited;
126
+ }
127
+ else {
128
+ for (const d of parsed.destructured) {
129
+ extracted.props.push(clean({ name: d.name, default: d.default, required: false, bindable: d.bindable }));
130
+ }
131
+ }
132
+ return {
133
+ props: extracted,
134
+ warnings,
135
+ watchFiles: [...new Set([file, ...(typed?.files ?? [])])]
136
+ };
137
+ }
138
+ }
139
+ /** `file-tree-item.svelte` → `FileTreeItem`. */
140
+ export function componentName(file) {
141
+ return path
142
+ .basename(file, '.svelte')
143
+ .split(/[^A-Za-z0-9]+/)
144
+ .filter(Boolean)
145
+ .map((word) => word[0].toUpperCase() + word.slice(1))
146
+ .join('');
147
+ }
148
+ function renderMarkdown(text) {
149
+ return marked.parse(text, { async: false, gfm: true })
150
+ .trim()
151
+ .replace(/^<p>([\s\S]*)<\/p>$/, (match, inner) => inner.includes('<p>') ? match : inner);
152
+ }
153
+ function clean(prop) {
154
+ return Object.fromEntries(Object.entries(prop).filter(([, value]) => value !== undefined));
155
+ }
156
+ function message(error) {
157
+ return error instanceof Error ? error.message : String(error);
158
+ }
@@ -0,0 +1,17 @@
1
+ export interface DestructuredProp {
2
+ name: string;
3
+ /** Source text of the default value. */
4
+ default?: string;
5
+ bindable: boolean;
6
+ }
7
+ export interface ParsedComponent {
8
+ /** Whether the instance script calls `$props()` at all. */
9
+ hasProps: boolean;
10
+ /** Whether the `$props()` declaration carries a type annotation. */
11
+ typed: boolean;
12
+ /** Destructured props, in source order. Empty for `let props = $props()`. */
13
+ destructured: DestructuredProp[];
14
+ /** Type parameter names from `generics="…"`. */
15
+ typeParameters: string[];
16
+ }
17
+ export declare function parseComponent(code: string, filename: string): ParsedComponent;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * The syntax half of props extraction: what can be read from a component's
3
+ * source without TypeScript. That is the `$props()` destructuring (names,
4
+ * default values, `$bindable()`) and the `generics`
5
+ * attribute. Types, optionality and descriptions come from ./typescript.ts.
6
+ */
7
+ import { parse } from 'svelte/compiler';
8
+ import { splitTopLevel } from '../../utils/type-text.js';
9
+ export function parseComponent(code, filename) {
10
+ const ast = parse(code, { modern: true, filename });
11
+ const typeParameters = genericsOf(ast.instance?.attributes ?? [], code);
12
+ const empty = {
13
+ hasProps: false,
14
+ typed: false,
15
+ destructured: [],
16
+ typeParameters
17
+ };
18
+ if (!ast.instance)
19
+ return empty;
20
+ const declarator = findPropsDeclarator(ast.instance.content);
21
+ if (!declarator)
22
+ return empty;
23
+ const id = declarator.id;
24
+ const typed = Boolean(id.typeAnnotation);
25
+ if (id.type !== 'ObjectPattern')
26
+ return { ...empty, hasProps: true, typed };
27
+ const destructured = [];
28
+ for (const property of id.properties) {
29
+ // `...rest` is read from the type instead (inherited attributes).
30
+ if (property.type === 'RestElement')
31
+ continue;
32
+ const name = keyName(property.key);
33
+ if (name === undefined)
34
+ continue;
35
+ destructured.push({ name, ...defaultOf(property.value, code) });
36
+ }
37
+ return { hasProps: true, typed, destructured, typeParameters };
38
+ }
39
+ function findPropsDeclarator(node) {
40
+ let found;
41
+ walk(node, (n) => {
42
+ if (found)
43
+ return false;
44
+ if (n.type === 'VariableDeclarator' && isCall(n.init, '$props')) {
45
+ found = n;
46
+ return false;
47
+ }
48
+ return true;
49
+ });
50
+ return found;
51
+ }
52
+ function walk(node, visit) {
53
+ if (!visit(node))
54
+ return;
55
+ for (const value of Object.values(node)) {
56
+ if (Array.isArray(value)) {
57
+ for (const child of value)
58
+ if (isNode(child))
59
+ walk(child, visit);
60
+ }
61
+ else if (isNode(value)) {
62
+ walk(value, visit);
63
+ }
64
+ }
65
+ }
66
+ function isNode(value) {
67
+ return typeof value === 'object' && value !== null && typeof value.type === 'string';
68
+ }
69
+ function isCall(node, callee) {
70
+ if (!node || node.type !== 'CallExpression')
71
+ return false;
72
+ const fn = node.callee;
73
+ return fn.type === 'Identifier' && fn.name === callee;
74
+ }
75
+ function keyName(key) {
76
+ if (key.type === 'Identifier')
77
+ return key.name;
78
+ if (key.type === 'Literal' && typeof key.value === 'string')
79
+ return key.value;
80
+ return undefined;
81
+ }
82
+ function defaultOf(value, code) {
83
+ if (value.type !== 'AssignmentPattern')
84
+ return { bindable: false };
85
+ const right = value.right;
86
+ if (isCall(right, '$bindable')) {
87
+ const [fallback] = right.arguments;
88
+ return { bindable: true, default: fallback ? source(fallback, code) : undefined };
89
+ }
90
+ return { bindable: false, default: source(right, code) };
91
+ }
92
+ function source(node, code) {
93
+ return code.slice(node.start, node.end);
94
+ }
95
+ /** `generics="T extends string, U = number"` → `['T', 'U']`. */
96
+ function genericsOf(attributes, code) {
97
+ for (const attr of attributes) {
98
+ if (attr.type !== 'Attribute' || attr.name !== 'generics')
99
+ continue;
100
+ const [text] = attr.value;
101
+ if (!text || text.type !== 'Text')
102
+ return [];
103
+ return splitTopLevel(String(text.data ?? source(text, code)), ',')
104
+ .map((param) => /^\s*([A-Za-z_$][\w$]*)/.exec(param)?.[1])
105
+ .filter((name) => Boolean(name));
106
+ }
107
+ return [];
108
+ }
@@ -0,0 +1,54 @@
1
+ import type TS from 'typescript';
2
+ type Ts = typeof TS;
3
+ type Svelte2tsx = typeof import('svelte2tsx').svelte2tsx;
4
+ export interface TypedProp {
5
+ name: string;
6
+ type: string;
7
+ optional: boolean;
8
+ /** JSDoc description, as markdown. */
9
+ description?: string;
10
+ /** `@deprecated` text (`''` when the tag is bare). */
11
+ deprecated?: string;
12
+ /** `@default` text, which wins over the destructured default. */
13
+ defaultTag?: string;
14
+ /** Tagged `@internal`: left out of the table. */
15
+ internal: boolean;
16
+ }
17
+ export interface TypedInheritedAttributes {
18
+ kind: 'element' | 'type';
19
+ label: string;
20
+ }
21
+ export interface TypedComponent {
22
+ props: TypedProp[];
23
+ inherited: TypedInheritedAttributes[];
24
+ /** Project files the component imports, directly or not, for HMR. */
25
+ files: string[];
26
+ }
27
+ export interface PropsTypeChecker {
28
+ /**
29
+ * Read several components against one program: building the program is the
30
+ * expensive part, so concurrent props imports share it. Each entry is
31
+ * `undefined` when that component has no typed `$props()`, or the error it
32
+ * threw.
33
+ */
34
+ read(files: string[]): (TypedComponent | undefined | Error)[];
35
+ }
36
+ export declare const MISSING_PEERS: string;
37
+ /** The optional peer dependencies, plus svelte2tsx's ambient shims. */
38
+ export interface Peers {
39
+ ts: Ts;
40
+ svelte2tsx: Svelte2tsx;
41
+ shims: string[];
42
+ }
43
+ /** Load the optional peers once per process; `undefined` when not installed. */
44
+ export declare function loadPeers(): Promise<Peers | undefined>;
45
+ /** A file's modification time, or `-1` when it can't be read. */
46
+ export declare function mtimeOf(file: string): number;
47
+ /** Whether a path lies inside a `node_modules` directory, on any platform. */
48
+ export declare function inNodeModules(file: string): boolean;
49
+ export declare function createPropsTypeChecker({ ts, svelte2tsx, shims }: Peers, options: {
50
+ root: string;
51
+ tsconfig?: string;
52
+ svelteVersion: string;
53
+ }): PropsTypeChecker;
54
+ export {};