mirror of https://github.com/sveltejs/svelte
parent
beb3f653ee
commit
6b603f2a3e
@ -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<string, string>} */
|
||||
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;
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
export type * from 'svelte/action';
|
||||
@ -0,0 +1 @@
|
||||
export type * from 'svelte/animate';
|
||||
@ -0,0 +1 @@
|
||||
export type * from 'svelte/compiler';
|
||||
@ -0,0 +1 @@
|
||||
export type * from 'svelte/easing';
|
||||
@ -0,0 +1 @@
|
||||
export type * from 'svelte';
|
||||
@ -0,0 +1 @@
|
||||
export type * from 'svelte/internal';
|
||||
@ -0,0 +1 @@
|
||||
export type * from 'svelte/motion';
|
||||
@ -0,0 +1 @@
|
||||
export type * from 'svelte';
|
||||
@ -0,0 +1 @@
|
||||
export type * from 'svelte/store';
|
||||
@ -0,0 +1 @@
|
||||
export type * from 'svelte/transition';
|
||||
@ -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<string, ts.Statement>} */
|
||||
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<ts.Statement>} 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(`- <span class="tag">default</span> \`${tag.comment}\``);
|
||||
break;
|
||||
|
||||
case 'returns':
|
||||
bullets.push(`- <span class="tag">returns</span> ${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()
|
||||
);
|
||||
@ -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": []
|
||||
}
|
||||
];
|
||||
Loading…
Reference in new issue