From c7a619da591d2b3ba879d591c389d91f04212b91 Mon Sep 17 00:00:00 2001 From: Taku Fukada Date: Tue, 9 Jul 2024 13:36:25 +0900 Subject: [PATCH 01/29] svelte: autodocs with svelte2tsx --- code/frameworks/svelte-vite/package.json | 5 +- .../svelte-vite/src/plugins/svelte-docgen.ts | 108 +++++---- .../svelte-vite/src/plugins/ts2doc.ts | 209 ++++++++++++++++++ .../ButtonTypeScriptRunes.svelte | 53 +++++ .../ts-runes-docs.stories.js | 13 ++ code/yarn.lock | 23 +- 6 files changed, 359 insertions(+), 52 deletions(-) create mode 100644 code/frameworks/svelte-vite/src/plugins/ts2doc.ts create mode 100644 code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ButtonTypeScriptRunes.svelte create mode 100644 code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-runes-docs.stories.js diff --git a/code/frameworks/svelte-vite/package.json b/code/frameworks/svelte-vite/package.json index 0d15aacccf1..b08f95da8c1 100644 --- a/code/frameworks/svelte-vite/package.json +++ b/code/frameworks/svelte-vite/package.json @@ -51,14 +51,15 @@ "@storybook/svelte": "workspace:*", "magic-string": "^0.30.0", "svelte-preprocess": "^5.1.1", + "svelte2tsx": "^0.7.13", "sveltedoc-parser": "^4.2.1", - "ts-dedent": "^2.2.0" + "ts-dedent": "^2.2.0", + "typescript": "^5.3.2" }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^3.0.1", "@types/node": "^18.0.0", "svelte": "^5.0.0-next.65", - "typescript": "^5.3.2", "vite": "^4.0.0" }, "peerDependencies": { diff --git a/code/frameworks/svelte-vite/src/plugins/svelte-docgen.ts b/code/frameworks/svelte-vite/src/plugins/svelte-docgen.ts index 27db39238e7..923aed7617d 100644 --- a/code/frameworks/svelte-vite/src/plugins/svelte-docgen.ts +++ b/code/frameworks/svelte-vite/src/plugins/svelte-docgen.ts @@ -7,6 +7,7 @@ import type { SvelteComponentDoc, SvelteParserOptions } from 'sveltedoc-parser'; import { logger } from 'storybook/internal/node-logger'; import { preprocess } from 'svelte/compiler'; import { replace, typescript } from 'svelte-preprocess'; +import { ts2doc } from './ts2doc'; /* * Patch sveltedoc-parser internal options. @@ -73,69 +74,78 @@ export async function svelteDocgen(svelteOptions: Record = {}): Pro async transform(src: string, id: string) { if (!filter(id)) return undefined; - if (preprocessOptions && !docPreprocessOptions) { - /* - * We can't use vitePreprocess() for the documentation - * because it uses esbuild which removes jsdoc. - * - * By default, only typescript is transpiled, and style tags are removed. - * - * Note: these preprocessors are only used to make the component - * compatible to sveltedoc-parser (no ts), not to compile - * the component. - */ - docPreprocessOptions = [replace([[//gims, '']])]; - - try { - const ts = require.resolve('typescript'); - if (ts) { - docPreprocessOptions.unshift(typescript()); + const resource = path.relative(cwd, id); + const rawSource = fs.readFileSync(resource).toString(); + + // Use ts2doc to get props information + const { hasRuneProps, data } = ts2doc(rawSource); + + let componentDoc: SvelteComponentDoc & { keywords?: string[] } = {}; + + if (!hasRuneProps) { + // legacy mode (slots and events) + + if (preprocessOptions && !docPreprocessOptions) { + /* + * We can't use vitePreprocess() for the documentation + * because it uses esbuild which removes jsdoc. + * + * By default, only typescript is transpiled, and style tags are removed. + * + * Note: these preprocessors are only used to make the component + * compatible to sveltedoc-parser (no ts), not to compile + * the component. + */ + docPreprocessOptions = [replace([[//gims, '']])]; + + try { + const ts = require.resolve('typescript'); + if (ts) { + docPreprocessOptions.unshift(typescript()); + } + } catch { + // this will error in JavaScript-only projects, this is okay } - } catch { - // this will error in JavaScript-only projects, this is okay } - } - const resource = path.relative(cwd, id); - - let docOptions; - if (docPreprocessOptions) { - const rawSource = fs.readFileSync(resource).toString(); - - const { code: fileContent } = await preprocess(rawSource, docPreprocessOptions, { - filename: resource, - }); + let docOptions; + if (docPreprocessOptions) { + const { code: fileContent } = await preprocess(rawSource, docPreprocessOptions, { + filename: resource, + }); + + docOptions = { + fileContent, + }; + } else { + docOptions = { filename: resource }; + } - docOptions = { - fileContent, + // set SvelteDoc options + const options: SvelteParserOptions = { + ...docOptions, + version: 3, }; - } else { - docOptions = { filename: resource }; - } - // set SvelteDoc options - const options: SvelteParserOptions = { - ...docOptions, - version: 3, - }; - - const s = new MagicString(src); - - let componentDoc: SvelteComponentDoc & { keywords?: string[] }; - try { - componentDoc = await svelteDoc.parse(options); - } catch (error: any) { - componentDoc = { keywords: [], data: [] }; - if (logDocgen) { - logger.error(error); + try { + componentDoc = await svelteDoc.parse(options); + } catch (error: any) { + componentDoc = { keywords: [], data: [] }; + if (logDocgen) { + logger.error(error); + } } } + // Always use props info from ts2doc + componentDoc.data = data; + // get filename for source content const file = path.basename(resource); componentDoc.name = path.basename(file); + const s = new MagicString(src); const componentName = getNameFromFilename(resource); s.append(`;${componentName}.__docgen = ${JSON.stringify(componentDoc)}`); diff --git a/code/frameworks/svelte-vite/src/plugins/ts2doc.ts b/code/frameworks/svelte-vite/src/plugins/ts2doc.ts new file mode 100644 index 00000000000..a1096724b02 --- /dev/null +++ b/code/frameworks/svelte-vite/src/plugins/ts2doc.ts @@ -0,0 +1,209 @@ +import ts from 'typescript'; +import { svelte2tsx } from 'svelte2tsx'; +import { VERSION } from 'svelte/compiler'; +import { type SvelteDataItem } from 'sveltedoc-parser'; + +function getInitializerValue(initializer?: ts.Node) { + if (initializer === undefined) { + return undefined; + } + if (ts.isNumericLiteral(initializer)) { + return Number(initializer.text); + } + if (ts.isStringLiteral(initializer)) { + return `"${initializer.text}"`; + } + if (initializer.kind === ts.SyntaxKind.TrueKeyword) { + return true; + } + if (initializer.kind === ts.SyntaxKind.FalseKeyword) { + return false; + } + return initializer.getText(); +} + +/* + * Make sveltedoc-parser compatible data from .svelte file with svelte2tsx and TypeScript. + */ +export function ts2doc(fileContent: string) { + const shimFilename = require.resolve('svelte2tsx/svelte-shims-v4.d.ts'); + const shimContent = ts.sys.readFile(shimFilename) || ''; + const shimSourceFile = ts.createSourceFile( + shimFilename, + shimContent, + ts.ScriptTarget.Latest, + true + ); + + const tsx = svelte2tsx(fileContent, { + version: VERSION, + isTsFile: true, + mode: 'dts', + }); + const currentSourceFile = ts.createSourceFile('tmp.ts', tsx.code, ts.ScriptTarget.Latest, true); + + const host = ts.createCompilerHost({}); + host.getSourceFile = (fileName, languageVersion, onError) => { + if (fileName === 'tmp.ts') { + return currentSourceFile; + } else if (fileName === shimContent) { + return shimSourceFile; + } else { + // ignore other files + return; + } + }; + + // Create a program with the custom compiler host + const program = ts.createProgram([currentSourceFile.fileName, shimSourceFile.fileName], {}, host); + const checker = program.getTypeChecker(); + + const propMap: Map = new Map(); + + const renderFunction = currentSourceFile.statements.find((statement) => { + return ts.isFunctionDeclaration(statement) && statement.name?.text === 'render'; + }) as ts.FunctionDeclaration | undefined; + if (renderFunction === undefined) { + return { + runeUsed: false, + data: [], + }; + } + + function getPropsFromTypeLiteral(type: ts.TypeLiteralNode) { + const members = type.members; + + members.forEach((member) => { + if (ts.isPropertySignature(member)) { + const name = member.name.getText(); + let typeString = ''; + if (member.type !== undefined) { + const memberType = checker.getTypeFromTypeNode(member.type); + typeString = checker.typeToString(memberType); + } + const jsDoc = ts.getJSDocCommentsAndTags(member); + const docComments = jsDoc + .map((doc) => { + let s = ts.getTextOfJSDocComment(doc.comment) || ''; + doc.forEachChild((child) => { + // Type information from JSDoc comment + if (ts.isJSDocTypeTag(child)) { + let t = ''; + child.typeExpression.forEachChild((ty) => { + t += ty.getText(); + }); + if (t.length > 0) { + typeString = t; + } + s += ts.getTextOfJSDocComment(child.comment); + } + }); + return s; + }) + .join('\n'); + + // mimic the structure of sveltedoc-parser. + propMap.set(name, { + name: name, + visibility: 'public', + description: docComments, + keywords: [], + kind: 'let', + type: { kind: 'type', text: typeString, type: typeString }, + static: false, + readonly: false, + importPath: undefined, + originalName: undefined, + localName: undefined, + defaultValue: undefined, + }); + } + }); + } + + const hasRuneProps = tsx.code.includes('$$ComponentProps'); + if (hasRuneProps) { + // Rune props + + // Try to get prop types from 'type $$ComponentProps = { ... }' + function visitPropsTypeAlias(node: ts.Node) { + if (ts.isTypeAliasDeclaration(node) && node.name.text === '$$ComponentProps') { + const typeAlias = node as ts.TypeAliasDeclaration; + getPropsFromTypeLiteral(typeAlias.type as ts.TypeLiteralNode); + } + ts.forEachChild(node, visitPropsTypeAlias); + } + visitPropsTypeAlias(renderFunction); + + // Obtain default values from 'let { ... }: $$ComponentProps = ...' + function visitObjectBinding(node: ts.Node) { + if (ts.isVariableDeclaration(node) && ts.isObjectBindingPattern(node.name)) { + const type = node.type; + if (type && ts.isTypeReferenceNode(type) && type.getText() === '$$ComponentProps') { + const bindingPattern = node.name; + bindingPattern.elements.forEach((element) => { + if (ts.isBindingElement(element)) { + const name = element.propertyName?.getText() || element.name.getText(); + const initializer = getInitializerValue(element.initializer); + const prop = propMap.get(name); + if (initializer !== undefined && prop) { + prop.defaultValue = initializer; + } + } + }); + } + } + ts.forEachChild(node, visitObjectBinding); + } + visitObjectBinding(currentSourceFile); + } else { + // Legacy props (data) + + // Try to get prop types from 'return { ... } as { props: ... }' + renderFunction.body?.forEachChild((statement) => { + if (ts.isReturnStatement(statement)) { + const returnExpression = statement.expression; + if (returnExpression === undefined) { + return; + } + if (ts.isObjectLiteralExpression(returnExpression)) { + returnExpression.properties.forEach((property) => { + if (ts.isPropertyAssignment(property) && property.name.getText() === 'props') { + const propsObject = property.initializer; + if ( + ts.isAsExpression(propsObject) && + ts.isObjectLiteralExpression(propsObject.expression) + ) { + const type = propsObject.type; + if (ts.isTypeLiteralNode(type)) { + getPropsFromTypeLiteral(type); + } + } + } + }); + } + } + }); + + // Try to get default values from 'let = ...' + renderFunction.body?.forEachChild((statement) => { + if (ts.isVariableStatement(statement)) { + statement.declarationList.declarations.forEach((declaration) => { + if (ts.isVariableDeclaration(declaration) && ts.isIdentifier(declaration.name)) { + const name = declaration.name.getText(); + const prop = propMap.get(name); + if (prop && prop.defaultValue === undefined) { + const initializer = getInitializerValue(declaration.initializer); + prop.defaultValue = initializer; + } + } + }); + } + }); + } + + return { + hasRuneProps, + data: Array.from(propMap.values()), + }; +} diff --git a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ButtonTypeScriptRunes.svelte b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ButtonTypeScriptRunes.svelte new file mode 100644 index 00000000000..91222371036 --- /dev/null +++ b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ButtonTypeScriptRunes.svelte @@ -0,0 +1,53 @@ + + + diff --git a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-runes-docs.stories.js b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-runes-docs.stories.js new file mode 100644 index 00000000000..daa8ccc4db8 --- /dev/null +++ b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-runes-docs.stories.js @@ -0,0 +1,13 @@ +import ButtonTypescriptRunes from './ButtonTypeScriptRunes.svelte'; + +export default { + title: 'stories/renderers/svelte/ts-runes-docs', + component: ButtonTypescriptRunes, + args: { + primary: true, + label: 'Button', + }, + tags: ['autodocs'], +}; + +export const Primary = {}; diff --git a/code/yarn.lock b/code/yarn.lock index f22ee0dc024..6b5525e5330 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6644,6 +6644,7 @@ __metadata: magic-string: "npm:^0.30.0" svelte: "npm:^5.0.0-next.65" svelte-preprocess: "npm:^5.1.1" + svelte2tsx: "npm:^0.7.13" sveltedoc-parser: "npm:^4.2.1" ts-dedent: "npm:^2.2.0" typescript: "npm:^5.3.2" @@ -12437,6 +12438,13 @@ __metadata: languageName: node linkType: hard +"dedent-js@npm:^1.0.1": + version: 1.0.1 + resolution: "dedent-js@npm:1.0.1" + checksum: 10c0/a8cff2e02d5a1ce64615c5c53c9789e7ef1abb9ae7bf2322dc991fcbaf08d901ace1a679c1e021de15a85db7787b8ccfb02011e1f394afef0f698fc857a47009 + languageName: node + linkType: hard + "dedent@npm:^0.7.0": version: 0.7.0 resolution: "dedent@npm:0.7.0" @@ -21898,7 +21906,7 @@ __metadata: languageName: node linkType: hard -"pascal-case@npm:^3.1.2": +"pascal-case@npm:^3.1.1, pascal-case@npm:^3.1.2": version: 3.1.2 resolution: "pascal-case@npm:3.1.2" dependencies: @@ -26155,6 +26163,19 @@ __metadata: languageName: node linkType: hard +"svelte2tsx@npm:^0.7.13": + version: 0.7.13 + resolution: "svelte2tsx@npm:0.7.13" + dependencies: + dedent-js: "npm:^1.0.1" + pascal-case: "npm:^3.1.1" + peerDependencies: + svelte: ^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0 + typescript: ^4.9.4 || ^5.0.0 + checksum: 10c0/a6df2873e551d116bae8b4664b1838a52c6aa50ad50e30e41611e8bf390c525ceb97b53d034f2e0030a640e157e1f88ac33d52b6e4abb54861b3fc120aae82f7 + languageName: node + linkType: hard + "svelte@npm:^4.0.0": version: 4.2.8 resolution: "svelte@npm:4.2.8" From ad9429bf490f3343bff2ec61048c95746df9bfab Mon Sep 17 00:00:00 2001 From: Taku Fukada Date: Wed, 10 Jul 2024 20:20:58 +0900 Subject: [PATCH 02/29] rename: ts2doc -> generateDocgen --- .../svelte-vite/src/plugins/{ts2doc.ts => generateDocgen.ts} | 5 ++++- code/frameworks/svelte-vite/src/plugins/svelte-docgen.ts | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) rename code/frameworks/svelte-vite/src/plugins/{ts2doc.ts => generateDocgen.ts} (98%) diff --git a/code/frameworks/svelte-vite/src/plugins/ts2doc.ts b/code/frameworks/svelte-vite/src/plugins/generateDocgen.ts similarity index 98% rename from code/frameworks/svelte-vite/src/plugins/ts2doc.ts rename to code/frameworks/svelte-vite/src/plugins/generateDocgen.ts index a1096724b02..4762945c2e6 100644 --- a/code/frameworks/svelte-vite/src/plugins/ts2doc.ts +++ b/code/frameworks/svelte-vite/src/plugins/generateDocgen.ts @@ -7,6 +7,9 @@ function getInitializerValue(initializer?: ts.Node) { if (initializer === undefined) { return undefined; } + if (initializer.kind === ts.SyntaxKind.NullKeyword) { + return null; + } if (ts.isNumericLiteral(initializer)) { return Number(initializer.text); } @@ -25,7 +28,7 @@ function getInitializerValue(initializer?: ts.Node) { /* * Make sveltedoc-parser compatible data from .svelte file with svelte2tsx and TypeScript. */ -export function ts2doc(fileContent: string) { +export function generateDocgen(fileContent: string) { const shimFilename = require.resolve('svelte2tsx/svelte-shims-v4.d.ts'); const shimContent = ts.sys.readFile(shimFilename) || ''; const shimSourceFile = ts.createSourceFile( diff --git a/code/frameworks/svelte-vite/src/plugins/svelte-docgen.ts b/code/frameworks/svelte-vite/src/plugins/svelte-docgen.ts index 923aed7617d..c32954486b1 100644 --- a/code/frameworks/svelte-vite/src/plugins/svelte-docgen.ts +++ b/code/frameworks/svelte-vite/src/plugins/svelte-docgen.ts @@ -7,7 +7,7 @@ import type { SvelteComponentDoc, SvelteParserOptions } from 'sveltedoc-parser'; import { logger } from 'storybook/internal/node-logger'; import { preprocess } from 'svelte/compiler'; import { replace, typescript } from 'svelte-preprocess'; -import { ts2doc } from './ts2doc'; +import { generateDocgen } from './generateDocgen'; /* * Patch sveltedoc-parser internal options. @@ -78,7 +78,7 @@ export async function svelteDocgen(svelteOptions: Record = {}): Pro const rawSource = fs.readFileSync(resource).toString(); // Use ts2doc to get props information - const { hasRuneProps, data } = ts2doc(rawSource); + const { hasRuneProps, data } = generateDocgen(rawSource); let componentDoc: SvelteComponentDoc & { keywords?: string[] } = {}; From e38d5c0b6b5fcb5e74283f3169f15d4d881855e5 Mon Sep 17 00:00:00 2001 From: Taku Fukada Date: Fri, 12 Jul 2024 02:38:14 +0900 Subject: [PATCH 03/29] svelte2tsx + babel/parser (remove typescript dep) --- code/frameworks/svelte-vite/package.json | 8 +- .../svelte-vite/src/plugins/generateDocgen.ts | 539 ++++++++++++------ .../svelte-vite/src/plugins/svelte-docgen.ts | 76 ++- .../ButtonTypeScriptRunes.svelte | 6 +- .../svelte/src/docs/extractArgTypes.ts | 15 +- code/yarn.lock | 156 +++++ 6 files changed, 606 insertions(+), 194 deletions(-) diff --git a/code/frameworks/svelte-vite/package.json b/code/frameworks/svelte-vite/package.json index b08f95da8c1..95097c3da97 100644 --- a/code/frameworks/svelte-vite/package.json +++ b/code/frameworks/svelte-vite/package.json @@ -47,19 +47,23 @@ "prep": "node --loader ../../../scripts/node_modules/esbuild-register/loader.js -r ../../../scripts/node_modules/esbuild-register/register.js ../../../scripts/prepare/bundle.ts" }, "dependencies": { + "@babel/traverse": "^7.24.7", "@storybook/builder-vite": "workspace:*", "@storybook/svelte": "workspace:*", + "comment-parser": "^1.4.1", "magic-string": "^0.30.0", "svelte-preprocess": "^5.1.1", "svelte2tsx": "^0.7.13", "sveltedoc-parser": "^4.2.1", - "ts-dedent": "^2.2.0", - "typescript": "^5.3.2" + "ts-dedent": "^2.2.0" }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^3.0.1", + "@types/babel__generator": "^7.6.8", + "@types/babel__traverse": "^7.20.6", "@types/node": "^18.0.0", "svelte": "^5.0.0-next.65", + "typescript": "^5.3.2", "vite": "^4.0.0" }, "peerDependencies": { diff --git a/code/frameworks/svelte-vite/src/plugins/generateDocgen.ts b/code/frameworks/svelte-vite/src/plugins/generateDocgen.ts index 4762945c2e6..7fd191499ab 100644 --- a/code/frameworks/svelte-vite/src/plugins/generateDocgen.ts +++ b/code/frameworks/svelte-vite/src/plugins/generateDocgen.ts @@ -1,212 +1,399 @@ -import ts from 'typescript'; import { svelte2tsx } from 'svelte2tsx'; import { VERSION } from 'svelte/compiler'; -import { type SvelteDataItem } from 'sveltedoc-parser'; -function getInitializerValue(initializer?: ts.Node) { - if (initializer === undefined) { - return undefined; - } - if (initializer.kind === ts.SyntaxKind.NullKeyword) { - return null; +import { parse } from '@babel/parser'; +import traverse from '@babel/traverse'; +import generate from '@babel/generator'; +import type { Comment, Expression, TSType } from '@babel/types'; +import { parse as parseComment } from 'comment-parser'; + +export type Docgen = { + name?: string; + props: PropInfo[]; +}; + +export type PropInfo = { + name: string; + type?: Type; + defaultValue?: string; + description?: string; + runes?: boolean; +}; + +export type EventInfo = { + name: string; +}; + +type BaseType = { + /** Permits undefined or not */ + optional?: boolean; +}; + +type ScalarType = BaseType & { + type: 'number' | 'string' | 'boolean' | 'symbol' | 'any' | 'null'; +}; + +type FunctionType = BaseType & { + type: 'function'; + text: string; +}; + +type LiteralType = BaseType & { + type: 'literal'; + value: string | number | boolean; + text: string; +}; + +type ArrayType = BaseType & { + type: 'array'; +}; + +type ObjectType = BaseType & { + type: 'object'; +}; + +type UnionType = BaseType & { + type: 'union'; + types: Type[]; +}; + +type IntersectionType = BaseType & { + type: 'intersection'; + types: Type[]; +}; + +type ReferenceType = BaseType & { + type: 'reference'; + text: string; +}; + +type OtherType = BaseType & { + type: 'other'; + text: string; +}; + +export type Type = + | ScalarType + | LiteralType + | FunctionType + | ArrayType + | ObjectType + | OtherType + | ReferenceType + | UnionType + | IntersectionType; + +/** + * Try to infer a type from a initializer expression (for when there is no type annotation) + */ +function inferTypeFromInitializer(expr: Expression): Type | undefined { + switch (expr.type) { + case 'ObjectExpression': + return { type: 'object' }; + case 'StringLiteral': + return { type: 'string' }; + case 'TemplateLiteral': + return { type: 'string' }; + case 'NumericLiteral': + return { type: 'number' }; + case 'BooleanLiteral': + return { type: 'boolean' }; + case 'ArrayExpression': + return { type: 'array' }; + case 'NullLiteral': + return undefined; // cannot infer + default: + return undefined; } - if (ts.isNumericLiteral(initializer)) { - return Number(initializer.text); +} + +function parseType(type: TSType): Type | undefined { + switch (type.type) { + case 'TSNumberKeyword': + return { type: 'number' }; + case 'TSStringKeyword': + return { type: 'string' }; + case 'TSBooleanKeyword': + return { type: 'boolean' }; + case 'TSSymbolKeyword': + return { type: 'symbol' }; + case 'TSAnyKeyword': + return { type: 'any' }; + case 'TSNullKeyword': + return { type: 'null' }; + case 'TSObjectKeyword': + return { type: 'object' }; + case 'TSFunctionType': + return { type: 'function', text: generate(type).code }; + case 'TSTypeReference': + return { type: 'reference', text: generate(type).code }; + case 'TSLiteralType': + const text = generate(type.literal).code; + switch (type.literal.type) { + case 'StringLiteral': + return { + type: 'literal', + value: type.literal.value, + text, + }; + case 'NumericLiteral': + return { + type: 'literal', + value: type.literal.value, + text, + }; + case 'BooleanLiteral': + return { type: 'literal', value: type.literal.value, text: text }; + } + return undefined; } - if (ts.isStringLiteral(initializer)) { - return `"${initializer.text}"`; + if (type.type == 'TSTypeLiteral') { + return { type: 'object' }; + } else if (type.type == 'TSUnionType') { + // e.g. `string | number | undefined` + let optional: boolean | undefined = undefined; + const types: Type[] = []; + type.types.forEach((t) => { + if (t.type === 'TSUndefinedKeyword') { + optional = true; + } else { + const ty = parseType(t); + if (ty) { + types.push(ty); + } + } + }); + if (types.length === 1) { + // e.g. `string | undefined` => string? + return { ...types[0], optional }; + } else if (types.length > 1) { + return { type: 'union', optional, types }; + } + } else if (type.type == 'TSIntersectionType') { + // e.g. `A & B` + const types: Type[] = type.types + .map((t) => { + return parseType(t); + }) + .filter((t) => t !== undefined); + return { type: 'intersection', types }; } - if (initializer.kind === ts.SyntaxKind.TrueKeyword) { - return true; + return undefined; +} + +/** + * Try to parse a type text like `string | number | undefined` to a Type object. + */ +function tryParseJSDocType(text: string): Type | undefined { + let ast; + try { + ast = parse(`let x: ${text};`, { plugins: ['typescript'] }); + } catch { + return undefined; } - if (initializer.kind === ts.SyntaxKind.FalseKeyword) { - return false; + + const stmt = ast.program.body[0]; + if (stmt.type === 'VariableDeclaration') { + for (const decl of stmt.declarations) { + if (decl.id.type == 'Identifier') { + if (decl.id.typeAnnotation?.type === 'TSTypeAnnotation') { + const a = parseType(decl.id.typeAnnotation.typeAnnotation); + return a; + } + } + } } - return initializer.getText(); + return undefined; } -/* - * Make sveltedoc-parser compatible data from .svelte file with svelte2tsx and TypeScript. +/** + * Extract JSDoc comments */ -export function generateDocgen(fileContent: string) { - const shimFilename = require.resolve('svelte2tsx/svelte-shims-v4.d.ts'); - const shimContent = ts.sys.readFile(shimFilename) || ''; - const shimSourceFile = ts.createSourceFile( - shimFilename, - shimContent, - ts.ScriptTarget.Latest, - true - ); +function parseComments(leadingComments?: Comment[] | null) { + if (!leadingComments) { + return {}; + } + let type: Type | undefined = undefined; + let description: string | undefined = undefined; + + const content = leadingComments + .filter((c) => c.type === 'CommentBlock') + .map((c) => c.value) + .join('\n'); + + if (!content.startsWith('*')) { + // not a TSDoc/JSDoc + return {}; + } + + const blocks = parseComment('/*' + content + '*/'); + const cc: string[] = []; + blocks.forEach((block) => { + cc.push(block.description); + block.tags.forEach((tag) => { + // JSDoc @type tag + if (tag.tag === 'type') { + type ||= tryParseJSDocType(tag.type); + } + cc.push(tag.name); + cc.push(tag.description); + }); + description = cc.filter((c) => c).join(' '); + }); + + return { + description, + type, + }; +} +export function generateDocgen(fileContent: string): Docgen { const tsx = svelte2tsx(fileContent, { version: VERSION, isTsFile: true, - mode: 'dts', + mode: 'ts', }); - const currentSourceFile = ts.createSourceFile('tmp.ts', tsx.code, ts.ScriptTarget.Latest, true); - - const host = ts.createCompilerHost({}); - host.getSourceFile = (fileName, languageVersion, onError) => { - if (fileName === 'tmp.ts') { - return currentSourceFile; - } else if (fileName === shimContent) { - return shimSourceFile; - } else { - // ignore other files - return; - } - }; - - // Create a program with the custom compiler host - const program = ts.createProgram([currentSourceFile.fileName, shimSourceFile.fileName], {}, host); - const checker = program.getTypeChecker(); - const propMap: Map = new Map(); - - const renderFunction = currentSourceFile.statements.find((statement) => { - return ts.isFunctionDeclaration(statement) && statement.name?.text === 'render'; - }) as ts.FunctionDeclaration | undefined; - if (renderFunction === undefined) { - return { - runeUsed: false, - data: [], - }; + let ast: ReturnType; + try { + ast = parse(tsx.code, { + sourceType: 'module', + plugins: ['typescript'], + allowImportExportEverywhere: true, + allowAwaitOutsideFunction: true, + }); + } catch { + return { props: [] }; } - function getPropsFromTypeLiteral(type: ts.TypeLiteralNode) { - const members = type.members; + const propMap: Map = new Map(); + // const events: EventInfo[] = []; - members.forEach((member) => { - if (ts.isPropertySignature(member)) { - const name = member.name.getText(); - let typeString = ''; - if (member.type !== undefined) { - const memberType = checker.getTypeFromTypeNode(member.type); - typeString = checker.typeToString(memberType); - } - const jsDoc = ts.getJSDocCommentsAndTags(member); - const docComments = jsDoc - .map((doc) => { - let s = ts.getTextOfJSDocComment(doc.comment) || ''; - doc.forEachChild((child) => { - // Type information from JSDoc comment - if (ts.isJSDocTypeTag(child)) { - let t = ''; - child.typeExpression.forEachChild((ty) => { - t += ty.getText(); - }); - if (t.length > 0) { - typeString = t; - } - s += ts.getTextOfJSDocComment(child.comment); - } - }); - return s; - }) - .join('\n'); - - // mimic the structure of sveltedoc-parser. - propMap.set(name, { - name: name, - visibility: 'public', - description: docComments, - keywords: [], - kind: 'let', - type: { kind: 'type', text: typeString, type: typeString }, - static: false, - readonly: false, - importPath: undefined, - originalName: undefined, - localName: undefined, - defaultValue: undefined, - }); + traverse(ast, { + FunctionDeclaration: (funcPath) => { + if (funcPath.node.id && funcPath.node.id.name !== 'render') { + return; } - }); - } + funcPath.traverse({ + TSTypeAliasDeclaration(path) { + if ( + path.node.id.name !== '$$ComponentProps' || + path.node.typeAnnotation.type !== 'TSTypeLiteral' + ) { + return; + } + const members = path.node.typeAnnotation.members; + members.forEach((member) => { + if (member.type === 'TSPropertySignature' && member.key.type === 'Identifier') { + const name = member.key.name; - const hasRuneProps = tsx.code.includes('$$ComponentProps'); - if (hasRuneProps) { - // Rune props + const type = + member.typeAnnotation && member.typeAnnotation.type === 'TSTypeAnnotation' + ? parseType(member.typeAnnotation.typeAnnotation) + : undefined; - // Try to get prop types from 'type $$ComponentProps = { ... }' - function visitPropsTypeAlias(node: ts.Node) { - if (ts.isTypeAliasDeclaration(node) && node.name.text === '$$ComponentProps') { - const typeAlias = node as ts.TypeAliasDeclaration; - getPropsFromTypeLiteral(typeAlias.type as ts.TypeLiteralNode); - } - ts.forEachChild(node, visitPropsTypeAlias); - } - visitPropsTypeAlias(renderFunction); - - // Obtain default values from 'let { ... }: $$ComponentProps = ...' - function visitObjectBinding(node: ts.Node) { - if (ts.isVariableDeclaration(node) && ts.isObjectBindingPattern(node.name)) { - const type = node.type; - if (type && ts.isTypeReferenceNode(type) && type.getText() === '$$ComponentProps') { - const bindingPattern = node.name; - bindingPattern.elements.forEach((element) => { - if (ts.isBindingElement(element)) { - const name = element.propertyName?.getText() || element.name.getText(); - const initializer = getInitializerValue(element.initializer); - const prop = propMap.get(name); - if (initializer !== undefined && prop) { - prop.defaultValue = initializer; + if (type && member.optional) { + type.optional = true; } + + const { description } = parseComments(member.leadingComments); + + propMap.set(name, { + ...propMap.get(name), + name, + type: type, + description, + runes: true, + }); } }); - } - } - ts.forEachChild(node, visitObjectBinding); - } - visitObjectBinding(currentSourceFile); - } else { - // Legacy props (data) - - // Try to get prop types from 'return { ... } as { props: ... }' - renderFunction.body?.forEachChild((statement) => { - if (ts.isReturnStatement(statement)) { - const returnExpression = statement.expression; - if (returnExpression === undefined) { - return; - } - if (ts.isObjectLiteralExpression(returnExpression)) { - returnExpression.properties.forEach((property) => { - if (ts.isPropertyAssignment(property) && property.name.getText() === 'props') { - const propsObject = property.initializer; + }, + VariableDeclaration: (path) => { + if (path.node.kind !== 'let' || path.parent !== funcPath.node.body) { + return; + } + + path.node.declarations.forEach((declaration) => { + if ( + declaration.id.type === 'ObjectPattern' && + declaration.id.typeAnnotation && + declaration.id.typeAnnotation.type === 'TSTypeAnnotation' + ) { + // Get default values from Svelte 5's `let { ... } = $props();` + + const typeAnnotation = declaration.id.typeAnnotation.typeAnnotation; if ( - ts.isAsExpression(propsObject) && - ts.isObjectLiteralExpression(propsObject.expression) + typeAnnotation.type !== 'TSTypeReference' || + typeAnnotation.typeName.type !== 'Identifier' || + typeAnnotation.typeName.name !== '$$ComponentProps' ) { - const type = propsObject.type; - if (ts.isTypeLiteralNode(type)) { - getPropsFromTypeLiteral(type); + return; + } + + declaration.id.properties.forEach((property) => { + if (property.type === 'ObjectProperty') { + if (property.key.type !== 'Identifier') { + return; + } + const name = property.key.name; + if (property.value.type === 'AssignmentPattern') { + const defaultValue = generate(property.value.right, { + compact: true, + }).code; + propMap.set(name, { + ...propMap.get(name), + name, + defaultValue, + runes: true, + }); + } } + }); + } else if (declaration.id.type === 'Identifier') { + // Get props from Svelte 4's `export let a = ...` + + const name = declaration.id.name; + if (tsx.exportedNames.has(name)) { + let typeName: Type | undefined = undefined; + if ( + declaration.id.typeAnnotation && + declaration.id.typeAnnotation.type === 'TSTypeAnnotation' + ) { + const typeAnnotation = declaration.id.typeAnnotation.typeAnnotation; + typeName = parseType(typeAnnotation); + } + + const { description, type: typeFromComment } = parseComments( + path.node.leadingComments + ); + if (typeName === undefined && typeFromComment) { + typeName = typeFromComment; + } + + if (typeName === undefined && declaration.init) { + typeName = inferTypeFromInitializer(declaration.init); + } + + const initializer = declaration.init + ? generate(declaration.init, { compact: true }).code + : undefined; + + propMap.set(name, { + ...propMap.get(name), + type: typeName, + name, + description, + defaultValue: initializer, + }); } } }); - } - } - }); - - // Try to get default values from 'let = ...' - renderFunction.body?.forEachChild((statement) => { - if (ts.isVariableStatement(statement)) { - statement.declarationList.declarations.forEach((declaration) => { - if (ts.isVariableDeclaration(declaration) && ts.isIdentifier(declaration.name)) { - const name = declaration.name.getText(); - const prop = propMap.get(name); - if (prop && prop.defaultValue === undefined) { - const initializer = getInitializerValue(declaration.initializer); - prop.defaultValue = initializer; - } - } - }); - } - }); - } + }, + }); + }, + }); return { - hasRuneProps, - data: Array.from(propMap.values()), + props: Array.from(propMap.values()), }; } diff --git a/code/frameworks/svelte-vite/src/plugins/svelte-docgen.ts b/code/frameworks/svelte-vite/src/plugins/svelte-docgen.ts index c32954486b1..2d5b9d16bf9 100644 --- a/code/frameworks/svelte-vite/src/plugins/svelte-docgen.ts +++ b/code/frameworks/svelte-vite/src/plugins/svelte-docgen.ts @@ -3,11 +3,11 @@ import MagicString from 'magic-string'; import path from 'path'; import fs from 'fs'; import svelteDoc from 'sveltedoc-parser'; -import type { SvelteComponentDoc, SvelteParserOptions } from 'sveltedoc-parser'; +import type { SvelteComponentDoc, SvelteParserOptions, JSDocType } from 'sveltedoc-parser'; import { logger } from 'storybook/internal/node-logger'; import { preprocess } from 'svelte/compiler'; import { replace, typescript } from 'svelte-preprocess'; -import { generateDocgen } from './generateDocgen'; +import { generateDocgen, type Docgen, type Type } from './generateDocgen'; /* * Patch sveltedoc-parser internal options. @@ -59,6 +59,66 @@ function getNameFromFilename(filename: string) { return base[0].toUpperCase() + base.slice(1); } +function formatToSvelteDocParserType(type: Type): JSDocType { + switch (type.type) { + case 'string': + return { kind: 'type', type: 'string', text: 'string' }; + case 'number': + return { kind: 'type', type: 'number', text: 'number' }; + case 'boolean': + return { kind: 'type', type: 'boolean', text: 'boolean' }; + case 'symbol': + return { kind: 'type', type: 'symbol', text: 'symbol' }; + case 'object': + return { kind: 'type', type: 'object', text: 'object' }; + case 'null': + return { kind: 'type', type: 'other', text: 'null' }; + case 'any': + return { kind: 'type', type: 'other', text: 'any' }; + case 'array': + return { kind: 'type', type: 'array', text: 'array' }; + case 'reference': + return { kind: 'type', type: 'other', text: type.text }; + case 'other': + return { kind: 'type', type: 'other', text: type.text }; + case 'function': + return { kind: 'function', text: type.text }; + case 'literal': + return { kind: 'const', type: typeof type.value, value: type.value, text: type.text }; + case 'union': { + const nonNull = type.types.filter((t) => t.type !== 'null'); // ignore null + const text = nonNull.map((t): string => formatToSvelteDocParserType(t).text).join(' | '); + const types = nonNull.map((t) => formatToSvelteDocParserType(t)); + return types.length === 1 ? types[0] : { kind: 'union', type: types, text }; + } + case 'intersection': { + const text = type.types.map((t): string => formatToSvelteDocParserType(t).text).join(' & '); + return { kind: 'type', type: 'intersection', text }; + } + } +} + +function emulateSvelteDocParserDataItems(docgen: Docgen) { + const data = docgen.props.map((p) => { + const required = p.runes && p.defaultValue === undefined && p.type && !p.type.optional; + return { + name: p.name, + visibility: 'public', + description: p.description, + keywords: required ? [{ name: 'required', description: '' }] : [], + kind: 'let', + type: p.type ? formatToSvelteDocParserType(p.type) : undefined, + static: false, + readonly: false, + importPath: undefined, + originalName: undefined, + localName: undefined, + defaultValue: p.defaultValue, + }; + }); + return data; +} + export async function svelteDocgen(svelteOptions: Record = {}): Promise { const cwd = process.cwd(); const { preprocess: preprocessOptions, logDocgen = false } = svelteOptions; @@ -77,13 +137,17 @@ export async function svelteDocgen(svelteOptions: Record = {}): Pro const resource = path.relative(cwd, id); const rawSource = fs.readFileSync(resource).toString(); - // Use ts2doc to get props information - const { hasRuneProps, data } = generateDocgen(rawSource); + // Get props information + const docgen = generateDocgen(rawSource); + const hasRuneProps = docgen.props.some((p) => p.runes); + const data = emulateSvelteDocParserDataItems(docgen); let componentDoc: SvelteComponentDoc & { keywords?: string[] } = {}; if (!hasRuneProps) { - // legacy mode (slots and events) + // Use sveltedoc-parser for generating documentation of events and slots. + // + // Note: Events and slots will be deprecated in Svelte 5. if (preprocessOptions && !docPreprocessOptions) { /* @@ -137,7 +201,7 @@ export async function svelteDocgen(svelteOptions: Record = {}): Pro } } - // Always use props info from ts2doc + // Always use props info from generateDocgen componentDoc.data = data; // get filename for source content diff --git a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ButtonTypeScriptRunes.svelte b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ButtonTypeScriptRunes.svelte index 91222371036..91c1f4b8adf 100644 --- a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ButtonTypeScriptRunes.svelte +++ b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ButtonTypeScriptRunes.svelte @@ -24,11 +24,11 @@ /** * Snippet contents */ - children: Snippet; + children?: Snippet; /** * Text contents */ - label?: string; + label: string; /** * Click handler */ @@ -47,7 +47,7 @@ > {#if label} {label} - {:else} + {:else if children} {@render children()} {/if} diff --git a/code/renderers/svelte/src/docs/extractArgTypes.ts b/code/renderers/svelte/src/docs/extractArgTypes.ts index a81539bd1b4..62dc9e0506d 100644 --- a/code/renderers/svelte/src/docs/extractArgTypes.ts +++ b/code/renderers/svelte/src/docs/extractArgTypes.ts @@ -35,7 +35,7 @@ export const createArgTypes = (docgen: SvelteComponentDoc) => { if (docgen.data) { docgen.data.forEach((item) => { results[item.name] = { - control: parseTypeToControl(item.type), + ...parseTypeToControl(item.type), name: item.name, description: item.description || undefined, type: { @@ -100,25 +100,26 @@ const parseTypeToControl = (type: JSDocType | undefined): any => { if (type.kind === 'type') { switch (type.type) { case 'string': - return { type: 'text' }; - + return { control: { type: 'text' } }; case 'enum': - return { type: 'radio' }; + return { control: { type: 'radio' } }; case 'any': - return { type: 'object' }; + return { control: { type: 'object' } }; default: - return { type: type.type }; + return { control: { type: type.type } }; } } else if (type.kind === 'union') { // @ts-expect-error TODO: fix, this seems like a broke in package update if (Array.isArray(type.type) && !type.type.find((t) => t.type !== 'string')) { return { - type: 'radio', + control: { type: 'select' }, options: type.type .filter((t) => t.kind === 'const') .map((t) => (t as JSDocTypeConst).value), }; } + } else if (type.kind === 'function') { + return { control: null }; } return null; diff --git a/code/yarn.lock b/code/yarn.lock index 6b5525e5330..7b047de42d9 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -370,6 +370,16 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/code-frame@npm:7.24.7" + dependencies: + "@babel/highlight": "npm:^7.24.7" + picocolors: "npm:^1.0.0" + checksum: 10c0/ab0af539473a9f5aeaac7047e377cb4f4edd255a81d84a76058595f8540784cc3fbe8acf73f1e073981104562490aabfb23008cd66dc677a456a4ed5390fdde6 + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.23.5, @babel/compat-data@npm:^7.24.4": version: 7.24.4 resolution: "@babel/compat-data@npm:7.24.4" @@ -470,6 +480,18 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/generator@npm:7.24.7" + dependencies: + "@babel/types": "npm:^7.24.7" + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + jsesc: "npm:^2.5.1" + checksum: 10c0/06b1f3350baf527a3309e50ffd7065f7aee04dd06e1e7db794ddfde7fe9d81f28df64edd587173f8f9295496a7ddb74b9a185d4bf4de7bb619e6d4ec45c8fd35 + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:7.22.5, @babel/helper-annotate-as-pure@npm:^7.18.6, @babel/helper-annotate-as-pure@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-annotate-as-pure@npm:7.22.5" @@ -570,6 +592,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-environment-visitor@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-environment-visitor@npm:7.24.7" + dependencies: + "@babel/types": "npm:^7.24.7" + checksum: 10c0/36ece78882b5960e2d26abf13cf15ff5689bf7c325b10a2895a74a499e712de0d305f8d78bb382dd3c05cfba7e47ec98fe28aab5674243e0625cd38438dd0b2d + languageName: node + linkType: hard + "@babel/helper-function-name@npm:^7.22.5, @babel/helper-function-name@npm:^7.23.0": version: 7.23.0 resolution: "@babel/helper-function-name@npm:7.23.0" @@ -580,6 +611,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-function-name@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-function-name@npm:7.24.7" + dependencies: + "@babel/template": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + checksum: 10c0/e5e41e6cf86bd0f8bf272cbb6e7c5ee0f3e9660414174435a46653efba4f2479ce03ce04abff2aa2ef9359cf057c79c06cb7b134a565ad9c0e8a50dcdc3b43c4 + languageName: node + linkType: hard + "@babel/helper-hoist-variables@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-hoist-variables@npm:7.22.5" @@ -589,6 +630,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-hoist-variables@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-hoist-variables@npm:7.24.7" + dependencies: + "@babel/types": "npm:^7.24.7" + checksum: 10c0/19ee37563bbd1219f9d98991ad0e9abef77803ee5945fd85aa7aa62a67c69efca9a801696a1b58dda27f211e878b3327789e6fd2a6f6c725ccefe36774b5ce95 + languageName: node + linkType: hard + "@babel/helper-member-expression-to-functions@npm:^7.23.0": version: 7.23.0 resolution: "@babel/helper-member-expression-to-functions@npm:7.23.0" @@ -691,6 +741,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-split-export-declaration@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-split-export-declaration@npm:7.24.7" + dependencies: + "@babel/types": "npm:^7.24.7" + checksum: 10c0/0254577d7086bf09b01bbde98f731d4fcf4b7c3fa9634fdb87929801307c1f6202a1352e3faa5492450fa8da4420542d44de604daf540704ff349594a78184f6 + languageName: node + linkType: hard + "@babel/helper-string-parser@npm:^7.23.4": version: 7.23.4 resolution: "@babel/helper-string-parser@npm:7.23.4" @@ -698,6 +757,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-string-parser@npm:7.24.7" + checksum: 10c0/47840c7004e735f3dc93939c77b099bb41a64bf3dda0cae62f60e6f74a5ff80b63e9b7cf77b5ec25a324516381fc994e1f62f922533236a8e3a6af57decb5e1e + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.22.20": version: 7.22.20 resolution: "@babel/helper-validator-identifier@npm:7.22.20" @@ -705,6 +771,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-validator-identifier@npm:7.24.7" + checksum: 10c0/87ad608694c9477814093ed5b5c080c2e06d44cb1924ae8320474a74415241223cc2a725eea2640dd783ff1e3390e5f95eede978bc540e870053152e58f1d651 + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.22.15, @babel/helper-validator-option@npm:^7.23.5": version: 7.23.5 resolution: "@babel/helper-validator-option@npm:7.23.5" @@ -746,6 +819,18 @@ __metadata: languageName: node linkType: hard +"@babel/highlight@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/highlight@npm:7.24.7" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.24.7" + chalk: "npm:^2.4.2" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.0.0" + checksum: 10c0/674334c571d2bb9d1c89bdd87566383f59231e16bcdcf5bb7835babdf03c9ae585ca0887a7b25bdf78f303984af028df52831c7989fecebb5101cc132da9393a + languageName: node + linkType: hard + "@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.11.5, @babel/parser@npm:^7.13.16, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.21.4, @babel/parser@npm:^7.23.0, @babel/parser@npm:^7.23.5, @babel/parser@npm:^7.23.6, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.24.0, @babel/parser@npm:^7.24.1, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.4.5, @babel/parser@npm:^7.6.0, @babel/parser@npm:^7.9.6": version: 7.24.4 resolution: "@babel/parser@npm:7.24.4" @@ -755,6 +840,15 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/parser@npm:7.24.7" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/8b244756872185a1c6f14b979b3535e682ff08cb5a2a5fd97cc36c017c7ef431ba76439e95e419d43000c5b07720495b00cf29a7f0d9a483643d08802b58819b + languageName: node + linkType: hard + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.24.4": version: 7.24.4 resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.24.4" @@ -2224,6 +2318,17 @@ __metadata: languageName: node linkType: hard +"@babel/template@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/template@npm:7.24.7" + dependencies: + "@babel/code-frame": "npm:^7.24.7" + "@babel/parser": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + checksum: 10c0/95b0b3ee80fcef685b7f4426f5713a855ea2cd5ac4da829b213f8fb5afe48a2a14683c2ea04d446dbc7f711c33c5cd4a965ef34dcbe5bc387c9e966b67877ae3 + languageName: node + linkType: hard + "@babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.16.0, @babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.23.9, @babel/traverse@npm:^7.24.0, @babel/traverse@npm:^7.24.1, @babel/traverse@npm:^7.4.5": version: 7.24.1 resolution: "@babel/traverse@npm:7.24.1" @@ -2242,6 +2347,24 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/traverse@npm:7.24.7" + dependencies: + "@babel/code-frame": "npm:^7.24.7" + "@babel/generator": "npm:^7.24.7" + "@babel/helper-environment-visitor": "npm:^7.24.7" + "@babel/helper-function-name": "npm:^7.24.7" + "@babel/helper-hoist-variables": "npm:^7.24.7" + "@babel/helper-split-export-declaration": "npm:^7.24.7" + "@babel/parser": "npm:^7.24.7" + "@babel/types": "npm:^7.24.7" + debug: "npm:^4.3.1" + globals: "npm:^11.1.0" + checksum: 10c0/a5135e589c3f1972b8877805f50a084a04865ccb1d68e5e1f3b94a8841b3485da4142e33413d8fd76bc0e6444531d3adf1f59f359c11ffac452b743d835068ab + languageName: node + linkType: hard + "@babel/types@npm:^7.0.0, @babel/types@npm:^7.11.5, @babel/types@npm:^7.18.9, @babel/types@npm:^7.2.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.4, @babel/types@npm:^7.22.15, @babel/types@npm:^7.22.19, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.23.4, @babel/types@npm:^7.23.6, @babel/types@npm:^7.23.9, @babel/types@npm:^7.24.0, @babel/types@npm:^7.4.4, @babel/types@npm:^7.6.1, @babel/types@npm:^7.7.2, @babel/types@npm:^7.8.3, @babel/types@npm:^7.9.6": version: 7.24.0 resolution: "@babel/types@npm:7.24.0" @@ -2253,6 +2376,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/types@npm:7.24.7" + dependencies: + "@babel/helper-string-parser": "npm:^7.24.7" + "@babel/helper-validator-identifier": "npm:^7.24.7" + to-fast-properties: "npm:^2.0.0" + checksum: 10c0/d9ecbfc3eb2b05fb1e6eeea546836ac30d990f395ef3fe3f75ced777a222c3cfc4489492f72e0ce3d9a5a28860a1ce5f81e66b88cf5088909068b3ff4fab72c1 + languageName: node + linkType: hard + "@base2/pretty-print-object@npm:1.0.1": version: 1.0.1 resolution: "@base2/pretty-print-object@npm:1.0.1" @@ -6637,10 +6771,14 @@ __metadata: version: 0.0.0-use.local resolution: "@storybook/svelte-vite@workspace:frameworks/svelte-vite" dependencies: + "@babel/traverse": "npm:^7.24.7" "@storybook/builder-vite": "workspace:*" "@storybook/svelte": "workspace:*" "@sveltejs/vite-plugin-svelte": "npm:^3.0.1" + "@types/babel__generator": "npm:^7.6.8" + "@types/babel__traverse": "npm:^7.20.6" "@types/node": "npm:^18.0.0" + comment-parser: "npm:^1.4.1" magic-string: "npm:^0.30.0" svelte: "npm:^5.0.0-next.65" svelte-preprocess: "npm:^5.1.1" @@ -7201,6 +7339,15 @@ __metadata: languageName: node linkType: hard +"@types/babel__generator@npm:^7.6.8": + version: 7.6.8 + resolution: "@types/babel__generator@npm:7.6.8" + dependencies: + "@babel/types": "npm:^7.0.0" + checksum: 10c0/f0ba105e7d2296bf367d6e055bb22996886c114261e2cb70bf9359556d0076c7a57239d019dee42bb063f565bade5ccb46009bce2044b2952d964bf9a454d6d2 + languageName: node + linkType: hard + "@types/babel__plugin-transform-runtime@npm:^7": version: 7.9.5 resolution: "@types/babel__plugin-transform-runtime@npm:7.9.5" @@ -7234,6 +7381,15 @@ __metadata: languageName: node linkType: hard +"@types/babel__traverse@npm:^7.20.6": + version: 7.20.6 + resolution: "@types/babel__traverse@npm:7.20.6" + dependencies: + "@babel/types": "npm:^7.20.7" + checksum: 10c0/7ba7db61a53e28cac955aa99af280d2600f15a8c056619c05b6fc911cbe02c61aa4f2823299221b23ce0cce00b294c0e5f618ec772aa3f247523c2e48cf7b888 + languageName: node + linkType: hard + "@types/body-parser@npm:*": version: 1.19.3 resolution: "@types/body-parser@npm:1.19.3" From 43bc7d51a755bf1c21feb37be78d22ed53b24afa Mon Sep 17 00:00:00 2001 From: Taku Fukada Date: Fri, 12 Jul 2024 16:53:10 +0900 Subject: [PATCH 04/29] svelte docgen: enrich stories --- code/frameworks/svelte-vite/package.json | 2 + .../svelte-vite/src/plugins/generateDocgen.ts | 112 ++++++++++-------- .../svelte-vite/src/plugins/svelte-docgen.ts | 16 ++- .../ButtonTypeScript.svelte | 21 +++- .../ButtonTypeScript.svelte | 27 ++++- ...s.svelte => ButtonTypeScriptRunes1.svelte} | 1 + .../ButtonTypeScriptRunes2.svelte | 55 +++++++++ ...s.stories.js => ts-runes1-docs.stories.js} | 4 +- .../ts-runes2-docs.stories.js | 13 ++ .../ButtonTypeScript.svelte | 21 +++- .../template/stories/jsdoc-docs.stories.js | 11 ++ .../template/stories/views/ButtonJSDoc.svelte | 64 ++++++++++ .../stories/views/ButtonJavaScript.svelte | 29 ++++- code/yarn.lock | 41 +++++++ 14 files changed, 354 insertions(+), 63 deletions(-) rename code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/{ButtonTypeScriptRunes.svelte => ButtonTypeScriptRunes1.svelte} (99%) create mode 100644 code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ButtonTypeScriptRunes2.svelte rename code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/{ts-runes-docs.stories.js => ts-runes1-docs.stories.js} (57%) create mode 100644 code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-runes2-docs.stories.js create mode 100644 code/renderers/svelte/template/stories/jsdoc-docs.stories.js create mode 100644 code/renderers/svelte/template/stories/views/ButtonJSDoc.svelte diff --git a/code/frameworks/svelte-vite/package.json b/code/frameworks/svelte-vite/package.json index 601dd8346a8..6c806f3b2f7 100644 --- a/code/frameworks/svelte-vite/package.json +++ b/code/frameworks/svelte-vite/package.json @@ -47,6 +47,8 @@ "prep": "node --loader ../../../scripts/node_modules/esbuild-register/loader.js -r ../../../scripts/node_modules/esbuild-register/register.js ../../../scripts/prepare/bundle.ts" }, "dependencies": { + "@babel/generator": "^7.24.8", + "@babel/parser": "^7.24.8", "@babel/traverse": "^7.24.7", "@storybook/builder-vite": "workspace:*", "@storybook/svelte": "workspace:*", diff --git a/code/frameworks/svelte-vite/src/plugins/generateDocgen.ts b/code/frameworks/svelte-vite/src/plugins/generateDocgen.ts index 7fd191499ab..7a1ab405290 100644 --- a/code/frameworks/svelte-vite/src/plugins/generateDocgen.ts +++ b/code/frameworks/svelte-vite/src/plugins/generateDocgen.ts @@ -20,10 +20,6 @@ export type PropInfo = { runes?: boolean; }; -export type EventInfo = { - name: string; -}; - type BaseType = { /** Permits undefined or not */ optional?: boolean; @@ -32,41 +28,33 @@ type BaseType = { type ScalarType = BaseType & { type: 'number' | 'string' | 'boolean' | 'symbol' | 'any' | 'null'; }; - type FunctionType = BaseType & { type: 'function'; text: string; }; - type LiteralType = BaseType & { type: 'literal'; value: string | number | boolean; text: string; }; - type ArrayType = BaseType & { type: 'array'; }; - type ObjectType = BaseType & { type: 'object'; }; - type UnionType = BaseType & { type: 'union'; types: Type[]; }; - type IntersectionType = BaseType & { type: 'intersection'; types: Type[]; }; - type ReferenceType = BaseType & { type: 'reference'; text: string; }; - type OtherType = BaseType & { type: 'other'; text: string; @@ -84,7 +72,7 @@ export type Type = | IntersectionType; /** - * Try to infer a type from a initializer expression (for when there is no type annotation) + * Try to infer a type from an initializer expression (for when there is no type annotation) */ function inferTypeFromInitializer(expr: Expression): Type | undefined { switch (expr.type) { @@ -171,11 +159,11 @@ function parseType(type: TSType): Type | undefined { } } else if (type.type == 'TSIntersectionType') { // e.g. `A & B` - const types: Type[] = type.types + const types = type.types .map((t) => { return parseType(t); }) - .filter((t) => t !== undefined); + .filter((t) => t !== undefined) as Type[]; return { type: 'intersection', types }; } return undefined; @@ -197,8 +185,7 @@ function tryParseJSDocType(text: string): Type | undefined { for (const decl of stmt.declarations) { if (decl.id.type == 'Identifier') { if (decl.id.typeAnnotation?.type === 'TSTypeAnnotation') { - const a = parseType(decl.id.typeAnnotation.typeAnnotation); - return a; + return parseType(decl.id.typeAnnotation.typeAnnotation); } } } @@ -207,7 +194,7 @@ function tryParseJSDocType(text: string): Type | undefined { } /** - * Extract JSDoc comments + * Parse JSDoc comments */ function parseComments(leadingComments?: Comment[] | null) { if (!leadingComments) { @@ -267,7 +254,7 @@ export function generateDocgen(fileContent: string): Docgen { } const propMap: Map = new Map(); - // const events: EventInfo[] = []; + let propTypeName = '$$ComponentProps'; traverse(ast, { FunctionDeclaration: (funcPath) => { @@ -275,38 +262,29 @@ export function generateDocgen(fileContent: string): Docgen { return; } funcPath.traverse({ - TSTypeAliasDeclaration(path) { - if ( - path.node.id.name !== '$$ComponentProps' || - path.node.typeAnnotation.type !== 'TSTypeLiteral' - ) { + ReturnStatement: (path) => { + // For runes mode: Get the name of props type alias from `return { props: {} as MyProps, ... }` + if (path.parent !== funcPath.node.body) { return; } - const members = path.node.typeAnnotation.members; - members.forEach((member) => { - if (member.type === 'TSPropertySignature' && member.key.type === 'Identifier') { - const name = member.key.name; - - const type = - member.typeAnnotation && member.typeAnnotation.type === 'TSTypeAnnotation' - ? parseType(member.typeAnnotation.typeAnnotation) - : undefined; - - if (type && member.optional) { - type.optional = true; + const argument = path.node.argument; + if (argument?.type === 'ObjectExpression') { + argument.properties.forEach((property) => { + if (property.type === 'ObjectProperty') { + if (property.key.type === 'Identifier' && property.key.name === 'props') { + if (property.value.type == 'TSAsExpression') { + const typeAnnotation = property.value.typeAnnotation; + if ( + typeAnnotation?.type === 'TSTypeReference' && + typeAnnotation.typeName.type === 'Identifier' + ) { + propTypeName = typeAnnotation.typeName.name; + } + } + } } - - const { description } = parseComments(member.leadingComments); - - propMap.set(name, { - ...propMap.get(name), - name, - type: type, - description, - runes: true, - }); - } - }); + }); + } }, VariableDeclaration: (path) => { if (path.node.kind !== 'let' || path.parent !== funcPath.node.body) { @@ -319,7 +297,7 @@ export function generateDocgen(fileContent: string): Docgen { declaration.id.typeAnnotation && declaration.id.typeAnnotation.type === 'TSTypeAnnotation' ) { - // Get default values from Svelte 5's `let { ... } = $props();` + // For runes mode: Collect default values from `let { ... } = $props();` const typeAnnotation = declaration.id.typeAnnotation.typeAnnotation; if ( @@ -350,7 +328,7 @@ export function generateDocgen(fileContent: string): Docgen { } }); } else if (declaration.id.type === 'Identifier') { - // Get props from Svelte 4's `export let a = ...` + // For legacy mode: Collect props info from `export let a = ...` const name = declaration.id.name; if (tsx.exportedNames.has(name)) { @@ -393,6 +371,40 @@ export function generateDocgen(fileContent: string): Docgen { }, }); + // For runes mode: Try to find and parse the props type alias. + traverse(ast, { + TSTypeAliasDeclaration(path) { + if (path.node.id.name !== propTypeName || path.node.typeAnnotation.type !== 'TSTypeLiteral') { + return; + } + const members = path.node.typeAnnotation.members; + members.forEach((member) => { + if (member.type === 'TSPropertySignature' && member.key.type === 'Identifier') { + const name = member.key.name; + + const type = + member.typeAnnotation && member.typeAnnotation.type === 'TSTypeAnnotation' + ? parseType(member.typeAnnotation.typeAnnotation) + : undefined; + + if (type && member.optional) { + type.optional = true; + } + + const { description } = parseComments(member.leadingComments); + + propMap.set(name, { + ...propMap.get(name), + name, + type: type, + description, + runes: true, + }); + } + }); + }, + }); + return { props: Array.from(propMap.values()), }; diff --git a/code/frameworks/svelte-vite/src/plugins/svelte-docgen.ts b/code/frameworks/svelte-vite/src/plugins/svelte-docgen.ts index 2d5b9d16bf9..8733ed697fe 100644 --- a/code/frameworks/svelte-vite/src/plugins/svelte-docgen.ts +++ b/code/frameworks/svelte-vite/src/plugins/svelte-docgen.ts @@ -3,7 +3,12 @@ import MagicString from 'magic-string'; import path from 'path'; import fs from 'fs'; import svelteDoc from 'sveltedoc-parser'; -import type { SvelteComponentDoc, SvelteParserOptions, JSDocType } from 'sveltedoc-parser'; +import type { + SvelteComponentDoc, + SvelteDataItem, + SvelteParserOptions, + JSDocType, +} from 'sveltedoc-parser'; import { logger } from 'storybook/internal/node-logger'; import { preprocess } from 'svelte/compiler'; import { replace, typescript } from 'svelte-preprocess'; @@ -98,8 +103,8 @@ function formatToSvelteDocParserType(type: Type): JSDocType { } } -function emulateSvelteDocParserDataItems(docgen: Docgen) { - const data = docgen.props.map((p) => { +function transformToSvelteDocParserDataItems(docgen: Docgen): SvelteDataItem[] { + return docgen.props.map((p) => { const required = p.runes && p.defaultValue === undefined && p.type && !p.type.optional; return { name: p.name, @@ -114,9 +119,8 @@ function emulateSvelteDocParserDataItems(docgen: Docgen) { originalName: undefined, localName: undefined, defaultValue: p.defaultValue, - }; + } satisfies SvelteDataItem; }); - return data; } export async function svelteDocgen(svelteOptions: Record = {}): Promise { @@ -140,7 +144,7 @@ export async function svelteDocgen(svelteOptions: Record = {}): Pro // Get props information const docgen = generateDocgen(rawSource); const hasRuneProps = docgen.props.some((p) => p.runes); - const data = emulateSvelteDocParserDataItems(docgen); + const data = transformToSvelteDocParserDataItems(docgen); let componentDoc: SvelteComponentDoc & { keywords?: string[] } = {}; diff --git a/code/frameworks/svelte-vite/template/stories_svelte-vite-default-ts/ButtonTypeScript.svelte b/code/frameworks/svelte-vite/template/stories_svelte-vite-default-ts/ButtonTypeScript.svelte index cd00f38a3d5..ecd4ab2c79f 100644 --- a/code/frameworks/svelte-vite/template/stories_svelte-vite-default-ts/ButtonTypeScript.svelte +++ b/code/frameworks/svelte-vite/template/stories_svelte-vite-default-ts/ButtonTypeScript.svelte @@ -7,6 +7,8 @@ // @ts-ignore const Button = globalThis.Components?.Button; + import { createEventDispatcher } from 'svelte'; + /** * Rounds the button */ @@ -23,14 +25,31 @@ */ export let text: string = 'You clicked'; + const dispatch = createEventDispatcher(); + function handleClick(_event: MouseEvent) { count += 1; } + + function onMouseHover(event) { + dispatch('mousehover', event); + }

Button TypeScript

- diff --git a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-runes-docs.stories.js b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-runes1-docs.stories.js similarity index 57% rename from code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-runes-docs.stories.js rename to code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-runes1-docs.stories.js index daa8ccc4db8..93ee9838e2c 100644 --- a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-runes-docs.stories.js +++ b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-runes1-docs.stories.js @@ -1,7 +1,7 @@ -import ButtonTypescriptRunes from './ButtonTypeScriptRunes.svelte'; +import ButtonTypescriptRunes from './ButtonTypeScriptRunes1.svelte'; export default { - title: 'stories/renderers/svelte/ts-runes-docs', + title: 'stories/renderers/svelte/ts-runes1-docs', component: ButtonTypescriptRunes, args: { primary: true, diff --git a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-runes2-docs.stories.js b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-runes2-docs.stories.js new file mode 100644 index 00000000000..86787ea459a --- /dev/null +++ b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-runes2-docs.stories.js @@ -0,0 +1,13 @@ +import ButtonTypescriptRunes from './ButtonTypeScriptRunes2.svelte'; + +export default { + title: 'stories/renderers/svelte/ts-runes2-docs', + component: ButtonTypescriptRunes, + args: { + primary: true, + label: 'Button', + }, + tags: ['autodocs'], +}; + +export const Primary = {}; diff --git a/code/frameworks/sveltekit/template/stories_svelte-kit-skeleton-ts/ButtonTypeScript.svelte b/code/frameworks/sveltekit/template/stories_svelte-kit-skeleton-ts/ButtonTypeScript.svelte index cd00f38a3d5..ecd4ab2c79f 100644 --- a/code/frameworks/sveltekit/template/stories_svelte-kit-skeleton-ts/ButtonTypeScript.svelte +++ b/code/frameworks/sveltekit/template/stories_svelte-kit-skeleton-ts/ButtonTypeScript.svelte @@ -7,6 +7,8 @@ // @ts-ignore const Button = globalThis.Components?.Button; + import { createEventDispatcher } from 'svelte'; + /** * Rounds the button */ @@ -23,14 +25,31 @@ */ export let text: string = 'You clicked'; + const dispatch = createEventDispatcher(); + function handleClick(_event: MouseEvent) { count += 1; } + + function onMouseHover(event) { + dispatch('mousehover', event); + }

Button TypeScript

- diff --git a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ButtonTypeScriptRunes2.svelte b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ButtonTypeScriptRunes2.svelte deleted file mode 100644 index 64055f173f5..00000000000 --- a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ButtonTypeScriptRunes2.svelte +++ /dev/null @@ -1,43 +0,0 @@ - - - diff --git a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/DocsJSDocRunes.svelte b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/DocsJSDocRunes.svelte new file mode 100644 index 00000000000..4f7646c9d0f --- /dev/null +++ b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/DocsJSDocRunes.svelte @@ -0,0 +1,51 @@ + + +
Docs: JSDoc + Runes (svelte2tsx@0.7.13 does not support this yet.)
diff --git a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/DocsTS.svelte b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/DocsTS.svelte new file mode 100644 index 00000000000..182fa1e89f9 --- /dev/null +++ b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/DocsTS.svelte @@ -0,0 +1,89 @@ + + +
Docs: TS
diff --git a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/DocsTSRunes1.svelte b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/DocsTSRunes1.svelte new file mode 100644 index 00000000000..e6a80b5c268 --- /dev/null +++ b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/DocsTSRunes1.svelte @@ -0,0 +1,98 @@ + + +
Docs: TS + Runes (style 1)
diff --git a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/DocsTSRunes2.svelte b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/DocsTSRunes2.svelte new file mode 100644 index 00000000000..77b76dd69ec --- /dev/null +++ b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/DocsTSRunes2.svelte @@ -0,0 +1,103 @@ + + +
Docs: TS + Runes (style 2)
diff --git a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/docs-jsdoc-runes.stories.js b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/docs-jsdoc-runes.stories.js new file mode 100644 index 00000000000..ce9d9d95cd4 --- /dev/null +++ b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/docs-jsdoc-runes.stories.js @@ -0,0 +1,10 @@ +import DocsJSDocRunes from './DocsJSDocRunes.svelte'; + +export default { + title: 'stories/renderers/svelte/docs-jsdoc-runes', + component: DocsJSDocRunes, + args: {}, + tags: ['autodocs'], +}; + +export const Primary = {}; diff --git a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/docs-ts-runes1.stories.js b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/docs-ts-runes1.stories.js new file mode 100644 index 00000000000..ba18e3064bc --- /dev/null +++ b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/docs-ts-runes1.stories.js @@ -0,0 +1,10 @@ +import DocsTSRunes1 from './DocsTSRunes1.svelte'; + +export default { + title: 'stories/renderers/svelte/docs-ts-runes1', + component: DocsTSRunes1, + args: {}, + tags: ['autodocs'], +}; + +export const Primary = {}; diff --git a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/docs-ts-runes2.stories.js b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/docs-ts-runes2.stories.js new file mode 100644 index 00000000000..110ac49dde3 --- /dev/null +++ b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/docs-ts-runes2.stories.js @@ -0,0 +1,10 @@ +import DocsTSRunes2 from './DocsTSRunes2.svelte'; + +export default { + title: 'stories/renderers/svelte/docs-ts-runes2', + component: DocsTSRunes2, + args: {}, + tags: ['autodocs'], +}; + +export const Primary = {}; diff --git a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/docs-ts.stories.js b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/docs-ts.stories.js new file mode 100644 index 00000000000..0124aa9b58f --- /dev/null +++ b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/docs-ts.stories.js @@ -0,0 +1,10 @@ +import DocsTS from './DocsTS.svelte'; + +export default { + title: 'stories/renderers/svelte/docs-ts', + component: DocsTS, + args: {}, + tags: ['autodocs'], +}; + +export const Primary = {}; diff --git a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-docs.stories.js b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-docs.stories.js deleted file mode 100644 index f33428810ee..00000000000 --- a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-docs.stories.js +++ /dev/null @@ -1,12 +0,0 @@ -import ButtonTypescript from './ButtonTypeScript.svelte'; - -export default { - title: 'stories/renderers/svelte/ts-docs', - component: ButtonTypescript, - args: { - primary: true, - }, - tags: ['autodocs'], -}; - -export const Primary = {}; diff --git a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-runes-docs1.stories.js b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-runes-docs1.stories.js deleted file mode 100644 index babea4e8414..00000000000 --- a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-runes-docs1.stories.js +++ /dev/null @@ -1,13 +0,0 @@ -import ButtonTypescriptRunes from './ButtonTypeScriptRunes1.svelte'; - -export default { - title: 'stories/renderers/svelte/ts-runes-docs1', - component: ButtonTypescriptRunes, - args: { - primary: true, - label: 'Button', - }, - tags: ['autodocs'], -}; - -export const Primary = {}; diff --git a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-runes-docs2.stories.js b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-runes-docs2.stories.js deleted file mode 100644 index 6cec9477719..00000000000 --- a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/ts-runes-docs2.stories.js +++ /dev/null @@ -1,13 +0,0 @@ -import ButtonTypescriptRunes from './ButtonTypeScriptRunes2.svelte'; - -export default { - title: 'stories/renderers/svelte/ts-runes-docs2', - component: ButtonTypescriptRunes, - args: { - primary: true, - label: 'Button', - }, - tags: ['autodocs'], -}; - -export const Primary = {}; diff --git a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/types.ts b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/types.ts index 827daca6b6f..dfa3a8f977a 100644 --- a/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/types.ts +++ b/code/frameworks/sveltekit/template/stories_svelte-kit-prerelease-ts/types.ts @@ -1,18 +1,2 @@ -import type { Snippet } from 'svelte'; - -export type MyPropsA = { - /** - * Snippet contents - */ - children?: Snippet; - /** - * Text contents - */ - label: string; - /** - * Click handler - */ - onclick?: () => void; -}; - -export type Sizes = 'small' | 'medium' | 'large'; +export type LiteralNumbers = 100 | 1000 | 10000; +export type LiteralStrings = 'apple' | 'grape' | 'orange'; diff --git a/code/frameworks/sveltekit/template/stories_svelte-kit-skeleton-ts/ButtonTypeScript.svelte b/code/frameworks/sveltekit/template/stories_svelte-kit-skeleton-ts/ButtonTypeScript.svelte deleted file mode 100644 index 24836e7f82a..00000000000 --- a/code/frameworks/sveltekit/template/stories_svelte-kit-skeleton-ts/ButtonTypeScript.svelte +++ /dev/null @@ -1,65 +0,0 @@ - - -

Button TypeScript

- -