From a0f86c5e1b77f4768793ac918edc87631486c7db Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 15 Jun 2025 15:41:51 -0400 Subject: [PATCH 01/91] use local version of esrap, for now --- package.json | 8 ++++++++ packages/svelte/package.json | 4 ++-- .../_expected/client/index.svelte.js | 1 + .../_expected/server/index.svelte.js | 1 + .../_expected/client/main.svelte.js | 1 + .../_expected/server/main.svelte.js | 1 + .../_expected/client/index.svelte.js | 2 ++ .../_expected/server/index.svelte.js | 2 ++ .../_expected/client/index.svelte.js | 1 + .../_expected/client/index.svelte.js | 1 - .../_expected/client/module.svelte.js | 1 + .../_expected/server/index.svelte.js | 1 - .../_expected/server/module.svelte.js | 1 + pnpm-lock.yaml | 18 +++++++++--------- 14 files changed, 30 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 62581782d72d..4f5748cc5dd2 100644 --- a/package.json +++ b/package.json @@ -42,5 +42,13 @@ "typescript-eslint": "^8.24.0", "v8-natives": "^1.2.5", "vitest": "^2.1.9" + }, + "pnpm": { + "overrides": { + "esrap": "link:../../esrap" + } + }, + "dependencies": { + "esrap": "link:../../../../esrap" } } diff --git a/packages/svelte/package.json b/packages/svelte/package.json index d2fbdb32f74c..1b1276182b55 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -164,14 +164,14 @@ "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", - "@sveltejs/acorn-typescript": "^1.0.5", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", - "esrap": "^1.4.8", + "esrap": "https://pkg.pr.new/sveltejs/esrap@a275a5c", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", diff --git a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js index ba3f4b155a31..a87a356d580b 100644 --- a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/client/index.svelte.js @@ -22,6 +22,7 @@ export default function Bind_component_snippet($$anchor) { get value() { return $.get(value); }, + set value($$value) { $.set(value, $$value, true); } diff --git a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js index cadae2cf15c0..e2c0ee29a587 100644 --- a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js @@ -16,6 +16,7 @@ export default function Bind_component_snippet($$payload) { get value() { return value; }, + set value($$value) { value = $$value; $$settled = false; diff --git a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js index 28bb01fb18df..d84b674f88f4 100644 --- a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js +++ b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js @@ -6,6 +6,7 @@ var root = $.from_html(`
'test'; var fragment = root(); var div = $.first_child(fragment); diff --git a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/server/main.svelte.js b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/server/main.svelte.js index 4ea5edb6a0ac..cf731d8187b4 100644 --- a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/server/main.svelte.js +++ b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/server/main.svelte.js @@ -3,6 +3,7 @@ import * as $ from 'svelte/internal/server'; export default function Main($$payload) { // needs to be a snapshot test because jsdom does auto-correct the attribute casing let x = 'test'; + let y = () => 'test'; $$payload.out += ` `; diff --git a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js index 762a23754c9b..218951b83610 100644 --- a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js @@ -14,6 +14,7 @@ export default function Function_prop_no_getter($$anchor) { onmousedown: () => $.set(count, $.get(count) + 1), onmouseup, onmouseenter: () => $.set(count, plusOne($.get(count)), true), + children: ($$anchor, $$slotProps) => { $.next(); @@ -22,6 +23,7 @@ export default function Function_prop_no_getter($$anchor) { $.template_effect(() => $.set_text(text, `clicks: ${$.get(count) ?? ''}`)); $.append($$anchor, text); }, + $$slots: { default: true } }); } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js index 88f6f55ee74a..7d37abd97b1c 100644 --- a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js @@ -13,9 +13,11 @@ export default function Function_prop_no_getter($$payload) { onmousedown: () => count += 1, onmouseup, onmouseenter: () => count = plusOne(count), + children: ($$payload) => { $$payload.out += `clicks: ${$.escape(count)}`; }, + $$slots: { default: true } }); } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/functional-templating/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/functional-templating/_expected/client/index.svelte.js index 792d5421e1be..d4034dc55dd7 100644 --- a/packages/svelte/tests/snapshot/samples/functional-templating/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/functional-templating/_expected/client/index.svelte.js @@ -6,6 +6,7 @@ var root = $.from_tree( [ ['h1', null, 'hello'], ' ', + [ 'div', { class: 'potato' }, diff --git a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/index.svelte.js index ebbe191dcbe4..884e919f14d8 100644 --- a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/index.svelte.js @@ -4,5 +4,4 @@ import * as $ from 'svelte/internal/client'; import { random } from './module.svelte'; export default function Imports_in_modules($$anchor) { - } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/module.svelte.js b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/module.svelte.js index 0d366e6258ff..feab7bf8dad8 100644 --- a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/module.svelte.js +++ b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/module.svelte.js @@ -1,5 +1,6 @@ /* module.svelte.js generated by Svelte VERSION */ import * as $ from 'svelte/internal/client'; + import { random } from './export'; export { random }; \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/index.svelte.js index 4cd6bc59d782..75de235220bd 100644 --- a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/index.svelte.js @@ -2,5 +2,4 @@ import * as $ from 'svelte/internal/server'; import { random } from './module.svelte'; export default function Imports_in_modules($$payload) { - } \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/module.svelte.js b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/module.svelte.js index 2e0af8af84d8..fbbf1b955e99 100644 --- a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/module.svelte.js +++ b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/module.svelte.js @@ -1,5 +1,6 @@ /* module.svelte.js generated by Svelte VERSION */ import * as $ from 'svelte/internal/server'; + import { random } from './export'; export { random }; \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cfbc54df3363..08373e6b9d36 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,9 +4,16 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + esrap: link:../../esrap + importers: .: + dependencies: + esrap: + specifier: link:../../esrap + version: link:../../esrap devDependencies: '@changesets/cli': specifier: ^2.27.8 @@ -87,8 +94,8 @@ importers: specifier: ^1.2.1 version: 1.2.1 esrap: - specifier: ^1.4.8 - version: 1.4.8 + specifier: link:../../../../esrap + version: link:../../../../esrap is-reference: specifier: ^3.0.3 version: 3.0.3 @@ -1261,9 +1268,6 @@ packages: resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} engines: {node: '>=0.10'} - esrap@1.4.8: - resolution: {integrity: sha512-jlENbjZ7lqgJV9/OmgAtVqrFFMwsl70ctOgPIg5oTdQVGC13RSkMdtvAmu7ZTLax92c9ljnIG0xleEkdL69hwg==} - esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} @@ -3622,10 +3626,6 @@ snapshots: dependencies: estraverse: 5.3.0 - esrap@1.4.8: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 - esrecurse@4.3.0: dependencies: estraverse: 5.3.0 From 486d10c8806482940d15435a3c87d66a37a89525 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 16 Jun 2025 14:00:14 -0400 Subject: [PATCH 02/91] WIP --- .../svelte/scripts/process-messages/index.js | 133 +++++++----------- packages/svelte/src/compiler/errors.js | 18 ++- packages/svelte/src/compiler/index.js | 1 + packages/svelte/src/compiler/legacy.js | 1 + .../src/compiler/phases/3-transform/index.js | 7 +- packages/svelte/src/compiler/print/index.js | 133 ++++++++++++++++++ .../svelte/src/compiler/types/template.d.ts | 2 +- packages/svelte/src/compiler/warnings.js | 29 ++-- packages/svelte/src/internal/client/errors.js | 24 +++- .../svelte/src/internal/client/warnings.js | 24 +++- packages/svelte/src/internal/server/errors.js | 3 + packages/svelte/src/internal/shared/errors.js | 6 + .../svelte/src/internal/shared/warnings.js | 10 +- packages/svelte/tests/parser-modern/test.ts | 25 +++- packages/svelte/types/index.d.ts | 6 +- playgrounds/sandbox/run.js | 6 +- 16 files changed, 312 insertions(+), 116 deletions(-) create mode 100644 packages/svelte/src/compiler/print/index.js diff --git a/packages/svelte/scripts/process-messages/index.js b/packages/svelte/scripts/process-messages/index.js index 81c59271de2e..d246cfbba5c4 100644 --- a/packages/svelte/scripts/process-messages/index.js +++ b/packages/svelte/scripts/process-messages/index.js @@ -4,6 +4,7 @@ import fs from 'node:fs'; import * as acorn from 'acorn'; import { walk } from 'zimmerframe'; import * as esrap from 'esrap'; +import ts from 'esrap/languages/ts'; const DIR = '../../documentation/docs/98-reference/.generated'; @@ -98,55 +99,18 @@ function run() { .replace(/\r\n/g, '\n'); /** - * @type {Array<{ - * type: string; - * value: string; - * start: number; - * end: number - * }>} + * @type {any[]} */ const comments = []; let ast = acorn.parse(source, { ecmaVersion: 'latest', sourceType: 'module', - onComment: (block, value, start, end) => { - if (block && /\n/.test(value)) { - let a = start; - while (a > 0 && source[a - 1] !== '\n') a -= 1; - - let b = a; - while (/[ \t]/.test(source[b])) b += 1; - - const indentation = source.slice(a, b); - value = value.replace(new RegExp(`^${indentation}`, 'gm'), ''); - } - - comments.push({ type: block ? 'Block' : 'Line', value, start, end }); - } + locations: true, + onComment: comments }); ast = walk(ast, null, { - _(node, { next }) { - let comment; - - while (comments[0] && comments[0].start < node.start) { - comment = comments.shift(); - // @ts-expect-error - (node.leadingComments ||= []).push(comment); - } - - next(); - - if (comments[0]) { - const slice = source.slice(node.end, comments[0].start); - - if (/^[,) \t]*$/.test(slice)) { - // @ts-expect-error - node.trailingComments = [comments.shift()]; - } - } - }, // @ts-expect-error Identifier(node, context) { if (node.name === 'CODES') { @@ -161,11 +125,6 @@ function run() { } }); - if (comments.length > 0) { - // @ts-expect-error - (ast.trailingComments ||= []).push(...comments); - } - const category = messages[name]; // find the `export function CODE` node @@ -184,6 +143,16 @@ function run() { const template_node = ast.body[index]; ast.body.splice(index, 1); + const jsdoc = comments.findLast((comment) => comment.start < template_node.start); + + const printed = esrap.print( + ast, + // @ts-expect-error + ts({ + comments: comments.filter((comment) => comment !== jsdoc) + }) + ); + for (const code in category) { const { messages } = category[code]; /** @type {string[]} */ @@ -273,41 +242,6 @@ function run() { } const clone = walk(/** @type {import('estree').Node} */ (template_node), null, { - // @ts-expect-error Block is a block comment, which is not recognised - Block(node, context) { - if (!node.value.includes('PARAMETER')) return; - - const value = /** @type {string} */ (node.value) - .split('\n') - .map((line) => { - if (line === ' * MESSAGE') { - return messages[messages.length - 1] - .split('\n') - .map((line) => ` * ${line}`) - .join('\n'); - } - - if (line.includes('PARAMETER')) { - return vars - .map((name, i) => { - const optional = i >= group[0].vars.length; - - return optional - ? ` * @param {string | undefined | null} [${name}]` - : ` * @param {string} ${name}`; - }) - .join('\n'); - } - - return line; - }) - .filter((x) => x !== '') - .join('\n'); - - if (value !== node.value) { - return { ...node, value }; - } - }, FunctionDeclaration(node, context) { if (node.id.name !== 'CODE') return; @@ -394,16 +328,49 @@ function run() { } }); + const jsdoc_clone = { + ...jsdoc, + value: /** @type {string} */ (jsdoc.value) + .split('\n') + .map((line) => { + if (line === ' * MESSAGE') { + return messages[messages.length - 1] + .split('\n') + .map((line) => ` * ${line}`) + .join('\n'); + } + + if (line.includes('PARAMETER')) { + return vars + .map((name, i) => { + const optional = i >= group[0].vars.length; + + return optional + ? ` * @param {string | undefined | null} [${name}]` + : ` * @param {string} ${name}`; + }) + .join('\n'); + } + + return line; + }) + .filter((x) => x !== '') + .join('\n') + }; + + // @ts-expect-error + const block = esrap.print({ ...ast, body: [clone] }, ts({ comments: [jsdoc_clone] })).code; + + printed.code += `\n\n${block}`; + // @ts-expect-error ast.body.push(clone); } - const module = esrap.print(ast); - fs.writeFileSync( dest, `/* This file is generated by scripts/process-messages/index.js. Do not edit! */\n\n` + - module.code, + printed.code, 'utf-8' ); } diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js index 25e72340c64d..ec71fc85a275 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -4,21 +4,25 @@ import { CompileDiagnostic } from './utils/compile_diagnostic.js'; /** @typedef {{ start?: number, end?: number }} NodeLike */ class InternalCompileError extends Error { - message = ''; // ensure this property is enumerable + message = ''; + + // ensure this property is enumerable #diagnostic; /** - * @param {string} code - * @param {string} message - * @param {[number, number] | undefined} position - */ + * @param {string} code + * @param {string} message + * @param {[number, number] | undefined} position + */ constructor(code, message, position) { super(message); this.stack = ''; // avoid unnecessary noise; don't set it as a class property or it becomes enumerable + // We want to extend from Error so that various bundler plugins properly handle it. // But we also want to share the same object shape with that of warnings, therefore // we create an instance of the shared class an copy over its properties. this.#diagnostic = new CompileDiagnostic(code, message, position); + Object.assign(this, this.#diagnostic); this.name = 'CompileError'; } @@ -816,7 +820,9 @@ export function bind_invalid_expression(node) { * @returns {never} */ export function bind_invalid_name(node, name, explanation) { - e(node, 'bind_invalid_name', `${explanation ? `\`bind:${name}\` is not a valid binding. ${explanation}` : `\`bind:${name}\` is not a valid binding`}\nhttps://svelte.dev/e/bind_invalid_name`); + e(node, 'bind_invalid_name', `${explanation + ? `\`bind:${name}\` is not a valid binding. ${explanation}` + : `\`bind:${name}\` is not a valid binding`}\nhttps://svelte.dev/e/bind_invalid_name`); } /** diff --git a/packages/svelte/src/compiler/index.js b/packages/svelte/src/compiler/index.js index 756a88a824b6..ac0797927eb6 100644 --- a/packages/svelte/src/compiler/index.js +++ b/packages/svelte/src/compiler/index.js @@ -11,6 +11,7 @@ import { transform_component, transform_module } from './phases/3-transform/inde import { validate_component_options, validate_module_options } from './validate-options.js'; import * as state from './state.js'; export { default as preprocess } from './preprocess/index.js'; +export { print } from './print/index.js'; /** * `compile` converts your `.svelte` source code into a JavaScript module that exports a component diff --git a/packages/svelte/src/compiler/legacy.js b/packages/svelte/src/compiler/legacy.js index f6b7e4b0548d..85345bca4a22 100644 --- a/packages/svelte/src/compiler/legacy.js +++ b/packages/svelte/src/compiler/legacy.js @@ -451,6 +451,7 @@ export function convert(source, ast) { SpreadAttribute(node) { return { ...node, type: 'Spread' }; }, + // @ts-ignore StyleSheet(node, context) { return { ...node, diff --git a/packages/svelte/src/compiler/phases/3-transform/index.js b/packages/svelte/src/compiler/phases/3-transform/index.js index f96fd64ec7a9..2d045bf36362 100644 --- a/packages/svelte/src/compiler/phases/3-transform/index.js +++ b/packages/svelte/src/compiler/phases/3-transform/index.js @@ -1,6 +1,7 @@ /** @import { ValidatedCompileOptions, CompileResult, ValidatedModuleCompileOptions } from '#compiler' */ /** @import { ComponentAnalysis, Analysis } from '../types' */ import { print } from 'esrap'; +import ts from 'esrap/languages/ts'; import { VERSION } from '../../../version.js'; import { server_component, server_module } from './server/transform-server.js'; import { client_component, client_module } from './client/transform-client.js'; @@ -34,7 +35,8 @@ export function transform_component(analysis, source, options) { const js_source_name = get_source_name(options.filename, options.outputFilename, 'input.svelte'); - const js = print(program, { + // @ts-ignore TODO + const js = print(program, ts(), { // include source content; makes it easier/more robust looking up the source map code // (else esrap does return null for source and sourceMapContent which may trip up tooling) sourceMapContent: source, @@ -94,7 +96,8 @@ export function transform_module(analysis, source, options) { } return { - js: print(program, { + // @ts-expect-error + js: print(program, ts(), { // include source content; makes it easier/more robust looking up the source map code // (else esrap does return null for source and sourceMapContent which may trip up tooling) sourceMapContent: source, diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js new file mode 100644 index 000000000000..d3c6f31c77ad --- /dev/null +++ b/packages/svelte/src/compiler/print/index.js @@ -0,0 +1,133 @@ +/** @import { AST } from '#compiler'; */ +/** @import { Visitors } from 'esrap' */ +import * as esrap from 'esrap'; +import ts from 'esrap/languages/ts'; + +/** + * @param {AST.SvelteNode} ast + */ +export function print(ast) { + // @ts-expect-error some bullshit + return esrap.print(ast, { + ...ts(), + ...visitors + }); +} + +/** @type {Visitors} */ +const visitors = { + Root(node, context) { + if (node.options) { + throw new Error('TODO'); + } + + for (const item of [node.module, node.instance, node.fragment, node.css]) { + if (!item) continue; + + context.margin(); + context.newline(); + context.visit(item); + } + }, + Script(node, context) { + context.write(''); + + context.indent(); + context.newline(); + context.visit(node.content); + context.dedent(); + context.newline(); + + context.write(''); + }, + Fragment(node, context) { + for (let i = 0; i < node.nodes.length; i += 1) { + const child = node.nodes[i]; + + if (child.type === 'Text') { + let data = child.data; + + if (i === 0) data = data.trimStart(); + if (i === node.nodes.length - 1) data = data.trimEnd(); + + context.write(data); + } else { + context.visit(child); + } + } + }, + Attribute(node, context) { + context.write(node.name); + + if (node.value === true) return; + + context.write('='); + + if (Array.isArray(node.value)) { + if (node.value.length > 1) { + context.write('"'); + } + + for (const chunk of node.value) { + context.visit(chunk); + } + + if (node.value.length > 1) { + context.write('"'); + } + } else { + context.visit(node.value); + } + }, + Text(node, context) { + context.write(node.data); + }, + ExpressionTag(node, context) { + context.write('{'); + context.visit(node.expression); + context.write('}'); + }, + IfBlock(node, context) { + context.write('{#if '); + context.visit(node.test); + context.write('}'); + + context.visit(node.consequent); + + // TODO handle alternate/else if + + context.write('{/if}'); + }, + RegularElement(node, context) { + context.write('<' + node.name); + + for (const attribute of node.attributes) { + // TODO handle multiline + context.write(' '); + context.visit(attribute); + } + + context.write('>'); + + // TODO handle void elements + if (node.fragment) { + context.visit(node.fragment); + } + + context.write(``); + }, + TransitionDirective(node, context) { + // TODO + } +}; diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index cefc7fa7a20d..7d2a0397b185 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -572,7 +572,7 @@ export namespace AST { | AST.Comment | Block; - export type SvelteNode = Node | TemplateNode | AST.Fragment | _CSS.Node; + export type SvelteNode = Node | TemplateNode | AST.Fragment | _CSS.Node | Script; export type { _CSS as CSS }; } diff --git a/packages/svelte/src/compiler/warnings.js b/packages/svelte/src/compiler/warnings.js index 1226190891b2..e6bc7adf9cee 100644 --- a/packages/svelte/src/compiler/warnings.js +++ b/packages/svelte/src/compiler/warnings.js @@ -1,12 +1,6 @@ /* This file is generated by scripts/process-messages/index.js. Do not edit! */ -import { - warnings, - ignore_stack, - ignore_map, - warning_filter -} from './state.js'; - +import { warnings, ignore_stack, ignore_map, warning_filter } from './state.js'; import { CompileDiagnostic } from './utils/compile_diagnostic.js'; /** @typedef {{ start?: number, end?: number }} NodeLike */ @@ -14,10 +8,10 @@ class InternalCompileWarning extends CompileDiagnostic { name = 'CompileWarning'; /** - * @param {string} code - * @param {string} message - * @param {[number, number] | undefined} position - */ + * @param {string} code + * @param {string} message + * @param {[number, number] | undefined} position + */ constructor(code, message, position) { super(code, message, position); } @@ -40,6 +34,7 @@ function w(node, code, message) { const warning = new InternalCompileWarning(code, message, node && node.start !== undefined ? [node.start, node.end ?? node.start] : undefined); if (!warning_filter(warning)) return; + warnings.push(warning); } @@ -496,7 +491,9 @@ export function a11y_role_supports_aria_props_implicit(node, attribute, role, na * @param {string | undefined | null} [suggestion] */ export function a11y_unknown_aria_attribute(node, attribute, suggestion) { - w(node, 'a11y_unknown_aria_attribute', `${suggestion ? `Unknown aria attribute 'aria-${attribute}'. Did you mean '${suggestion}'?` : `Unknown aria attribute 'aria-${attribute}'`}\nhttps://svelte.dev/e/a11y_unknown_aria_attribute`); + w(node, 'a11y_unknown_aria_attribute', `${suggestion + ? `Unknown aria attribute 'aria-${attribute}'. Did you mean '${suggestion}'?` + : `Unknown aria attribute 'aria-${attribute}'`}\nhttps://svelte.dev/e/a11y_unknown_aria_attribute`); } /** @@ -506,7 +503,9 @@ export function a11y_unknown_aria_attribute(node, attribute, suggestion) { * @param {string | undefined | null} [suggestion] */ export function a11y_unknown_role(node, role, suggestion) { - w(node, 'a11y_unknown_role', `${suggestion ? `Unknown role '${role}'. Did you mean '${suggestion}'?` : `Unknown role '${role}'`}\nhttps://svelte.dev/e/a11y_unknown_role`); + w(node, 'a11y_unknown_role', `${suggestion + ? `Unknown role '${role}'. Did you mean '${suggestion}'?` + : `Unknown role '${role}'`}\nhttps://svelte.dev/e/a11y_unknown_role`); } /** @@ -534,7 +533,9 @@ export function legacy_code(node, code, suggestion) { * @param {string | undefined | null} [suggestion] */ export function unknown_code(node, code, suggestion) { - w(node, 'unknown_code', `${suggestion ? `\`${code}\` is not a recognised code (did you mean \`${suggestion}\`?)` : `\`${code}\` is not a recognised code`}\nhttps://svelte.dev/e/unknown_code`); + w(node, 'unknown_code', `${suggestion + ? `\`${code}\` is not a recognised code (did you mean \`${suggestion}\`?)` + : `\`${code}\` is not a recognised code`}\nhttps://svelte.dev/e/unknown_code`); } /** diff --git a/packages/svelte/src/internal/client/errors.js b/packages/svelte/src/internal/client/errors.js index 429dd99da9b9..042cd9132e7f 100644 --- a/packages/svelte/src/internal/client/errors.js +++ b/packages/svelte/src/internal/client/errors.js @@ -11,6 +11,7 @@ export function bind_invalid_checkbox_value() { const error = new Error(`bind_invalid_checkbox_value\nUsing \`bind:value\` together with a checkbox input is not allowed. Use \`bind:checked\` instead\nhttps://svelte.dev/e/bind_invalid_checkbox_value`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/bind_invalid_checkbox_value`); @@ -29,6 +30,7 @@ export function bind_invalid_export(component, key, name) { const error = new Error(`bind_invalid_export\nComponent ${component} has an export named \`${key}\` that a consumer component is trying to access using \`bind:${key}\`, which is disallowed. Instead, use \`bind:this\` (e.g. \`<${name} bind:this={component} />\`) and then access the property on the bound component instance (e.g. \`component.${key}\`)\nhttps://svelte.dev/e/bind_invalid_export`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/bind_invalid_export`); @@ -47,6 +49,7 @@ export function bind_not_bindable(key, component, name) { const error = new Error(`bind_not_bindable\nA component is attempting to bind to a non-bindable property \`${key}\` belonging to ${component} (i.e. \`<${name} bind:${key}={...}>\`). To mark a property as bindable: \`let { ${key} = $bindable() } = $props()\`\nhttps://svelte.dev/e/bind_not_bindable`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/bind_not_bindable`); @@ -64,6 +67,7 @@ export function component_api_changed(method, component) { const error = new Error(`component_api_changed\nCalling \`${method}\` on a component instance (of ${component}) is no longer valid in Svelte 5\nhttps://svelte.dev/e/component_api_changed`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/component_api_changed`); @@ -81,6 +85,7 @@ export function component_api_invalid_new(component, name) { const error = new Error(`component_api_invalid_new\nAttempted to instantiate ${component} with \`new ${name}\`, which is no longer valid in Svelte 5. If this component is not under your control, set the \`compatibility.componentApi\` compiler option to \`4\` to keep it working.\nhttps://svelte.dev/e/component_api_invalid_new`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/component_api_invalid_new`); @@ -96,6 +101,7 @@ export function derived_references_self() { const error = new Error(`derived_references_self\nA derived value cannot reference itself recursively\nhttps://svelte.dev/e/derived_references_self`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/derived_references_self`); @@ -111,9 +117,12 @@ export function derived_references_self() { */ export function each_key_duplicate(a, b, value) { if (DEV) { - const error = new Error(`each_key_duplicate\n${value ? `Keyed each block has duplicate key \`${value}\` at indexes ${a} and ${b}` : `Keyed each block has duplicate key at indexes ${a} and ${b}`}\nhttps://svelte.dev/e/each_key_duplicate`); + const error = new Error(`each_key_duplicate\n${value + ? `Keyed each block has duplicate key \`${value}\` at indexes ${a} and ${b}` + : `Keyed each block has duplicate key at indexes ${a} and ${b}`}\nhttps://svelte.dev/e/each_key_duplicate`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/each_key_duplicate`); @@ -130,6 +139,7 @@ export function effect_in_teardown(rune) { const error = new Error(`effect_in_teardown\n\`${rune}\` cannot be used inside an effect cleanup function\nhttps://svelte.dev/e/effect_in_teardown`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/effect_in_teardown`); @@ -145,6 +155,7 @@ export function effect_in_unowned_derived() { const error = new Error(`effect_in_unowned_derived\nEffect cannot be created inside a \`$derived\` value that was not itself created inside an effect\nhttps://svelte.dev/e/effect_in_unowned_derived`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/effect_in_unowned_derived`); @@ -161,6 +172,7 @@ export function effect_orphan(rune) { const error = new Error(`effect_orphan\n\`${rune}\` can only be used inside an effect (e.g. during component initialisation)\nhttps://svelte.dev/e/effect_orphan`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/effect_orphan`); @@ -176,6 +188,7 @@ export function effect_update_depth_exceeded() { const error = new Error(`effect_update_depth_exceeded\nMaximum update depth exceeded. This can happen when a reactive block or effect repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops\nhttps://svelte.dev/e/effect_update_depth_exceeded`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/effect_update_depth_exceeded`); @@ -191,6 +204,7 @@ export function hydration_failed() { const error = new Error(`hydration_failed\nFailed to hydrate the application\nhttps://svelte.dev/e/hydration_failed`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/hydration_failed`); @@ -206,6 +220,7 @@ export function invalid_snippet() { const error = new Error(`invalid_snippet\nCould not \`{@render}\` snippet due to the expression being \`null\` or \`undefined\`. Consider using optional chaining \`{@render snippet?.()}\`\nhttps://svelte.dev/e/invalid_snippet`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/invalid_snippet`); @@ -222,6 +237,7 @@ export function lifecycle_legacy_only(name) { const error = new Error(`lifecycle_legacy_only\n\`${name}(...)\` cannot be used in runes mode\nhttps://svelte.dev/e/lifecycle_legacy_only`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/lifecycle_legacy_only`); @@ -238,6 +254,7 @@ export function props_invalid_value(key) { const error = new Error(`props_invalid_value\nCannot do \`bind:${key}={undefined}\` when \`${key}\` has a fallback value\nhttps://svelte.dev/e/props_invalid_value`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/props_invalid_value`); @@ -254,6 +271,7 @@ export function props_rest_readonly(property) { const error = new Error(`props_rest_readonly\nRest element properties of \`$props()\` such as \`${property}\` are readonly\nhttps://svelte.dev/e/props_rest_readonly`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/props_rest_readonly`); @@ -270,6 +288,7 @@ export function rune_outside_svelte(rune) { const error = new Error(`rune_outside_svelte\nThe \`${rune}\` rune is only available inside \`.svelte\` and \`.svelte.js/ts\` files\nhttps://svelte.dev/e/rune_outside_svelte`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/rune_outside_svelte`); @@ -285,6 +304,7 @@ export function state_descriptors_fixed() { const error = new Error(`state_descriptors_fixed\nProperty descriptors defined on \`$state\` objects must contain \`value\` and always be \`enumerable\`, \`configurable\` and \`writable\`.\nhttps://svelte.dev/e/state_descriptors_fixed`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/state_descriptors_fixed`); @@ -300,6 +320,7 @@ export function state_prototype_fixed() { const error = new Error(`state_prototype_fixed\nCannot set prototype of \`$state\` object\nhttps://svelte.dev/e/state_prototype_fixed`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/state_prototype_fixed`); @@ -315,6 +336,7 @@ export function state_unsafe_mutation() { const error = new Error(`state_unsafe_mutation\nUpdating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without \`$state\`\nhttps://svelte.dev/e/state_unsafe_mutation`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/state_unsafe_mutation`); diff --git a/packages/svelte/src/internal/client/warnings.js b/packages/svelte/src/internal/client/warnings.js index e07892a4b064..74f9041b91dd 100644 --- a/packages/svelte/src/internal/client/warnings.js +++ b/packages/svelte/src/internal/client/warnings.js @@ -25,7 +25,13 @@ export function assignment_value_stale(property, location) { */ export function binding_property_non_reactive(binding, location) { if (DEV) { - console.warn(`%c[svelte] binding_property_non_reactive\n%c${location ? `\`${binding}\` (${location}) is binding to a non-reactive property` : `\`${binding}\` is binding to a non-reactive property`}\nhttps://svelte.dev/e/binding_property_non_reactive`, bold, normal); + console.warn( + `%c[svelte] binding_property_non_reactive\n%c${location + ? `\`${binding}\` (${location}) is binding to a non-reactive property` + : `\`${binding}\` is binding to a non-reactive property`}\nhttps://svelte.dev/e/binding_property_non_reactive`, + bold, + normal + ); } else { console.warn(`https://svelte.dev/e/binding_property_non_reactive`); } @@ -76,7 +82,13 @@ export function hydration_attribute_changed(attribute, html, value) { */ export function hydration_html_changed(location) { if (DEV) { - console.warn(`%c[svelte] hydration_html_changed\n%c${location ? `The value of an \`{@html ...}\` block ${location} changed between server and client renders. The client value will be ignored in favour of the server value` : 'The value of an `{@html ...}` block changed between server and client renders. The client value will be ignored in favour of the server value'}\nhttps://svelte.dev/e/hydration_html_changed`, bold, normal); + console.warn( + `%c[svelte] hydration_html_changed\n%c${location + ? `The value of an \`{@html ...}\` block ${location} changed between server and client renders. The client value will be ignored in favour of the server value` + : 'The value of an `{@html ...}` block changed between server and client renders. The client value will be ignored in favour of the server value'}\nhttps://svelte.dev/e/hydration_html_changed`, + bold, + normal + ); } else { console.warn(`https://svelte.dev/e/hydration_html_changed`); } @@ -88,7 +100,13 @@ export function hydration_html_changed(location) { */ export function hydration_mismatch(location) { if (DEV) { - console.warn(`%c[svelte] hydration_mismatch\n%c${location ? `Hydration failed because the initial UI does not match what was rendered on the server. The error occurred near ${location}` : 'Hydration failed because the initial UI does not match what was rendered on the server'}\nhttps://svelte.dev/e/hydration_mismatch`, bold, normal); + console.warn( + `%c[svelte] hydration_mismatch\n%c${location + ? `Hydration failed because the initial UI does not match what was rendered on the server. The error occurred near ${location}` + : 'Hydration failed because the initial UI does not match what was rendered on the server'}\nhttps://svelte.dev/e/hydration_mismatch`, + bold, + normal + ); } else { console.warn(`https://svelte.dev/e/hydration_mismatch`); } diff --git a/packages/svelte/src/internal/server/errors.js b/packages/svelte/src/internal/server/errors.js index 38c545c84ec8..e47530c9aaf9 100644 --- a/packages/svelte/src/internal/server/errors.js +++ b/packages/svelte/src/internal/server/errors.js @@ -1,5 +1,7 @@ /* This file is generated by scripts/process-messages/index.js. Do not edit! */ + + /** * `%name%(...)` is not available on the server * @param {string} name @@ -9,5 +11,6 @@ export function lifecycle_function_unavailable(name) { const error = new Error(`lifecycle_function_unavailable\n\`${name}(...)\` is not available on the server\nhttps://svelte.dev/e/lifecycle_function_unavailable`); error.name = 'Svelte error'; + throw error; } \ No newline at end of file diff --git a/packages/svelte/src/internal/shared/errors.js b/packages/svelte/src/internal/shared/errors.js index b8606fbf6f7d..6bcc35016a70 100644 --- a/packages/svelte/src/internal/shared/errors.js +++ b/packages/svelte/src/internal/shared/errors.js @@ -11,6 +11,7 @@ export function invalid_default_snippet() { const error = new Error(`invalid_default_snippet\nCannot use \`{@render children(...)}\` if the parent component uses \`let:\` directives. Consider using a named snippet instead\nhttps://svelte.dev/e/invalid_default_snippet`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/invalid_default_snippet`); @@ -26,6 +27,7 @@ export function invalid_snippet_arguments() { const error = new Error(`invalid_snippet_arguments\nA snippet function was passed invalid arguments. Snippets should only be instantiated via \`{@render ...}\`\nhttps://svelte.dev/e/invalid_snippet_arguments`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/invalid_snippet_arguments`); @@ -42,6 +44,7 @@ export function lifecycle_outside_component(name) { const error = new Error(`lifecycle_outside_component\n\`${name}(...)\` can only be used during component initialisation\nhttps://svelte.dev/e/lifecycle_outside_component`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/lifecycle_outside_component`); @@ -57,6 +60,7 @@ export function snippet_without_render_tag() { const error = new Error(`snippet_without_render_tag\nAttempted to render a snippet without a \`{@render}\` block. This would cause the snippet code to be stringified instead of its content being rendered to the DOM. To fix this, change \`{snippet}\` to \`{@render snippet()}\`.\nhttps://svelte.dev/e/snippet_without_render_tag`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/snippet_without_render_tag`); @@ -73,6 +77,7 @@ export function store_invalid_shape(name) { const error = new Error(`store_invalid_shape\n\`${name}\` is not a store with a \`subscribe\` method\nhttps://svelte.dev/e/store_invalid_shape`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/store_invalid_shape`); @@ -88,6 +93,7 @@ export function svelte_element_invalid_this_value() { const error = new Error(`svelte_element_invalid_this_value\nThe \`this\` prop on \`\` must be a string, if defined\nhttps://svelte.dev/e/svelte_element_invalid_this_value`); error.name = 'Svelte error'; + throw error; } else { throw new Error(`https://svelte.dev/e/svelte_element_invalid_this_value`); diff --git a/packages/svelte/src/internal/shared/warnings.js b/packages/svelte/src/internal/shared/warnings.js index 281be0838211..0acca4418410 100644 --- a/packages/svelte/src/internal/shared/warnings.js +++ b/packages/svelte/src/internal/shared/warnings.js @@ -25,11 +25,15 @@ export function dynamic_void_element_content(tag) { */ export function state_snapshot_uncloneable(properties) { if (DEV) { - console.warn(`%c[svelte] state_snapshot_uncloneable\n%c${properties - ? `The following properties cannot be cloned with \`$state.snapshot\` — the return value contains the originals: + console.warn( + `%c[svelte] state_snapshot_uncloneable\n%c${properties + ? `The following properties cannot be cloned with \`$state.snapshot\` — the return value contains the originals: ${properties}` - : 'Value cannot be cloned with `$state.snapshot` — the original value was returned'}\nhttps://svelte.dev/e/state_snapshot_uncloneable`, bold, normal); + : 'Value cannot be cloned with `$state.snapshot` — the original value was returned'}\nhttps://svelte.dev/e/state_snapshot_uncloneable`, + bold, + normal + ); } else { console.warn(`https://svelte.dev/e/state_snapshot_uncloneable`); } diff --git a/packages/svelte/tests/parser-modern/test.ts b/packages/svelte/tests/parser-modern/test.ts index b47d4a48796e..08d8aafeab0c 100644 --- a/packages/svelte/tests/parser-modern/test.ts +++ b/packages/svelte/tests/parser-modern/test.ts @@ -1,8 +1,9 @@ import * as fs from 'node:fs'; import { assert, it } from 'vitest'; -import { parse } from 'svelte/compiler'; +import { parse, print } from 'svelte/compiler'; import { try_load_json } from '../helpers.js'; import { suite, type BaseTest } from '../suite.js'; +import { walk } from 'zimmerframe'; interface ParserTest extends BaseTest {} @@ -30,6 +31,28 @@ const { test, run } = suite(async (config, cwd) => { const expected = try_load_json(`${cwd}/output.json`); 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-') + }) + ) + ); + + fs.writeFileSync(`${cwd}/_actual.svelte`, JSON.stringify(printed.code, null, '\t')); + + const actual_cleaned = walk(actual, null, { + _(node, context) {} + }); + + const reparsed_cleaned = walk(actual, null, { + _(node, context) {} + }); + + assert.deepEqual(actual_cleaned, reparsed_cleaned); }); export { test }; diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 1a83e0d0f100..b8dbc96a115d 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -1493,7 +1493,7 @@ declare module 'svelte/compiler' { | AST.Comment | Block; - export type SvelteNode = Node | TemplateNode | AST.Fragment | _CSS.Node; + export type SvelteNode = Node | TemplateNode | AST.Fragment | _CSS.Node | Script; export type { _CSS as CSS }; } @@ -1505,6 +1505,10 @@ declare module 'svelte/compiler' { export function preprocess(source: string, preprocessor: PreprocessorGroup | PreprocessorGroup[], options?: { filename?: string; } | undefined): Promise; + export function print(ast: AST.SvelteNode): { + code: string; + map: any; + }; /** * The current version, as set in package.json. * */ diff --git a/playgrounds/sandbox/run.js b/playgrounds/sandbox/run.js index 2029937f52dc..348e43b9c062 100644 --- a/playgrounds/sandbox/run.js +++ b/playgrounds/sandbox/run.js @@ -3,7 +3,7 @@ import * as path from 'node:path'; import { fileURLToPath } from 'node:url'; import { parseArgs } from 'node:util'; import { globSync } from 'tinyglobby'; -import { compile, compileModule, parse, migrate } from 'svelte/compiler'; +import { compile, compileModule, parse, print, migrate } from 'svelte/compiler'; const argv = parseArgs({ options: { runes: { type: 'boolean' } }, args: process.argv.slice(2) }); @@ -70,6 +70,10 @@ for (const generate of /** @type {const} */ (['client', 'server'])) { } catch (e) { console.warn(`Error migrating ${file}`, e); } + + const printed = print(ast); + + write(`${cwd}/output/printed/${file}`, printed.code); } const compiled = compile(source, { From 97c5d98f7944666ecf9de57e9088ba288ca1e67a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 16 Jun 2025 16:59:26 -0400 Subject: [PATCH 03/91] WIP --- packages/svelte/src/compiler/index.js | 2 +- .../src/compiler/phases/1-parse/acorn.js | 51 ++++++++++++------- .../src/compiler/phases/1-parse/index.js | 2 + .../compiler/phases/1-parse/read/context.js | 11 ++-- .../phases/1-parse/read/expression.js | 7 ++- .../compiler/phases/1-parse/read/script.js | 2 +- .../src/compiler/phases/1-parse/state/tag.js | 7 ++- .../src/compiler/phases/2-analyze/index.js | 14 +++-- .../3-transform/client/transform-client.js | 3 ++ .../src/compiler/phases/3-transform/index.js | 22 +++++--- .../3-transform/server/transform-server.js | 3 ++ .../svelte/src/compiler/phases/types.d.ts | 2 + packages/svelte/src/compiler/print/index.js | 9 +++- .../svelte/src/compiler/types/template.d.ts | 2 + .../_expected/client/module.svelte.js | 3 +- .../_expected/server/module.svelte.js | 3 +- 16 files changed, 103 insertions(+), 40 deletions(-) diff --git a/packages/svelte/src/compiler/index.js b/packages/svelte/src/compiler/index.js index ac0797927eb6..11db09193607 100644 --- a/packages/svelte/src/compiler/index.js +++ b/packages/svelte/src/compiler/index.js @@ -70,7 +70,7 @@ export function compileModule(source, options) { const validated = validate_module_options(options, ''); state.reset(source, validated); - const analysis = analyze_module(parse_acorn(source, false), validated); + const analysis = analyze_module(source, validated); return transform_module(analysis, source, validated); } diff --git a/packages/svelte/src/compiler/phases/1-parse/acorn.js b/packages/svelte/src/compiler/phases/1-parse/acorn.js index 26a09abb66b7..5a1e693bdccb 100644 --- a/packages/svelte/src/compiler/phases/1-parse/acorn.js +++ b/packages/svelte/src/compiler/phases/1-parse/acorn.js @@ -5,14 +5,27 @@ import { tsPlugin } from '@sveltejs/acorn-typescript'; const ParserWithTS = acorn.Parser.extend(tsPlugin()); +/** + * @typedef {Comment & { + * start: number; + * end: number; + * }} CommentWithLocation + */ + /** * @param {string} source + * @param {Comment[]} comments * @param {boolean} typescript * @param {boolean} [is_script] */ -export function parse(source, typescript, is_script) { +export function parse(source, comments, typescript, is_script) { const parser = typescript ? ParserWithTS : acorn.Parser; - const { onComment, add_comments } = get_comment_handlers(source); + + const { onComment, add_comments } = get_comment_handlers( + source, + /** @type {CommentWithLocation[]} */ (comments) + ); + // @ts-ignore const parse_statement = parser.prototype.parseStatement; @@ -53,13 +66,18 @@ export function parse(source, typescript, is_script) { /** * @param {string} source + * @param {Comment[]} comments * @param {boolean} typescript * @param {number} index * @returns {acorn.Expression & { leadingComments?: CommentWithLocation[]; trailingComments?: CommentWithLocation[]; }} */ -export function parse_expression_at(source, typescript, index) { +export function parse_expression_at(source, comments, typescript, index) { const parser = typescript ? ParserWithTS : acorn.Parser; - const { onComment, add_comments } = get_comment_handlers(source); + + const { onComment, add_comments } = get_comment_handlers( + source, + /** @type {CommentWithLocation[]} */ (comments) + ); const ast = parser.parseExpressionAt(source, index, { onComment, @@ -78,18 +96,9 @@ export function parse_expression_at(source, typescript, index) { * to add them after the fact. They are needed in order to support `svelte-ignore` comments * in JS code and so that `prettier-plugin-svelte` doesn't remove all comments when formatting. * @param {string} source + * @param {CommentWithLocation[]} comments */ -function get_comment_handlers(source) { - /** - * @typedef {Comment & { - * start: number; - * end: number; - * }} CommentWithLocation - */ - - /** @type {CommentWithLocation[]} */ - const comments = []; - +function get_comment_handlers(source, comments) { return { /** * @param {boolean} block @@ -97,7 +106,7 @@ function get_comment_handlers(source) { * @param {number} start * @param {number} end */ - onComment: (block, value, start, end) => { + onComment: (block, value, start, end, start_loc, end_loc) => { if (block && /\n/.test(value)) { let a = start; while (a > 0 && source[a - 1] !== '\n') a -= 1; @@ -109,13 +118,21 @@ function get_comment_handlers(source) { value = value.replace(new RegExp(`^${indentation}`, 'gm'), ''); } - comments.push({ type: block ? 'Block' : 'Line', value, start, end }); + comments.push({ + type: block ? 'Block' : 'Line', + value, + start, + end, + loc: { start: start_loc, end: end_loc } + }); }, /** @param {acorn.Node & { leadingComments?: CommentWithLocation[]; trailingComments?: CommentWithLocation[]; }} ast */ add_comments(ast) { if (comments.length === 0) return; + comments = comments.slice(); + walk(ast, null, { _(node, { next, path }) { let comment; diff --git a/packages/svelte/src/compiler/phases/1-parse/index.js b/packages/svelte/src/compiler/phases/1-parse/index.js index 6cc5b58aa666..b8ae8199ebc4 100644 --- a/packages/svelte/src/compiler/phases/1-parse/index.js +++ b/packages/svelte/src/compiler/phases/1-parse/index.js @@ -1,4 +1,5 @@ /** @import { AST } from '#compiler' */ +/** @import { Comment } from 'estree' */ // @ts-expect-error acorn type definitions are borked in the release we use import { isIdentifierStart, isIdentifierChar } from 'acorn'; import fragment from './state/fragment.js'; @@ -87,6 +88,7 @@ export class Parser { type: 'Root', fragment: create_fragment(), options: null, + comments: [], metadata: { ts: this.ts } diff --git a/packages/svelte/src/compiler/phases/1-parse/read/context.js b/packages/svelte/src/compiler/phases/1-parse/read/context.js index b1189018306c..282288e2a22f 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/context.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/context.js @@ -59,7 +59,12 @@ export default function read_pattern(parser) { space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1); const expression = /** @type {any} */ ( - parse_expression_at(`${space_with_newline}(${pattern_string} = 1)`, parser.ts, start - 1) + parse_expression_at( + `${space_with_newline}(${pattern_string} = 1)`, + parser.root.comments, + parser.ts, + start - 1 + ) ).left; expression.typeAnnotation = read_type_annotation(parser); @@ -96,13 +101,13 @@ function read_type_annotation(parser) { // parameters as part of a sequence expression instead, and will then error on optional // parameters (`?:`). Therefore replace that sequence with something that will not error. parser.template.slice(parser.index).replace(/\?\s*:/g, ':'); - let expression = parse_expression_at(template, parser.ts, a); + let expression = parse_expression_at(template, parser.root.comments, parser.ts, a); // `foo: bar = baz` gets mangled — fix it if (expression.type === 'AssignmentExpression') { let b = expression.right.start; while (template[b] !== '=') b -= 1; - expression = parse_expression_at(template.slice(0, b), parser.ts, a); + expression = parse_expression_at(template.slice(0, b), parser.root.comments, parser.ts, a); } // `array as item: string, index` becomes `string, index`, which is mistaken as a sequence expression - fix that diff --git a/packages/svelte/src/compiler/phases/1-parse/read/expression.js b/packages/svelte/src/compiler/phases/1-parse/read/expression.js index a596cdf572cb..bad0c4ae9610 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/expression.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/expression.js @@ -34,7 +34,12 @@ export function get_loose_identifier(parser, opening_token) { */ export default function read_expression(parser, opening_token, disallow_loose) { try { - const node = parse_expression_at(parser.template, parser.ts, parser.index); + const node = parse_expression_at( + parser.template, + parser.root.comments, + parser.ts, + parser.index + ); let num_parens = 0; diff --git a/packages/svelte/src/compiler/phases/1-parse/read/script.js b/packages/svelte/src/compiler/phases/1-parse/read/script.js index 629012781188..9ce449f20074 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/script.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/script.js @@ -34,7 +34,7 @@ export function read_script(parser, start, attributes) { let ast; try { - ast = acorn.parse(source, parser.ts, true); + ast = acorn.parse(source, parser.root.comments, parser.ts, true); } catch (err) { parser.acorn_error(err); } diff --git a/packages/svelte/src/compiler/phases/1-parse/state/tag.js b/packages/svelte/src/compiler/phases/1-parse/state/tag.js index 4153463c8361..f86b7bfec64f 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/tag.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/tag.js @@ -389,7 +389,12 @@ function open(parser) { let function_expression = matched ? /** @type {ArrowFunctionExpression} */ ( - parse_expression_at(prelude + `${params} => {}`, parser.ts, params_start) + parse_expression_at( + prelude + `${params} => {}`, + parser.root.comments, + parser.ts, + params_start + ) ) : { params: [] }; diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index fded183b86c3..530089dd67a7 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -1,8 +1,9 @@ -/** @import { Expression, Node, Program } from 'estree' */ +/** @import { Comment, Expression, Node, Program } from 'estree' */ /** @import { Binding, AST, ValidatedCompileOptions, ValidatedModuleCompileOptions } from '#compiler' */ /** @import { AnalysisState, Visitors } from './types' */ /** @import { Analysis, ComponentAnalysis, Js, ReactiveStatement, Template } from '../types' */ import { walk } from 'zimmerframe'; +import { parse } from '../1-parse/acorn.js'; import * as e from '../../errors.js'; import * as w from '../../warnings.js'; import { extract_identifiers } from '../../utils/ast.js'; @@ -231,11 +232,16 @@ function get_component_name(filename) { const RESERVED = ['$$props', '$$restProps', '$$slots']; /** - * @param {Program} ast + * @param {string} source * @param {ValidatedModuleCompileOptions} options * @returns {Analysis} */ -export function analyze_module(ast, options) { +export function analyze_module(source, options) { + /** @type {Comment[]} */ + const comments = []; + + const ast = parse(source, comments, false, false); + const { scope, scopes } = create_scopes(ast, new ScopeRoot(), false, null); for (const [name, references] of scope.references) { @@ -259,6 +265,7 @@ export function analyze_module(ast, options) { runes: true, immutable: true, tracing: false, + comments, classes: new Map() }; @@ -429,6 +436,7 @@ export function analyze_component(root, source, options) { module, instance, template, + comments: root.comments, elements: [], runes, tracing: false, diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index e2e006c14bec..e85a35cf8ed9 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -362,6 +362,9 @@ export function client_component(analysis, options) { .../** @type {ESTree.Statement[]} */ (template.body) ]); + // trick esrap into including comments + component_block.loc = instance.loc; + if (!analysis.runes) { // Bind static exports to props so that people can access them with bind:x for (const { name, alias } of analysis.exports) { diff --git a/packages/svelte/src/compiler/phases/3-transform/index.js b/packages/svelte/src/compiler/phases/3-transform/index.js index 2d045bf36362..ae49470d7c68 100644 --- a/packages/svelte/src/compiler/phases/3-transform/index.js +++ b/packages/svelte/src/compiler/phases/3-transform/index.js @@ -36,7 +36,7 @@ export function transform_component(analysis, source, options) { const js_source_name = get_source_name(options.filename, options.outputFilename, 'input.svelte'); // @ts-ignore TODO - const js = print(program, ts(), { + const js = print(program, ts({ comments: analysis.comments }), { // include source content; makes it easier/more robust looking up the source map code // (else esrap does return null for source and sourceMapContent which may trip up tooling) sourceMapContent: source, @@ -95,14 +95,20 @@ export function transform_module(analysis, source, options) { ]; } + // @ts-expect-error + const js = print(program, ts({ comments: analysis.comments }), { + // include source content; makes it easier/more robust looking up the source map code + // (else esrap does return null for source and sourceMapContent which may trip up tooling) + sourceMapContent: source, + sourceMapSource: get_source_name(options.filename, undefined, 'input.svelte.js') + }); + + // prepend comment + js.code = `/* ${basename} generated by Svelte v${VERSION} */\n${js.code}`; + js.map.mappings = ';' + js.map.mappings; + return { - // @ts-expect-error - js: print(program, ts(), { - // include source content; makes it easier/more robust looking up the source map code - // (else esrap does return null for source and sourceMapContent which may trip up tooling) - sourceMapContent: source, - sourceMapSource: get_source_name(options.filename, undefined, 'input.svelte.js') - }), + js, css: null, metadata: { runes: true diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index 7a3d6bef6c31..86346b864c45 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -242,6 +242,9 @@ export function server_component(analysis, options) { .../** @type {Statement[]} */ (template.body) ]); + // trick esrap into including comments + component_block.loc = instance.loc; + if (analysis.props_id) { // need to be placed on first line of the component for hydration component_block.body.unshift( diff --git a/packages/svelte/src/compiler/phases/types.d.ts b/packages/svelte/src/compiler/phases/types.d.ts index 67cbd75ff86f..aeb6184724a9 100644 --- a/packages/svelte/src/compiler/phases/types.d.ts +++ b/packages/svelte/src/compiler/phases/types.d.ts @@ -2,6 +2,7 @@ import type { AST, Binding, StateField } from '#compiler'; import type { AssignmentExpression, ClassBody, + Comment, Identifier, LabeledStatement, Node, @@ -37,6 +38,7 @@ export interface Analysis { runes: boolean; immutable: boolean; tracing: boolean; + comments: Comment[]; classes: Map>; diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index d3c6f31c77ad..bf17b4c1e6f6 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -9,7 +9,8 @@ import ts from 'esrap/languages/ts'; export function print(ast) { // @ts-expect-error some bullshit return esrap.print(ast, { - ...ts(), + // @ts-expect-error some bullshit + ...ts({ comments: ast.type === 'Root' ? ast.comments : [] }), ...visitors }); } @@ -127,7 +128,13 @@ const visitors = { context.write(``); }, + OnDirective(node, context) { + // TODO + }, TransitionDirective(node, context) { // TODO + }, + Comment(node, context) { + // TODO } }; diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index 7d2a0397b185..60f6ec3bdbac 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -72,6 +72,8 @@ export namespace AST { instance: Script | null; /** The parsed `'); }, + 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 08d8aafeab0c..da9119441a04 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 }; From 683ac717adb97dfb21009f8d0c863a56919186d7 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Mon, 16 Jun 2025 20:04:09 -0700 Subject: [PATCH 06/91] add `Declaration` visitor --- packages/svelte/src/compiler/print/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 7e77da73d8a8..72908d283832 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -1,4 +1,5 @@ /** @import { AST } from '#compiler'; */ +/** @import { _CSS } from '../types/css.js'; */ /** @import { Visitors } from 'esrap' */ import * as esrap from 'esrap'; import ts from 'esrap/languages/ts'; @@ -16,7 +17,7 @@ export function print(ast) { }); } -/** @type {Visitors} */ +/** @type {Visitors} */ const visitors = { Root(node, context) { if (node.options) { @@ -236,7 +237,7 @@ const visitors = { }, Declaration(node, context) { - context.write('foo: bar;'); + context.write(`${node.property}: ${node.value};`); }, EachBlock(node, context) { From 45755ea852a86c47b5639a6191381ec181a8e703 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Mon, 16 Jun 2025 20:08:08 -0700 Subject: [PATCH 07/91] add `TransitionDirective` --- packages/svelte/src/compiler/print/index.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 72908d283832..7a4e2686fe95 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -455,7 +455,16 @@ const visitors = { }, TransitionDirective(node, context) { - // TODO + const directive = node.intro && node.outro ? 'transition' : node.intro ? 'in' : 'out'; + context.write(`${directive}:${node.name}`); + for (const modifier of node.modifiers) { + context.write(`|${modifier}`); + } + if (node.expression !== null) { + context.write('={'); + context.visit(node.expression); + context.write('}'); + } }, TypeSelector(node, context) { From 222dd4102230a99b69bd84290b0b57a5368b1f09 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Mon, 16 Jun 2025 20:12:43 -0700 Subject: [PATCH 08/91] `UseDirective`, `OnDirective` --- packages/svelte/src/compiler/print/index.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 7a4e2686fe95..8cbffd468066 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -293,7 +293,15 @@ const visitors = { }, OnDirective(node, context) { - // TODO + context.write(`on:${node.name}`); + for (const modifier of node.modifiers) { + context.write(`|${modifier}`); + } + if (node.expression !== null) { + context.write('={'); + context.visit(node.expression); + context.write('}'); + } }, PseudoClassSelector(node, context) { @@ -469,5 +477,14 @@ const visitors = { TypeSelector(node, context) { context.write(node.name); + }, + + UseDirective(node, context) { + context.write(`use:${node.name}`); + if (node.expression !== null) { + context.write('={'); + context.visit(node.expression); + context.write('}'); + } } }; From 1e7b439533f3d3fbbb62a933b4ba2b236374cb4b Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Mon, 16 Jun 2025 20:16:49 -0700 Subject: [PATCH 09/91] more directives --- packages/svelte/src/compiler/print/index.js | 30 +++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 8cbffd468066..6faa247ce66d 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -205,6 +205,15 @@ const visitors = { context.write('}'); }, + ClassDirective(node, context) { + context.write(`class:${node.name}`); + if (node.expression !== null) { + context.write('={'); + context.visit(node.expression); + context.write('}'); + } + }, + ClassSelector(node, context) { context.write(`.${node.name}`); }, @@ -288,6 +297,15 @@ const visitors = { context.write('{/if}'); }, + LetDirective(node, context) { + context.write(`let:${node.name}`); + if (node.expression !== null) { + context.write('={'); + context.visit(node.expression); + context.write('}'); + } + }, + Nth(node, context) { context.write(node.value); // TODO is this right? }, @@ -425,6 +443,18 @@ const visitors = { context.write('{/snippet}'); }, + StyleDirective(node, context) { + context.write(`style:${node.name}`); + for (const modifier of node.modifiers) { + context.write(`|${modifier}`); + } + if (node.expression !== null) { + context.write('={'); + context.visit(node.expression); + context.write('}'); + } + }, + StyleSheet(node, context) { context.write(' Date: Mon, 16 Jun 2025 20:36:39 -0700 Subject: [PATCH 10/91] `SpreadAttribute`, directive shorthands --- packages/svelte/src/compiler/print/index.js | 39 ++++++++++++++++----- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 6faa247ce66d..aaa1e1566a81 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -1,5 +1,4 @@ /** @import { AST } from '#compiler'; */ -/** @import { _CSS } from '../types/css.js'; */ /** @import { Visitors } from 'esrap' */ import * as esrap from 'esrap'; import ts from 'esrap/languages/ts'; @@ -17,7 +16,7 @@ export function print(ast) { }); } -/** @type {Visitors} */ +/** @type {Visitors} */ const visitors = { Root(node, context) { if (node.options) { @@ -207,7 +206,10 @@ const visitors = { ClassDirective(node, context) { context.write(`class:${node.name}`); - if (node.expression !== null) { + if ( + node.expression !== null && + !(node.expression.type === 'Identifier' && node.expression.name === node.name) + ) { context.write('={'); context.visit(node.expression); context.write('}'); @@ -299,7 +301,10 @@ const visitors = { LetDirective(node, context) { context.write(`let:${node.name}`); - if (node.expression !== null) { + if ( + node.expression !== null && + !(node.expression.type === 'Identifier' && node.expression.name === node.name) + ) { context.write('={'); context.visit(node.expression); context.write('}'); @@ -315,7 +320,10 @@ const visitors = { for (const modifier of node.modifiers) { context.write(`|${modifier}`); } - if (node.expression !== null) { + if ( + node.expression !== null && + !(node.expression.type === 'Identifier' && node.expression.name === node.name) + ) { context.write('={'); context.visit(node.expression); context.write('}'); @@ -443,12 +451,21 @@ const visitors = { context.write('{/snippet}'); }, + SpreadAttribute(node, context) { + context.write('{...'); + context.visit(node.expression); + context.write('}'); + }, + StyleDirective(node, context) { context.write(`style:${node.name}`); for (const modifier of node.modifiers) { context.write(`|${modifier}`); } - if (node.expression !== null) { + if ( + node.expression !== null && + !(node.expression.type === 'Identifier' && node.expression.name === node.name) + ) { context.write('={'); context.visit(node.expression); context.write('}'); @@ -498,7 +515,10 @@ const visitors = { for (const modifier of node.modifiers) { context.write(`|${modifier}`); } - if (node.expression !== null) { + if ( + node.expression !== null && + !(node.expression.type === 'Identifier' && node.expression.name === node.name) + ) { context.write('={'); context.visit(node.expression); context.write('}'); @@ -511,7 +531,10 @@ const visitors = { UseDirective(node, context) { context.write(`use:${node.name}`); - if (node.expression !== null) { + if ( + node.expression !== null && + !(node.expression.type === 'Identifier' && node.expression.name === node.name) + ) { context.write('={'); context.visit(node.expression); context.write('}'); From 4c404ac44d485811db37a73541962b3fe4296eff Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Mon, 16 Jun 2025 20:41:46 -0700 Subject: [PATCH 11/91] `{#if ...} {:else ...}` --- packages/svelte/src/compiler/print/index.js | 26 ++++++++++++++------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index aaa1e1566a81..3618a9c33774 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -288,15 +288,25 @@ const visitors = { }, IfBlock(node, context) { - context.write('{#if '); - context.visit(node.test); - context.write('}'); - - context.visit(node.consequent); - - // TODO handle alternate/else if + if (node.elseif) { + context.write('{:else if '); + context.visit(node.test); + context.write('}'); + context.visit(node.consequent); + } else { + context.write('{#if '); + context.visit(node.test); + context.write('}'); - context.write('{/if}'); + context.visit(node.consequent); + if (node.alternate !== null) { + if (!(node.alternate.type === 'IfBlock' && node.alternate.elseif)) { + context.write('{:else}'); + } + context.visit(node.alternate); + } + context.write('{/if}'); + } }, LetDirective(node, context) { From c646f9742bb44e9f291c6c4248c71aa0d58cf9f4 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Mon, 16 Jun 2025 20:42:49 -0700 Subject: [PATCH 12/91] fix --- packages/svelte/src/compiler/print/index.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 3618a9c33774..eb40fa355650 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -300,7 +300,13 @@ const visitors = { context.visit(node.consequent); if (node.alternate !== null) { - if (!(node.alternate.type === 'IfBlock' && node.alternate.elseif)) { + if ( + !( + node.alternate.nodes.length === 1 && + node.alternate.nodes[0].type === 'IfBlock' && + node.alternate.nodes[0].elseif + ) + ) { context.write('{:else}'); } context.visit(node.alternate); From d96412b63f5342c99a05fff44d637bd5d4c3f6b7 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Mon, 16 Jun 2025 20:44:13 -0700 Subject: [PATCH 13/91] more --- packages/svelte/src/compiler/print/index.js | 24 +++++++++++---------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index eb40fa355650..7495721d7ee4 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -299,18 +299,20 @@ const visitors = { context.write('}'); context.visit(node.consequent); - if (node.alternate !== null) { - if ( - !( - node.alternate.nodes.length === 1 && - node.alternate.nodes[0].type === 'IfBlock' && - node.alternate.nodes[0].elseif - ) - ) { - context.write('{:else}'); - } - context.visit(node.alternate); + } + if (node.alternate !== null) { + if ( + !( + node.alternate.nodes.length === 1 && + node.alternate.nodes[0].type === 'IfBlock' && + node.alternate.nodes[0].elseif + ) + ) { + context.write('{:else}'); } + context.visit(node.alternate); + } + if (!node.elseif) { context.write('{/if}'); } }, From 566da5b95f0d451215603cfd8b03239a1191bbe7 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Mon, 16 Jun 2025 20:54:21 -0700 Subject: [PATCH 14/91] add tags, `AnimateDirective` --- packages/svelte/src/compiler/print/index.js | 37 +++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 7495721d7ee4..771c997fdf83 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -81,6 +81,18 @@ const visitors = { } }, + AnimateDirective(node, context) { + context.write(`animate:${node.name}`); + if ( + node.expression !== null && + !(node.expression.type === 'Identifier' && node.expression.name === node.name) + ) { + context.write('={'); + context.visit(node.expression); + context.write('}'); + } + }, + Atrule(node, context) { context.write(`@${node.name}`); if (node.prelude) context.write(` ${node.prelude}`); @@ -247,6 +259,25 @@ const visitors = { } }, + ConstTag(node, context) { + context.write('{@const '); + context.visit(node.declaration); // TODO does this work? + context.write('}'); + }, + + DebugTag(node, context) { + context.write('{@debug '); + let started = false; + for (const identifier of node.identifiers) { + if (started) { + context.write(', '); + } + context.visit(identifier); + started = true; + } + context.write('}'); + }, + Declaration(node, context) { context.write(`${node.property}: ${node.value};`); }, @@ -287,6 +318,12 @@ const visitors = { context.write('}'); }, + HtmlTag(node, context) { + context.write('{@html '); + context.visit(node.expression); + context.write('}'); + }, + IfBlock(node, context) { if (node.elseif) { context.write('{:else if '); From 0b9f5607c61dd62bbc5f9acb9d850d67c105feb7 Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Mon, 16 Jun 2025 21:00:40 -0700 Subject: [PATCH 15/91] `KeyBlock` --- packages/svelte/src/compiler/print/index.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 771c997fdf83..213fa3b1f881 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -16,7 +16,7 @@ export function print(ast) { }); } -/** @type {Visitors} */ +/** @type {Visitors} */ const visitors = { Root(node, context) { if (node.options) { @@ -354,6 +354,14 @@ const visitors = { } }, + KeyBlock(node, context) { + context.write('{#key '); + context.visit(node.expression); + context.write('}'); + context.visit(node.fragment); + context.write('{/key}'); + }, + LetDirective(node, context) { context.write(`let:${node.name}`); if ( From 0de2182acc4072610fa51d45efeee920603dfb4c Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Mon, 16 Jun 2025 21:14:04 -0700 Subject: [PATCH 16/91] `SelectorList`, `` --- packages/svelte/src/compiler/print/index.js | 166 +++++++++++++++++++- 1 file changed, 165 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 213fa3b1f881..ab5180fe6e32 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -261,7 +261,7 @@ const visitors = { ConstTag(node, context) { context.write('{@const '); - context.visit(node.declaration); // TODO does this work? + context.visit(node.declaration); context.write('}'); }, @@ -477,6 +477,18 @@ const visitors = { context.visit(node.block); }, + SelectorList(node, context) { + let started = false; + for (const selector of node.children) { + if (started) { + context.write(', '); + } + + context.visit(selector); + started = true; + } + }, + SlotElement(node, context) { context.write(''); }, + SvelteBoundary(node, context) { + context.write(''); + context.visit(node.fragment); + context.write(``); + } else { + context.write('/>'); + } + }, + + SvelteComponent(node, context) { + context.write(''); + context.visit(node.fragment); + context.write(``); + } else { + context.write('/>'); + } + }, + + SvelteDocument(node, context) { + context.write(''); + context.visit(node.fragment); + context.write(``); + } else { + context.write('/>'); + } + }, + + SvelteElement(node, context) { + context.write(''); + context.visit(node.fragment); + context.write(``); + } else { + context.write('/>'); + } + }, + + SvelteFragment(node, context) { + context.write(''); + context.visit(node.fragment); + context.write(``); + } else { + context.write('/>'); + } + }, + + SvelteHead(node, context) { + context.write(''); + context.visit(node.fragment); + context.write(``); + } else { + context.write('/>'); + } + }, + + SvelteSelf(node, context) { + context.write(''); + context.visit(node.fragment); + context.write(``); + } else { + context.write('/>'); + } + }, + + SvelteWindow(node, context) { + context.write(''); + context.visit(node.fragment); + context.write(``); + } else { + context.write('/>'); + } + }, + Text(node, context) { context.write(node.data); }, From e41bbe7a1cb053f6868a7978c11cc85407658aae Mon Sep 17 00:00:00 2001 From: ComputerGuy <63362464+Ocean-OS@users.noreply.github.com> Date: Sat, 21 Jun 2025 12:22:24 -0700 Subject: [PATCH 17/91] quote text in `Attribute` visitor --- packages/svelte/src/compiler/print/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index ab5180fe6e32..45feedff4cc5 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -119,7 +119,7 @@ const visitors = { context.write('='); if (Array.isArray(node.value)) { - if (node.value.length > 1) { + if (node.value.length > 1 || node.value[0].type === 'Text') { context.write('"'); } @@ -127,7 +127,7 @@ const visitors = { context.visit(chunk); } - if (node.value.length > 1) { + if (node.value.length > 1 || node.value[0].type === 'Text') { context.write('"'); } } else { From 0a61c80be3a73c1380b40df55e37e3d145aa6ba7 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 23 Jun 2025 06:50:10 -0400 Subject: [PATCH 18/91] tweak test logic to reduce false negatives --- packages/svelte/src/compiler/print/index.js | 2 +- packages/svelte/tests/parser-modern/test.ts | 55 +++++++++++++++------ 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 45feedff4cc5..f0c540987082 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -16,7 +16,7 @@ export function print(ast) { }); } -/** @type {Visitors} */ +/** @type {Visitors} */ const visitors = { Root(node, context) { if (node.options) { diff --git a/packages/svelte/tests/parser-modern/test.ts b/packages/svelte/tests/parser-modern/test.ts index da9119441a04..74fabda63369 100644 --- a/packages/svelte/tests/parser-modern/test.ts +++ b/packages/svelte/tests/parser-modern/test.ts @@ -49,26 +49,51 @@ const { test, run } = suite(async (config, cwd) => { fs.writeFileSync(`${cwd}/_actual.svelte`, printed.code); - const actual_cleaned = walk(actual, null, { - _(node, context) { - delete node.loc; - context.next(); - } - }); - delete reparsed.comments; - const reparsed_cleaned = walk(reparsed, null, { - _(node, context) { - delete node.loc; - context.next(); - } - }); - - assert.deepEqual(actual_cleaned, reparsed_cleaned); + assert.deepEqual(clean(actual), clean(reparsed)); } }); +function clean(ast: import('svelte/compiler').AST.SvelteNode) { + return walk(ast, null, { + _(node, context) { + // @ts-ignore + delete node.start; + // @ts-ignore + delete node.end; + // @ts-ignore + delete node.loc; + // @ts-ignore + delete node.leadingComments; + // @ts-ignore + delete node.trailingComments; + + context.next(); + }, + Fragment(node, context) { + return { + ...node, + nodes: node.nodes + .map((child, i) => { + if (child.type === 'Text') { + if (i === 0) { + child = { ...child, data: child.data.trimStart() }; + } + + if (i === node.nodes.length - 1) { + child = { ...child, data: child.data.trimEnd() }; + } + + if (!child.data) return null; + } + }) + .filter(Boolean) + }; + } + }); +} + export { test }; await run(__dirname); From 5db4ea878afb7a71e543e7bc32c20eec411e31b2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 23 Jun 2025 06:52:56 -0400 Subject: [PATCH 19/91] fix --- packages/svelte/tests/parser-modern/test.ts | 42 +++++++++++---------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/packages/svelte/tests/parser-modern/test.ts b/packages/svelte/tests/parser-modern/test.ts index 74fabda63369..152f8332a6ea 100644 --- a/packages/svelte/tests/parser-modern/test.ts +++ b/packages/svelte/tests/parser-modern/test.ts @@ -4,6 +4,7 @@ import { parse, print } from 'svelte/compiler'; import { try_load_json } from '../helpers.js'; import { suite, type BaseTest } from '../suite.js'; import { walk } from 'zimmerframe'; +import type { AST } from 'svelte/compiler'; interface ParserTest extends BaseTest {} @@ -55,7 +56,7 @@ const { test, run } = suite(async (config, cwd) => { } }); -function clean(ast: import('svelte/compiler').AST.SvelteNode) { +function clean(ast: AST.SvelteNode) { return walk(ast, null, { _(node, context) { // @ts-ignore @@ -72,24 +73,27 @@ function clean(ast: import('svelte/compiler').AST.SvelteNode) { context.next(); }, Fragment(node, context) { - return { - ...node, - nodes: node.nodes - .map((child, i) => { - if (child.type === 'Text') { - if (i === 0) { - child = { ...child, data: child.data.trimStart() }; - } - - if (i === node.nodes.length - 1) { - child = { ...child, data: child.data.trimEnd() }; - } - - if (!child.data) return null; - } - }) - .filter(Boolean) - }; + const nodes: AST.SvelteNode[] = []; + + for (let i = 0; i < node.nodes.length; i += 1) { + let child = node.nodes[i]; + + if (child.type === 'Text') { + if (i === 0) { + child = { ...child, data: child.data.trimStart() }; + } + + if (i === node.nodes.length - 1) { + child = { ...child, data: child.data.trimEnd() }; + } + + if (!child.data) continue; + } + + nodes.push(context.visit(child)); + } + + return { ...node, nodes } as AST.Fragment; } }); } From b80eb3e297af7babd914cb253d2801e389cb018d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 23 Jun 2025 07:00:30 -0400 Subject: [PATCH 20/91] fix --- packages/svelte/tests/parser-modern/test.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/svelte/tests/parser-modern/test.ts b/packages/svelte/tests/parser-modern/test.ts index 152f8332a6ea..4130bd8d613e 100644 --- a/packages/svelte/tests/parser-modern/test.ts +++ b/packages/svelte/tests/parser-modern/test.ts @@ -72,6 +72,14 @@ function clean(ast: AST.SvelteNode) { context.next(); }, + StyleSheet(node, context) { + return { + type: node.type, + attributes: node.attributes.map((attribute) => context.visit(attribute)), + children: node.children.map((child) => context.visit(child)), + content: {} + } as AST.SvelteNode; + }, Fragment(node, context) { const nodes: AST.SvelteNode[] = []; @@ -80,11 +88,19 @@ function clean(ast: AST.SvelteNode) { if (child.type === 'Text') { if (i === 0) { - child = { ...child, data: child.data.trimStart() }; + child = { + ...child, + data: child.data.trimStart(), + raw: child.raw.trimStart() + }; } if (i === node.nodes.length - 1) { - child = { ...child, data: child.data.trimEnd() }; + child = { + ...child, + data: child.data.trimEnd(), + raw: child.raw.trimEnd() + }; } if (!child.data) continue; From 4018b5024724a9035ca934aa5a97d4f8a54e681a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 23 Jun 2025 07:07:20 -0400 Subject: [PATCH 21/91] add separate test suite --- .../tests/print/samples/basic/input.svelte | 7 ++++ .../tests/print/samples/basic/output.svelte | 5 +++ packages/svelte/tests/print/test.ts | 32 +++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 packages/svelte/tests/print/samples/basic/input.svelte create mode 100644 packages/svelte/tests/print/samples/basic/output.svelte create mode 100644 packages/svelte/tests/print/test.ts diff --git a/packages/svelte/tests/print/samples/basic/input.svelte b/packages/svelte/tests/print/samples/basic/input.svelte new file mode 100644 index 000000000000..bff6bee537b4 --- /dev/null +++ b/packages/svelte/tests/print/samples/basic/input.svelte @@ -0,0 +1,7 @@ + + +

+ Hello {name}! +

diff --git a/packages/svelte/tests/print/samples/basic/output.svelte b/packages/svelte/tests/print/samples/basic/output.svelte new file mode 100644 index 000000000000..22b3c84db099 --- /dev/null +++ b/packages/svelte/tests/print/samples/basic/output.svelte @@ -0,0 +1,5 @@ + + +

Hello {name}!

\ No newline at end of file diff --git a/packages/svelte/tests/print/test.ts b/packages/svelte/tests/print/test.ts new file mode 100644 index 000000000000..776a6a4ca47f --- /dev/null +++ b/packages/svelte/tests/print/test.ts @@ -0,0 +1,32 @@ +import * as fs from 'node:fs'; +import { assert, it } from 'vitest'; +import { parse, print } from 'svelte/compiler'; +import { try_load_json } from '../helpers.js'; +import { suite, type BaseTest } from '../suite.js'; +import { walk } from 'zimmerframe'; +import type { AST } from 'svelte/compiler'; + +interface ParserTest extends BaseTest {} + +const { test, run } = suite(async (config, cwd) => { + const input = fs.readFileSync(`${cwd}/input.svelte`, 'utf-8'); + + const ast = parse(input, { modern: true }); + const output = print(ast); + + // run `UPDATE_SNAPSHOTS=true pnpm test parser` to update parser tests + if (process.env.UPDATE_SNAPSHOTS) { + fs.writeFileSync(`${cwd}/output.svelte`, output.code); + } else { + fs.writeFileSync(`${cwd}/_actual.svelte`, output.code); + + const file = `${cwd}/output.svelte`; + + const expected = fs.existsSync(file) ? fs.readFileSync(file, 'utf-8') : ''; + assert.deepEqual(output.code, expected); + } +}); + +export { test }; + +await run(__dirname); From 1ca1be5a030010404f3bb60a20e858fafbba732c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 23 Jun 2025 07:45:30 -0400 Subject: [PATCH 22/91] fix --- .../imports-in-modules/_expected/client/index.svelte.js | 3 +-- .../imports-in-modules/_expected/server/index.svelte.js | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/index.svelte.js index 884e919f14d8..ad6beb0c7b0c 100644 --- a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/index.svelte.js @@ -3,5 +3,4 @@ import 'svelte/internal/flags/legacy'; import * as $ from 'svelte/internal/client'; import { random } from './module.svelte'; -export default function Imports_in_modules($$anchor) { -} \ No newline at end of file +export default function Imports_in_modules($$anchor) {} diff --git a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/index.svelte.js index 75de235220bd..da97f06aada3 100644 --- a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/index.svelte.js @@ -1,5 +1,4 @@ import * as $ from 'svelte/internal/server'; import { random } from './module.svelte'; -export default function Imports_in_modules($$payload) { -} \ No newline at end of file +export default function Imports_in_modules($$payload) {} From 80370e3a26e378c896272c116c94688105482fa2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 23 Jun 2025 13:07:33 -0400 Subject: [PATCH 23/91] more --- packages/svelte/src/compiler/print/index.js | 85 +++++++++++++++++-- .../tests/print/samples/block/input.svelte | 1 + .../tests/print/samples/block/output.svelte | 5 ++ packages/svelte/tests/print/test.ts | 2 +- 4 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 packages/svelte/tests/print/samples/block/input.svelte create mode 100644 packages/svelte/tests/print/samples/block/output.svelte diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index f0c540987082..a387a31cace3 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -1,5 +1,5 @@ /** @import { AST } from '#compiler'; */ -/** @import { Visitors } from 'esrap' */ +/** @import { Context, Visitors } from 'esrap' */ import * as esrap from 'esrap'; import ts from 'esrap/languages/ts'; import { is_void } from '../../utils.js'; @@ -65,20 +65,71 @@ const visitors = { }, Fragment(node, context) { - for (let i = 0; i < node.nodes.length; i += 1) { - const child = node.nodes[i]; + const join = context.new(); + + /** @type {Context[]} */ + const contexts = []; - if (child.type === 'Text') { - let data = child.data; + let sequence = context.new(); - if (i === 0) data = data.trimStart(); - if (i === node.nodes.length - 1) data = data.trimEnd(); + let multiline = false; - context.write(data); + function flush() { + if (sequence.empty()) { + return; + } + + contexts.push(sequence); + sequence = context.new(); + } + + for (let i = 0; i < node.nodes.length; i += 1) { + const child_node = node.nodes[i]; + const prev = node.nodes[i - 1]; + const next = node.nodes[i + 1]; + + const prev_is_text = prev && (prev.type === 'Text' || prev.type === 'ExpressionTag'); + const next_is_text = next && (next.type === 'Text' || next.type === 'ExpressionTag'); + + if (child_node.type === 'Text' || child_node.type === 'ExpressionTag') { + if (child_node.type === 'Text') { + let { data } = child_node; + + let a = !prev_is_text && data !== (data = data.trimStart()); + let b = !next_is_text && data !== (data = data.trimEnd()); + + if (data === '') { + if (prev && next) sequence.append(join); + } else { + if (a && prev) sequence.append(join); + sequence.write(data); + if (b && next) sequence.append(join); + } + } else { + sequence.visit(child_node); + } } else { - context.visit(child); + flush(); + const child_context = context.new(); + child_context.visit(child_node); + + contexts.push(child_context); + + multiline ||= child_context.multiline; } } + + flush(); + + if (multiline) { + join.newline(); + } else { + join.write(' '); + } + + for (const child_context of contexts) { + context.append(child_context); + } }, AnimateDirective(node, context) { @@ -329,14 +380,24 @@ const visitors = { context.write('{:else if '); context.visit(node.test); context.write('}'); + + context.indent(); + context.newline(); context.visit(node.consequent); + context.dedent(); + context.newline(); } else { context.write('{#if '); context.visit(node.test); context.write('}'); + context.indent(); + context.newline(); context.visit(node.consequent); + context.dedent(); + context.newline(); } + if (node.alternate !== null) { if ( !( @@ -347,8 +408,14 @@ const visitors = { ) { context.write('{:else}'); } + + context.indent(); + context.newline(); context.visit(node.alternate); + context.dedent(); + context.newline(); } + if (!node.elseif) { context.write('{/if}'); } diff --git a/packages/svelte/tests/print/samples/block/input.svelte b/packages/svelte/tests/print/samples/block/input.svelte new file mode 100644 index 000000000000..470f7a1efbdc --- /dev/null +++ b/packages/svelte/tests/print/samples/block/input.svelte @@ -0,0 +1 @@ +{#if condition} yes {:else} no {/if} diff --git a/packages/svelte/tests/print/samples/block/output.svelte b/packages/svelte/tests/print/samples/block/output.svelte new file mode 100644 index 000000000000..e0ff317fc8f4 --- /dev/null +++ b/packages/svelte/tests/print/samples/block/output.svelte @@ -0,0 +1,5 @@ +{#if condition} + yes +{:else} + no +{/if} diff --git a/packages/svelte/tests/print/test.ts b/packages/svelte/tests/print/test.ts index 776a6a4ca47f..7600adbcc92b 100644 --- a/packages/svelte/tests/print/test.ts +++ b/packages/svelte/tests/print/test.ts @@ -23,7 +23,7 @@ const { test, run } = suite(async (config, cwd) => { const file = `${cwd}/output.svelte`; const expected = fs.existsSync(file) ? fs.readFileSync(file, 'utf-8') : ''; - assert.deepEqual(output.code, expected); + assert.deepEqual(output.code.trim(), expected.trim()); } }); From 4538f80259b8f1eca2b5086ecf0ee9c69712dd5a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 23 Jun 2025 16:34:30 -0400 Subject: [PATCH 24/91] slightly nicer printing --- packages/svelte/src/compiler/print/index.js | 215 ++++++++++++-------- packages/svelte/tests/parser-modern/test.ts | 22 +- 2 files changed, 137 insertions(+), 100 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index a387a31cace3..0e81cd6faf3a 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -16,6 +16,29 @@ export function print(ast) { }); } +/** + * @param {Context} context + * @param {AST.SvelteNode} node + */ +function block(context, node, allow_inline = false) { + const child_context = context.new(); + child_context.visit(node); + + if (child_context.empty()) { + return; + } + + if (allow_inline && !child_context.multiline) { + context.append(child_context); + } else { + context.indent(); + context.newline(); + context.append(child_context); + context.dedent(); + context.newline(); + } +} + /** @type {Visitors} */ const visitors = { Root(node, context) { @@ -54,81 +77,100 @@ const visitors = { } context.write('>'); - - context.indent(); - context.newline(); - context.visit(node.content); - context.dedent(); - context.newline(); - + block(context, node.content); context.write(''); }, Fragment(node, context) { - const join = context.new(); + /** @type {AST.SvelteNode[][]} */ + const items = []; - /** @type {Context[]} */ - const contexts = []; + /** @type {AST.SvelteNode[]} */ + let sequence = []; - let sequence = context.new(); - - let multiline = false; - - function flush() { - if (sequence.empty()) { - return; - } - - contexts.push(sequence); - sequence = context.new(); - } + const flush = () => { + items.push(sequence); + sequence = []; + }; for (let i = 0; i < node.nodes.length; i += 1) { - const child_node = node.nodes[i]; + let child_node = node.nodes[i]; + const prev = node.nodes[i - 1]; const next = node.nodes[i + 1]; - const prev_is_text = prev && (prev.type === 'Text' || prev.type === 'ExpressionTag'); - const next_is_text = next && (next.type === 'Text' || next.type === 'ExpressionTag'); + if (child_node.type === 'Text') { + child_node = { ...child_node }; // always clone, so we can safely mutate + + child_node.data = child_node.data.replace(/[^\S]+/g, ' '); + + // trim fragment + if (i === 0) { + child_node.data = child_node.data.trimStart(); + } + + if (i === node.nodes.length - 1) { + child_node.data = child_node.data.trimEnd(); + } - if (child_node.type === 'Text' || child_node.type === 'ExpressionTag') { - if (child_node.type === 'Text') { - let { data } = child_node; + if (child_node.data === '') { + continue; + } + + if (child_node.data.startsWith(' ') && prev && prev.type !== 'ExpressionTag') { + flush(); + child_node.data = child_node.data.trimStart(); + } - let a = !prev_is_text && data !== (data = data.trimStart()); - let b = !next_is_text && data !== (data = data.trimEnd()); + if (child_node.data !== '') { + sequence.push({ ...child_node, data: child_node.data }); - if (data === '') { - if (prev && next) sequence.append(join); - } else { - if (a && prev) sequence.append(join); - sequence.write(data); - if (b && next) sequence.append(join); + if (child_node.data.endsWith(' ') && next && next.type !== 'ExpressionTag') { + flush(); + child_node.data = child_node.data.trimStart(); } - } else { - sequence.visit(child_node); } } else { - flush(); - const child_context = context.new(); - child_context.visit(child_node); + sequence.push(child_node); + } + } + + flush(); + + let multiline = false; + let width = 0; - contexts.push(child_context); + const child_contexts = items.map((sequence) => { + const child_context = context.new(); + for (const node of sequence) { + child_context.visit(node); multiline ||= child_context.multiline; } - } - flush(); + width += child_context.measure(); - if (multiline) { - join.newline(); - } else { - join.write(' '); - } + return child_context; + }); + + multiline ||= width > 30; + + for (let i = 0; i < child_contexts.length; i += 1) { + const prev = child_contexts[i]; + const next = child_contexts[i + 1]; + + context.append(prev); - for (const child_context of contexts) { - context.append(child_context); + if (next) { + if (prev.multiline || next.multiline) { + context.margin(); + context.newline(); + } else if (multiline) { + context.newline(); + } else { + context.write(' '); + } + } } }, @@ -192,7 +234,7 @@ const visitors = { if (node.pending) { context.write('}'); - context.visit(node.pending); + block(context, node.pending); context.write('{:'); } else { context.write(' '); @@ -202,7 +244,8 @@ const visitors = { context.write(node.value ? 'then ' : 'then'); if (node.value) context.visit(node.value); context.write('}'); - context.visit(node.then); + + block(context, node.then); if (node.catch) { context.write('{:'); @@ -213,7 +256,8 @@ const visitors = { context.write(node.value ? 'catch ' : 'catch'); if (node.error) context.visit(node.error); context.write('}'); - context.visit(node.catch); + + block(context, node.catch); } context.write('{/await}'); @@ -303,7 +347,7 @@ const visitors = { if (node.fragment.nodes.length > 0) { context.write('>'); - context.visit(node.fragment); + block(context, node.fragment, true); context.write(``); } else { context.write(' />'); @@ -353,7 +397,8 @@ const visitors = { } context.write('}'); - context.visit(node.body); + + block(context, node.body); if (node.fallback) { context.write('{:else}'); @@ -381,21 +426,13 @@ const visitors = { context.visit(node.test); context.write('}'); - context.indent(); - context.newline(); - context.visit(node.consequent); - context.dedent(); - context.newline(); + block(context, node.consequent); } else { context.write('{#if '); context.visit(node.test); context.write('}'); - context.indent(); - context.newline(); - context.visit(node.consequent); - context.dedent(); - context.newline(); + block(context, node.consequent); } if (node.alternate !== null) { @@ -409,11 +446,7 @@ const visitors = { context.write('{:else}'); } - context.indent(); - context.newline(); - context.visit(node.alternate); - context.dedent(); - context.newline(); + block(context, node.alternate); } if (!node.elseif) { @@ -425,7 +458,7 @@ const visitors = { context.write('{#key '); context.visit(node.expression); context.write('}'); - context.visit(node.fragment); + block(context, node.fragment); context.write('{/key}'); }, @@ -487,24 +520,28 @@ const visitors = { }, RegularElement(node, context) { - context.write('<' + node.name); + const child_context = context.new(); + + child_context.write('<' + node.name); for (const attribute of node.attributes) { // TODO handle multiline - context.write(' '); - context.visit(attribute); + child_context.write(' '); + child_context.visit(attribute); } if (is_void(node.name)) { - context.write(' />'); + child_context.write(' />'); } else { - context.write('>'); + child_context.write('>'); if (node.fragment) { - context.visit(node.fragment); - context.write(``); + block(child_context, node.fragment, child_context.measure() < 30); + child_context.write(``); } } + + context.append(child_context); }, RelativeSelector(node, context) { @@ -566,7 +603,7 @@ const visitors = { if (node.fragment.nodes.length > 0) { context.write('>'); - context.visit(node.fragment); + context.visit(node.fragment); // TODO block/inline context.write(''); } else { context.write(' />'); @@ -589,7 +626,7 @@ const visitors = { } context.write(')}'); - context.visit(node.body); + block(context, node.body); context.write('{/snippet}'); }, @@ -658,7 +695,7 @@ const visitors = { if (node.fragment) { context.write('>'); - context.visit(node.fragment); + block(context, node.fragment, true); context.write(``); } else { context.write('/>'); @@ -680,7 +717,7 @@ const visitors = { if (node.fragment) { context.write('>'); - context.visit(node.fragment); + block(context, node.fragment, true); context.write(``); } else { context.write('/>'); @@ -698,7 +735,7 @@ const visitors = { if (node.fragment) { context.write('>'); - context.visit(node.fragment); + block(context, node.fragment, true); context.write(``); } else { context.write('/>'); @@ -720,7 +757,7 @@ const visitors = { if (node.fragment) { context.write('>'); - context.visit(node.fragment); + block(context, node.fragment, true); context.write(``); } else { context.write('/>'); @@ -738,7 +775,7 @@ const visitors = { if (node.fragment) { context.write('>'); - context.visit(node.fragment); + block(context, node.fragment, true); context.write(``); } else { context.write('/>'); @@ -756,7 +793,7 @@ const visitors = { if (node.fragment) { context.write('>'); - context.visit(node.fragment); + block(context, node.fragment, true); context.write(``); } else { context.write('/>'); @@ -774,7 +811,7 @@ const visitors = { if (node.fragment) { context.write('>'); - context.visit(node.fragment); + block(context, node.fragment, true); context.write(``); } else { context.write('/>'); @@ -792,7 +829,7 @@ const visitors = { if (node.fragment) { context.write('>'); - context.visit(node.fragment); + block(context, node.fragment, true); context.write(``); } else { context.write('/>'); diff --git a/packages/svelte/tests/parser-modern/test.ts b/packages/svelte/tests/parser-modern/test.ts index 4130bd8d613e..4f9bae4a056c 100644 --- a/packages/svelte/tests/parser-modern/test.ts +++ b/packages/svelte/tests/parser-modern/test.ts @@ -87,23 +87,23 @@ function clean(ast: AST.SvelteNode) { let child = node.nodes[i]; if (child.type === 'Text') { + child = { + ...child, + data: child.data.replace(/[^\S]+/g, ' '), + raw: child.raw.replace(/[^\S]+/g, ' ') + }; + if (i === 0) { - child = { - ...child, - data: child.data.trimStart(), - raw: child.raw.trimStart() - }; + child.data = child.data.trimStart(); + child.raw = child.raw.trimStart(); } if (i === node.nodes.length - 1) { - child = { - ...child, - data: child.data.trimEnd(), - raw: child.raw.trimEnd() - }; + child.data = child.data.trimEnd(); + child.raw = child.raw.trimEnd(); } - if (!child.data) continue; + if (child.data === '') continue; } nodes.push(context.visit(child)); From e47f5794d0633448ab0bb62e0f820ef485ffe524 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 23 Jun 2025 16:50:47 -0400 Subject: [PATCH 25/91] install from pkg.pr.new --- package.json | 8 -------- packages/svelte/package.json | 2 +- pnpm-lock.yaml | 19 ++++++++++--------- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 4f5748cc5dd2..62581782d72d 100644 --- a/package.json +++ b/package.json @@ -42,13 +42,5 @@ "typescript-eslint": "^8.24.0", "v8-natives": "^1.2.5", "vitest": "^2.1.9" - }, - "pnpm": { - "overrides": { - "esrap": "link:../../esrap" - } - }, - "dependencies": { - "esrap": "link:../../../../esrap" } } diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 1b1276182b55..4a294f923eb4 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -171,7 +171,7 @@ "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", - "esrap": "https://pkg.pr.new/sveltejs/esrap@a275a5c", + "esrap": "https://pkg.pr.new/sveltejs/esrap@b5ba3da", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 08373e6b9d36..eb1b3810ebfc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,16 +4,9 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false -overrides: - esrap: link:../../esrap - importers: .: - dependencies: - esrap: - specifier: link:../../esrap - version: link:../../esrap devDependencies: '@changesets/cli': specifier: ^2.27.8 @@ -94,8 +87,8 @@ importers: specifier: ^1.2.1 version: 1.2.1 esrap: - specifier: link:../../../../esrap - version: link:../../../../esrap + specifier: https://pkg.pr.new/sveltejs/esrap@b5ba3da + version: https://pkg.pr.new/sveltejs/esrap@b5ba3da is-reference: specifier: ^3.0.3 version: 3.0.3 @@ -1268,6 +1261,10 @@ packages: resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} engines: {node: '>=0.10'} + esrap@https://pkg.pr.new/sveltejs/esrap@b5ba3da: + resolution: {tarball: https://pkg.pr.new/sveltejs/esrap@b5ba3da} + version: 1.4.9 + esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} @@ -3626,6 +3623,10 @@ snapshots: dependencies: estraverse: 5.3.0 + esrap@https://pkg.pr.new/sveltejs/esrap@b5ba3da: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + esrecurse@4.3.0: dependencies: estraverse: 5.3.0 From df3be44c276a68c28c4708b1952cea2e24ef696d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 23 Jun 2025 16:54:23 -0400 Subject: [PATCH 26/91] merge main --- .../imports-in-modules/_expected/client/index.svelte.js | 2 +- .../imports-in-modules/_expected/client/module.svelte.js | 2 +- .../imports-in-modules/_expected/server/index.svelte.js | 2 +- .../imports-in-modules/_expected/server/module.svelte.js | 2 +- .../snapshot/samples/purity/_expected/client/index.svelte.js | 4 +--- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/index.svelte.js index ad6beb0c7b0c..0eab38919c5e 100644 --- a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/index.svelte.js @@ -3,4 +3,4 @@ import 'svelte/internal/flags/legacy'; import * as $ from 'svelte/internal/client'; import { random } from './module.svelte'; -export default function Imports_in_modules($$anchor) {} +export default function Imports_in_modules($$anchor) {} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/module.svelte.js b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/module.svelte.js index 0afcd51e4b12..0d366e6258ff 100644 --- a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/module.svelte.js +++ b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/client/module.svelte.js @@ -2,4 +2,4 @@ import * as $ from 'svelte/internal/client'; import { random } from './export'; -export { random }; +export { random }; \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/index.svelte.js index da97f06aada3..2ed863d68f3a 100644 --- a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/index.svelte.js @@ -1,4 +1,4 @@ import * as $ from 'svelte/internal/server'; import { random } from './module.svelte'; -export default function Imports_in_modules($$payload) {} +export default function Imports_in_modules($$payload) {} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/module.svelte.js b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/module.svelte.js index d57d7c2d5a01..2e0af8af84d8 100644 --- a/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/module.svelte.js +++ b/packages/svelte/tests/snapshot/samples/imports-in-modules/_expected/server/module.svelte.js @@ -2,4 +2,4 @@ import * as $ from 'svelte/internal/server'; import { random } from './export'; -export { random }; +export { random }; \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js index da6fdf44d881..f3a93432da7a 100644 --- a/packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/purity/_expected/client/index.svelte.js @@ -8,9 +8,7 @@ export default function Purity($$anchor) { var fragment = root(); var p = $.first_child(fragment); - p.textContent = ( - $.untrack(() => Math.max(0, Math.min(0, 100))) - ); + p.textContent = ($.untrack(() => Math.max(0, Math.min(0, 100)))); var p_1 = $.sibling(p, 2); From c474d679e9e1be5f4e985ea7ef31fc5184b6d2c9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 23 Jun 2025 17:55:18 -0400 Subject: [PATCH 27/91] bump --- packages/svelte/package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/svelte/package.json b/packages/svelte/package.json index cc04eda4da8f..cced2562cb17 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -171,7 +171,7 @@ "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", - "esrap": "https://pkg.pr.new/sveltejs/esrap@b5ba3da", + "esrap": "https://pkg.pr.new/sveltejs/esrap@ef4051a", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eb1b3810ebfc..f722e5e939b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -87,8 +87,8 @@ importers: specifier: ^1.2.1 version: 1.2.1 esrap: - specifier: https://pkg.pr.new/sveltejs/esrap@b5ba3da - version: https://pkg.pr.new/sveltejs/esrap@b5ba3da + specifier: https://pkg.pr.new/sveltejs/esrap@ef4051a + version: https://pkg.pr.new/sveltejs/esrap@ef4051a is-reference: specifier: ^3.0.3 version: 3.0.3 @@ -1261,8 +1261,8 @@ packages: resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} engines: {node: '>=0.10'} - esrap@https://pkg.pr.new/sveltejs/esrap@b5ba3da: - resolution: {tarball: https://pkg.pr.new/sveltejs/esrap@b5ba3da} + esrap@https://pkg.pr.new/sveltejs/esrap@ef4051a: + resolution: {tarball: https://pkg.pr.new/sveltejs/esrap@ef4051a} version: 1.4.9 esrecurse@4.3.0: @@ -3623,7 +3623,7 @@ snapshots: dependencies: estraverse: 5.3.0 - esrap@https://pkg.pr.new/sveltejs/esrap@b5ba3da: + esrap@https://pkg.pr.new/sveltejs/esrap@ef4051a: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 From e904753ecc96d5a2a1bac282044f1c8e63257e87 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 23 Jun 2025 18:16:13 -0400 Subject: [PATCH 28/91] fix --- packages/svelte/package.json | 2 +- .../svelte/scripts/process-messages/index.js | 2 -- .../src/compiler/phases/1-parse/acorn.js | 7 ++++- packages/svelte/src/compiler/print/index.js | 27 ++++++++++++------- .../svelte/src/compiler/types/template.d.ts | 2 +- pnpm-lock.yaml | 10 +++---- 6 files changed, 31 insertions(+), 19 deletions(-) diff --git a/packages/svelte/package.json b/packages/svelte/package.json index cced2562cb17..1cc5870f43be 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -171,7 +171,7 @@ "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", - "esrap": "https://pkg.pr.new/sveltejs/esrap@ef4051a", + "esrap": "https://pkg.pr.new/sveltejs/esrap@17c22f5", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", diff --git a/packages/svelte/scripts/process-messages/index.js b/packages/svelte/scripts/process-messages/index.js index d246cfbba5c4..80bf0bade626 100644 --- a/packages/svelte/scripts/process-messages/index.js +++ b/packages/svelte/scripts/process-messages/index.js @@ -147,7 +147,6 @@ function run() { const printed = esrap.print( ast, - // @ts-expect-error ts({ comments: comments.filter((comment) => comment !== jsdoc) }) @@ -358,7 +357,6 @@ function run() { .join('\n') }; - // @ts-expect-error const block = esrap.print({ ...ast, body: [clone] }, ts({ comments: [jsdoc_clone] })).code; printed.code += `\n\n${block}`; diff --git a/packages/svelte/src/compiler/phases/1-parse/acorn.js b/packages/svelte/src/compiler/phases/1-parse/acorn.js index f28ceab4cec8..6fd7fcaf4d85 100644 --- a/packages/svelte/src/compiler/phases/1-parse/acorn.js +++ b/packages/svelte/src/compiler/phases/1-parse/acorn.js @@ -107,6 +107,8 @@ function get_comment_handlers(source, comments, index = 0) { * @param {string} value * @param {number} start * @param {number} end + * @param {import('acorn').Position} [start_loc] + * @param {import('acorn').Position} [end_loc] */ onComment: (block, value, start, end, start_loc, end_loc) => { if (block && /\n/.test(value)) { @@ -125,7 +127,10 @@ function get_comment_handlers(source, comments, index = 0) { value, start, end, - loc: { start: start_loc, end: end_loc } + loc: { + start: /** @type {import('acorn').Position} */ (start_loc), + end: /** @type {import('acorn').Position} */ (end_loc) + } }); }, diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 0e81cd6faf3a..afa355663253 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -10,7 +10,6 @@ import { is_void } from '../../utils.js'; export function print(ast) { // @ts-expect-error some bullshit return esrap.print(ast, { - // @ts-expect-error some bullshit ...ts({ comments: ast.type === 'Root' ? ast.comments : [] }), ...visitors }); @@ -641,13 +640,23 @@ const visitors = { for (const modifier of node.modifiers) { context.write(`|${modifier}`); } - if ( - node.expression !== null && - !(node.expression.type === 'Identifier' && node.expression.name === node.name) - ) { - context.write('={'); - context.visit(node.expression); - context.write('}'); + + if (node.value === true) { + return; + } + + context.write('='); + + if (Array.isArray(node.value)) { + context.write('"'); + + for (const tag of node.value) { + context.visit(tag); + } + + context.write('"'); + } else { + context.visit(node.value); } }, @@ -746,7 +755,7 @@ const visitors = { context.write('` element, if exists */ module: Script | null; /** Comments found in -

Hello {name}!

\ No newline at end of file +

Hello {name}!

diff --git a/packages/svelte/tests/print/test.ts b/packages/svelte/tests/print/test.ts index 8f24a7fb2148..c0f72c15d851 100644 --- a/packages/svelte/tests/print/test.ts +++ b/packages/svelte/tests/print/test.ts @@ -10,17 +10,18 @@ const { test, run } = suite(async (config, cwd) => { const ast = parse(input, { modern: true }); const output = print(ast); + const outputCode = output.code.endsWith('\n') ? output.code : output.code + '\n'; // run `UPDATE_SNAPSHOTS=true pnpm test print` to update print tests if (process.env.UPDATE_SNAPSHOTS) { - fs.writeFileSync(`${cwd}/output.svelte`, output.code); + fs.writeFileSync(`${cwd}/output.svelte`, outputCode); } else { - fs.writeFileSync(`${cwd}/_actual.svelte`, output.code); + fs.writeFileSync(`${cwd}/_actual.svelte`, outputCode); const file = `${cwd}/output.svelte`; const expected = fs.existsSync(file) ? fs.readFileSync(file, 'utf-8') : ''; - assert.deepEqual(output.code.trim().replaceAll('\r', ''), expected.trim().replaceAll('\r', '')); + assert.deepEqual(outputCode.trim().replaceAll('\r', ''), expected.trim().replaceAll('\r', '')); } }); From ab7fd7d9efbe098cf4b761af01847b0a0db2dcbb Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Fri, 21 Nov 2025 19:29:11 +0100 Subject: [PATCH 41/91] common tests --- packages/svelte/tests/print/samples/script/input.svelte | 3 +++ packages/svelte/tests/print/samples/script/output.svelte | 3 +++ .../svelte/tests/print/samples/svelte-options/input.svelte | 1 + .../svelte/tests/print/samples/svelte-options/output.svelte | 1 + 4 files changed, 8 insertions(+) create mode 100644 packages/svelte/tests/print/samples/script/input.svelte create mode 100644 packages/svelte/tests/print/samples/script/output.svelte create mode 100644 packages/svelte/tests/print/samples/svelte-options/input.svelte create mode 100644 packages/svelte/tests/print/samples/svelte-options/output.svelte diff --git a/packages/svelte/tests/print/samples/script/input.svelte b/packages/svelte/tests/print/samples/script/input.svelte new file mode 100644 index 000000000000..f094ccf550fb --- /dev/null +++ b/packages/svelte/tests/print/samples/script/input.svelte @@ -0,0 +1,3 @@ + diff --git a/packages/svelte/tests/print/samples/script/output.svelte b/packages/svelte/tests/print/samples/script/output.svelte new file mode 100644 index 000000000000..da5f5725bf8c --- /dev/null +++ b/packages/svelte/tests/print/samples/script/output.svelte @@ -0,0 +1,3 @@ + diff --git a/packages/svelte/tests/print/samples/svelte-options/input.svelte b/packages/svelte/tests/print/samples/svelte-options/input.svelte new file mode 100644 index 000000000000..70e2dda5c262 --- /dev/null +++ b/packages/svelte/tests/print/samples/svelte-options/input.svelte @@ -0,0 +1 @@ + diff --git a/packages/svelte/tests/print/samples/svelte-options/output.svelte b/packages/svelte/tests/print/samples/svelte-options/output.svelte new file mode 100644 index 000000000000..70e2dda5c262 --- /dev/null +++ b/packages/svelte/tests/print/samples/svelte-options/output.svelte @@ -0,0 +1 @@ + From 86fe74d57e95f0fd25f7c7c0ad350394227f325f Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Fri, 21 Nov 2025 19:29:42 +0100 Subject: [PATCH 42/91] tests for A-nodes --- packages/svelte/src/compiler/print/index.js | 1 + .../tests/print/samples/animate/input.svelte | 1 + .../tests/print/samples/animate/output.svelte | 1 + .../tests/print/samples/atrule/input.svelte | 19 ++++++++++++++ .../tests/print/samples/atrule/output.svelte | 25 +++++++++++++++++++ .../tests/print/samples/attach/input.svelte | 13 ++++++++++ .../tests/print/samples/attach/output.svelte | 13 ++++++++++ .../print/samples/attribute/input.svelte | 1 + .../print/samples/attribute/output.svelte | 1 + .../print/samples/await-block/input.svelte | 10 ++++++++ .../print/samples/await-block/output.svelte | 10 ++++++++ 11 files changed, 95 insertions(+) create mode 100644 packages/svelte/tests/print/samples/animate/input.svelte create mode 100644 packages/svelte/tests/print/samples/animate/output.svelte create mode 100644 packages/svelte/tests/print/samples/atrule/input.svelte create mode 100644 packages/svelte/tests/print/samples/atrule/output.svelte create mode 100644 packages/svelte/tests/print/samples/attach/input.svelte create mode 100644 packages/svelte/tests/print/samples/attach/output.svelte create mode 100644 packages/svelte/tests/print/samples/attribute/input.svelte create mode 100644 packages/svelte/tests/print/samples/attribute/output.svelte create mode 100644 packages/svelte/tests/print/samples/await-block/input.svelte create mode 100644 packages/svelte/tests/print/samples/await-block/output.svelte diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index d908cc1435d5..2c2588eece9d 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -188,6 +188,7 @@ const visitors = { }, Atrule(node, context) { + // TODO seems to produce too many new lines sometimes. Also new lines above style tag? context.write(`@${node.name}`); if (node.prelude) context.write(` ${node.prelude}`); diff --git a/packages/svelte/tests/print/samples/animate/input.svelte b/packages/svelte/tests/print/samples/animate/input.svelte new file mode 100644 index 000000000000..7cd314f71438 --- /dev/null +++ b/packages/svelte/tests/print/samples/animate/input.svelte @@ -0,0 +1 @@ +
{item}
diff --git a/packages/svelte/tests/print/samples/animate/output.svelte b/packages/svelte/tests/print/samples/animate/output.svelte new file mode 100644 index 000000000000..7cd314f71438 --- /dev/null +++ b/packages/svelte/tests/print/samples/animate/output.svelte @@ -0,0 +1 @@ +
{item}
diff --git a/packages/svelte/tests/print/samples/atrule/input.svelte b/packages/svelte/tests/print/samples/atrule/input.svelte new file mode 100644 index 000000000000..52e2b978dfb9 --- /dev/null +++ b/packages/svelte/tests/print/samples/atrule/input.svelte @@ -0,0 +1,19 @@ + diff --git a/packages/svelte/tests/print/samples/atrule/output.svelte b/packages/svelte/tests/print/samples/atrule/output.svelte new file mode 100644 index 000000000000..6f8ce139f10a --- /dev/null +++ b/packages/svelte/tests/print/samples/atrule/output.svelte @@ -0,0 +1,25 @@ + + + diff --git a/packages/svelte/tests/print/samples/attach/input.svelte b/packages/svelte/tests/print/samples/attach/input.svelte new file mode 100644 index 000000000000..e92604b92b39 --- /dev/null +++ b/packages/svelte/tests/print/samples/attach/input.svelte @@ -0,0 +1,13 @@ + + +
...
diff --git a/packages/svelte/tests/print/samples/attach/output.svelte b/packages/svelte/tests/print/samples/attach/output.svelte new file mode 100644 index 000000000000..e92604b92b39 --- /dev/null +++ b/packages/svelte/tests/print/samples/attach/output.svelte @@ -0,0 +1,13 @@ + + +
...
diff --git a/packages/svelte/tests/print/samples/attribute/input.svelte b/packages/svelte/tests/print/samples/attribute/input.svelte new file mode 100644 index 000000000000..4da846f8e6b8 --- /dev/null +++ b/packages/svelte/tests/print/samples/attribute/input.svelte @@ -0,0 +1 @@ +
diff --git a/packages/svelte/tests/print/samples/attribute/output.svelte b/packages/svelte/tests/print/samples/attribute/output.svelte new file mode 100644 index 000000000000..4da846f8e6b8 --- /dev/null +++ b/packages/svelte/tests/print/samples/attribute/output.svelte @@ -0,0 +1 @@ +
diff --git a/packages/svelte/tests/print/samples/await-block/input.svelte b/packages/svelte/tests/print/samples/await-block/input.svelte new file mode 100644 index 000000000000..10f0b3fb9e1e --- /dev/null +++ b/packages/svelte/tests/print/samples/await-block/input.svelte @@ -0,0 +1,10 @@ +{#await promise} + +

waiting for the promise to resolve...

+{:then value} + +

The value is {value}

+{:catch error} + +

Something went wrong: {error.message}

+{/await} diff --git a/packages/svelte/tests/print/samples/await-block/output.svelte b/packages/svelte/tests/print/samples/await-block/output.svelte new file mode 100644 index 000000000000..10f0b3fb9e1e --- /dev/null +++ b/packages/svelte/tests/print/samples/await-block/output.svelte @@ -0,0 +1,10 @@ +{#await promise} + +

waiting for the promise to resolve...

+{:then value} + +

The value is {value}

+{:catch error} + +

Something went wrong: {error.message}

+{/await} From 825821d64fba3bcebc5d8d4601ccc67293b07c1a Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Fri, 21 Nov 2025 19:30:59 +0100 Subject: [PATCH 43/91] fix interface --- packages/svelte/tests/print/test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/svelte/tests/print/test.ts b/packages/svelte/tests/print/test.ts index c0f72c15d851..aa007a7a5405 100644 --- a/packages/svelte/tests/print/test.ts +++ b/packages/svelte/tests/print/test.ts @@ -3,9 +3,9 @@ import { assert } from 'vitest'; import { parse, print } from 'svelte/compiler'; import { suite, type BaseTest } from '../suite.js'; -interface ParserTest extends BaseTest {} +interface PrintTest extends BaseTest {} -const { test, run } = suite(async (config, cwd) => { +const { test, run } = suite(async (config, cwd) => { const input = fs.readFileSync(`${cwd}/input.svelte`, 'utf-8'); const ast = parse(input, { modern: true }); From e89cc8a35de1f7dc862da047ac50d9bbe89066dd Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Fri, 21 Nov 2025 19:36:09 +0100 Subject: [PATCH 44/91] tests for B-nodes --- .../svelte/tests/print/samples/bind-directive/input.svelte | 1 + .../svelte/tests/print/samples/bind-directive/output.svelte | 1 + packages/svelte/tests/print/samples/block/input.svelte | 4 ++++ packages/svelte/tests/print/samples/block/output.svelte | 4 ++++ 4 files changed, 10 insertions(+) create mode 100644 packages/svelte/tests/print/samples/bind-directive/input.svelte create mode 100644 packages/svelte/tests/print/samples/bind-directive/output.svelte diff --git a/packages/svelte/tests/print/samples/bind-directive/input.svelte b/packages/svelte/tests/print/samples/bind-directive/input.svelte new file mode 100644 index 000000000000..3b7e08921efe --- /dev/null +++ b/packages/svelte/tests/print/samples/bind-directive/input.svelte @@ -0,0 +1 @@ + diff --git a/packages/svelte/tests/print/samples/bind-directive/output.svelte b/packages/svelte/tests/print/samples/bind-directive/output.svelte new file mode 100644 index 000000000000..3b7e08921efe --- /dev/null +++ b/packages/svelte/tests/print/samples/bind-directive/output.svelte @@ -0,0 +1 @@ + diff --git a/packages/svelte/tests/print/samples/block/input.svelte b/packages/svelte/tests/print/samples/block/input.svelte index 470f7a1efbdc..a881a77192b4 100644 --- a/packages/svelte/tests/print/samples/block/input.svelte +++ b/packages/svelte/tests/print/samples/block/input.svelte @@ -1 +1,5 @@ {#if condition} yes {:else} no {/if} + +{#each items as item, i} +

{i}: {item}

+{/each} diff --git a/packages/svelte/tests/print/samples/block/output.svelte b/packages/svelte/tests/print/samples/block/output.svelte index e0ff317fc8f4..187bcd83485f 100644 --- a/packages/svelte/tests/print/samples/block/output.svelte +++ b/packages/svelte/tests/print/samples/block/output.svelte @@ -3,3 +3,7 @@ {:else} no {/if} + +{#each items as item, i} +

{i}: {item}

+{/each} From 83b19d5a91d93c6cc61425f607ce691cb63b9a1f Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Fri, 21 Nov 2025 19:57:10 +0100 Subject: [PATCH 45/91] delete basic test --- packages/svelte/tests/print/samples/basic/input.svelte | 7 ------- packages/svelte/tests/print/samples/basic/output.svelte | 5 ----- 2 files changed, 12 deletions(-) delete mode 100644 packages/svelte/tests/print/samples/basic/input.svelte delete mode 100644 packages/svelte/tests/print/samples/basic/output.svelte diff --git a/packages/svelte/tests/print/samples/basic/input.svelte b/packages/svelte/tests/print/samples/basic/input.svelte deleted file mode 100644 index bff6bee537b4..000000000000 --- a/packages/svelte/tests/print/samples/basic/input.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - -

- Hello {name}! -

diff --git a/packages/svelte/tests/print/samples/basic/output.svelte b/packages/svelte/tests/print/samples/basic/output.svelte deleted file mode 100644 index aa19c0abc4b3..000000000000 --- a/packages/svelte/tests/print/samples/basic/output.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - -

Hello {name}!

From b4c66841afa023faa237e088caab71bd47da5de2 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Fri, 21 Nov 2025 20:08:05 +0100 Subject: [PATCH 46/91] tests for C-nodes --- packages/svelte/src/compiler/print/index.js | 1 + .../print/samples/class-directive/input.svelte | 8 ++++++++ .../print/samples/class-directive/output.svelte | 8 ++++++++ .../print/samples/class-selector/input.svelte | 9 +++++++++ .../print/samples/class-selector/output.svelte | 11 +++++++++++ .../tests/print/samples/comment/input.svelte | 5 +++++ .../tests/print/samples/comment/output.svelte | 4 ++++ .../print/samples/complex-selector/input.svelte | 5 +++++ .../print/samples/complex-selector/output.svelte | 15 +++++++++++++++ .../tests/print/samples/component/input.svelte | 8 ++++++++ .../tests/print/samples/component/output.svelte | 6 ++++++ .../svelte/tests/print/samples/const/input.svelte | 8 ++++++++ .../tests/print/samples/const/output.svelte | 8 ++++++++ 13 files changed, 96 insertions(+) create mode 100644 packages/svelte/tests/print/samples/class-directive/input.svelte create mode 100644 packages/svelte/tests/print/samples/class-directive/output.svelte create mode 100644 packages/svelte/tests/print/samples/class-selector/input.svelte create mode 100644 packages/svelte/tests/print/samples/class-selector/output.svelte create mode 100644 packages/svelte/tests/print/samples/comment/input.svelte create mode 100644 packages/svelte/tests/print/samples/comment/output.svelte create mode 100644 packages/svelte/tests/print/samples/complex-selector/input.svelte create mode 100644 packages/svelte/tests/print/samples/complex-selector/output.svelte create mode 100644 packages/svelte/tests/print/samples/component/input.svelte create mode 100644 packages/svelte/tests/print/samples/component/output.svelte create mode 100644 packages/svelte/tests/print/samples/const/input.svelte create mode 100644 packages/svelte/tests/print/samples/const/output.svelte diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 2c2588eece9d..5481b342fecb 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -357,6 +357,7 @@ const visitors = { }, ConstTag(node, context) { + // TODO this is buggy context.write('{@const '); context.visit(node.declaration); context.write('}'); diff --git a/packages/svelte/tests/print/samples/class-directive/input.svelte b/packages/svelte/tests/print/samples/class-directive/input.svelte new file mode 100644 index 000000000000..5e6311193007 --- /dev/null +++ b/packages/svelte/tests/print/samples/class-directive/input.svelte @@ -0,0 +1,8 @@ + + +
+ Hello world! +
diff --git a/packages/svelte/tests/print/samples/class-directive/output.svelte b/packages/svelte/tests/print/samples/class-directive/output.svelte new file mode 100644 index 000000000000..ec88a869b795 --- /dev/null +++ b/packages/svelte/tests/print/samples/class-directive/output.svelte @@ -0,0 +1,8 @@ + + +
+ Hello world! +
diff --git a/packages/svelte/tests/print/samples/class-selector/input.svelte b/packages/svelte/tests/print/samples/class-selector/input.svelte new file mode 100644 index 000000000000..7f5cf64c14d7 --- /dev/null +++ b/packages/svelte/tests/print/samples/class-selector/input.svelte @@ -0,0 +1,9 @@ + diff --git a/packages/svelte/tests/print/samples/class-selector/output.svelte b/packages/svelte/tests/print/samples/class-selector/output.svelte new file mode 100644 index 000000000000..e6f2370f7ddb --- /dev/null +++ b/packages/svelte/tests/print/samples/class-selector/output.svelte @@ -0,0 +1,11 @@ + + + diff --git a/packages/svelte/tests/print/samples/comment/input.svelte b/packages/svelte/tests/print/samples/comment/input.svelte new file mode 100644 index 000000000000..d144ced1b939 --- /dev/null +++ b/packages/svelte/tests/print/samples/comment/input.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/svelte/tests/print/samples/comment/output.svelte b/packages/svelte/tests/print/samples/comment/output.svelte new file mode 100644 index 000000000000..6cfd2d4c93a9 --- /dev/null +++ b/packages/svelte/tests/print/samples/comment/output.svelte @@ -0,0 +1,4 @@ + + diff --git a/packages/svelte/tests/print/samples/complex-selector/input.svelte b/packages/svelte/tests/print/samples/complex-selector/input.svelte new file mode 100644 index 000000000000..388c24434c2e --- /dev/null +++ b/packages/svelte/tests/print/samples/complex-selector/input.svelte @@ -0,0 +1,5 @@ + diff --git a/packages/svelte/tests/print/samples/complex-selector/output.svelte b/packages/svelte/tests/print/samples/complex-selector/output.svelte new file mode 100644 index 000000000000..b4a2bbd2b4a9 --- /dev/null +++ b/packages/svelte/tests/print/samples/complex-selector/output.svelte @@ -0,0 +1,15 @@ + + + diff --git a/packages/svelte/tests/print/samples/component/input.svelte b/packages/svelte/tests/print/samples/component/input.svelte new file mode 100644 index 000000000000..c258d7d609e3 --- /dev/null +++ b/packages/svelte/tests/print/samples/component/input.svelte @@ -0,0 +1,8 @@ + + + + + Hello World + diff --git a/packages/svelte/tests/print/samples/component/output.svelte b/packages/svelte/tests/print/samples/component/output.svelte new file mode 100644 index 000000000000..ec8b827883e4 --- /dev/null +++ b/packages/svelte/tests/print/samples/component/output.svelte @@ -0,0 +1,6 @@ + + + +Hello World diff --git a/packages/svelte/tests/print/samples/const/input.svelte b/packages/svelte/tests/print/samples/const/input.svelte new file mode 100644 index 000000000000..50ada2dd6642 --- /dev/null +++ b/packages/svelte/tests/print/samples/const/input.svelte @@ -0,0 +1,8 @@ + + +{#each boxes as box} + {@const area = box.width * box.height} + {box.width} * {box.height} = {area} +{/each} diff --git a/packages/svelte/tests/print/samples/const/output.svelte b/packages/svelte/tests/print/samples/const/output.svelte new file mode 100644 index 000000000000..8c333e213ff5 --- /dev/null +++ b/packages/svelte/tests/print/samples/const/output.svelte @@ -0,0 +1,8 @@ + + +{#each boxes as box} + {@const const area = box.width * box.height;} + {box.width} * {box.height} = {area} +{/each} From 3914ade55b1667c8fd1270a2d43fdadb5411cd2d Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Fri, 21 Nov 2025 20:43:15 +0100 Subject: [PATCH 47/91] combine css tests --- .../print/samples/class-selector/input.svelte | 9 --------- .../samples/class-selector/output.svelte | 11 ---------- .../samples/complex-selector/input.svelte | 5 ----- .../samples/complex-selector/output.svelte | 15 -------------- .../samples/{atrule => style}/input.svelte | 12 +++++++++++ .../samples/{atrule => style}/output.svelte | 20 +++++++++++++++++++ 6 files changed, 32 insertions(+), 40 deletions(-) delete mode 100644 packages/svelte/tests/print/samples/class-selector/input.svelte delete mode 100644 packages/svelte/tests/print/samples/class-selector/output.svelte delete mode 100644 packages/svelte/tests/print/samples/complex-selector/input.svelte delete mode 100644 packages/svelte/tests/print/samples/complex-selector/output.svelte rename packages/svelte/tests/print/samples/{atrule => style}/input.svelte (61%) rename packages/svelte/tests/print/samples/{atrule => style}/output.svelte (60%) diff --git a/packages/svelte/tests/print/samples/class-selector/input.svelte b/packages/svelte/tests/print/samples/class-selector/input.svelte deleted file mode 100644 index 7f5cf64c14d7..000000000000 --- a/packages/svelte/tests/print/samples/class-selector/input.svelte +++ /dev/null @@ -1,9 +0,0 @@ - diff --git a/packages/svelte/tests/print/samples/class-selector/output.svelte b/packages/svelte/tests/print/samples/class-selector/output.svelte deleted file mode 100644 index e6f2370f7ddb..000000000000 --- a/packages/svelte/tests/print/samples/class-selector/output.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/packages/svelte/tests/print/samples/complex-selector/input.svelte b/packages/svelte/tests/print/samples/complex-selector/input.svelte deleted file mode 100644 index 388c24434c2e..000000000000 --- a/packages/svelte/tests/print/samples/complex-selector/input.svelte +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/packages/svelte/tests/print/samples/complex-selector/output.svelte b/packages/svelte/tests/print/samples/complex-selector/output.svelte deleted file mode 100644 index b4a2bbd2b4a9..000000000000 --- a/packages/svelte/tests/print/samples/complex-selector/output.svelte +++ /dev/null @@ -1,15 +0,0 @@ - - - diff --git a/packages/svelte/tests/print/samples/atrule/input.svelte b/packages/svelte/tests/print/samples/style/input.svelte similarity index 61% rename from packages/svelte/tests/print/samples/atrule/input.svelte rename to packages/svelte/tests/print/samples/style/input.svelte index 52e2b978dfb9..dd81aa1f6bb3 100644 --- a/packages/svelte/tests/print/samples/atrule/input.svelte +++ b/packages/svelte/tests/print/samples/style/input.svelte @@ -1,4 +1,12 @@ diff --git a/packages/svelte/tests/print/samples/atrule/output.svelte b/packages/svelte/tests/print/samples/style/output.svelte similarity index 60% rename from packages/svelte/tests/print/samples/atrule/output.svelte rename to packages/svelte/tests/print/samples/style/output.svelte index 6f8ce139f10a..4dfc918d1f91 100644 --- a/packages/svelte/tests/print/samples/atrule/output.svelte +++ b/packages/svelte/tests/print/samples/style/output.svelte @@ -1,6 +1,14 @@ From 8f3ca9e1b9493f18cb76da4939a17c398d8a2d0b Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Fri, 21 Nov 2025 20:54:29 +0100 Subject: [PATCH 48/91] tests for E-nodes --- packages/svelte/src/compiler/print/index.js | 1 + .../tests/print/samples/each-block/input.svelte | 15 +++++++++++++++ .../tests/print/samples/each-block/output.svelte | 15 +++++++++++++++ .../print/samples/expression-tag/input.svelte | 2 ++ .../print/samples/expression-tag/output.svelte | 2 ++ 5 files changed, 35 insertions(+) create mode 100644 packages/svelte/tests/print/samples/each-block/input.svelte create mode 100644 packages/svelte/tests/print/samples/each-block/output.svelte create mode 100644 packages/svelte/tests/print/samples/expression-tag/input.svelte create mode 100644 packages/svelte/tests/print/samples/expression-tag/output.svelte diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 5481b342fecb..dc3358bed217 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -404,6 +404,7 @@ const visitors = { block(context, node.body); if (node.fallback) { + // TODO new lines context.write('{:else}'); context.visit(node.fallback); } diff --git a/packages/svelte/tests/print/samples/each-block/input.svelte b/packages/svelte/tests/print/samples/each-block/input.svelte new file mode 100644 index 000000000000..a859705fecfb --- /dev/null +++ b/packages/svelte/tests/print/samples/each-block/input.svelte @@ -0,0 +1,15 @@ +{#each items as { id, name, qty }, i (id)} +
  • {i + 1}: {name} x {qty}
  • +{/each} + +{#each objects as { id, ...rest }} +
  • {id}
  • +{/each} + +{#each expression}...{/each} + +{#each todos as todo} +

    {todo.text}

    +{:else} +

    No tasks today!

    +{/each} diff --git a/packages/svelte/tests/print/samples/each-block/output.svelte b/packages/svelte/tests/print/samples/each-block/output.svelte new file mode 100644 index 000000000000..a8642f96ad9c --- /dev/null +++ b/packages/svelte/tests/print/samples/each-block/output.svelte @@ -0,0 +1,15 @@ +{#each items as { id, name, qty }, i (id)} +
  • {i + 1}: {name} x {qty}
  • +{/each} + +{#each objects as { id, ...rest }} +
  • {id}
  • +{/each} + +{#each expression} + ... +{/each} + +{#each todos as todo} +

    {todo.text}

    +{:else}

    No tasks today!

    {/each} diff --git a/packages/svelte/tests/print/samples/expression-tag/input.svelte b/packages/svelte/tests/print/samples/expression-tag/input.svelte new file mode 100644 index 000000000000..3920c3b40a6c --- /dev/null +++ b/packages/svelte/tests/print/samples/expression-tag/input.svelte @@ -0,0 +1,2 @@ +{name} +{count + 1} diff --git a/packages/svelte/tests/print/samples/expression-tag/output.svelte b/packages/svelte/tests/print/samples/expression-tag/output.svelte new file mode 100644 index 000000000000..3920c3b40a6c --- /dev/null +++ b/packages/svelte/tests/print/samples/expression-tag/output.svelte @@ -0,0 +1,2 @@ +{name} +{count + 1} From 1fe285b64ef71256ce714a8eab76e1958aefb16b Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Fri, 21 Nov 2025 20:58:32 +0100 Subject: [PATCH 49/91] tests for H-nodes --- packages/svelte/tests/print/samples/html-tag/input.svelte | 3 +++ packages/svelte/tests/print/samples/html-tag/output.svelte | 1 + 2 files changed, 4 insertions(+) create mode 100644 packages/svelte/tests/print/samples/html-tag/input.svelte create mode 100644 packages/svelte/tests/print/samples/html-tag/output.svelte diff --git a/packages/svelte/tests/print/samples/html-tag/input.svelte b/packages/svelte/tests/print/samples/html-tag/input.svelte new file mode 100644 index 000000000000..d021ee8fa679 --- /dev/null +++ b/packages/svelte/tests/print/samples/html-tag/input.svelte @@ -0,0 +1,3 @@ +
    + {@html content} +
    diff --git a/packages/svelte/tests/print/samples/html-tag/output.svelte b/packages/svelte/tests/print/samples/html-tag/output.svelte new file mode 100644 index 000000000000..84777d9b6ab1 --- /dev/null +++ b/packages/svelte/tests/print/samples/html-tag/output.svelte @@ -0,0 +1 @@ +
    {@html content}
    From ae0852e413526009182f154015fa411fd28739b1 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Fri, 21 Nov 2025 21:03:31 +0100 Subject: [PATCH 50/91] tests for I-nodes --- packages/svelte/src/compiler/print/index.js | 1 + packages/svelte/tests/print/samples/if-block/input.svelte | 7 +++++++ packages/svelte/tests/print/samples/if-block/output.svelte | 7 +++++++ 3 files changed, 15 insertions(+) create mode 100644 packages/svelte/tests/print/samples/if-block/input.svelte create mode 100644 packages/svelte/tests/print/samples/if-block/output.svelte diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index dc3358bed217..b17f0078d245 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -450,6 +450,7 @@ const visitors = { context.write('{:else}'); } + // TODO inconsistent indentation behavior here block(context, node.alternate); } diff --git a/packages/svelte/tests/print/samples/if-block/input.svelte b/packages/svelte/tests/print/samples/if-block/input.svelte new file mode 100644 index 000000000000..0a444a125252 --- /dev/null +++ b/packages/svelte/tests/print/samples/if-block/input.svelte @@ -0,0 +1,7 @@ +{#if porridge.temperature > 100} +

    too hot!

    +{:else if 80 > porridge.temperature} +

    too cold!

    +{:else} +

    just right!

    +{/if} diff --git a/packages/svelte/tests/print/samples/if-block/output.svelte b/packages/svelte/tests/print/samples/if-block/output.svelte new file mode 100644 index 000000000000..9940a3c1c294 --- /dev/null +++ b/packages/svelte/tests/print/samples/if-block/output.svelte @@ -0,0 +1,7 @@ +{#if porridge.temperature > 100} +

    too hot!

    + {:else if 80 > porridge.temperature} +

    too cold!

    + {:else} +

    just right!

    +{/if} From b612430e8c5bab7b5ba49e369dc82b2a137f26b4 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Fri, 21 Nov 2025 21:05:30 +0100 Subject: [PATCH 51/91] tests for K-nodes --- packages/svelte/tests/print/samples/key-block/input.svelte | 3 +++ packages/svelte/tests/print/samples/key-block/output.svelte | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 packages/svelte/tests/print/samples/key-block/input.svelte create mode 100644 packages/svelte/tests/print/samples/key-block/output.svelte diff --git a/packages/svelte/tests/print/samples/key-block/input.svelte b/packages/svelte/tests/print/samples/key-block/input.svelte new file mode 100644 index 000000000000..9d09f8f00aff --- /dev/null +++ b/packages/svelte/tests/print/samples/key-block/input.svelte @@ -0,0 +1,3 @@ +{#key value} + +{/key} diff --git a/packages/svelte/tests/print/samples/key-block/output.svelte b/packages/svelte/tests/print/samples/key-block/output.svelte new file mode 100644 index 000000000000..9d09f8f00aff --- /dev/null +++ b/packages/svelte/tests/print/samples/key-block/output.svelte @@ -0,0 +1,3 @@ +{#key value} + +{/key} From 273e3d7448556ae415d3b83018c77592dc39b2ec Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Fri, 21 Nov 2025 21:13:51 +0100 Subject: [PATCH 52/91] tests for L-nodes --- packages/svelte/src/compiler/print/index.js | 1 + packages/svelte/tests/print/samples/let-directive/input.svelte | 3 +++ .../svelte/tests/print/samples/let-directive/output.svelte | 1 + 3 files changed, 5 insertions(+) create mode 100644 packages/svelte/tests/print/samples/let-directive/input.svelte create mode 100644 packages/svelte/tests/print/samples/let-directive/output.svelte diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index b17f0078d245..a9e8a15bda7e 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -468,6 +468,7 @@ const visitors = { }, LetDirective(node, context) { + // TODO new lines context.write(`let:${node.name}`); if ( node.expression !== null && diff --git a/packages/svelte/tests/print/samples/let-directive/input.svelte b/packages/svelte/tests/print/samples/let-directive/input.svelte new file mode 100644 index 000000000000..c6b49c1561a4 --- /dev/null +++ b/packages/svelte/tests/print/samples/let-directive/input.svelte @@ -0,0 +1,3 @@ + +
    {processed.text}
    +
    diff --git a/packages/svelte/tests/print/samples/let-directive/output.svelte b/packages/svelte/tests/print/samples/let-directive/output.svelte new file mode 100644 index 000000000000..c7254150e317 --- /dev/null +++ b/packages/svelte/tests/print/samples/let-directive/output.svelte @@ -0,0 +1 @@ +
    {processed.text}
    From 78e602c0632b17aff4a91e54b0b350642d79558e Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Fri, 21 Nov 2025 21:15:54 +0100 Subject: [PATCH 53/91] tests for N-nodes --- packages/svelte/tests/print/samples/style/input.svelte | 1 + packages/svelte/tests/print/samples/style/output.svelte | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/packages/svelte/tests/print/samples/style/input.svelte b/packages/svelte/tests/print/samples/style/input.svelte index dd81aa1f6bb3..d50a88a0d8e2 100644 --- a/packages/svelte/tests/print/samples/style/input.svelte +++ b/packages/svelte/tests/print/samples/style/input.svelte @@ -28,4 +28,5 @@ .container .item { color: red; } nav > ul.menu { display: flex; } h2 + p.note { margin-top: 0; } + li:nth-child(2n + 1) { color: red; } diff --git a/packages/svelte/tests/print/samples/style/output.svelte b/packages/svelte/tests/print/samples/style/output.svelte index 4dfc918d1f91..59bc88d730d4 100644 --- a/packages/svelte/tests/print/samples/style/output.svelte +++ b/packages/svelte/tests/print/samples/style/output.svelte @@ -42,4 +42,8 @@ h2 + p.note { margin-top: 0; } + + li:nth-child(2n + 1) { + color: red; + } From eb9cc5d6bdcb9fe8f6b635368d78a1e8c54f2573 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Fri, 21 Nov 2025 21:21:13 +0100 Subject: [PATCH 54/91] add todo --- packages/svelte/src/compiler/print/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index a9e8a15bda7e..b1563e85fa53 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -40,6 +40,8 @@ function block(context, node, allow_inline = false) { } } +// TODO split css and svelte visitors? Currently they are really mixed together + /** @type {Visitors} */ const visitors = { Root(node, context) { From 8c357df22c060b422113dbe8763a2d9c071e10bc Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Fri, 21 Nov 2025 21:22:01 +0100 Subject: [PATCH 55/91] remove other todo --- packages/svelte/src/compiler/print/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index b1563e85fa53..ede7ddd1b9ba 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -483,7 +483,7 @@ const visitors = { }, Nth(node, context) { - context.write(node.value); // TODO is this right? + context.write(node.value); }, OnDirective(node, context) { From f2b4bdfd15c5c76463899a7bef9d089e616d8e8d Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 22 Nov 2025 10:25:40 +0100 Subject: [PATCH 56/91] tests for O-nodes --- .../tests/print/samples/on-directive/input.svelte | 11 +++++++++++ .../tests/print/samples/on-directive/output.svelte | 11 +++++++++++ 2 files changed, 22 insertions(+) create mode 100644 packages/svelte/tests/print/samples/on-directive/input.svelte create mode 100644 packages/svelte/tests/print/samples/on-directive/output.svelte diff --git a/packages/svelte/tests/print/samples/on-directive/input.svelte b/packages/svelte/tests/print/samples/on-directive/input.svelte new file mode 100644 index 000000000000..976749696a4d --- /dev/null +++ b/packages/svelte/tests/print/samples/on-directive/input.svelte @@ -0,0 +1,11 @@ + + + diff --git a/packages/svelte/tests/print/samples/on-directive/output.svelte b/packages/svelte/tests/print/samples/on-directive/output.svelte new file mode 100644 index 000000000000..976749696a4d --- /dev/null +++ b/packages/svelte/tests/print/samples/on-directive/output.svelte @@ -0,0 +1,11 @@ + + + From db337bdd3d140efac72f5c94b0f89fc6832a498e Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 22 Nov 2025 10:27:46 +0100 Subject: [PATCH 57/91] tests for P-nodes --- packages/svelte/tests/print/samples/style/input.svelte | 5 +++++ .../svelte/tests/print/samples/style/output.svelte | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/packages/svelte/tests/print/samples/style/input.svelte b/packages/svelte/tests/print/samples/style/input.svelte index d50a88a0d8e2..95789c26df1a 100644 --- a/packages/svelte/tests/print/samples/style/input.svelte +++ b/packages/svelte/tests/print/samples/style/input.svelte @@ -29,4 +29,9 @@ nav > ul.menu { display: flex; } h2 + p.note { margin-top: 0; } li:nth-child(2n + 1) { color: red; } + .button:hover { opacity: 0.5; } + .card::before { + content: ""; + display: block; + } diff --git a/packages/svelte/tests/print/samples/style/output.svelte b/packages/svelte/tests/print/samples/style/output.svelte index 59bc88d730d4..84150322d474 100644 --- a/packages/svelte/tests/print/samples/style/output.svelte +++ b/packages/svelte/tests/print/samples/style/output.svelte @@ -46,4 +46,14 @@ li:nth-child(2n + 1) { color: red; } + + .button:hover { + opacity: 0.5; + } + + .card::before { + content: ""; + + display: block; + } From 5c1c81ea997b87e58fdee6f9d5b3452f0410a525 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 22 Nov 2025 10:33:49 +0100 Subject: [PATCH 58/91] tests for R-nodes --- .../print/samples/regular-element/input.svelte | 2 ++ .../print/samples/regular-element/output.svelte | 2 ++ .../tests/print/samples/render-tag/input.svelte | 7 +++++++ .../tests/print/samples/render-tag/output.svelte | 7 +++++++ .../svelte/tests/print/samples/style/input.svelte | 6 ++++++ .../svelte/tests/print/samples/style/output.svelte | 13 +++++++++++++ 6 files changed, 37 insertions(+) create mode 100644 packages/svelte/tests/print/samples/regular-element/input.svelte create mode 100644 packages/svelte/tests/print/samples/regular-element/output.svelte create mode 100644 packages/svelte/tests/print/samples/render-tag/input.svelte create mode 100644 packages/svelte/tests/print/samples/render-tag/output.svelte diff --git a/packages/svelte/tests/print/samples/regular-element/input.svelte b/packages/svelte/tests/print/samples/regular-element/input.svelte new file mode 100644 index 000000000000..c39f4f487f9c --- /dev/null +++ b/packages/svelte/tests/print/samples/regular-element/input.svelte @@ -0,0 +1,2 @@ + +
    diff --git a/packages/svelte/tests/print/samples/regular-element/output.svelte b/packages/svelte/tests/print/samples/regular-element/output.svelte new file mode 100644 index 000000000000..c39f4f487f9c --- /dev/null +++ b/packages/svelte/tests/print/samples/regular-element/output.svelte @@ -0,0 +1,2 @@ + +
    diff --git a/packages/svelte/tests/print/samples/render-tag/input.svelte b/packages/svelte/tests/print/samples/render-tag/input.svelte new file mode 100644 index 000000000000..b301e4b0d730 --- /dev/null +++ b/packages/svelte/tests/print/samples/render-tag/input.svelte @@ -0,0 +1,7 @@ +{#snippet sum(a, b)} +

    {a} + {b} = {a + b}

    +{/snippet} + +{@render sum(1, 2)} +{@render sum(3, 4)} +{@render sum(5, 6)} diff --git a/packages/svelte/tests/print/samples/render-tag/output.svelte b/packages/svelte/tests/print/samples/render-tag/output.svelte new file mode 100644 index 000000000000..b301e4b0d730 --- /dev/null +++ b/packages/svelte/tests/print/samples/render-tag/output.svelte @@ -0,0 +1,7 @@ +{#snippet sum(a, b)} +

    {a} + {b} = {a + b}

    +{/snippet} + +{@render sum(1, 2)} +{@render sum(3, 4)} +{@render sum(5, 6)} diff --git a/packages/svelte/tests/print/samples/style/input.svelte b/packages/svelte/tests/print/samples/style/input.svelte index 95789c26df1a..94312774ece5 100644 --- a/packages/svelte/tests/print/samples/style/input.svelte +++ b/packages/svelte/tests/print/samples/style/input.svelte @@ -34,4 +34,10 @@ content: ""; display: block; } + .container > .item { color: red; } + h1 + p { margin-top: 0; } + + .container .item, nav > ul.menu { + color: red; + } diff --git a/packages/svelte/tests/print/samples/style/output.svelte b/packages/svelte/tests/print/samples/style/output.svelte index 84150322d474..deaad3f801b1 100644 --- a/packages/svelte/tests/print/samples/style/output.svelte +++ b/packages/svelte/tests/print/samples/style/output.svelte @@ -56,4 +56,17 @@ display: block; } + + .container > .item { + color: red; + } + + h1 + p { + margin-top: 0; + } + + .container .item, + nav > ul.menu { + color: red; + } From 424f221de2c22ad441224cd489d500231152a48d Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 22 Nov 2025 10:57:28 +0100 Subject: [PATCH 59/91] tests for S-nodes --- packages/svelte/src/compiler/print/index.js | 6 ++++++ .../tests/print/samples/slot-element/input.svelte | 3 +++ .../tests/print/samples/slot-element/output.svelte | 1 + .../tests/print/samples/snippet-block/input.svelte | 3 +++ .../tests/print/samples/snippet-block/output.svelte | 3 +++ .../print/samples/spread-attribute/input.svelte | 1 + .../print/samples/spread-attribute/output.svelte | 1 + .../tests/print/samples/style-directive/input.svelte | 2 ++ .../print/samples/style-directive/output.svelte | 4 ++++ .../tests/print/samples/svelte-boundary/input.svelte | 7 +++++++ .../print/samples/svelte-boundary/output.svelte | 7 +++++++ .../print/samples/svelte-component/input.svelte | 1 + .../print/samples/svelte-component/output.svelte | 1 + .../tests/print/samples/svelte-document/input.svelte | 1 + .../print/samples/svelte-document/output.svelte | 1 + .../tests/print/samples/svelte-element/input.svelte | 7 +++++++ .../tests/print/samples/svelte-element/output.svelte | 5 +++++ .../tests/print/samples/svelte-fragment/input.svelte | 11 +++++++++++ .../print/samples/svelte-fragment/output.svelte | 12 ++++++++++++ .../tests/print/samples/svelte-head/input.svelte | 4 ++++ .../tests/print/samples/svelte-self/input.svelte | 10 ++++++++++ .../tests/print/samples/svelte-self/output.svelte | 10 ++++++++++ .../tests/print/samples/svelte-window/input.svelte | 7 +++++++ .../tests/print/samples/svelte-window/output.svelte | 7 +++++++ 24 files changed, 115 insertions(+) create mode 100644 packages/svelte/tests/print/samples/slot-element/input.svelte create mode 100644 packages/svelte/tests/print/samples/slot-element/output.svelte create mode 100644 packages/svelte/tests/print/samples/snippet-block/input.svelte create mode 100644 packages/svelte/tests/print/samples/snippet-block/output.svelte create mode 100644 packages/svelte/tests/print/samples/spread-attribute/input.svelte create mode 100644 packages/svelte/tests/print/samples/spread-attribute/output.svelte create mode 100644 packages/svelte/tests/print/samples/style-directive/input.svelte create mode 100644 packages/svelte/tests/print/samples/style-directive/output.svelte create mode 100644 packages/svelte/tests/print/samples/svelte-boundary/input.svelte create mode 100644 packages/svelte/tests/print/samples/svelte-boundary/output.svelte create mode 100644 packages/svelte/tests/print/samples/svelte-component/input.svelte create mode 100644 packages/svelte/tests/print/samples/svelte-component/output.svelte create mode 100644 packages/svelte/tests/print/samples/svelte-document/input.svelte create mode 100644 packages/svelte/tests/print/samples/svelte-document/output.svelte create mode 100644 packages/svelte/tests/print/samples/svelte-element/input.svelte create mode 100644 packages/svelte/tests/print/samples/svelte-element/output.svelte create mode 100644 packages/svelte/tests/print/samples/svelte-fragment/input.svelte create mode 100644 packages/svelte/tests/print/samples/svelte-fragment/output.svelte create mode 100644 packages/svelte/tests/print/samples/svelte-head/input.svelte create mode 100644 packages/svelte/tests/print/samples/svelte-self/input.svelte create mode 100644 packages/svelte/tests/print/samples/svelte-self/output.svelte create mode 100644 packages/svelte/tests/print/samples/svelte-window/input.svelte create mode 100644 packages/svelte/tests/print/samples/svelte-window/output.svelte diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index ede7ddd1b9ba..2c0772b57eb1 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -733,6 +733,7 @@ const visitors = { context.visit(attribute); } + // TODO handling of self-closing does not seem to work if (node.fragment) { context.write('>'); block(context, node.fragment, true); @@ -751,6 +752,7 @@ const visitors = { context.visit(attribute); } + // TODO handling of self-closing does not seem to work if (node.fragment) { context.write('>'); block(context, node.fragment, true); @@ -763,6 +765,7 @@ const visitors = { SvelteElement(node, context) { context.write(''); block(context, node.fragment, true); @@ -827,6 +831,7 @@ const visitors = { context.visit(attribute); } + // TODO handling of self-closing does not seem to work if (node.fragment) { context.write('>'); block(context, node.fragment, true); @@ -845,6 +850,7 @@ const visitors = { context.visit(attribute); } + // TODO handling of self-closing does not seem to work if (node.fragment) { context.write('>'); block(context, node.fragment, true); diff --git a/packages/svelte/tests/print/samples/slot-element/input.svelte b/packages/svelte/tests/print/samples/slot-element/input.svelte new file mode 100644 index 000000000000..b92372f9842d --- /dev/null +++ b/packages/svelte/tests/print/samples/slot-element/input.svelte @@ -0,0 +1,3 @@ + diff --git a/packages/svelte/tests/print/samples/slot-element/output.svelte b/packages/svelte/tests/print/samples/slot-element/output.svelte new file mode 100644 index 000000000000..9289d49fb3f4 --- /dev/null +++ b/packages/svelte/tests/print/samples/slot-element/output.svelte @@ -0,0 +1 @@ + diff --git a/packages/svelte/tests/print/samples/snippet-block/input.svelte b/packages/svelte/tests/print/samples/snippet-block/input.svelte new file mode 100644 index 000000000000..47f8efd9e964 --- /dev/null +++ b/packages/svelte/tests/print/samples/snippet-block/input.svelte @@ -0,0 +1,3 @@ +{#snippet name(param1, param2, paramN)} + Foo +{/snippet} diff --git a/packages/svelte/tests/print/samples/snippet-block/output.svelte b/packages/svelte/tests/print/samples/snippet-block/output.svelte new file mode 100644 index 000000000000..bb172f1db6f6 --- /dev/null +++ b/packages/svelte/tests/print/samples/snippet-block/output.svelte @@ -0,0 +1,3 @@ +{#snippet name(param1, param2, paramN)} + Foo +{/snippet} diff --git a/packages/svelte/tests/print/samples/spread-attribute/input.svelte b/packages/svelte/tests/print/samples/spread-attribute/input.svelte new file mode 100644 index 000000000000..836425cae3b0 --- /dev/null +++ b/packages/svelte/tests/print/samples/spread-attribute/input.svelte @@ -0,0 +1 @@ +
    diff --git a/packages/svelte/tests/print/samples/spread-attribute/output.svelte b/packages/svelte/tests/print/samples/spread-attribute/output.svelte new file mode 100644 index 000000000000..836425cae3b0 --- /dev/null +++ b/packages/svelte/tests/print/samples/spread-attribute/output.svelte @@ -0,0 +1 @@ +
    diff --git a/packages/svelte/tests/print/samples/style-directive/input.svelte b/packages/svelte/tests/print/samples/style-directive/input.svelte new file mode 100644 index 000000000000..2951fc9daa87 --- /dev/null +++ b/packages/svelte/tests/print/samples/style-directive/input.svelte @@ -0,0 +1,2 @@ +
    ...
    +
    ...
    diff --git a/packages/svelte/tests/print/samples/style-directive/output.svelte b/packages/svelte/tests/print/samples/style-directive/output.svelte new file mode 100644 index 000000000000..ef84e662a097 --- /dev/null +++ b/packages/svelte/tests/print/samples/style-directive/output.svelte @@ -0,0 +1,4 @@ +
    ...
    +
    + ... +
    diff --git a/packages/svelte/tests/print/samples/svelte-boundary/input.svelte b/packages/svelte/tests/print/samples/svelte-boundary/input.svelte new file mode 100644 index 000000000000..2cbd32daf073 --- /dev/null +++ b/packages/svelte/tests/print/samples/svelte-boundary/input.svelte @@ -0,0 +1,7 @@ + +

    {await delayed('hello!')}

    + + {#snippet pending()} +

    loading...

    + {/snippet} +
    diff --git a/packages/svelte/tests/print/samples/svelte-boundary/output.svelte b/packages/svelte/tests/print/samples/svelte-boundary/output.svelte new file mode 100644 index 000000000000..2cbd32daf073 --- /dev/null +++ b/packages/svelte/tests/print/samples/svelte-boundary/output.svelte @@ -0,0 +1,7 @@ + +

    {await delayed('hello!')}

    + + {#snippet pending()} +

    loading...

    + {/snippet} +
    diff --git a/packages/svelte/tests/print/samples/svelte-component/input.svelte b/packages/svelte/tests/print/samples/svelte-component/input.svelte new file mode 100644 index 000000000000..046ee0f8934f --- /dev/null +++ b/packages/svelte/tests/print/samples/svelte-component/input.svelte @@ -0,0 +1 @@ + diff --git a/packages/svelte/tests/print/samples/svelte-component/output.svelte b/packages/svelte/tests/print/samples/svelte-component/output.svelte new file mode 100644 index 000000000000..17422c8d7f77 --- /dev/null +++ b/packages/svelte/tests/print/samples/svelte-component/output.svelte @@ -0,0 +1 @@ + diff --git a/packages/svelte/tests/print/samples/svelte-document/input.svelte b/packages/svelte/tests/print/samples/svelte-document/input.svelte new file mode 100644 index 000000000000..b99b973e0d43 --- /dev/null +++ b/packages/svelte/tests/print/samples/svelte-document/input.svelte @@ -0,0 +1 @@ + diff --git a/packages/svelte/tests/print/samples/svelte-document/output.svelte b/packages/svelte/tests/print/samples/svelte-document/output.svelte new file mode 100644 index 000000000000..17079ae7ee17 --- /dev/null +++ b/packages/svelte/tests/print/samples/svelte-document/output.svelte @@ -0,0 +1 @@ + diff --git a/packages/svelte/tests/print/samples/svelte-element/input.svelte b/packages/svelte/tests/print/samples/svelte-element/input.svelte new file mode 100644 index 000000000000..388c5a6090fc --- /dev/null +++ b/packages/svelte/tests/print/samples/svelte-element/input.svelte @@ -0,0 +1,7 @@ + + + + This text cannot appear inside an hr element + diff --git a/packages/svelte/tests/print/samples/svelte-element/output.svelte b/packages/svelte/tests/print/samples/svelte-element/output.svelte new file mode 100644 index 000000000000..844b7e5ba124 --- /dev/null +++ b/packages/svelte/tests/print/samples/svelte-element/output.svelte @@ -0,0 +1,5 @@ + + +This text cannot appear inside an hr element diff --git a/packages/svelte/tests/print/samples/svelte-fragment/input.svelte b/packages/svelte/tests/print/samples/svelte-fragment/input.svelte new file mode 100644 index 000000000000..eb80023626a6 --- /dev/null +++ b/packages/svelte/tests/print/samples/svelte-fragment/input.svelte @@ -0,0 +1,11 @@ + + + +

    Hello

    + +

    All rights reserved.

    +

    Copyright (c) 2019 Svelte Industries

    +
    +
    diff --git a/packages/svelte/tests/print/samples/svelte-fragment/output.svelte b/packages/svelte/tests/print/samples/svelte-fragment/output.svelte new file mode 100644 index 000000000000..73a4451ed821 --- /dev/null +++ b/packages/svelte/tests/print/samples/svelte-fragment/output.svelte @@ -0,0 +1,12 @@ + + + +

    Hello

    + + +

    All rights reserved.

    +

    Copyright (c) 2019 Svelte Industries

    +
    +
    diff --git a/packages/svelte/tests/print/samples/svelte-head/input.svelte b/packages/svelte/tests/print/samples/svelte-head/input.svelte new file mode 100644 index 000000000000..0a08871aba9b --- /dev/null +++ b/packages/svelte/tests/print/samples/svelte-head/input.svelte @@ -0,0 +1,4 @@ + + Hello world! + + diff --git a/packages/svelte/tests/print/samples/svelte-self/input.svelte b/packages/svelte/tests/print/samples/svelte-self/input.svelte new file mode 100644 index 000000000000..7711defef14d --- /dev/null +++ b/packages/svelte/tests/print/samples/svelte-self/input.svelte @@ -0,0 +1,10 @@ + + +{#if count > 0} +

    counting down... {count}

    + +{:else} +

    lift-off!

    +{/if} diff --git a/packages/svelte/tests/print/samples/svelte-self/output.svelte b/packages/svelte/tests/print/samples/svelte-self/output.svelte new file mode 100644 index 000000000000..15031a39fad9 --- /dev/null +++ b/packages/svelte/tests/print/samples/svelte-self/output.svelte @@ -0,0 +1,10 @@ + + +{#if count > 0} +

    counting down... {count}

    + +{:else} +

    lift-off!

    +{/if} diff --git a/packages/svelte/tests/print/samples/svelte-window/input.svelte b/packages/svelte/tests/print/samples/svelte-window/input.svelte new file mode 100644 index 000000000000..054e584e1916 --- /dev/null +++ b/packages/svelte/tests/print/samples/svelte-window/input.svelte @@ -0,0 +1,7 @@ + + + diff --git a/packages/svelte/tests/print/samples/svelte-window/output.svelte b/packages/svelte/tests/print/samples/svelte-window/output.svelte new file mode 100644 index 000000000000..03997acab12d --- /dev/null +++ b/packages/svelte/tests/print/samples/svelte-window/output.svelte @@ -0,0 +1,7 @@ + + + From 0e6fcd3ab99bcce670ce29c0fc63451d528fbea2 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 22 Nov 2025 11:02:30 +0100 Subject: [PATCH 60/91] tests for T-nodes --- .../svelte/tests/print/samples/text/input.svelte | 1 + .../svelte/tests/print/samples/text/output.svelte | 1 + .../print/samples/transition-directive/input.svelte | 11 +++++++++++ .../samples/transition-directive/output.svelte | 13 +++++++++++++ 4 files changed, 26 insertions(+) create mode 100644 packages/svelte/tests/print/samples/text/input.svelte create mode 100644 packages/svelte/tests/print/samples/text/output.svelte create mode 100644 packages/svelte/tests/print/samples/transition-directive/input.svelte create mode 100644 packages/svelte/tests/print/samples/transition-directive/output.svelte diff --git a/packages/svelte/tests/print/samples/text/input.svelte b/packages/svelte/tests/print/samples/text/input.svelte new file mode 100644 index 000000000000..a04fa3c48c12 --- /dev/null +++ b/packages/svelte/tests/print/samples/text/input.svelte @@ -0,0 +1 @@ +

    Hello world

    diff --git a/packages/svelte/tests/print/samples/text/output.svelte b/packages/svelte/tests/print/samples/text/output.svelte new file mode 100644 index 000000000000..a04fa3c48c12 --- /dev/null +++ b/packages/svelte/tests/print/samples/text/output.svelte @@ -0,0 +1 @@ +

    Hello world

    diff --git a/packages/svelte/tests/print/samples/transition-directive/input.svelte b/packages/svelte/tests/print/samples/transition-directive/input.svelte new file mode 100644 index 000000000000..7fd08df6ef5a --- /dev/null +++ b/packages/svelte/tests/print/samples/transition-directive/input.svelte @@ -0,0 +1,11 @@ + + + + +{#if visible} +
    fades in and out
    +{/if} diff --git a/packages/svelte/tests/print/samples/transition-directive/output.svelte b/packages/svelte/tests/print/samples/transition-directive/output.svelte new file mode 100644 index 000000000000..ac997bb5245b --- /dev/null +++ b/packages/svelte/tests/print/samples/transition-directive/output.svelte @@ -0,0 +1,13 @@ + + + + +{#if visible} +
    fades in and out
    +{/if} From b146e42a3d3570cc35775b721297df269862ada2 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 22 Nov 2025 11:03:46 +0100 Subject: [PATCH 61/91] tests for U-nodes --- .../tests/print/samples/use-directive/input.svelte | 9 +++++++++ .../tests/print/samples/use-directive/output.svelte | 9 +++++++++ 2 files changed, 18 insertions(+) create mode 100644 packages/svelte/tests/print/samples/use-directive/input.svelte create mode 100644 packages/svelte/tests/print/samples/use-directive/output.svelte diff --git a/packages/svelte/tests/print/samples/use-directive/input.svelte b/packages/svelte/tests/print/samples/use-directive/input.svelte new file mode 100644 index 000000000000..3b9f5562fb64 --- /dev/null +++ b/packages/svelte/tests/print/samples/use-directive/input.svelte @@ -0,0 +1,9 @@ + + +
    ...
    diff --git a/packages/svelte/tests/print/samples/use-directive/output.svelte b/packages/svelte/tests/print/samples/use-directive/output.svelte new file mode 100644 index 000000000000..3b9f5562fb64 --- /dev/null +++ b/packages/svelte/tests/print/samples/use-directive/output.svelte @@ -0,0 +1,9 @@ + + +
    ...
    From 5fb2bb4f4262d6b27a2df913126bb71e312fd9d4 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 22 Nov 2025 11:14:06 +0100 Subject: [PATCH 62/91] seperate css and svelte visitors for clarity --- packages/svelte/src/compiler/print/index.js | 216 ++++++++++---------- 1 file changed, 109 insertions(+), 107 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 2c0772b57eb1..e3ba4089b1a2 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -12,7 +12,8 @@ export function print(ast) { ast, /** @type {Visitors} */ ({ ...ts({ comments: ast.type === 'Root' ? ast.comments : [] }), - ...visitors + ...svelte_visitors, + ...css_visitors }) ); } @@ -40,10 +41,115 @@ function block(context, node, allow_inline = false) { } } -// TODO split css and svelte visitors? Currently they are really mixed together +/** @type {Visitors} */ +const css_visitors = { + Atrule(node, context) { + // TODO seems to produce too many new lines sometimes. Also new lines above style tag? + context.write(`@${node.name}`); + if (node.prelude) context.write(` ${node.prelude}`); + + if (node.block) { + context.write(' '); + context.visit(node.block); + } else { + context.write(';'); + } + }, + + ClassSelector(node, context) { + context.write(`.${node.name}`); + }, + + ComplexSelector(node, context) { + for (const selector of node.children) { + context.visit(selector); + } + }, + + Declaration(node, context) { + context.write(`${node.property}: ${node.value};`); + }, + + Nth(node, context) { + context.write(node.value); + }, + + 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}`); + }, + + 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); + } + }, + + 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); + }, + + SelectorList(node, context) { + let started = false; + for (const selector of node.children) { + if (started) { + context.write(', '); + } + + context.visit(selector); + started = true; + } + }, + + TypeSelector(node, context) { + context.write(node.name); + } +}; /** @type {Visitors} */ -const visitors = { +const svelte_visitors = { Root(node, context) { if (node.options) { context.write(''); }, - ComplexSelector(node, context) { - for (const selector of node.children) { - context.visit(selector); - } - }, - Component(node, context) { context.write(`<${node.name}`); @@ -378,10 +461,6 @@ const visitors = { context.write('}'); }, - Declaration(node, context) { - context.write(`${node.property}: ${node.value};`); - }, - EachBlock(node, context) { context.write('{#each '); context.visit(node.expression); @@ -482,10 +561,6 @@ const visitors = { } }, - Nth(node, context) { - context.write(node.value); - }, - OnDirective(node, context) { context.write(`on:${node.name}`); for (const modifier of node.modifiers) { @@ -501,32 +576,6 @@ const visitors = { } }, - 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) { const child_context = context.new(); @@ -552,55 +601,12 @@ const visitors = { context.append(child_context); }, - 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); - }, - - SelectorList(node, context) { - let started = false; - for (const selector of node.children) { - if (started) { - context.write(', '); - } - - context.visit(selector); - started = true; - } - }, - SlotElement(node, context) { context.write(' Date: Sat, 22 Nov 2025 11:24:48 +0100 Subject: [PATCH 63/91] fix failing test --- packages/svelte/src/compiler/print/index.js | 18 ++++++++++++++++++ .../print/samples/svelte-head/output.svelte | 4 ++++ 2 files changed, 22 insertions(+) create mode 100644 packages/svelte/tests/print/samples/svelte-head/output.svelte diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index e3ba4089b1a2..ddf2149829a6 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -870,6 +870,24 @@ const svelte_visitors = { context.write(node.data); }, + TitleElement(node, context) { + context.write(''); + block(context, node.fragment, true); + context.write(``); + } else { + context.write('/>'); + } + }, + TransitionDirective(node, context) { const directive = node.intro && node.outro ? 'transition' : node.intro ? 'in' : 'out'; context.write(`${directive}:${node.name}`); diff --git a/packages/svelte/tests/print/samples/svelte-head/output.svelte b/packages/svelte/tests/print/samples/svelte-head/output.svelte new file mode 100644 index 000000000000..0a08871aba9b --- /dev/null +++ b/packages/svelte/tests/print/samples/svelte-head/output.svelte @@ -0,0 +1,4 @@ + + Hello world! + + From 386ec29031f82b2f3b6023c56bfb517b7ffbf783 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 22 Nov 2025 11:31:13 +0100 Subject: [PATCH 64/91] rename early tests --- .../print/samples/{animate => animate-directive}/input.svelte | 0 .../print/samples/{animate => animate-directive}/output.svelte | 0 .../tests/print/samples/{attach => attach-tag}/input.svelte | 0 .../tests/print/samples/{attach => attach-tag}/output.svelte | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename packages/svelte/tests/print/samples/{animate => animate-directive}/input.svelte (100%) rename packages/svelte/tests/print/samples/{animate => animate-directive}/output.svelte (100%) rename packages/svelte/tests/print/samples/{attach => attach-tag}/input.svelte (100%) rename packages/svelte/tests/print/samples/{attach => attach-tag}/output.svelte (100%) diff --git a/packages/svelte/tests/print/samples/animate/input.svelte b/packages/svelte/tests/print/samples/animate-directive/input.svelte similarity index 100% rename from packages/svelte/tests/print/samples/animate/input.svelte rename to packages/svelte/tests/print/samples/animate-directive/input.svelte diff --git a/packages/svelte/tests/print/samples/animate/output.svelte b/packages/svelte/tests/print/samples/animate-directive/output.svelte similarity index 100% rename from packages/svelte/tests/print/samples/animate/output.svelte rename to packages/svelte/tests/print/samples/animate-directive/output.svelte diff --git a/packages/svelte/tests/print/samples/attach/input.svelte b/packages/svelte/tests/print/samples/attach-tag/input.svelte similarity index 100% rename from packages/svelte/tests/print/samples/attach/input.svelte rename to packages/svelte/tests/print/samples/attach-tag/input.svelte diff --git a/packages/svelte/tests/print/samples/attach/output.svelte b/packages/svelte/tests/print/samples/attach-tag/output.svelte similarity index 100% rename from packages/svelte/tests/print/samples/attach/output.svelte rename to packages/svelte/tests/print/samples/attach-tag/output.svelte From c7befaf6815a927d3e23511834a581565a99485b Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 22 Nov 2025 11:32:29 +0100 Subject: [PATCH 65/91] fix const test --- packages/svelte/src/compiler/print/index.js | 3 +-- .../tests/print/samples/{const => const-tag}/input.svelte | 0 .../tests/print/samples/{const => const-tag}/output.svelte | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) rename packages/svelte/tests/print/samples/{const => const-tag}/input.svelte (100%) rename packages/svelte/tests/print/samples/{const => const-tag}/output.svelte (69%) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index ddf2149829a6..39374376e4fa 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -442,8 +442,7 @@ const svelte_visitors = { }, ConstTag(node, context) { - // TODO this is buggy - context.write('{@const '); + context.write('{@'); context.visit(node.declaration); context.write('}'); }, diff --git a/packages/svelte/tests/print/samples/const/input.svelte b/packages/svelte/tests/print/samples/const-tag/input.svelte similarity index 100% rename from packages/svelte/tests/print/samples/const/input.svelte rename to packages/svelte/tests/print/samples/const-tag/input.svelte diff --git a/packages/svelte/tests/print/samples/const/output.svelte b/packages/svelte/tests/print/samples/const-tag/output.svelte similarity index 69% rename from packages/svelte/tests/print/samples/const/output.svelte rename to packages/svelte/tests/print/samples/const-tag/output.svelte index 8c333e213ff5..d9d8addcbb67 100644 --- a/packages/svelte/tests/print/samples/const/output.svelte +++ b/packages/svelte/tests/print/samples/const-tag/output.svelte @@ -3,6 +3,6 @@ {#each boxes as box} - {@const const area = box.width * box.height;} + {@const area = box.width * box.height;} {box.width} * {box.height} = {area} {/each} From 4c4856da34f1f191ae1dd791f51e8877c7bfa287 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 22 Nov 2025 11:33:49 +0100 Subject: [PATCH 66/91] fix svelte-element --- packages/svelte/src/compiler/print/index.js | 5 ++--- .../svelte/tests/print/samples/svelte-element/output.svelte | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 39374376e4fa..d42ee4616a23 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -768,12 +768,11 @@ const svelte_visitors = { }, SvelteElement(node, context) { - context.write(' -This text cannot appear inside an hr element +This text cannot appear inside an hr element From e6b325761f0d56041f69327b2ab94244d3b8944f Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 22 Nov 2025 11:38:21 +0100 Subject: [PATCH 67/91] move block to css visitors --- packages/svelte/src/compiler/print/index.js | 54 ++++++++++----------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index d42ee4616a23..a5384218a377 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -56,6 +56,33 @@ const css_visitors = { } }, + 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}`); }, @@ -381,33 +408,6 @@ const svelte_visitors = { 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('}'); - }, - ClassDirective(node, context) { context.write(`class:${node.name}`); if ( From 7910be8af15256d989c752b3aa88a37d67387217 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 22 Nov 2025 12:18:02 +0100 Subject: [PATCH 68/91] fix css output --- packages/svelte/src/compiler/print/index.js | 2 -- packages/svelte/tests/print/samples/style/output.svelte | 3 --- 2 files changed, 5 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index a5384218a377..32b6d458b4f3 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -44,7 +44,6 @@ function block(context, node, allow_inline = false) { /** @type {Visitors} */ const css_visitors = { Atrule(node, context) { - // TODO seems to produce too many new lines sometimes. Also new lines above style tag? context.write(`@${node.name}`); if (node.prelude) context.write(` ${node.prelude}`); @@ -67,7 +66,6 @@ const css_visitors = { for (const child of node.children) { if (started) { - context.margin(); context.newline(); } diff --git a/packages/svelte/tests/print/samples/style/output.svelte b/packages/svelte/tests/print/samples/style/output.svelte index deaad3f801b1..0f87f1094184 100644 --- a/packages/svelte/tests/print/samples/style/output.svelte +++ b/packages/svelte/tests/print/samples/style/output.svelte @@ -19,7 +19,6 @@ from { opacity: 0; } - to { opacity: 1; } @@ -27,7 +26,6 @@ @font-face { font-family: "MyFont"; - src: url("/fonts/MyFont.woff2") format("woff2"); } @@ -53,7 +51,6 @@ .card::before { content: ""; - display: block; } From 90ea50bb1c983284e50ccf5db6da53d94e6dcf92 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 22 Nov 2025 14:49:44 +0100 Subject: [PATCH 69/91] fix #if indentation --- packages/svelte/src/compiler/print/index.js | 6 +++--- .../svelte/tests/print/samples/if-block/output.svelte | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 32b6d458b4f3..dc0d459f297b 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -526,10 +526,10 @@ const svelte_visitors = { ) ) { context.write('{:else}'); + block(context, node.alternate); + } else { + context.visit(node.alternate); } - - // TODO inconsistent indentation behavior here - block(context, node.alternate); } if (!node.elseif) { diff --git a/packages/svelte/tests/print/samples/if-block/output.svelte b/packages/svelte/tests/print/samples/if-block/output.svelte index 9940a3c1c294..4a65d637dbb4 100644 --- a/packages/svelte/tests/print/samples/if-block/output.svelte +++ b/packages/svelte/tests/print/samples/if-block/output.svelte @@ -1,7 +1,7 @@ {#if porridge.temperature > 100} -

    too hot!

    - {:else if 80 > porridge.temperature} -

    too cold!

    - {:else} -

    just right!

    +

    too hot1!

    +{:else if 80 > porridge.temperature} +

    too cold!

    +{:else} +

    just right!

    {/if} From 4a87f5aaf8e6ae6dbfff3cd9da1c255c0e865db4 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 22 Nov 2025 14:56:49 +0100 Subject: [PATCH 70/91] fix self closing tag problems --- packages/svelte/src/compiler/print/index.js | 40 +++++++++---------- .../print/samples/if-block/output.svelte | 2 +- .../samples/svelte-component/output.svelte | 2 +- .../samples/svelte-document/output.svelte | 2 +- .../print/samples/svelte-self/output.svelte | 2 +- .../print/samples/svelte-window/output.svelte | 2 +- 6 files changed, 23 insertions(+), 27 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index dc0d459f297b..9583d7d0322f 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -714,21 +714,21 @@ const svelte_visitors = { context.visit(attribute); } - if (node.fragment) { + if (node.fragment && node.fragment.nodes.length > 0) { context.write('>'); block(context, node.fragment, true); context.write(``); } else { - context.write('/>'); + context.write(' />'); } }, SvelteComponent(node, context) { context.write(' 0) { context.write('>'); block(context, node.fragment, true); context.write(``); } else { - context.write('/>'); + context.write(' />'); } }, @@ -755,13 +754,12 @@ const svelte_visitors = { context.visit(attribute); } - // TODO handling of self-closing does not seem to work - if (node.fragment) { + if (node.fragment && node.fragment.nodes.length > 0) { context.write('>'); block(context, node.fragment, true); context.write(`
    `); } else { - context.write('/>'); + context.write(' />'); } }, @@ -779,12 +777,12 @@ const svelte_visitors = { } // TODO new line handling not working? - if (node.fragment) { + if (node.fragment && node.fragment.nodes.length > 0) { context.write('>'); block(context, node.fragment, true); context.write(``); } else { - context.write('/>'); + context.write(' />'); } }, @@ -797,12 +795,12 @@ const svelte_visitors = { context.visit(attribute); } - if (node.fragment) { + if (node.fragment && node.fragment.nodes.length > 0) { context.write('>'); block(context, node.fragment, true); context.write(``); } else { - context.write('/>'); + context.write(' />'); } }, @@ -815,12 +813,12 @@ const svelte_visitors = { context.visit(attribute); } - if (node.fragment) { + if (node.fragment && node.fragment.nodes.length > 0) { context.write('>'); block(context, node.fragment, true); context.write(``); } else { - context.write('/>'); + context.write(' />'); } }, @@ -833,13 +831,12 @@ const svelte_visitors = { context.visit(attribute); } - // TODO handling of self-closing does not seem to work - if (node.fragment) { + if (node.fragment && node.fragment.nodes.length > 0) { context.write('>'); block(context, node.fragment, true); context.write(``); } else { - context.write('/>'); + context.write(' />'); } }, @@ -852,13 +849,12 @@ const svelte_visitors = { context.visit(attribute); } - // TODO handling of self-closing does not seem to work - if (node.fragment) { + if (node.fragment && node.fragment.nodes.length > 0) { context.write('>'); block(context, node.fragment, true); context.write(``); } else { - context.write('/>'); + context.write(' />'); } }, diff --git a/packages/svelte/tests/print/samples/if-block/output.svelte b/packages/svelte/tests/print/samples/if-block/output.svelte index 4a65d637dbb4..0a444a125252 100644 --- a/packages/svelte/tests/print/samples/if-block/output.svelte +++ b/packages/svelte/tests/print/samples/if-block/output.svelte @@ -1,5 +1,5 @@ {#if porridge.temperature > 100} -

    too hot1!

    +

    too hot!

    {:else if 80 > porridge.temperature}

    too cold!

    {:else} diff --git a/packages/svelte/tests/print/samples/svelte-component/output.svelte b/packages/svelte/tests/print/samples/svelte-component/output.svelte index 17422c8d7f77..046ee0f8934f 100644 --- a/packages/svelte/tests/print/samples/svelte-component/output.svelte +++ b/packages/svelte/tests/print/samples/svelte-component/output.svelte @@ -1 +1 @@ - + diff --git a/packages/svelte/tests/print/samples/svelte-document/output.svelte b/packages/svelte/tests/print/samples/svelte-document/output.svelte index 17079ae7ee17..b99b973e0d43 100644 --- a/packages/svelte/tests/print/samples/svelte-document/output.svelte +++ b/packages/svelte/tests/print/samples/svelte-document/output.svelte @@ -1 +1 @@ - + diff --git a/packages/svelte/tests/print/samples/svelte-self/output.svelte b/packages/svelte/tests/print/samples/svelte-self/output.svelte index 15031a39fad9..7711defef14d 100644 --- a/packages/svelte/tests/print/samples/svelte-self/output.svelte +++ b/packages/svelte/tests/print/samples/svelte-self/output.svelte @@ -4,7 +4,7 @@ {#if count > 0}

    counting down... {count}

    - + {:else}

    lift-off!

    {/if} diff --git a/packages/svelte/tests/print/samples/svelte-window/output.svelte b/packages/svelte/tests/print/samples/svelte-window/output.svelte index 03997acab12d..054e584e1916 100644 --- a/packages/svelte/tests/print/samples/svelte-window/output.svelte +++ b/packages/svelte/tests/print/samples/svelte-window/output.svelte @@ -4,4 +4,4 @@ } - + From 080ca6ff076e281a3645c18118ca7ec4199f9e42 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 22 Nov 2025 19:40:33 +0100 Subject: [PATCH 71/91] use common method for attributes --- packages/svelte/src/compiler/print/index.js | 119 ++++---------------- 1 file changed, 25 insertions(+), 94 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 9583d7d0322f..c132e48a270a 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -41,6 +41,17 @@ function block(context, node, allow_inline = false) { } } +/** + * @param {(AST.AttachTag | AST.Attribute | AST.SpreadAttribute | AST.Directive)[]} attributes + * @param {Context} context + */ +function attributes(attributes, context) { + for (const attribute of attributes) { + context.write(' '); + context.visit(attribute); + } +} + /** @type {Visitors} */ const css_visitors = { Atrule(node, context) { @@ -204,12 +215,7 @@ const svelte_visitors = { Script(node, context) { context.write(''); block(context, node.content); context.write(''); @@ -424,12 +430,7 @@ const svelte_visitors = { Component(node, context) { context.write(`<${node.name}`); - - for (let i = 0; i < node.attributes.length; i += 1) { - context.write(' '); - context.visit(node.attributes[i]); - } - + attributes(node.attributes, context); if (node.fragment.nodes.length > 0) { context.write('>'); block(context, node.fragment, true); @@ -577,13 +578,7 @@ const svelte_visitors = { const child_context = context.new(); child_context.write('<' + node.name); - - for (const attribute of node.attributes) { - // TODO handle multiline - child_context.write(' '); - child_context.visit(attribute); - } - + attributes(node.attributes, child_context); if (is_void(node.name)) { child_context.write(' />'); } else { @@ -606,12 +601,7 @@ const svelte_visitors = { SlotElement(node, context) { context.write(' 0) { context.write('>'); context.visit(node.fragment); // TODO block/inline @@ -674,12 +664,7 @@ const svelte_visitors = { StyleSheet(node, context) { context.write(''); if (node.children.length > 0) { @@ -707,13 +692,7 @@ const svelte_visitors = { SvelteBoundary(node, context) { context.write(' 0) { context.write('>'); block(context, node.fragment, true); @@ -729,13 +708,7 @@ const svelte_visitors = { context.write(' this={'); context.visit(node.expression); context.write('}'); - - for (const attribute of node.attributes) { - // TODO handle multiline - context.write(' '); - context.visit(attribute); - } - + attributes(node.attributes, context); if (node.fragment && node.fragment.nodes.length > 0) { context.write('>'); block(context, node.fragment, true); @@ -747,13 +720,7 @@ const svelte_visitors = { SvelteDocument(node, context) { context.write(' 0) { context.write('>'); block(context, node.fragment, true); @@ -769,13 +736,7 @@ const svelte_visitors = { context.write('this={'); context.visit(node.tag); context.write('}'); - - for (const attribute of node.attributes) { - // TODO handle multiline - context.write(' '); - context.visit(attribute); - } - + attributes(node.attributes, context); // TODO new line handling not working? if (node.fragment && node.fragment.nodes.length > 0) { context.write('>'); @@ -788,13 +749,7 @@ const svelte_visitors = { SvelteFragment(node, context) { context.write(' 0) { context.write('>'); block(context, node.fragment, true); @@ -806,13 +761,7 @@ const svelte_visitors = { SvelteHead(node, context) { context.write(' 0) { context.write('>'); block(context, node.fragment, true); @@ -824,13 +773,7 @@ const svelte_visitors = { SvelteSelf(node, context) { context.write(' 0) { context.write('>'); block(context, node.fragment, true); @@ -842,13 +785,7 @@ const svelte_visitors = { SvelteWindow(node, context) { context.write(' 0) { context.write('>'); block(context, node.fragment, true); @@ -864,13 +801,7 @@ const svelte_visitors = { TitleElement(node, context) { context.write(''); block(context, node.fragment, true); From 8d732bc114111522d23cd66e3aaee8aaf1c97531 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 22 Nov 2025 19:54:48 +0100 Subject: [PATCH 72/91] put attributes into multiple lines if too long --- packages/svelte/src/compiler/print/index.js | 28 ++++++++++++++++--- .../print/samples/let-directive/output.svelte | 4 ++- .../print/samples/on-directive/output.svelte | 3 +- .../samples/style-directive/output.svelte | 5 +++- .../samples/svelte-document/output.svelte | 4 ++- .../print/samples/svelte-head/output.svelte | 4 ++- .../transition-directive/output.svelte | 3 +- 7 files changed, 41 insertions(+), 10 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index c132e48a270a..5c0544e19421 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -4,6 +4,9 @@ import * as esrap from 'esrap'; import ts from 'esrap/languages/ts'; import { is_void } from '../../utils.js'; +/** Threshold for when content should be formatted on separate lines */ +const LINE_BREAK_THRESHOLD = 30; + /** * @param {AST.SvelteNode} ast */ @@ -46,9 +49,26 @@ function block(context, node, allow_inline = false) { * @param {Context} context */ function attributes(attributes, context) { + // Measure total width of all attributes + const child_context = context.new(); + for (const attribute of attributes) { - context.write(' '); - context.visit(attribute); + child_context.write(' '); + child_context.visit(attribute); + } + + // Format on multiple lines if too wide + const multiline = child_context.measure() > LINE_BREAK_THRESHOLD; + + if (multiline) { + context.indent(); + for (const attribute of attributes) { + context.newline(); + context.visit(attribute); + } + context.dedent(); + } else { + context.append(child_context); } } @@ -293,7 +313,7 @@ const svelte_visitors = { return child_context; }); - multiline ||= width > 30; + multiline ||= width > LINE_BREAK_THRESHOLD; for (let i = 0; i < child_contexts.length; i += 1) { const prev = child_contexts[i]; @@ -585,7 +605,7 @@ const svelte_visitors = { child_context.write('>'); if (node.fragment) { - block(child_context, node.fragment, child_context.measure() < 30); + block(child_context, node.fragment, child_context.measure() < LINE_BREAK_THRESHOLD); child_context.write(``); } } diff --git a/packages/svelte/tests/print/samples/let-directive/output.svelte b/packages/svelte/tests/print/samples/let-directive/output.svelte index c7254150e317..c70a80b75b2e 100644 --- a/packages/svelte/tests/print/samples/let-directive/output.svelte +++ b/packages/svelte/tests/print/samples/let-directive/output.svelte @@ -1 +1,3 @@ -
    {processed.text}
    +
    {processed.text}
    diff --git a/packages/svelte/tests/print/samples/on-directive/output.svelte b/packages/svelte/tests/print/samples/on-directive/output.svelte index 976749696a4d..4e88f2edf6e5 100644 --- a/packages/svelte/tests/print/samples/on-directive/output.svelte +++ b/packages/svelte/tests/print/samples/on-directive/output.svelte @@ -6,6 +6,7 @@ } - diff --git a/packages/svelte/tests/print/samples/style-directive/output.svelte b/packages/svelte/tests/print/samples/style-directive/output.svelte index ef84e662a097..68a9c6432811 100644 --- a/packages/svelte/tests/print/samples/style-directive/output.svelte +++ b/packages/svelte/tests/print/samples/style-directive/output.svelte @@ -1,4 +1,7 @@
    ...
    -
    +
    ...
    diff --git a/packages/svelte/tests/print/samples/svelte-document/output.svelte b/packages/svelte/tests/print/samples/svelte-document/output.svelte index b99b973e0d43..1583bdc16114 100644 --- a/packages/svelte/tests/print/samples/svelte-document/output.svelte +++ b/packages/svelte/tests/print/samples/svelte-document/output.svelte @@ -1 +1,3 @@ - + diff --git a/packages/svelte/tests/print/samples/svelte-head/output.svelte b/packages/svelte/tests/print/samples/svelte-head/output.svelte index 0a08871aba9b..b28dd7d9aea6 100644 --- a/packages/svelte/tests/print/samples/svelte-head/output.svelte +++ b/packages/svelte/tests/print/samples/svelte-head/output.svelte @@ -1,4 +1,6 @@ Hello world! - + diff --git a/packages/svelte/tests/print/samples/transition-directive/output.svelte b/packages/svelte/tests/print/samples/transition-directive/output.svelte index ac997bb5245b..ec11fb066910 100644 --- a/packages/svelte/tests/print/samples/transition-directive/output.svelte +++ b/packages/svelte/tests/print/samples/transition-directive/output.svelte @@ -4,7 +4,8 @@ let visible = $state(false); - From 452c997fbe90eeda7ce0fc429e4de243611f7326 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 22 Nov 2025 19:57:45 +0100 Subject: [PATCH 73/91] fix new lines for #each:else --- packages/svelte/src/compiler/print/index.js | 3 +-- packages/svelte/tests/print/samples/each-block/output.svelte | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 5c0544e19421..87999c6ecbcd 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -503,9 +503,8 @@ const svelte_visitors = { block(context, node.body); if (node.fallback) { - // TODO new lines context.write('{:else}'); - context.visit(node.fallback); + block(context, node.fallback); } context.write('{/each}'); diff --git a/packages/svelte/tests/print/samples/each-block/output.svelte b/packages/svelte/tests/print/samples/each-block/output.svelte index a8642f96ad9c..b19925a694c8 100644 --- a/packages/svelte/tests/print/samples/each-block/output.svelte +++ b/packages/svelte/tests/print/samples/each-block/output.svelte @@ -12,4 +12,6 @@ {#each todos as todo}

    {todo.text}

    -{:else}

    No tasks today!

    {/each} +{:else} +

    No tasks today!

    +{/each} From 2372f176c049544425a1d774e308b7ccf7797045 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 22 Nov 2025 20:01:52 +0100 Subject: [PATCH 74/91] fix svelte element new lines --- packages/svelte/src/compiler/print/index.js | 4 ++-- .../svelte/tests/print/samples/svelte-element/output.svelte | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 87999c6ecbcd..9f354966d2b3 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -756,10 +756,10 @@ const svelte_visitors = { context.visit(node.tag); context.write('}'); attributes(node.attributes, context); - // TODO new line handling not working? + if (node.fragment && node.fragment.nodes.length > 0) { context.write('>'); - block(context, node.fragment, true); + block(context, node.fragment); context.write(``); } else { context.write(' />'); diff --git a/packages/svelte/tests/print/samples/svelte-element/output.svelte b/packages/svelte/tests/print/samples/svelte-element/output.svelte index 342f8226c0e5..388c5a6090fc 100644 --- a/packages/svelte/tests/print/samples/svelte-element/output.svelte +++ b/packages/svelte/tests/print/samples/svelte-element/output.svelte @@ -2,4 +2,6 @@ let tag = $state('hr'); -This text cannot appear inside an hr element + + This text cannot appear inside an hr element + From 45ae8206a3cd8349b0eb3131dd202c5d66a7c404 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sat, 22 Nov 2025 20:28:09 +0100 Subject: [PATCH 75/91] rmeove usless comments & fix playground --- packages/svelte/src/compiler/print/index.js | 3 +-- playgrounds/sandbox/run.js | 10 ++++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 9f354966d2b3..3170b02e0a94 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -566,7 +566,6 @@ const svelte_visitors = { }, LetDirective(node, context) { - // TODO new lines context.write(`let:${node.name}`); if ( node.expression !== null && @@ -623,7 +622,7 @@ const svelte_visitors = { attributes(node.attributes, context); if (node.fragment.nodes.length > 0) { context.write('>'); - context.visit(node.fragment); // TODO block/inline + context.visit(node.fragment); context.write(''); } else { context.write(' />'); diff --git a/playgrounds/sandbox/run.js b/playgrounds/sandbox/run.js index edc3027c2a3f..35bffb67a22d 100644 --- a/playgrounds/sandbox/run.js +++ b/playgrounds/sandbox/run.js @@ -11,6 +11,7 @@ const MIGRATE = false; const FROM_HTML = true; const FROM_TREE = false; const DEV = false; +const PRINT = false; const argv = parseArgs({ options: { runes: { type: 'boolean' } }, args: process.argv.slice(2) }); @@ -71,6 +72,11 @@ for (const generate of /** @type {const} */ (['client', 'server'])) { '\t' ) ); + + if (PRINT) { + const printed = print(ast); + write(`${cwd}/output/printed/${file}`, printed.code); + } } if (MIGRATE) { @@ -81,10 +87,6 @@ for (const generate of /** @type {const} */ (['client', 'server'])) { console.warn(`Error migrating ${file}`, e); } } - - const printed = print(ast); - - write(`${cwd}/output/printed/${file}`, printed.code); } let from_html; From 0f0523b6421d8131b3c7abf021e02a3ba8d0fe71 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sun, 23 Nov 2025 08:48:26 +0100 Subject: [PATCH 76/91] improved formatting --- packages/svelte/src/compiler/print/index.js | 26 +++++++++++++++++-- .../print/samples/await-block/output.svelte | 10 +++++-- .../print/samples/each-block/output.svelte | 4 ++- .../samples/style-directive/output.svelte | 1 + .../samples/svelte-fragment/output.svelte | 5 +++- .../print/samples/svelte-head/output.svelte | 1 + 6 files changed, 41 insertions(+), 6 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 3170b02e0a94..9105191e2be7 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -47,6 +47,7 @@ function block(context, node, allow_inline = false) { /** * @param {(AST.AttachTag | AST.Attribute | AST.SpreadAttribute | AST.Directive)[]} attributes * @param {Context} context + * @returns {boolean} multiline */ function attributes(attributes, context) { // Measure total width of all attributes @@ -70,6 +71,8 @@ function attributes(attributes, context) { } else { context.append(child_context); } + + return multiline; } /** @type {Visitors} */ @@ -596,19 +599,38 @@ const svelte_visitors = { const child_context = context.new(); child_context.write('<' + node.name); - attributes(node.attributes, child_context); + let multiline_attributes = attributes(node.attributes, child_context); + let multiline = false; if (is_void(node.name)) { child_context.write(' />'); } else { child_context.write('>'); if (node.fragment) { - block(child_context, node.fragment, child_context.measure() < LINE_BREAK_THRESHOLD); + const sub_child_context = child_context.new(); + block(sub_child_context, node.fragment, child_context.measure() < LINE_BREAK_THRESHOLD); + + multiline ||= sub_child_context.measure() > LINE_BREAK_THRESHOLD; + + if (multiline) child_context.newline(); + if (multiline && !multiline_attributes && !sub_child_context.multiline) + child_context.indent(); + child_context.append(sub_child_context); + if (multiline && !multiline_attributes && !sub_child_context.multiline) + child_context.dedent(); + if (multiline) child_context.newline(); + child_context.write(``); } } + if (multiline || multiline_attributes) { + context.newline(); + } context.append(child_context); + if (multiline || multiline_attributes) { + context.newline(); + } }, RenderTag(node, context) { diff --git a/packages/svelte/tests/print/samples/await-block/output.svelte b/packages/svelte/tests/print/samples/await-block/output.svelte index 10f0b3fb9e1e..15397af76740 100644 --- a/packages/svelte/tests/print/samples/await-block/output.svelte +++ b/packages/svelte/tests/print/samples/await-block/output.svelte @@ -1,10 +1,16 @@ {#await promise} -

    waiting for the promise to resolve...

    + +

    + waiting for the promise to resolve... +

    {:then value}

    The value is {value}

    {:catch error} -

    Something went wrong: {error.message}

    + +

    + Something went wrong: {error.message} +

    {/await} diff --git a/packages/svelte/tests/print/samples/each-block/output.svelte b/packages/svelte/tests/print/samples/each-block/output.svelte index b19925a694c8..73d38427bd94 100644 --- a/packages/svelte/tests/print/samples/each-block/output.svelte +++ b/packages/svelte/tests/print/samples/each-block/output.svelte @@ -3,7 +3,9 @@ {/each} {#each objects as { id, ...rest }} -
  • {id}
  • +
  • + {id} +
  • {/each} {#each expression} diff --git a/packages/svelte/tests/print/samples/style-directive/output.svelte b/packages/svelte/tests/print/samples/style-directive/output.svelte index 68a9c6432811..0db4406dd420 100644 --- a/packages/svelte/tests/print/samples/style-directive/output.svelte +++ b/packages/svelte/tests/print/samples/style-directive/output.svelte @@ -1,4 +1,5 @@
    ...
    +

    All rights reserved.

    -

    Copyright (c) 2019 Svelte Industries

    + +

    + Copyright (c) 2019 Svelte Industries +

    diff --git a/packages/svelte/tests/print/samples/svelte-head/output.svelte b/packages/svelte/tests/print/samples/svelte-head/output.svelte index b28dd7d9aea6..53dafd1f8698 100644 --- a/packages/svelte/tests/print/samples/svelte-head/output.svelte +++ b/packages/svelte/tests/print/samples/svelte-head/output.svelte @@ -1,5 +1,6 @@ Hello world! + From 2e7abb8d25badeac588570b76c0192ac18f2c95f Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sun, 23 Nov 2025 09:18:01 +0100 Subject: [PATCH 77/91] support formatting for a lot more nodes --- packages/svelte/src/compiler/print/index.js | 176 ++++++------------ .../svelte/src/compiler/types/template.d.ts | 2 +- .../print/samples/attribute/output.svelte | 2 +- .../print/samples/let-directive/output.svelte | 5 +- .../samples/spread-attribute/output.svelte | 2 +- .../samples/svelte-boundary/output.svelte | 1 + .../samples/svelte-document/output.svelte | 1 + .../print/samples/svelte-head/output.svelte | 1 + packages/svelte/types/index.d.ts | 2 +- 9 files changed, 71 insertions(+), 121 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 9105191e2be7..72f4cf6d3d86 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -75,6 +75,56 @@ function attributes(attributes, context) { return multiline; } +/** + * @param {AST.BaseElement} node + * @param {Context} context + */ +function base_element(node, context) { + const child_context = context.new(); + + child_context.write('<' + node.name); + + if (node.type === 'SvelteComponent' || node.type === 'SvelteElement') { + context.write(' this={'); + // @ts-expect-error expression is not parse of the base node + context.visit(node.expression); + context.write('}'); + } + + let multiline_attributes = attributes(node.attributes, child_context); + let multiline = false; + if (is_void(node.name) || node.fragment.nodes.length === 0) { + child_context.write(' />'); + } else { + child_context.write('>'); + + if (node.fragment) { + const sub_child_context = child_context.new(); + block(sub_child_context, node.fragment, child_context.measure() < LINE_BREAK_THRESHOLD); + + multiline ||= sub_child_context.measure() > LINE_BREAK_THRESHOLD; + + if (multiline) child_context.newline(); + if (multiline && !multiline_attributes && !sub_child_context.multiline) + child_context.indent(); + child_context.append(sub_child_context); + if (multiline && !multiline_attributes && !sub_child_context.multiline) + child_context.dedent(); + if (multiline) child_context.newline(); + + child_context.write(``); + } + } + + if (multiline || multiline_attributes) { + context.newline(); + } + context.append(child_context); + if (multiline || multiline_attributes) { + context.newline(); + } +} + /** @type {Visitors} */ const css_visitors = { Atrule(node, context) { @@ -452,15 +502,7 @@ const svelte_visitors = { }, Component(node, context) { - context.write(`<${node.name}`); - attributes(node.attributes, context); - if (node.fragment.nodes.length > 0) { - context.write('>'); - block(context, node.fragment, true); - context.write(``); - } else { - context.write(' />'); - } + base_element(node, context); }, ConstTag(node, context) { @@ -596,41 +638,7 @@ const svelte_visitors = { }, RegularElement(node, context) { - const child_context = context.new(); - - child_context.write('<' + node.name); - let multiline_attributes = attributes(node.attributes, child_context); - let multiline = false; - if (is_void(node.name)) { - child_context.write(' />'); - } else { - child_context.write('>'); - - if (node.fragment) { - const sub_child_context = child_context.new(); - block(sub_child_context, node.fragment, child_context.measure() < LINE_BREAK_THRESHOLD); - - multiline ||= sub_child_context.measure() > LINE_BREAK_THRESHOLD; - - if (multiline) child_context.newline(); - if (multiline && !multiline_attributes && !sub_child_context.multiline) - child_context.indent(); - child_context.append(sub_child_context); - if (multiline && !multiline_attributes && !sub_child_context.multiline) - child_context.dedent(); - if (multiline) child_context.newline(); - - child_context.write(``); - } - } - - if (multiline || multiline_attributes) { - context.newline(); - } - context.append(child_context); - if (multiline || multiline_attributes) { - context.newline(); - } + base_element(node, context); }, RenderTag(node, context) { @@ -640,15 +648,7 @@ const svelte_visitors = { }, SlotElement(node, context) { - context.write(' 0) { - context.write('>'); - context.visit(node.fragment); - context.write(''); - } else { - context.write(' />'); - } + base_element(node, context); }, SnippetBlock(node, context) { @@ -731,15 +731,7 @@ const svelte_visitors = { }, SvelteBoundary(node, context) { - context.write(' 0) { - context.write('>'); - block(context, node.fragment, true); - context.write(``); - } else { - context.write(' />'); - } + base_element(node, context); }, SvelteComponent(node, context) { @@ -759,15 +751,7 @@ const svelte_visitors = { }, SvelteDocument(node, context) { - context.write(' 0) { - context.write('>'); - block(context, node.fragment, true); - context.write(``); - } else { - context.write(' />'); - } + base_element(node, context); }, SvelteElement(node, context) { @@ -788,51 +772,19 @@ const svelte_visitors = { }, SvelteFragment(node, context) { - context.write(' 0) { - context.write('>'); - block(context, node.fragment, true); - context.write(``); - } else { - context.write(' />'); - } + base_element(node, context); }, SvelteHead(node, context) { - context.write(' 0) { - context.write('>'); - block(context, node.fragment, true); - context.write(``); - } else { - context.write(' />'); - } + base_element(node, context); }, SvelteSelf(node, context) { - context.write(' 0) { - context.write('>'); - block(context, node.fragment, true); - context.write(``); - } else { - context.write(' />'); - } + base_element(node, context); }, SvelteWindow(node, context) { - context.write(' 0) { - context.write('>'); - block(context, node.fragment, true); - context.write(``); - } else { - context.write(' />'); - } + base_element(node, context); }, Text(node, context) { @@ -840,15 +792,7 @@ const svelte_visitors = { }, TitleElement(node, context) { - context.write(''); - block(context, node.fragment, true); - context.write(``); - } else { - context.write('/>'); - } + base_element(node, context); }, TransitionDirective(node, context) { diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index fd664f107c0e..0c63a7c16247 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -296,7 +296,7 @@ export namespace AST { expression: null | Expression; } - interface BaseElement extends BaseNode { + export interface BaseElement extends BaseNode { name: string; attributes: Array; fragment: Fragment; diff --git a/packages/svelte/tests/print/samples/attribute/output.svelte b/packages/svelte/tests/print/samples/attribute/output.svelte index 4da846f8e6b8..e07c35c5cc25 100644 --- a/packages/svelte/tests/print/samples/attribute/output.svelte +++ b/packages/svelte/tests/print/samples/attribute/output.svelte @@ -1 +1 @@ -
    +
    diff --git a/packages/svelte/tests/print/samples/let-directive/output.svelte b/packages/svelte/tests/print/samples/let-directive/output.svelte index c70a80b75b2e..35df703fe556 100644 --- a/packages/svelte/tests/print/samples/let-directive/output.svelte +++ b/packages/svelte/tests/print/samples/let-directive/output.svelte @@ -1,3 +1,6 @@ +
    {processed.text}
    + let:item={processed}> +
    {processed.text}
    + diff --git a/packages/svelte/tests/print/samples/spread-attribute/output.svelte b/packages/svelte/tests/print/samples/spread-attribute/output.svelte index 836425cae3b0..4f3d8adfb373 100644 --- a/packages/svelte/tests/print/samples/spread-attribute/output.svelte +++ b/packages/svelte/tests/print/samples/spread-attribute/output.svelte @@ -1 +1 @@ -
    +
    diff --git a/packages/svelte/tests/print/samples/svelte-boundary/output.svelte b/packages/svelte/tests/print/samples/svelte-boundary/output.svelte index 2cbd32daf073..3d16b9d3bd2d 100644 --- a/packages/svelte/tests/print/samples/svelte-boundary/output.svelte +++ b/packages/svelte/tests/print/samples/svelte-boundary/output.svelte @@ -1,3 +1,4 @@ +

    {await delayed('hello!')}

    diff --git a/packages/svelte/tests/print/samples/svelte-document/output.svelte b/packages/svelte/tests/print/samples/svelte-document/output.svelte index 1583bdc16114..f8ad1faf90e9 100644 --- a/packages/svelte/tests/print/samples/svelte-document/output.svelte +++ b/packages/svelte/tests/print/samples/svelte-document/output.svelte @@ -1,3 +1,4 @@ + diff --git a/packages/svelte/tests/print/samples/svelte-head/output.svelte b/packages/svelte/tests/print/samples/svelte-head/output.svelte index 53dafd1f8698..6482bf06a2de 100644 --- a/packages/svelte/tests/print/samples/svelte-head/output.svelte +++ b/packages/svelte/tests/print/samples/svelte-head/output.svelte @@ -1,3 +1,4 @@ + Hello world! diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index ecfada9cf346..e76579f1d1ae 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -1389,7 +1389,7 @@ declare module 'svelte/compiler' { expression: null | Expression; } - interface BaseElement extends BaseNode { + export interface BaseElement extends BaseNode { name: string; attributes: Array; fragment: Fragment; From d1218ccd4722f2888ceb3f7161cfcdbd9c4373c1 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sun, 23 Nov 2025 09:23:23 +0100 Subject: [PATCH 78/91] style --- packages/svelte/src/compiler/print/index.js | 24 +++++++++------------ 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 72f4cf6d3d86..de92e112d28a 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -95,25 +95,21 @@ function base_element(node, context) { let multiline = false; if (is_void(node.name) || node.fragment.nodes.length === 0) { child_context.write(' />'); - } else { + } else if (node.fragment) { child_context.write('>'); - if (node.fragment) { - const sub_child_context = child_context.new(); - block(sub_child_context, node.fragment, child_context.measure() < LINE_BREAK_THRESHOLD); + const sub_child_context = child_context.new(); + block(sub_child_context, node.fragment, child_context.measure() < LINE_BREAK_THRESHOLD); - multiline ||= sub_child_context.measure() > LINE_BREAK_THRESHOLD; + multiline ||= sub_child_context.measure() > LINE_BREAK_THRESHOLD; - if (multiline) child_context.newline(); - if (multiline && !multiline_attributes && !sub_child_context.multiline) - child_context.indent(); - child_context.append(sub_child_context); - if (multiline && !multiline_attributes && !sub_child_context.multiline) - child_context.dedent(); - if (multiline) child_context.newline(); + if (multiline) child_context.newline(); + if (multiline && !multiline_attributes && !sub_child_context.multiline) child_context.indent(); + child_context.append(sub_child_context); + if (multiline && !multiline_attributes && !sub_child_context.multiline) child_context.dedent(); + if (multiline) child_context.newline(); - child_context.write(``); - } + child_context.write(``); } if (multiline || multiline_attributes) { From 72ea549b8b9081ce58a718fc50b321f292cb2de8 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sun, 23 Nov 2025 09:27:11 +0100 Subject: [PATCH 79/91] fixes for formatting --- packages/svelte/src/compiler/print/index.js | 19 +++++++++---------- .../print/samples/await-block/output.svelte | 2 -- .../print/samples/let-directive/output.svelte | 1 - .../samples/style-directive/output.svelte | 1 - .../samples/svelte-boundary/output.svelte | 1 - .../samples/svelte-document/output.svelte | 1 - .../samples/svelte-fragment/output.svelte | 2 -- .../print/samples/svelte-head/output.svelte | 2 -- 8 files changed, 9 insertions(+), 20 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index de92e112d28a..8c1f24481ffd 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -92,7 +92,7 @@ function base_element(node, context) { } let multiline_attributes = attributes(node.attributes, child_context); - let multiline = false; + let multiline_content = false; if (is_void(node.name) || node.fragment.nodes.length === 0) { child_context.write(' />'); } else if (node.fragment) { @@ -101,22 +101,21 @@ function base_element(node, context) { const sub_child_context = child_context.new(); block(sub_child_context, node.fragment, child_context.measure() < LINE_BREAK_THRESHOLD); - multiline ||= sub_child_context.measure() > LINE_BREAK_THRESHOLD; + multiline_content ||= sub_child_context.measure() > LINE_BREAK_THRESHOLD; - if (multiline) child_context.newline(); - if (multiline && !multiline_attributes && !sub_child_context.multiline) child_context.indent(); + if (multiline_content) child_context.newline(); + if (multiline_content && !multiline_attributes && !sub_child_context.multiline) + child_context.indent(); child_context.append(sub_child_context); - if (multiline && !multiline_attributes && !sub_child_context.multiline) child_context.dedent(); - if (multiline) child_context.newline(); + if (multiline_content && !multiline_attributes && !sub_child_context.multiline) + child_context.dedent(); + if (multiline_content) child_context.newline(); child_context.write(``); } - if (multiline || multiline_attributes) { - context.newline(); - } context.append(child_context); - if (multiline || multiline_attributes) { + if (multiline_content || multiline_attributes) { context.newline(); } } diff --git a/packages/svelte/tests/print/samples/await-block/output.svelte b/packages/svelte/tests/print/samples/await-block/output.svelte index 15397af76740..a07c1398a2a4 100644 --- a/packages/svelte/tests/print/samples/await-block/output.svelte +++ b/packages/svelte/tests/print/samples/await-block/output.svelte @@ -1,6 +1,5 @@ {#await promise} -

    waiting for the promise to resolve...

    @@ -9,7 +8,6 @@

    The value is {value}

    {:catch error} -

    Something went wrong: {error.message}

    diff --git a/packages/svelte/tests/print/samples/let-directive/output.svelte b/packages/svelte/tests/print/samples/let-directive/output.svelte index 35df703fe556..bee91e1f64dd 100644 --- a/packages/svelte/tests/print/samples/let-directive/output.svelte +++ b/packages/svelte/tests/print/samples/let-directive/output.svelte @@ -1,4 +1,3 @@ - diff --git a/packages/svelte/tests/print/samples/style-directive/output.svelte b/packages/svelte/tests/print/samples/style-directive/output.svelte index 0db4406dd420..68a9c6432811 100644 --- a/packages/svelte/tests/print/samples/style-directive/output.svelte +++ b/packages/svelte/tests/print/samples/style-directive/output.svelte @@ -1,5 +1,4 @@
    ...
    -

    {await delayed('hello!')}

    diff --git a/packages/svelte/tests/print/samples/svelte-document/output.svelte b/packages/svelte/tests/print/samples/svelte-document/output.svelte index f8ad1faf90e9..1583bdc16114 100644 --- a/packages/svelte/tests/print/samples/svelte-document/output.svelte +++ b/packages/svelte/tests/print/samples/svelte-document/output.svelte @@ -1,4 +1,3 @@ - diff --git a/packages/svelte/tests/print/samples/svelte-fragment/output.svelte b/packages/svelte/tests/print/samples/svelte-fragment/output.svelte index b0e90b587bb3..71458f20892e 100644 --- a/packages/svelte/tests/print/samples/svelte-fragment/output.svelte +++ b/packages/svelte/tests/print/samples/svelte-fragment/output.svelte @@ -4,10 +4,8 @@

    Hello

    -

    All rights reserved.

    -

    Copyright (c) 2019 Svelte Industries

    diff --git a/packages/svelte/tests/print/samples/svelte-head/output.svelte b/packages/svelte/tests/print/samples/svelte-head/output.svelte index 6482bf06a2de..b28dd7d9aea6 100644 --- a/packages/svelte/tests/print/samples/svelte-head/output.svelte +++ b/packages/svelte/tests/print/samples/svelte-head/output.svelte @@ -1,7 +1,5 @@ - Hello world! - From 6bec5193ecfea11a8ba2dc05e11e7ff773949529 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sun, 23 Nov 2025 09:39:53 +0100 Subject: [PATCH 80/91] cleanup --- packages/svelte/src/compiler/print/index.js | 61 +++++++++++++++------ 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 8c1f24481ffd..66654840b075 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -24,6 +24,7 @@ export function print(ast) { /** * @param {Context} context * @param {AST.SvelteNode} node + * @param {boolean} allow_inline */ function block(context, node, allow_inline = false) { const child_context = context.new(); @@ -45,12 +46,16 @@ function block(context, node, allow_inline = false) { } /** - * @param {(AST.AttachTag | AST.Attribute | AST.SpreadAttribute | AST.Directive)[]} attributes + * @param {AST.BaseElement['attributes']} attributes * @param {Context} context - * @returns {boolean} multiline + * @returns {boolean} true if attributes were formatted on multiple lines */ function attributes(attributes, context) { - // Measure total width of all attributes + if (attributes.length === 0) { + return false; + } + + // Measure total width of all attributes when rendered inline const child_context = context.new(); for (const attribute of attributes) { @@ -58,7 +63,6 @@ function attributes(attributes, context) { child_context.visit(attribute); } - // Format on multiple lines if too wide const multiline = child_context.measure() > LINE_BREAK_THRESHOLD; if (multiline) { @@ -84,37 +88,58 @@ function base_element(node, context) { child_context.write('<' + node.name); + // Handle special Svelte components/elements that need 'this' attribute if (node.type === 'SvelteComponent' || node.type === 'SvelteElement') { context.write(' this={'); - // @ts-expect-error expression is not parse of the base node + // @ts-expect-error expression is not part of the base node interface context.visit(node.expression); context.write('}'); } - let multiline_attributes = attributes(node.attributes, child_context); + const multiline_attributes = attributes(node.attributes, child_context); + + const is_self_closing = is_void(node.name) || node.fragment.nodes.length === 0; let multiline_content = false; - if (is_void(node.name) || node.fragment.nodes.length === 0) { + + if (is_self_closing) { child_context.write(' />'); - } else if (node.fragment) { + } else { child_context.write('>'); - const sub_child_context = child_context.new(); - block(sub_child_context, node.fragment, child_context.measure() < LINE_BREAK_THRESHOLD); + // Process the element's content in a separate context for measurement + const content_context = child_context.new(); + const allow_inline_content = child_context.measure() < LINE_BREAK_THRESHOLD; + block(content_context, node.fragment, allow_inline_content); + + // Determine if content should be formatted on multiple lines + multiline_content = content_context.measure() > LINE_BREAK_THRESHOLD; + + if (multiline_content) { + child_context.newline(); - multiline_content ||= sub_child_context.measure() > LINE_BREAK_THRESHOLD; + // Only indent if attributes are inline and content itself isn't already multiline + const should_indent = !multiline_attributes && !content_context.multiline; + if (should_indent) { + child_context.indent(); + } + + child_context.append(content_context); - if (multiline_content) child_context.newline(); - if (multiline_content && !multiline_attributes && !sub_child_context.multiline) - child_context.indent(); - child_context.append(sub_child_context); - if (multiline_content && !multiline_attributes && !sub_child_context.multiline) - child_context.dedent(); - if (multiline_content) child_context.newline(); + if (should_indent) { + child_context.dedent(); + } + + child_context.newline(); + } else { + child_context.append(content_context); + } child_context.write(``); } context.append(child_context); + + if (is_self_closing) return; if (multiline_content || multiline_attributes) { context.newline(); } From aec7ff22d3fbcc4cdc00096fbbdede87272a7906 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Sun, 23 Nov 2025 09:52:48 +0100 Subject: [PATCH 81/91] make typescript happy --- packages/svelte/src/compiler/print/index.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 66654840b075..2b301f8045c5 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -89,11 +89,14 @@ function base_element(node, context) { child_context.write('<' + node.name); // Handle special Svelte components/elements that need 'this' attribute - if (node.type === 'SvelteComponent' || node.type === 'SvelteElement') { - context.write(' this={'); - // @ts-expect-error expression is not part of the base node interface - context.visit(node.expression); - context.write('}'); + if (node.type === 'SvelteComponent') { + child_context.write(' this={'); + child_context.visit(/** @type {AST.SvelteComponent} */ (node).expression); + child_context.write('}'); + } else if (node.type === 'SvelteElement') { + child_context.write(' this={'); + child_context.visit(/** @type {AST.SvelteElement} */ (node).tag); + child_context.write('}'); } const multiline_attributes = attributes(node.attributes, child_context); From 915e70c86f47cc235a29a44106ef940b5c399d59 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Mon, 24 Nov 2025 19:42:27 +0100 Subject: [PATCH 82/91] add formatting related test --- .../tests/print/samples/block/input.svelte | 4 +++ .../tests/print/samples/block/output.svelte | 10 +++++++ .../print/samples/formatting/input.svelte | 1 + .../print/samples/formatting/output.svelte | 28 +++++++++++++++++++ 4 files changed, 43 insertions(+) create mode 100644 packages/svelte/tests/print/samples/formatting/input.svelte create mode 100644 packages/svelte/tests/print/samples/formatting/output.svelte diff --git a/packages/svelte/tests/print/samples/block/input.svelte b/packages/svelte/tests/print/samples/block/input.svelte index a881a77192b4..84140c5c02a2 100644 --- a/packages/svelte/tests/print/samples/block/input.svelte +++ b/packages/svelte/tests/print/samples/block/input.svelte @@ -3,3 +3,7 @@ {#each items as item, i}

    {i}: {item}

    {/each} + +{#if condition}yes{:else}no{/if} + +{#each items as item, i}

    {i}: {item}

    {/each} diff --git a/packages/svelte/tests/print/samples/block/output.svelte b/packages/svelte/tests/print/samples/block/output.svelte index 187bcd83485f..e5d85908755d 100644 --- a/packages/svelte/tests/print/samples/block/output.svelte +++ b/packages/svelte/tests/print/samples/block/output.svelte @@ -7,3 +7,13 @@ {#each items as item, i}

    {i}: {item}

    {/each} + +{#if condition} + yes +{:else} + no +{/if} + +{#each items as item, i} +

    {i}: {item}

    +{/each} diff --git a/packages/svelte/tests/print/samples/formatting/input.svelte b/packages/svelte/tests/print/samples/formatting/input.svelte new file mode 100644 index 000000000000..9b1898e9c82d --- /dev/null +++ b/packages/svelte/tests/print/samples/formatting/input.svelte @@ -0,0 +1 @@ +

    {m.hello_world({ name: 'SvelteKit User' })}

    If you use VSCode, install the Sherlock i18n extensionfor a better i18n experience.

    diff --git a/packages/svelte/tests/print/samples/formatting/output.svelte b/packages/svelte/tests/print/samples/formatting/output.svelte new file mode 100644 index 000000000000..20777aeb705d --- /dev/null +++ b/packages/svelte/tests/print/samples/formatting/output.svelte @@ -0,0 +1,28 @@ + + +

    + {m.hello_world({ name: 'SvelteKit User' })} +

    +
    + + +
    +

    + If you use VSCode, install the + + + Sherlock i18n extension + + for a better i18n experience. +

    From 9305f5c707987fce41f5be73d736e36fa0e5503f Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Mon, 24 Nov 2025 19:58:10 +0100 Subject: [PATCH 83/91] add docs --- packages/svelte/src/compiler/print/index.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 2b301f8045c5..8c672e8712df 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -8,6 +8,12 @@ import { is_void } from '../../utils.js'; const LINE_BREAK_THRESHOLD = 30; /** + * `print` converts a Svelte AST node back into Svelte source code. + * It is primarily intended for tools that parse and transform components using the compiler’s modern AST representation. + * + * `print(ast)` requires an AST node produced by parse with modern: true, or any sub-node within that modern AST. + * The result contains the generated source and a corresponding source map. + * The output is valid Svelte, but formatting details such as whitespace or quoting may differ from the original. * @param {AST.SvelteNode} ast */ export function print(ast) { From fd35103a821dbcc270633d7fd3b46c4de178c9cb Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Mon, 24 Nov 2025 20:00:51 +0100 Subject: [PATCH 84/91] changeset --- .changeset/little-humans-occur.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/little-humans-occur.md diff --git a/.changeset/little-humans-occur.md b/.changeset/little-humans-occur.md new file mode 100644 index 000000000000..78b94ee26ebe --- /dev/null +++ b/.changeset/little-humans-occur.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: add `print(...)` function From b0b315efbeca2bcd5d4cc59ea17b628a67e77f67 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Mon, 24 Nov 2025 20:08:53 +0100 Subject: [PATCH 85/91] regenerate types --- packages/svelte/types/index.d.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index e76579f1d1ae..d2cb1cde9cea 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -1615,6 +1615,14 @@ declare module 'svelte/compiler' { export function preprocess(source: string, preprocessor: PreprocessorGroup | PreprocessorGroup[], options?: { filename?: string; } | undefined): Promise; + /** + * `print` converts a Svelte AST node back into Svelte source code. + * It is primarily intended for tools that parse and transform components using the compiler’s modern AST representation. + * + * `print(ast)` requires an AST node produced by parse with modern: true, or any sub-node within that modern AST. + * The result contains the generated source and a corresponding source map. + * The output is valid Svelte, but formatting details such as whitespace or quoting may differ from the original. + * */ export function print(ast: AST.SvelteNode): { code: string; map: any; From d4f8cee1788da4b3ecbcd5f5eb55d1ad2c03a622 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Tue, 25 Nov 2025 19:38:46 +0100 Subject: [PATCH 86/91] fix self-closing tags --- packages/svelte/src/compiler/print/index.js | 3 ++- packages/svelte/tests/print/samples/attribute/output.svelte | 2 +- packages/svelte/tests/print/samples/slot-element/output.svelte | 2 +- .../svelte/tests/print/samples/spread-attribute/output.svelte | 2 +- .../svelte/tests/print/samples/svelte-document/output.svelte | 2 +- packages/svelte/tests/print/samples/svelte-self/output.svelte | 2 +- .../svelte/tests/print/samples/svelte-window/output.svelte | 2 +- 7 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 8c672e8712df..de226f87369d 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -107,7 +107,8 @@ function base_element(node, context) { const multiline_attributes = attributes(node.attributes, child_context); - const is_self_closing = is_void(node.name) || node.fragment.nodes.length === 0; + const is_self_closing = + is_void(node.name) || (node.type === 'Component' && node.fragment.nodes.length === 0); let multiline_content = false; if (is_self_closing) { diff --git a/packages/svelte/tests/print/samples/attribute/output.svelte b/packages/svelte/tests/print/samples/attribute/output.svelte index e07c35c5cc25..4da846f8e6b8 100644 --- a/packages/svelte/tests/print/samples/attribute/output.svelte +++ b/packages/svelte/tests/print/samples/attribute/output.svelte @@ -1 +1 @@ -
    +
    diff --git a/packages/svelte/tests/print/samples/slot-element/output.svelte b/packages/svelte/tests/print/samples/slot-element/output.svelte index 9289d49fb3f4..4d59994078ad 100644 --- a/packages/svelte/tests/print/samples/slot-element/output.svelte +++ b/packages/svelte/tests/print/samples/slot-element/output.svelte @@ -1 +1 @@ - + diff --git a/packages/svelte/tests/print/samples/spread-attribute/output.svelte b/packages/svelte/tests/print/samples/spread-attribute/output.svelte index 4f3d8adfb373..836425cae3b0 100644 --- a/packages/svelte/tests/print/samples/spread-attribute/output.svelte +++ b/packages/svelte/tests/print/samples/spread-attribute/output.svelte @@ -1 +1 @@ -
    +
    diff --git a/packages/svelte/tests/print/samples/svelte-document/output.svelte b/packages/svelte/tests/print/samples/svelte-document/output.svelte index 1583bdc16114..f99f634ee807 100644 --- a/packages/svelte/tests/print/samples/svelte-document/output.svelte +++ b/packages/svelte/tests/print/samples/svelte-document/output.svelte @@ -1,3 +1,3 @@ + use:someAction> diff --git a/packages/svelte/tests/print/samples/svelte-self/output.svelte b/packages/svelte/tests/print/samples/svelte-self/output.svelte index 7711defef14d..15031a39fad9 100644 --- a/packages/svelte/tests/print/samples/svelte-self/output.svelte +++ b/packages/svelte/tests/print/samples/svelte-self/output.svelte @@ -4,7 +4,7 @@ {#if count > 0}

    counting down... {count}

    - + {:else}

    lift-off!

    {/if} diff --git a/packages/svelte/tests/print/samples/svelte-window/output.svelte b/packages/svelte/tests/print/samples/svelte-window/output.svelte index 054e584e1916..03997acab12d 100644 --- a/packages/svelte/tests/print/samples/svelte-window/output.svelte +++ b/packages/svelte/tests/print/samples/svelte-window/output.svelte @@ -4,4 +4,4 @@ } - + From f7a343dc25187ed7180b45440758b4b4776858ec Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Tue, 25 Nov 2025 20:01:52 +0100 Subject: [PATCH 87/91] fix bracket placement --- packages/svelte/src/compiler/print/index.js | 3 ++- .../svelte/tests/print/samples/formatting/output.svelte | 9 ++++++--- .../tests/print/samples/let-directive/output.svelte | 3 ++- .../tests/print/samples/on-directive/output.svelte | 3 ++- .../tests/print/samples/style-directive/output.svelte | 3 ++- .../tests/print/samples/svelte-document/output.svelte | 3 ++- .../svelte/tests/print/samples/svelte-head/output.svelte | 3 ++- .../print/samples/transition-directive/output.svelte | 3 ++- 8 files changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index de226f87369d..91b5b1bbcff8 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -78,6 +78,7 @@ function attributes(attributes, context) { context.visit(attribute); } context.dedent(); + context.newline(); } else { context.append(child_context); } @@ -112,7 +113,7 @@ function base_element(node, context) { let multiline_content = false; if (is_self_closing) { - child_context.write(' />'); + child_context.write(`${multiline_attributes ? '' : ' '}/>`); } else { child_context.write('>'); diff --git a/packages/svelte/tests/print/samples/formatting/output.svelte b/packages/svelte/tests/print/samples/formatting/output.svelte index 20777aeb705d..79a2dfed7259 100644 --- a/packages/svelte/tests/print/samples/formatting/output.svelte +++ b/packages/svelte/tests/print/samples/formatting/output.svelte @@ -8,11 +8,13 @@
    @@ -21,7 +23,8 @@ + target="_blank" + > Sherlock i18n extension for a better i18n experience. diff --git a/packages/svelte/tests/print/samples/let-directive/output.svelte b/packages/svelte/tests/print/samples/let-directive/output.svelte index bee91e1f64dd..473f7b36edbc 100644 --- a/packages/svelte/tests/print/samples/let-directive/output.svelte +++ b/packages/svelte/tests/print/samples/let-directive/output.svelte @@ -1,5 +1,6 @@ + let:item={processed} +>
    {processed.text}
    diff --git a/packages/svelte/tests/print/samples/on-directive/output.svelte b/packages/svelte/tests/print/samples/on-directive/output.svelte index 4e88f2edf6e5..f165dd021e88 100644 --- a/packages/svelte/tests/print/samples/on-directive/output.svelte +++ b/packages/svelte/tests/print/samples/on-directive/output.svelte @@ -7,6 +7,7 @@ diff --git a/packages/svelte/tests/print/samples/style-directive/output.svelte b/packages/svelte/tests/print/samples/style-directive/output.svelte index 68a9c6432811..5aa3a6dcdb96 100644 --- a/packages/svelte/tests/print/samples/style-directive/output.svelte +++ b/packages/svelte/tests/print/samples/style-directive/output.svelte @@ -2,6 +2,7 @@
    + style:background-color={darkMode ? 'black' : 'white'} +> ...
    diff --git a/packages/svelte/tests/print/samples/svelte-document/output.svelte b/packages/svelte/tests/print/samples/svelte-document/output.svelte index f99f634ee807..418ae4e00819 100644 --- a/packages/svelte/tests/print/samples/svelte-document/output.svelte +++ b/packages/svelte/tests/print/samples/svelte-document/output.svelte @@ -1,3 +1,4 @@ + use:someAction +> diff --git a/packages/svelte/tests/print/samples/svelte-head/output.svelte b/packages/svelte/tests/print/samples/svelte-head/output.svelte index b28dd7d9aea6..68d352260ef4 100644 --- a/packages/svelte/tests/print/samples/svelte-head/output.svelte +++ b/packages/svelte/tests/print/samples/svelte-head/output.svelte @@ -2,5 +2,6 @@ Hello world! + content="This is where the description goes for SEO" + /> diff --git a/packages/svelte/tests/print/samples/transition-directive/output.svelte b/packages/svelte/tests/print/samples/transition-directive/output.svelte index ec11fb066910..b7b6f8641835 100644 --- a/packages/svelte/tests/print/samples/transition-directive/output.svelte +++ b/packages/svelte/tests/print/samples/transition-directive/output.svelte @@ -5,7 +5,8 @@ From d272572fdabf7ed76f05c8ffb629991ddc663c44 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Tue, 25 Nov 2025 20:30:58 +0100 Subject: [PATCH 88/91] break length 50 --- packages/svelte/src/compiler/print/index.js | 12 ++++++++---- .../print/samples/await-block/output.svelte | 8 ++------ .../print/samples/class-directive/output.svelte | 4 +--- .../tests/print/samples/comment/output.svelte | 3 +-- .../tests/print/samples/each-block/output.svelte | 4 +--- .../print/samples/expression-tag/output.svelte | 3 +-- .../tests/print/samples/formatting/output.svelte | 16 +++------------- .../print/samples/let-directive/output.svelte | 7 +------ .../print/samples/on-directive/output.svelte | 6 +----- .../print/samples/regular-element/output.svelte | 3 +-- .../print/samples/svelte-fragment/output.svelte | 4 +--- .../samples/transition-directive/output.svelte | 6 +----- 12 files changed, 22 insertions(+), 54 deletions(-) diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 91b5b1bbcff8..4f435a5688f2 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -5,7 +5,7 @@ import ts from 'esrap/languages/ts'; import { is_void } from '../../utils.js'; /** Threshold for when content should be formatted on separate lines */ -const LINE_BREAK_THRESHOLD = 30; +const LINE_BREAK_THRESHOLD = 50; /** * `print` converts a Svelte AST node back into Svelte source code. @@ -148,10 +148,16 @@ function base_element(node, context) { child_context.write(``); } + const break_line_after = child_context.measure() > LINE_BREAK_THRESHOLD; + + if ((multiline_content || multiline_attributes) && !context.empty()) { + context.newline(); + } + context.append(child_context); if (is_self_closing) return; - if (multiline_content || multiline_attributes) { + if (multiline_content || multiline_attributes || break_line_after) { context.newline(); } } @@ -411,8 +417,6 @@ const svelte_visitors = { context.newline(); } else if (multiline) { context.newline(); - } else { - context.write(' '); } } } diff --git a/packages/svelte/tests/print/samples/await-block/output.svelte b/packages/svelte/tests/print/samples/await-block/output.svelte index a07c1398a2a4..10f0b3fb9e1e 100644 --- a/packages/svelte/tests/print/samples/await-block/output.svelte +++ b/packages/svelte/tests/print/samples/await-block/output.svelte @@ -1,14 +1,10 @@ {#await promise} -

    - waiting for the promise to resolve... -

    +

    waiting for the promise to resolve...

    {:then value}

    The value is {value}

    {:catch error} -

    - Something went wrong: {error.message} -

    +

    Something went wrong: {error.message}

    {/await} diff --git a/packages/svelte/tests/print/samples/class-directive/output.svelte b/packages/svelte/tests/print/samples/class-directive/output.svelte index ec88a869b795..14e5611331f3 100644 --- a/packages/svelte/tests/print/samples/class-directive/output.svelte +++ b/packages/svelte/tests/print/samples/class-directive/output.svelte @@ -3,6 +3,4 @@ let foo = false; -
    - Hello world! -
    +
    Hello world!
    diff --git a/packages/svelte/tests/print/samples/comment/output.svelte b/packages/svelte/tests/print/samples/comment/output.svelte index 6cfd2d4c93a9..f2f97b65be02 100644 --- a/packages/svelte/tests/print/samples/comment/output.svelte +++ b/packages/svelte/tests/print/samples/comment/output.svelte @@ -1,4 +1,3 @@ - - diff --git a/packages/svelte/tests/print/samples/each-block/output.svelte b/packages/svelte/tests/print/samples/each-block/output.svelte index 73d38427bd94..b19925a694c8 100644 --- a/packages/svelte/tests/print/samples/each-block/output.svelte +++ b/packages/svelte/tests/print/samples/each-block/output.svelte @@ -3,9 +3,7 @@ {/each} {#each objects as { id, ...rest }} -
  • - {id} -
  • +
  • {id}
  • {/each} {#each expression} diff --git a/packages/svelte/tests/print/samples/expression-tag/output.svelte b/packages/svelte/tests/print/samples/expression-tag/output.svelte index 3920c3b40a6c..9142a59631b0 100644 --- a/packages/svelte/tests/print/samples/expression-tag/output.svelte +++ b/packages/svelte/tests/print/samples/expression-tag/output.svelte @@ -1,2 +1 @@ -{name} -{count + 1} +{name}{count + 1} diff --git a/packages/svelte/tests/print/samples/formatting/output.svelte b/packages/svelte/tests/print/samples/formatting/output.svelte index 79a2dfed7259..7b4064258086 100644 --- a/packages/svelte/tests/print/samples/formatting/output.svelte +++ b/packages/svelte/tests/print/samples/formatting/output.svelte @@ -3,20 +3,10 @@ import { m } from '$lib/paraglide/messages.js'; -

    - {m.hello_world({ name: 'SvelteKit User' })} -

    +

    {m.hello_world({ name: 'SvelteKit User' })}

    - - + +

    If you use VSCode, install the diff --git a/packages/svelte/tests/print/samples/let-directive/output.svelte b/packages/svelte/tests/print/samples/let-directive/output.svelte index 473f7b36edbc..c7254150e317 100644 --- a/packages/svelte/tests/print/samples/let-directive/output.svelte +++ b/packages/svelte/tests/print/samples/let-directive/output.svelte @@ -1,6 +1 @@ - -

    {processed.text}
    - +
    {processed.text}
    diff --git a/packages/svelte/tests/print/samples/on-directive/output.svelte b/packages/svelte/tests/print/samples/on-directive/output.svelte index f165dd021e88..da0945678d11 100644 --- a/packages/svelte/tests/print/samples/on-directive/output.svelte +++ b/packages/svelte/tests/print/samples/on-directive/output.svelte @@ -6,8 +6,4 @@ } - + diff --git a/packages/svelte/tests/print/samples/regular-element/output.svelte b/packages/svelte/tests/print/samples/regular-element/output.svelte index c39f4f487f9c..0cf7c2472f1c 100644 --- a/packages/svelte/tests/print/samples/regular-element/output.svelte +++ b/packages/svelte/tests/print/samples/regular-element/output.svelte @@ -1,2 +1 @@ - -
    +
    diff --git a/packages/svelte/tests/print/samples/svelte-fragment/output.svelte b/packages/svelte/tests/print/samples/svelte-fragment/output.svelte index 71458f20892e..eb80023626a6 100644 --- a/packages/svelte/tests/print/samples/svelte-fragment/output.svelte +++ b/packages/svelte/tests/print/samples/svelte-fragment/output.svelte @@ -6,8 +6,6 @@

    Hello

    All rights reserved.

    -

    - Copyright (c) 2019 Svelte Industries -

    +

    Copyright (c) 2019 Svelte Industries

    diff --git a/packages/svelte/tests/print/samples/transition-directive/output.svelte b/packages/svelte/tests/print/samples/transition-directive/output.svelte index b7b6f8641835..7fd08df6ef5a 100644 --- a/packages/svelte/tests/print/samples/transition-directive/output.svelte +++ b/packages/svelte/tests/print/samples/transition-directive/output.svelte @@ -4,11 +4,7 @@ let visible = $state(false); - + {#if visible}
    fades in and out
    From b4af0d7641424ac983fa0bb31e7c817107e839d2 Mon Sep 17 00:00:00 2001 From: Manuel Serret Date: Tue, 25 Nov 2025 21:58:55 +0100 Subject: [PATCH 89/91] add support for additional comments --- packages/svelte/package.json | 2 +- packages/svelte/src/compiler/print/index.js | 9 ++- packages/svelte/src/compiler/print/types.d.ts | 6 ++ packages/svelte/types/index.d.ts | 7 +- pnpm-lock.yaml | 81 +++++++++---------- 5 files changed, 60 insertions(+), 45 deletions(-) create mode 100644 packages/svelte/src/compiler/print/types.d.ts diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 416493faf65b..a2ed4bdf7d00 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -176,7 +176,7 @@ "clsx": "^2.1.1", "devalue": "^5.5.0", "esm-env": "^1.2.1", - "esrap": "^2.1.0", + "esrap": "^2.2.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", diff --git a/packages/svelte/src/compiler/print/index.js b/packages/svelte/src/compiler/print/index.js index 4f435a5688f2..abd8b562977e 100644 --- a/packages/svelte/src/compiler/print/index.js +++ b/packages/svelte/src/compiler/print/index.js @@ -15,12 +15,17 @@ const LINE_BREAK_THRESHOLD = 50; * The result contains the generated source and a corresponding source map. * The output is valid Svelte, but formatting details such as whitespace or quoting may differ from the original. * @param {AST.SvelteNode} ast + * @param {import('./types.js').Options | undefined} options */ -export function print(ast) { +export function print(ast, options = undefined) { return esrap.print( ast, /** @type {Visitors} */ ({ - ...ts({ comments: ast.type === 'Root' ? ast.comments : [] }), + ...ts({ + comments: ast.type === 'Root' ? ast.comments : [], + getLeadingComments: options?.getLeadingComments, + getTrailingComments: options?.getTrailingComments + }), ...svelte_visitors, ...css_visitors }) diff --git a/packages/svelte/src/compiler/print/types.d.ts b/packages/svelte/src/compiler/print/types.d.ts new file mode 100644 index 000000000000..cf9b749e0e9f --- /dev/null +++ b/packages/svelte/src/compiler/print/types.d.ts @@ -0,0 +1,6 @@ +import ts from 'esrap/languages/ts'; + +export type Options = { + getLeadingComments?: NonNullable[0]>['getLeadingComments'] | undefined; + getTrailingComments?: NonNullable[0]>['getTrailingComments'] | undefined; +}; diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 8a02856421f7..8561268689ca 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -844,6 +844,7 @@ declare module 'svelte/compiler' { import type { SourceMap } from 'magic-string'; import type { ArrayExpression, ArrowFunctionExpression, VariableDeclaration, VariableDeclarator, Expression, Identifier, MemberExpression, Node, ObjectExpression, Pattern, Program, ChainExpression, SimpleCallExpression, SequenceExpression } from 'estree'; import type { Location } from 'locate-character'; + import type { default as ts } from 'esrap/languages/ts'; /** * `compile` converts your `.svelte` source code into a JavaScript module that exports a component * @@ -1624,7 +1625,7 @@ declare module 'svelte/compiler' { * The result contains the generated source and a corresponding source map. * The output is valid Svelte, but formatting details such as whitespace or quoting may differ from the original. * */ - export function print(ast: AST.SvelteNode): { + export function print(ast: AST.SvelteNode, options?: Options | undefined): { code: string; map: any; }; @@ -1811,6 +1812,10 @@ declare module 'svelte/compiler' { | SimpleSelector | Declaration; } + type Options = { + getLeadingComments?: NonNullable[0]>['getLeadingComments'] | undefined; + getTrailingComments?: NonNullable[0]>['getTrailingComments'] | undefined; + }; export {}; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0b1f57213d31..bdb120600b0c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -96,8 +96,8 @@ importers: specifier: ^1.2.1 version: 1.2.1 esrap: - specifier: ^2.1.0 - version: 2.1.0 + specifier: ^2.2.0 + version: 2.2.0 is-reference: specifier: ^3.0.3 version: 3.0.3 @@ -1178,8 +1178,8 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/project-service@8.46.2': - resolution: {integrity: sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==} + '@typescript-eslint/project-service@8.48.0': + resolution: {integrity: sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' @@ -1188,12 +1188,12 @@ packages: resolution: {integrity: sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/scope-manager@8.46.2': - resolution: {integrity: sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==} + '@typescript-eslint/scope-manager@8.48.0': + resolution: {integrity: sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.46.2': - resolution: {integrity: sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==} + '@typescript-eslint/tsconfig-utils@8.48.0': + resolution: {integrity: sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' @@ -1209,8 +1209,8 @@ packages: resolution: {integrity: sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.46.2': - resolution: {integrity: sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==} + '@typescript-eslint/types@8.48.0': + resolution: {integrity: sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/typescript-estree@8.26.0': @@ -1219,8 +1219,8 @@ packages: peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/typescript-estree@8.46.2': - resolution: {integrity: sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==} + '@typescript-eslint/typescript-estree@8.48.0': + resolution: {integrity: sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' @@ -1232,8 +1232,8 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/utils@8.46.2': - resolution: {integrity: sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==} + '@typescript-eslint/utils@8.48.0': + resolution: {integrity: sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -1243,8 +1243,8 @@ packages: resolution: {integrity: sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/visitor-keys@8.46.2': - resolution: {integrity: sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==} + '@typescript-eslint/visitor-keys@8.48.0': + resolution: {integrity: sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitest/coverage-v8@2.1.9': @@ -1660,8 +1660,8 @@ packages: resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} engines: {node: '>=0.10'} - esrap@2.1.0: - resolution: {integrity: sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==} + esrap@2.2.0: + resolution: {integrity: sha512-WBmtxe7R9C5mvL4n2le8nMUe4mD5V9oiK2vJpQ9I3y20ENPUomPcphBXE8D1x/Bm84oN1V+lOfgXxtqmxTp3Xg==} esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} @@ -3648,10 +3648,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.46.2(typescript@5.5.4)': + '@typescript-eslint/project-service@8.48.0(typescript@5.5.4)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.46.2(typescript@5.5.4) - '@typescript-eslint/types': 8.46.2 + '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.5.4) + '@typescript-eslint/types': 8.48.0 debug: 4.4.3 typescript: 5.5.4 transitivePeerDependencies: @@ -3662,12 +3662,12 @@ snapshots: '@typescript-eslint/types': 8.26.0 '@typescript-eslint/visitor-keys': 8.26.0 - '@typescript-eslint/scope-manager@8.46.2': + '@typescript-eslint/scope-manager@8.48.0': dependencies: - '@typescript-eslint/types': 8.46.2 - '@typescript-eslint/visitor-keys': 8.46.2 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/visitor-keys': 8.48.0 - '@typescript-eslint/tsconfig-utils@8.46.2(typescript@5.5.4)': + '@typescript-eslint/tsconfig-utils@8.48.0(typescript@5.5.4)': dependencies: typescript: 5.5.4 @@ -3684,7 +3684,7 @@ snapshots: '@typescript-eslint/types@8.26.0': {} - '@typescript-eslint/types@8.46.2': {} + '@typescript-eslint/types@8.48.0': {} '@typescript-eslint/typescript-estree@8.26.0(typescript@5.5.4)': dependencies: @@ -3700,17 +3700,16 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.46.2(typescript@5.5.4)': + '@typescript-eslint/typescript-estree@8.48.0(typescript@5.5.4)': dependencies: - '@typescript-eslint/project-service': 8.46.2(typescript@5.5.4) - '@typescript-eslint/tsconfig-utils': 8.46.2(typescript@5.5.4) - '@typescript-eslint/types': 8.46.2 - '@typescript-eslint/visitor-keys': 8.46.2 + '@typescript-eslint/project-service': 8.48.0(typescript@5.5.4) + '@typescript-eslint/tsconfig-utils': 8.48.0(typescript@5.5.4) + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/visitor-keys': 8.48.0 debug: 4.4.3 - fast-glob: 3.3.3 - is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.7.3 + tinyglobby: 0.2.15 ts-api-utils: 2.1.0(typescript@5.5.4) typescript: 5.5.4 transitivePeerDependencies: @@ -3727,12 +3726,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.46.2(eslint@9.9.1)(typescript@5.5.4)': + '@typescript-eslint/utils@8.48.0(eslint@9.9.1)(typescript@5.5.4)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.9.1) - '@typescript-eslint/scope-manager': 8.46.2 - '@typescript-eslint/types': 8.46.2 - '@typescript-eslint/typescript-estree': 8.46.2(typescript@5.5.4) + '@typescript-eslint/scope-manager': 8.48.0 + '@typescript-eslint/types': 8.48.0 + '@typescript-eslint/typescript-estree': 8.48.0(typescript@5.5.4) eslint: 9.9.1 typescript: 5.5.4 transitivePeerDependencies: @@ -3743,9 +3742,9 @@ snapshots: '@typescript-eslint/types': 8.26.0 eslint-visitor-keys: 4.2.1 - '@typescript-eslint/visitor-keys@8.46.2': + '@typescript-eslint/visitor-keys@8.48.0': dependencies: - '@typescript-eslint/types': 8.46.2 + '@typescript-eslint/types': 8.48.0 eslint-visitor-keys: 4.2.1 '@vitest/coverage-v8@2.1.9(vitest@2.1.9(@types/node@20.19.17)(jsdom@25.0.1)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0))': @@ -4145,7 +4144,7 @@ snapshots: eslint-plugin-n@17.16.1(eslint@9.9.1)(typescript@5.5.4): dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.9.1) - '@typescript-eslint/utils': 8.46.2(eslint@9.9.1)(typescript@5.5.4) + '@typescript-eslint/utils': 8.48.0(eslint@9.9.1)(typescript@5.5.4) enhanced-resolve: 5.18.3 eslint: 9.9.1 eslint-plugin-es-x: 7.8.0(eslint@9.9.1) @@ -4245,7 +4244,7 @@ snapshots: dependencies: estraverse: 5.3.0 - esrap@2.1.0: + esrap@2.2.0: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 From a6fb5d967d97bd8ec7a6c353bf1d5cb731c966f5 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 25 Nov 2025 19:23:33 -0500 Subject: [PATCH 90/91] remove abstract keyword --- .../src/compiler/phases/1-parse/remove_typescript_nodes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js b/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js index cb498c3c131a..2025f3bc8f8b 100644 --- a/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js +++ b/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js @@ -132,6 +132,7 @@ const visitors = { if (node.declare) { return b.empty; } + delete node.abstract; delete node.implements; return context.next(); }, From 7e0ee0cdb062c2a51bdf10b6d475bbae39308cda Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 25 Nov 2025 20:10:10 -0500 Subject: [PATCH 91/91] strip out more typescript gubbins --- .../compiler/phases/1-parse/remove_typescript_nodes.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js b/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js index 2025f3bc8f8b..0835c5fc7990 100644 --- a/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js +++ b/packages/svelte/src/compiler/phases/1-parse/remove_typescript_nodes.js @@ -27,6 +27,9 @@ const visitors = { delete n.typeArguments; delete n.returnType; delete n.accessibility; + delete n.readonly; + delete n.definite; + delete n.override; }, Decorator(node) { e.typescript_invalid_feature(node, 'decorators (related TSC proposal is not stage 4 yet)'); @@ -134,6 +137,12 @@ const visitors = { } delete node.abstract; delete node.implements; + delete node.superTypeArguments; + return context.next(); + }, + ClassExpression(node, context) { + delete node.implements; + delete node.superTypeArguments; return context.next(); }, MethodDefinition(node, context) {