diff --git a/.gitignore b/.gitignore index e70d995994..ef413b550a 100644 --- a/.gitignore +++ b/.gitignore @@ -20,5 +20,5 @@ node_modules /yarn-error.log _actual*.* _output -/types +types .eslintcache diff --git a/sites/svelte.dev/package-lock.json b/sites/svelte.dev/package-lock.json index 4afca89c2c..6ad4c6b379 100644 --- a/sites/svelte.dev/package-lock.json +++ b/sites/svelte.dev/package-lock.json @@ -34,6 +34,8 @@ "prettier-plugin-svelte": "^2.10.0", "prism-svelte": "^0.5.0", "prismjs": "^1.29.0", + "rollup": "^3.20.2", + "rollup-plugin-dts": "^5.3.0", "satori": "^0.4.4", "satori-html": "^0.3.2", "shelljs": "^0.8.5", @@ -46,6 +48,44 @@ "vite-imagetools": "^4.0.18" } }, + "node_modules/@babel/code-frame": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "dev": true, + "optional": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "optional": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@esbuild/android-arm": { "version": "0.16.17", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz", @@ -1488,6 +1528,36 @@ "integrity": "sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ==", "dev": true }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "optional": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-styles/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "optional": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/ansi-styles/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "optional": true + }, "node_modules/any-base": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", @@ -1699,6 +1769,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "optional": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -2094,6 +2179,16 @@ "@esbuild/win32-x64": "0.16.17" } }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/esm-env": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz", @@ -2423,6 +2518,16 @@ "node": ">= 0.4.0" } }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "optional": true, + "engines": { + "node": ">=4" + } + }, "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", @@ -2673,6 +2778,13 @@ "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", "dev": true }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "optional": true + }, "node_modules/jsonc-parser": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", @@ -3535,6 +3647,28 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup-plugin-dts": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-5.3.0.tgz", + "integrity": "sha512-8FXp0ZkyZj1iU5klkIJYLjIq/YZSwBoERu33QBDxm/1yw5UU4txrEtcmMkrq+ZiKu3Q4qvPCNqc3ovX6rjqzbQ==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.0" + }, + "engines": { + "node": ">=v14" + }, + "funding": { + "url": "https://github.com/sponsors/Swatinem" + }, + "optionalDependencies": { + "@babel/code-frame": "^7.18.6" + }, + "peerDependencies": { + "rollup": "^3.0.0", + "typescript": "^4.1 || ^5.0" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3969,6 +4103,19 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "optional": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", diff --git a/sites/svelte.dev/package.json b/sites/svelte.dev/package.json index f3904a7d7b..4cce4ba868 100644 --- a/sites/svelte.dev/package.json +++ b/sites/svelte.dev/package.json @@ -4,9 +4,9 @@ "description": "Docs and examples for Svelte", "type": "module", "scripts": { - "dev": "node scripts/generate_examples.js && node scripts/update.js && vite dev", - "build": "node scripts/generate_examples.js && node scripts/update.js && vite build", - "update": "node scripts/update.js --force=true", + "dev": "node scripts/update.js && vite dev", + "build": "node scripts/update.js && vite build", + "update": "node scripts/update.js --force=true && node scripts/generate-types.js && node scripts/generate_examples.js", "preview": "vite preview", "start": "node build", "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json", @@ -42,6 +42,8 @@ "prettier-plugin-svelte": "^2.10.0", "prism-svelte": "^0.5.0", "prismjs": "^1.29.0", + "rollup": "^3.20.2", + "rollup-plugin-dts": "^5.3.0", "satori": "^0.4.4", "satori-html": "^0.3.2", "shelljs": "^0.8.5", diff --git a/sites/svelte.dev/scripts/type-gen/compile-types.js b/sites/svelte.dev/scripts/type-gen/compile-types.js new file mode 100644 index 0000000000..3c2aad96e2 --- /dev/null +++ b/sites/svelte.dev/scripts/type-gen/compile-types.js @@ -0,0 +1,114 @@ +// @ts-check +import { rollup } from 'rollup'; +import dts from 'rollup-plugin-dts'; +import fs from 'node:fs'; +import ts from 'typescript' + +export async function get_bundled_types() { + const dtsSources = fs.readdirSync(new URL('./dts-sources', import.meta.url)); + + /** @type {Map} */ + const codes = new Map(); + + for (const file of dtsSources) { + const bundle = await rollup({ + input: new URL(`./dts-sources/${file}`, import.meta.url).pathname, + plugins: [dts({ respectExternal: true })], + external: ['estree-walker'] + }); + + codes.set( + (file === 'index.d.ts' ? 'svelte' : `svelte/${file}`).replace('.d.ts', ''), + await bundle.generate({ format: 'esm' }).then(({ output }) => output[0].code) + ); + } + + console.log(codes.get('svelte/action')); + + return codes; +} + + +/** + * Modify a TypeScript SourceFile to use export declarations instead of export specifiers. + * @param {ts.SourceFile} sourceFile - The TypeScript SourceFile to modify + * @returns {string} The modified TypeScript code + */ +function useExportDeclarations(sourceFile) { + const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + + /** + * Process the given node, modifying it if necessary. + * @param {ts.Node} node - The TypeScript node to process + * @returns {ts.Node} The processed TypeScript node + */ + function processNode(node) { + if (ts.isExportDeclaration(node)) { + const namedExports = node.exportClause; + + if (namedExports && ts.isNamedExports(namedExports)) { + /** @type {ts.Statement[]} */ + const newNodes = []; + + namedExports.elements.forEach((exportSpecifier) => { + const exportedIdentifier = exportSpecifier.name; + const exportedName = exportedIdentifier.text; + const exportedDeclaration = sourceFile.statements.find((statement) => { + if (ts.isInterfaceDeclaration(statement) && statement.name.text === exportedName) { + return true; + } + + if (ts.isFunctionDeclaration(statement) && statement.name && statement.name.text === exportedName) { + return true; + } + + if (ts.isVariableStatement(statement)) { + return statement.declarationList.declarations.some( + (declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === exportedName + ); + } + + return false; + }); + + if (exportedDeclaration) { + const newDeclaration = ts.factory.updateExportDeclaration( + exportedDeclaration, + exportedDeclaration.modifiers, +false, + + + ts.factory.createNodeArray([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], exportedDeclaration.modifiers) + ); + newNodes.push(newDeclaration as ts.Statement); + } + }); + + return ts.factory.createBlock(newNodes, true); + } + } + + return ts.visitEachChild(node, processNode, nullTransformationContext); + } + + const nullTransformationContext: ts.TransformationContext = { + enableEmitNotification: () => {}, + enableSubstitution: () => {}, + endLexicalEnvironment: () => [], + getCompilerOptions: () => ({}), + getEmitHost: () => ({}), + getEmitResolver: () => ({}), + hoistFunctionDeclaration: () => {}, + hoistVariableDeclaration: () => {}, + isEmitNotificationEnabled: () => false, + isSubstitutionEnabled: () => false, + onEmitNode: () => {}, + onSubstituteNode: () => node => node, + startLexicalEnvironment: () => {}, + }; + + const resultFile = ts.visitNode(sourceFile, processNode); + const resultCode = printer.printFile(resultFile); + + return resultCode; +} \ No newline at end of file diff --git a/sites/svelte.dev/scripts/type-gen/dts-sources/action.d.ts b/sites/svelte.dev/scripts/type-gen/dts-sources/action.d.ts new file mode 100644 index 0000000000..917ea7b5a8 --- /dev/null +++ b/sites/svelte.dev/scripts/type-gen/dts-sources/action.d.ts @@ -0,0 +1 @@ +export type * from 'svelte/action'; diff --git a/sites/svelte.dev/scripts/type-gen/dts-sources/animate.d.ts b/sites/svelte.dev/scripts/type-gen/dts-sources/animate.d.ts new file mode 100644 index 0000000000..51a59e8429 --- /dev/null +++ b/sites/svelte.dev/scripts/type-gen/dts-sources/animate.d.ts @@ -0,0 +1 @@ +export type * from 'svelte/animate'; diff --git a/sites/svelte.dev/scripts/type-gen/dts-sources/compiler.d.ts b/sites/svelte.dev/scripts/type-gen/dts-sources/compiler.d.ts new file mode 100644 index 0000000000..f8deff9afb --- /dev/null +++ b/sites/svelte.dev/scripts/type-gen/dts-sources/compiler.d.ts @@ -0,0 +1 @@ +export type * from 'svelte/compiler'; diff --git a/sites/svelte.dev/scripts/type-gen/dts-sources/easing.d.ts b/sites/svelte.dev/scripts/type-gen/dts-sources/easing.d.ts new file mode 100644 index 0000000000..b882e429c3 --- /dev/null +++ b/sites/svelte.dev/scripts/type-gen/dts-sources/easing.d.ts @@ -0,0 +1 @@ +export type * from 'svelte/easing'; diff --git a/sites/svelte.dev/scripts/type-gen/dts-sources/index.d.ts b/sites/svelte.dev/scripts/type-gen/dts-sources/index.d.ts new file mode 100644 index 0000000000..2988db0db1 --- /dev/null +++ b/sites/svelte.dev/scripts/type-gen/dts-sources/index.d.ts @@ -0,0 +1 @@ +export type * from 'svelte'; diff --git a/sites/svelte.dev/scripts/type-gen/dts-sources/internal.d.ts b/sites/svelte.dev/scripts/type-gen/dts-sources/internal.d.ts new file mode 100644 index 0000000000..10f0475eea --- /dev/null +++ b/sites/svelte.dev/scripts/type-gen/dts-sources/internal.d.ts @@ -0,0 +1 @@ +export type * from 'svelte/internal'; diff --git a/sites/svelte.dev/scripts/type-gen/dts-sources/motion.d.ts b/sites/svelte.dev/scripts/type-gen/dts-sources/motion.d.ts new file mode 100644 index 0000000000..7eaf311000 --- /dev/null +++ b/sites/svelte.dev/scripts/type-gen/dts-sources/motion.d.ts @@ -0,0 +1 @@ +export type * from 'svelte/motion'; diff --git a/sites/svelte.dev/scripts/type-gen/dts-sources/runtime.d.ts b/sites/svelte.dev/scripts/type-gen/dts-sources/runtime.d.ts new file mode 100644 index 0000000000..2988db0db1 --- /dev/null +++ b/sites/svelte.dev/scripts/type-gen/dts-sources/runtime.d.ts @@ -0,0 +1 @@ +export type * from 'svelte'; diff --git a/sites/svelte.dev/scripts/type-gen/dts-sources/store.d.ts b/sites/svelte.dev/scripts/type-gen/dts-sources/store.d.ts new file mode 100644 index 0000000000..f2b41123a1 --- /dev/null +++ b/sites/svelte.dev/scripts/type-gen/dts-sources/store.d.ts @@ -0,0 +1 @@ +export type * from 'svelte/store'; diff --git a/sites/svelte.dev/scripts/type-gen/dts-sources/transition.d.ts b/sites/svelte.dev/scripts/type-gen/dts-sources/transition.d.ts new file mode 100644 index 0000000000..516c9b6ee9 --- /dev/null +++ b/sites/svelte.dev/scripts/type-gen/dts-sources/transition.d.ts @@ -0,0 +1 @@ +export type * from 'svelte/transition'; diff --git a/sites/svelte.dev/scripts/type-gen/index.js b/sites/svelte.dev/scripts/type-gen/index.js new file mode 100644 index 0000000000..21405951ec --- /dev/null +++ b/sites/svelte.dev/scripts/type-gen/index.js @@ -0,0 +1,430 @@ +// @ts-check +import fs, { stat } from 'fs'; +import prettier from 'prettier'; +import ts from 'typescript'; +import { get_bundled_types } from './compile-types.js'; + +/** @typedef {{ name: string; comment: string; markdown: string; }} Extracted */ + +/** @type {Array<{ name: string; comment: string; exports: Extracted[]; types: Extracted[]; exempt?: boolean; }>} */ +const modules = []; + +/** @param {string} code */ +function findExportedDeclarations(code) { + const sourceFile = ts.createSourceFile('', code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); + /** @type {Map} */ + const exportedIdentifiers = new Map(); + const typeChecker = ts.createProgram([code], {}).getTypeChecker(); + + /** + * Recursively visit all nodes in the syntax tree, looking for export specifiers. + * When it finds an export specifier, it retrieves the symbol at the specifier location + * and locates the first declaration of the exported identifier. + * @param {ts.Node} node - The current TypeScript node to visit + */ + function visit(node) { + if (ts.isExportSpecifier(node)) { + const exportedIdentifier = node.name.getText(); + const symbol = typeChecker.getSymbolAtLocation(node.name); + + console.log(symbol); + + if (symbol && symbol.declarations && symbol.declarations.length > 0) { + const firstDeclaration = symbol.declarations[0]; + let parent = firstDeclaration; + while (parent && !ts.isStatement(parent)) { + parent = parent.parent; + } + if (parent) { + exportedIdentifiers.set(exportedIdentifier, parent); + } + } + } + + ts.forEachChild(node, visit); + } + + visit(sourceFile); + + return exportedIdentifiers; +} + +/** + * @param {string} code + * @param {ts.NodeArray} statements + */ +function get_types(code, statements) { + /** @type {Extracted[]} */ + const exports = []; + + /** @type {Extracted[]} */ + const types = []; + + if (statements) { + console.log(findExportedDeclarations(code)); + console.log(1); + for (const statement of statements) { + if (!ts.isExportDeclaration(statement)) continue; + if (!statement.exportClause) continue; + + // console.log(statement.exportClause.elements); + + const exports = statement + .getChildAt(1) + .getChildAt(1) + .getFullText() + .replace(/ /g, '') + .split(','); + + if (exports.length === 0) continue; + + if ( + ts.isClassDeclaration(statement) || + ts.isInterfaceDeclaration(statement) || + ts.isTypeAliasDeclaration(statement) || + ts.isModuleDeclaration(statement) || + ts.isVariableStatement(statement) || + ts.isFunctionDeclaration(statement) + ) { + console.log(3); + const name_node = ts.isVariableStatement(statement) + ? statement.declarationList.declarations[0] + : statement; + + // @ts-ignore no idea why it's complaining here + const name = name_node.name?.escapedText; + + let start = statement.pos; + let comment = ''; + + // @ts-ignore i think typescript is bad at typescript + if (statement.jsDoc) { + console.log(4); + // @ts-ignore + comment = statement.jsDoc[0].comment; + // @ts-ignore + start = statement.jsDoc[0].end; + } + + const i = code.indexOf('export', start); + start = i + 6; + + /** @type {string[]} */ + const children = []; + + let snippet_unformatted = code.slice(start, statement.end).trim(); + + if (ts.isInterfaceDeclaration(statement)) { + console.log(5); + if (statement.members.length > 0) { + console.log(6); + for (const member of statement.members) { + children.push(munge_type_element(member)); + } + + // collapse `interface Foo {/* lots of stuff*/}` into `interface Foo {…}` + const first = statement.members.at(0); + const last = statement.members.at(-1); + + let body_start = first.pos - start; + while (snippet_unformatted[body_start] !== '{') body_start -= 1; + + let body_end = last.end - start; + while (snippet_unformatted[body_end] !== '}') body_end += 1; + + snippet_unformatted = + snippet_unformatted.slice(0, body_start + 1) + + '/*…*/' + + snippet_unformatted.slice(body_end); + } + } + + const snippet = prettier + .format(snippet_unformatted, { + parser: 'typescript', + printWidth: 80, + useTabs: true, + singleQuote: true, + trailingComma: 'none' + }) + .replace(/\s*(\/\*…\*\/)\s*/g, '/*…*/') + .trim(); + + // console.log(ts.isVariableStatement(statement) || ts.isFunctionDeclaration(statement)); + + const collection = + ts.isVariableStatement(statement) || ts.isFunctionDeclaration(statement) + ? exports + : types; + + collection.push({ name, comment, snippet, children }); + } + } + + types.sort((a, b) => (a.name < b.name ? -1 : 1)); + exports.sort((a, b) => (a.name < b.name ? -1 : 1)); + } + + return { types, exports }; +} + +/** + * @param {ts.TypeElement} member + */ +function munge_type_element(member, depth = 1) { + // @ts-ignore + const doc = member.jsDoc?.[0]; + + /** @type {string} */ + const children = []; + + const name = member.name?.escapedText; + let snippet = member.getText(); + + for (let i = 0; i < depth; i += 1) { + snippet = snippet.replace(/^\t/gm, ''); + } + + if ( + ts.isPropertySignature(member) && + ts.isTypeLiteralNode(member.type) && + member.type.members.some((member) => member.jsDoc?.[0].comment) + ) { + let a = 0; + while (snippet[a] !== '{') a += 1; + + snippet = snippet.slice(0, a + 1) + '/*…*/}'; + + for (const child of member.type.members) { + children.push(munge_type_element(child, depth + 1)); + } + } + + /** @type {string[]} */ + const bullets = []; + + for (const tag of doc?.tags ?? []) { + const type = tag.tagName.escapedText; + + switch (tag.tagName.escapedText) { + case 'param': + bullets.push(`- \`${tag.name.getText()}\` ${tag.comment}`); + break; + + case 'default': + bullets.push(`- default \`${tag.comment}\``); + break; + + case 'returns': + bullets.push(`- returns ${tag.comment}`); + break; + + default: + console.log(`unhandled JSDoc tag: ${type}`); // TODO indicate deprecated stuff + } + } + + return { + name, + snippet, + comment: (doc?.comment ?? '') + .replace(/\/\/\/ type: (.+)/g, '/** @type {$1} */') + .replace(/^( )+/gm, (match, spaces) => { + return '\t'.repeat(match.length / 2); + }), + bullets, + children + }; +} + +/** + * Type declarations include fully qualified URLs so that they become links when + * you hover over names in an editor with TypeScript enabled. We need to remove + * the origin so that they become root-relative, so that they work in preview + * deployments and when developing locally + * @param {string} str + */ +function strip_origin(str) { + return str.replace(/https:\/\/svelte\.dev/g, ''); +} + +/** + * @param {string} str + */ +function read_d_ts_file(str) { + // We can't use JSDoc comments inside JSDoc, so we would get ts(7031) errors if + // we didn't ignore this error specifically for `/// file:` code examples + return str.replace( + /(\s*\*\s*)\/\/\/ file:/g, + (match, prefix) => prefix + '// @errors: 7031' + match + ); +} + +const bundled_types = await get_bundled_types(); + +// { +// const code = bundled_types.get('svelte/compiler') ?? ''; +// const node = ts.createSourceFile('compiler/index.d.ts', code, ts.ScriptTarget.Latest, true); + +// modules.push({ +// name: 'svelte/compiler', +// comment: '', +// ...get_types(code, node.statements) +// }); +// } + +// // TODO: This is not working yet +// { +// const code = bundled_types.get('svelte/runtime') ?? ''; +// const node = ts.createSourceFile('runtime/index.d.ts', code, ts.ScriptTarget.Latest, true); + +// modules.push({ +// name: 'svelte', +// comment: '', +// ...get_types(code, node.statements) +// }); +// } + +{ + const code = bundled_types.get('svelte/action') ?? ''; + const node = ts.createSourceFile('runtime/action/index.d.ts', code, ts.ScriptTarget.Latest, true); + + modules.push({ + name: 'svelte/action', + comment: '', + ...get_types(code, node.statements) + }); +} + +// { +// const code = bundled_types.get('svelte/animate') ?? ''; +// const node = ts.createSourceFile( +// 'runtime/animate/index.d.ts', +// code, +// ts.ScriptTarget.Latest, +// true +// ); + +// modules.push({ +// name: 'svelte/animate', +// comment: '', +// ...get_types(code, node.statements) +// }); +// } + +// { +// const code = bundled_types.get('svelte/easing') ?? ''; +// const node = ts.createSourceFile('runtime/easing/index.d.ts', code, ts.ScriptTarget.Latest, true); + +// console.log(node.statements); + +// modules.push({ +// name: 'svelte/easing', +// comment: '', +// ...get_types(code, node.statements) +// }); +// } + +// { +// const code = bundled_types.get('svelte/motion') ?? ''; +// const node = ts.createSourceFile('runtime/motion/index.d.ts', code, ts.ScriptTarget.Latest, true); + +// modules.push({ +// name: 'svelte/motion', +// comment: '', +// ...get_types(code, node.statements) +// }); +// } + +// { +// const code = bundled_types.get('svelte/store') ?? ''; +// const node = ts.createSourceFile('runtime/store/index.d.ts', code, ts.ScriptTarget.Latest, true); + +// modules.push({ +// name: 'svelte/store', +// comment: '', +// ...get_types(code, node.statements) +// }); +// } + +// { +// const code = bundled_types.get('svelte/transition') ?? ''; +// const node = ts.createSourceFile( +// 'runtime/transition/index.d.ts', +// code, +// ts.ScriptTarget.Latest, +// true +// ); + +// modules.push({ +// name: 'svelte/transition', +// comment: '', +// ...get_types(code, node.statements) +// }); +// } + +// { +// const code = bundled_types.get('svelte/internal') ?? ''; +// const node = ts.createSourceFile( +// 'runtime/internal/index.d.ts', +// code, +// ts.ScriptTarget.Latest, +// true +// ); + +// modules.push({ +// name: 'svelte/internal', +// comment: '', +// ...get_types(code, node.statements) +// }); +// } + +// const dir = fileURLToPath( +// new URL('../../../../packages/kit/types/synthetic', import.meta.url).href +// ); +// for (const file of fs.readdirSync(dir)) { +// if (!file.endsWith('.md')) continue; + +// const comment = strip_origin(read_d_ts_file(`${dir}/${file}`)); + +// modules.push({ +// name: file.replace(/\+/g, '/').slice(0, -3), +// comment, +// exports: [], +// types: [], +// exempt: true +// }); +// } + +// { +// const code = read_d_ts_file('types/ambient.d.ts'); +// const node = ts.createSourceFile('ambient.d.ts', code, ts.ScriptTarget.Latest, true); + +// for (const statement of node.statements) { +// if (ts.isModuleDeclaration(statement)) { +// // @ts-ignore +// const name = statement.name.text || statement.name.escapedText; + +// // @ts-ignore +// const comment = strip_origin(statement.jsDoc?.[0].comment ?? ''); + +// modules.push({ +// name, +// comment, +// // @ts-ignore +// ...get_types(code, statement.body?.statements) +// }); +// } +// } +// } + +modules.sort((a, b) => (a.name < b.name ? -1 : 1)); + +fs.writeFileSync( + new URL('../../src/lib/server/docs/type-info.js', import.meta.url), + ` +/* This file is generated by running \`node scripts/extract-types.js\` + in the packages/kit directory — do not edit it */ +export const modules = ${JSON.stringify(modules, null, ' ')}; +`.trim() +); diff --git a/sites/svelte.dev/src/lib/server/docs/type-info.js b/sites/svelte.dev/src/lib/server/docs/type-info.js new file mode 100644 index 0000000000..91fccbf959 --- /dev/null +++ b/sites/svelte.dev/src/lib/server/docs/type-info.js @@ -0,0 +1,10 @@ +/* This file is generated by running `node scripts/extract-types.js` + in the packages/kit directory — do not edit it */ +export const modules = [ + { + "name": "svelte/action", + "comment": "", + "types": [], + "exports": [] + } +]; \ No newline at end of file