From 3026a37268a3b01a7e3d2c2a4680282d4bb0c026 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 16 Jun 2025 21:29:22 -0400 Subject: [PATCH] more --- packages/svelte/src/compiler/print/index.js | 357 +++++++++++++++++++- packages/svelte/tests/parser-modern/test.ts | 48 ++- 2 files changed, 371 insertions(+), 34 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index bf17b4c1e6..7e77da73d8 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -2,6 +2,7 @@ /** @import { Visitors } from 'esrap' */ import * as esrap from 'esrap'; import ts from 'esrap/languages/ts'; +import { is_void } from '../../utils.js'; /** * @param {AST.SvelteNode} ast @@ -19,24 +20,34 @@ export function print(ast) { const visitors = { Root(node, context) { if (node.options) { - throw new Error('TODO'); + context.write(''); } + let started = false; + for (const item of [node.module, node.instance, node.fragment, node.css]) { if (!item) continue; - context.margin(); - context.newline(); + if (started) { + context.margin(); + context.newline(); + } + context.visit(item); + started = true; } }, + Script(node, context) { context.write(''); }, + Fragment(node, context) { for (let i = 0; i < node.nodes.length; i += 1) { const child = node.nodes[i]; @@ -68,6 +80,25 @@ const visitors = { } } }, + + Atrule(node, context) { + context.write(`@${node.name}`); + if (node.prelude) context.write(` ${node.prelude}`); + + if (node.block) { + context.write(' '); + context.visit(node.block); + } else { + context.write(';'); + } + }, + + AttachTag(node, context) { + context.write('{@attach '); + context.visit(node.expression); + context.write('}'); + }, + Attribute(node, context) { context.write(node.name); @@ -91,14 +122,159 @@ const visitors = { context.visit(node.value); } }, - Text(node, context) { - context.write(node.data); + + AwaitBlock(node, context) { + context.write(`{#await `); + context.visit(node.expression); + + if (node.pending) { + context.write('}'); + context.visit(node.pending); + context.write('{:'); + } else { + context.write(' '); + } + + if (node.then) { + context.write(node.value ? 'then ' : 'then'); + if (node.value) context.visit(node.value); + context.write('}'); + context.visit(node.then); + + if (node.catch) { + context.write('{:'); + } + } + + if (node.catch) { + context.write(node.value ? 'catch ' : 'catch'); + if (node.error) context.visit(node.error); + context.write('}'); + context.visit(node.catch); + } + + context.write('{/await}'); + }, + + BindDirective(node, context) { + context.write(`bind:${node.name}`); + + if (node.expression.type === 'Identifier' && node.expression.name === node.name) { + // shorthand + return; + } + + context.write('={'); + + if (node.expression.type === 'SequenceExpression') { + context.visit(node.expression.expressions[0]); + context.write(', '); + context.visit(node.expression.expressions[1]); + } else { + context.visit(node.expression); + } + + context.write('}'); + }, + + Block(node, context) { + context.write('{'); + + if (node.children.length > 0) { + context.indent(); + context.newline(); + + let started = false; + + for (const child of node.children) { + if (started) { + context.margin(); + context.newline(); + } + + context.visit(child); + + started = true; + } + + context.dedent(); + context.newline(); + } + + context.write('}'); + }, + + ClassSelector(node, context) { + context.write(`.${node.name}`); + }, + + Comment(node, context) { + context.write(''); + }, + + ComplexSelector(node, context) { + for (const selector of node.children) { + context.visit(selector); + } + }, + + Component(node, context) { + context.write(`<${node.name}`); + + for (let i = 0; i < node.attributes.length; i += 1) { + context.write(' '); + context.visit(node.attributes[i]); + } + + if (node.fragment.nodes.length > 0) { + context.write('>'); + context.visit(node.fragment); + context.write(``); + } else { + context.write(' />'); + } + }, + + Declaration(node, context) { + context.write('foo: bar;'); + }, + + EachBlock(node, context) { + context.write('{#each '); + context.visit(node.expression); + + if (node.context) { + context.write(' as '); + context.visit(node.context); + } + + if (node.index) { + context.write(`, ${node.index}`); + } + + if (node.key) { + context.write(' ('); + context.visit(node.key); + context.write(')'); + } + + context.write('}'); + context.visit(node.body); + + if (node.fallback) { + context.write('{:else}'); + context.visit(node.fallback); + } + + context.write('{/each}'); }, + ExpressionTag(node, context) { context.write('{'); context.visit(node.expression); context.write('}'); }, + IfBlock(node, context) { context.write('{#if '); context.visit(node.test); @@ -110,6 +286,41 @@ const visitors = { context.write('{/if}'); }, + + Nth(node, context) { + context.write(node.value); // TODO is this right? + }, + + OnDirective(node, context) { + // TODO + }, + + PseudoClassSelector(node, context) { + context.write(`:${node.name}`); + + if (node.args) { + context.write('('); + + let started = false; + + for (const arg of node.args.children) { + if (started) { + context.write(', '); + } + + context.visit(arg); + + started = true; + } + + context.write(')'); + } + }, + + PseudoElementSelector(node, context) { + context.write(`::${node.name}`); + }, + RegularElement(node, context) { context.write('<' + node.name); @@ -119,22 +330,134 @@ const visitors = { context.visit(attribute); } - context.write('>'); + if (is_void(node.name)) { + context.write(' />'); + } else { + context.write('>'); + + if (node.fragment) { + context.visit(node.fragment); + context.write(``); + } + } + }, + + RelativeSelector(node, context) { + if (node.combinator) { + if (node.combinator.name === ' ') { + context.write(' '); + } else { + context.write(` ${node.combinator.name} `); + } + } + + for (const selector of node.selectors) { + context.visit(selector); + } + }, + + RenderTag(node, context) { + context.write('{@render '); + context.visit(node.expression); + context.write('}'); + }, + + Rule(node, context) { + let started = false; + + for (const selector of node.prelude.children) { + if (started) { + context.write(','); + context.newline(); + } + + context.visit(selector); + started = true; + } + + context.write(' '); + context.visit(node.block); + }, + + SlotElement(node, context) { + context.write(' 0) { + context.write('>'); context.visit(node.fragment); + context.write(''); + } else { + context.write(' />'); } + }, - context.write(``); + SnippetBlock(node, context) { + context.write('{#snippet '); + context.visit(node.expression); + + if (node.typeParams) { + context.write(`<${node.typeParams}>`); + } + + context.write('('); + + for (let i = 0; i < node.parameters.length; i += 1) { + if (i > 0) context.write(', '); + context.visit(node.parameters[i]); + } + + context.write(')}'); + context.visit(node.body); + context.write('{/snippet}'); }, - OnDirective(node, context) { - // TODO + + StyleSheet(node, context) { + context.write(''); + + if (node.children.length > 0) { + context.indent(); + context.newline(); + + let started = false; + + for (const child of node.children) { + if (started) { + context.margin(); + context.newline(); + } + + context.visit(child); + started = true; + } + + context.dedent(); + context.newline(); + } + + context.write(''); + }, + + Text(node, context) { + context.write(node.data); }, + TransitionDirective(node, context) { // TODO }, - Comment(node, context) { - // TODO + + TypeSelector(node, context) { + context.write(node.name); } }; diff --git a/packages/svelte/tests/parser-modern/test.ts b/packages/svelte/tests/parser-modern/test.ts index 08d8aafeab..da9119441a 100644 --- a/packages/svelte/tests/parser-modern/test.ts +++ b/packages/svelte/tests/parser-modern/test.ts @@ -8,6 +8,8 @@ import { walk } from 'zimmerframe'; interface ParserTest extends BaseTest {} const { test, run } = suite(async (config, cwd) => { + const loose = cwd.split('/').pop()!.startsWith('loose-'); + const input = fs .readFileSync(`${cwd}/input.svelte`, 'utf-8') .replace(/\s+$/, '') @@ -22,6 +24,8 @@ const { test, run } = suite(async (config, cwd) => { ) ); + delete actual.comments; + // run `UPDATE_SNAPSHOTS=true pnpm test parser` to update parser tests if (process.env.UPDATE_SNAPSHOTS) { fs.writeFileSync(`${cwd}/output.json`, JSON.stringify(actual, null, '\t')); @@ -32,27 +36,37 @@ const { test, run } = suite(async (config, cwd) => { assert.deepEqual(actual, expected); } - const printed = print(actual); - const reparsed = JSON.parse( - JSON.stringify( - parse(printed.code, { - modern: true, - loose: cwd.split('/').pop()!.startsWith('loose-') - }) - ) - ); + if (!loose) { + const printed = print(actual); + const reparsed = JSON.parse( + JSON.stringify( + parse(printed.code, { + modern: true, + loose + }) + ) + ); - fs.writeFileSync(`${cwd}/_actual.svelte`, JSON.stringify(printed.code, null, '\t')); + fs.writeFileSync(`${cwd}/_actual.svelte`, printed.code); - const actual_cleaned = walk(actual, null, { - _(node, context) {} - }); + const actual_cleaned = walk(actual, null, { + _(node, context) { + delete node.loc; + context.next(); + } + }); - const reparsed_cleaned = walk(actual, null, { - _(node, context) {} - }); + delete reparsed.comments; - assert.deepEqual(actual_cleaned, reparsed_cleaned); + const reparsed_cleaned = walk(reparsed, null, { + _(node, context) { + delete node.loc; + context.next(); + } + }); + + assert.deepEqual(actual_cleaned, reparsed_cleaned); + } }); export { test };