From 8a59e40b99618c56870c8729d39456829527d630 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Sat, 2 Jan 2021 15:59:41 +0100 Subject: [PATCH] Children print rework (#160) Goals: - switch logic around: prepare nodes before printing docs, try to avoid modifying printed result - respect user's wish to have line breaks: fixes #143, fixes #117, closes #121 - generally try to be more in line with how prettier formats things ### BREAKING CHANGE Tags are broken up differently now than before --- CHANGELOG.md | 4 + src/embed.ts | 4 +- src/print/doc-helpers.ts | 54 +- src/print/helpers.ts | 10 + src/print/index.ts | 598 +++++++++++------- src/print/node-helpers.ts | 237 ++++++- test/formatting/index.ts | 20 +- .../block-element-break-children/input.html | 7 + .../block-element-break-children/output.html | 19 + .../input.html | 1 + .../output.html | 8 + .../block-element-break-long/input.html | 1 + .../block-element-break-long/output.html | 8 + .../block-element-break-subblocks/input.html | 1 + .../block-element-break-subblocks/output.html | 6 + .../input.html | 3 + .../output.html | 5 + .../block-element-with-children-ws/input.html | 15 + .../output.html | 12 + .../input.html | 4 + .../output.html | 10 +- .../inline-element-break-children/input.html | 7 + .../inline-element-break-children/output.html | 19 + .../input.html | 1 + .../output.html | 6 + .../inline-element-break-long/input.html | 1 + .../inline-element-break-long/output.html | 6 + .../input.html | 2 +- .../output.html | 7 +- .../input.html | 8 + .../output.html | 6 + .../input.html | 1 + .../output.html | 6 + .../output.html | 8 +- .../svelte-await-block-break/input.html | 7 + .../svelte-await-block-break/output.html | 24 + .../svelte-each-block-break/input.html | 5 + .../svelte-each-block-break/output.html | 15 + .../samples/svelte-if-block-break/input.html | 5 + .../samples/svelte-if-block-break/output.html | 15 + .../samples/svelte-key-block-break/input.html | 7 + .../svelte-key-block-break/output.html | 17 + .../output.html | 7 +- test/printer/index.ts | 2 +- test/printer/samples/await-inline.html | 2 + ...ment-many-attributes-bracket-new-line.html | 25 + ...y-attributes-bracket-new-line.options.json | 3 + ...-with-several-attributes-and-mustache.html | 7 +- test/printer/samples/empty-elements.html | 16 + .../samples/empty-elements.options.json | 3 + .../printer/samples/inline-blocks-nested.html | 16 + .../samples/inline-element-single-text.html | 8 + .../samples/prettier-ignore-nested.html | 8 +- test/printer/samples/toplevel-blocks.html | 10 + .../samples/toplevel-blocks.options.json | 3 + test/printer/samples/toplevel-blocks2.html | 9 + .../samples/toplevel-blocks2.options.json | 3 + 57 files changed, 1034 insertions(+), 288 deletions(-) create mode 100644 test/formatting/samples/block-element-break-children/input.html create mode 100644 test/formatting/samples/block-element-break-children/output.html create mode 100644 test/formatting/samples/block-element-break-long-whitespace/input.html create mode 100644 test/formatting/samples/block-element-break-long-whitespace/output.html create mode 100644 test/formatting/samples/block-element-break-long/input.html create mode 100644 test/formatting/samples/block-element-break-long/output.html create mode 100644 test/formatting/samples/block-element-break-subblocks/input.html create mode 100644 test/formatting/samples/block-element-break-subblocks/output.html create mode 100644 test/formatting/samples/block-element-with-children-no-ws/input.html create mode 100644 test/formatting/samples/block-element-with-children-no-ws/output.html create mode 100644 test/formatting/samples/block-element-with-children-ws/input.html create mode 100644 test/formatting/samples/block-element-with-children-ws/output.html create mode 100644 test/formatting/samples/inline-element-break-children/input.html create mode 100644 test/formatting/samples/inline-element-break-children/output.html create mode 100644 test/formatting/samples/inline-element-break-long-whitespace/input.html create mode 100644 test/formatting/samples/inline-element-break-long-whitespace/output.html create mode 100644 test/formatting/samples/inline-element-break-long/input.html create mode 100644 test/formatting/samples/inline-element-break-long/output.html create mode 100644 test/formatting/samples/inlineblock-element-break-subblocks/input.html create mode 100644 test/formatting/samples/inlineblock-element-break-subblocks/output.html create mode 100644 test/formatting/samples/svelte-await-block-break/input.html create mode 100644 test/formatting/samples/svelte-await-block-break/output.html create mode 100644 test/formatting/samples/svelte-each-block-break/input.html create mode 100644 test/formatting/samples/svelte-each-block-break/output.html create mode 100644 test/formatting/samples/svelte-if-block-break/input.html create mode 100644 test/formatting/samples/svelte-if-block-break/output.html create mode 100644 test/formatting/samples/svelte-key-block-break/input.html create mode 100644 test/formatting/samples/svelte-key-block-break/output.html create mode 100644 test/printer/samples/element-many-attributes-bracket-new-line.html create mode 100644 test/printer/samples/element-many-attributes-bracket-new-line.options.json create mode 100644 test/printer/samples/empty-elements.html create mode 100644 test/printer/samples/empty-elements.options.json create mode 100644 test/printer/samples/inline-blocks-nested.html create mode 100644 test/printer/samples/inline-element-single-text.html create mode 100644 test/printer/samples/toplevel-blocks.html create mode 100644 test/printer/samples/toplevel-blocks.options.json create mode 100644 test/printer/samples/toplevel-blocks2.html create mode 100644 test/printer/samples/toplevel-blocks2.options.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b202793..afa9df05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # prettier-plugin-svelte changelog +## Unreleased + +* Rework of the tag breaking logic with the goal to be more in line with how Prettier formats HTML. This includes respecting the user's decision to have child tags in seperate lines even if they don't exceed the maximum line width ([#143](https://github.com/sveltejs/prettier-plugin-svelte/issues/143), [#117](https://github.com/sveltejs/prettier-plugin-svelte/issues/117)). This is a breaking change because tags are broken up differently now than before. + ## 1.4.2 * Pass options to embedded parser ([#162](https://github.com/sveltejs/prettier-plugin-svelte/issues/162)) diff --git a/src/embed.ts b/src/embed.ts index db070e01..cb2c3d1a 100644 --- a/src/embed.ts +++ b/src/embed.ts @@ -4,9 +4,9 @@ import { snippedTagContentAttribute } from './lib/snipTagContent'; import { PrintFn } from './print'; import { getAttributeTextValue, - isNodeSupportedLanguage, - isIgnoreDirective, getPreviousNode, + isIgnoreDirective, + isNodeSupportedLanguage, } from './print/node-helpers'; import { Node } from './print/nodes'; diff --git a/src/print/doc-helpers.ts b/src/print/doc-helpers.ts index 662a4609..2e5f7a7d 100644 --- a/src/print/doc-helpers.ts +++ b/src/print/doc-helpers.ts @@ -1,42 +1,42 @@ import { Doc, doc } from 'prettier'; +import { findLastIndex } from './helpers'; -export function isLine(doc: Doc) { - return typeof doc === 'object' && doc.type === 'line' -} - -export function isLineDiscardedIfLonely(doc: Doc) { - return isLine(doc) && !(doc as doc.builders.Line).keepIfLonely +export function isLine(docToCheck: Doc) { + return ( + docToCheck === doc.builders.hardline || + (typeof docToCheck === 'object' && docToCheck.type === 'line') + ); } /** * Check if the doc is empty, i.e. consists of nothing more than empty strings (possibly nested). */ export function isEmptyDoc(doc: Doc): boolean { - if (typeof doc === 'string') { - return doc.length === 0; - } + if (typeof doc === 'string') { + return doc.length === 0; + } - if (doc.type === 'line') { - return !doc.keepIfLonely; - } + if (doc.type === 'line') { + return !doc.keepIfLonely; + } - const { contents } = doc as { contents?: Doc }; + const { contents } = doc as { contents?: Doc }; - if (contents) { - return isEmptyDoc(contents); - } + if (contents) { + return isEmptyDoc(contents); + } - const { parts } = doc as { parts?: Doc[] }; + const { parts } = doc as { parts?: Doc[] }; - if (parts) { - return isEmptyGroup(parts); - } + if (parts) { + return isEmptyGroup(parts); + } - return false; + return false; } export function isEmptyGroup(group: Doc[]): boolean { - return !group.find(doc => !isEmptyDoc(doc)) + return !group.find((doc) => !isEmptyDoc(doc)); } /** @@ -95,13 +95,3 @@ function getParts(doc: Doc): Doc[] | undefined { return doc.parts; } } - -function findLastIndex(isMatch: (item: T) => boolean, items: T[]) { - for (let i = items.length - 1; i >= 0; i--) { - if (isMatch(items[i])) { - return i; - } - } - - return -1; -} diff --git a/src/print/helpers.ts b/src/print/helpers.ts index ec326e40..2cff58e7 100644 --- a/src/print/helpers.ts +++ b/src/print/helpers.ts @@ -23,3 +23,13 @@ export function isPreTagContent(path: FastPath): boolean { export function flatten(arrays: T[][]): T[] { return ([] as T[]).concat.apply([], arrays); } + +export function findLastIndex(isMatch: (item: T, idx: number) => boolean, items: T[]) { + for (let i = items.length - 1; i >= 0; i--) { + if (isMatch(items[i], i)) { + return i; + } + } + + return -1; +} diff --git a/src/print/index.ts b/src/print/index.ts index 8ae032c9..d77abbad 100644 --- a/src/print/index.ts +++ b/src/print/index.ts @@ -1,33 +1,37 @@ -import { FastPath, Doc, doc, ParserOptions } from 'prettier'; -import { Node, IfBlockNode, AttributeNode } from './nodes'; -import { isASTNode, isPreTagContent, flatten } from './helpers'; +import { Doc, doc, FastPath, ParserOptions } from 'prettier'; +import { formattableAttributes, selfClosingTags } from '../lib/elements'; import { extractAttributes } from '../lib/extractAttributes'; import { getText } from '../lib/getText'; -import { parseSortOrder, SortOrderPart } from '../options'; import { hasSnippedContent, unsnipContent } from '../lib/snipTagContent'; -import { selfClosingTags, formattableAttributes } from '../lib/elements'; +import { parseSortOrder, SortOrderPart } from '../options'; +import { isLine, trim } from './doc-helpers'; +import { flatten, isASTNode, isPreTagContent } from './helpers'; import { - canBreakBefore, - canBreakAfter, - isInlineElement, - isInlineNode, + checkWhitespaceAtEndOfSvelteBlock, + checkWhitespaceAtStartOfSvelteBlock, + doesEmbedStartAt, + endsWithLinebreak, + getUnencodedText, + isBlockElement, isEmptyNode, - printRaw, - isNodeSupportedLanguage, + isIgnoreDirective, + isInlineElement, isLoneMustacheTag, + isNodeSupportedLanguage, isOrCanBeConvertedToShorthand, - isIgnoreDirective, - doesEmbedStartAt, - getUnencodedText, + isTextNodeEndingWithLinebreak, + isTextNodeEndingWithWhitespace, + isTextNodeStartingWithLinebreak, + isTextNodeStartingWithWhitespace, + printRaw, + shouldHugEnd, + shouldHugStart, + startsWithLinebreak, + trimChildren, + trimTextNodeLeft, + trimTextNodeRight, } from './node-helpers'; -import { - isLine, - isLineDiscardedIfLonely, - trim, - trimLeft, - trimRight, - isEmptyDoc, -} from './doc-helpers'; +import { AttributeNode, IfBlockNode, Node, TextNode } from './nodes'; const { concat, @@ -57,7 +61,9 @@ declare module 'prettier' { let ignoreNext = false; -const keepIfLonelyLine = { ...line, keepIfLonely: true, hard: true }; +function groupConcat(contents: doc.builders.Doc[]): doc.builders.Doc { + return group(concat(contents)); +} export function print(path: FastPath, options: ParserOptions, print: PrintFn): Doc { const n = path.getValue(); @@ -96,7 +102,7 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D }; parseSortOrder(options.svelteSortOrder).forEach((p) => addParts[p]()); ignoreNext = false; - return group(join(hardline, parts)); + return group(concat([join(hardline, parts)])); } const [open, close] = options.svelteStrictMode ? ['"{', '}"'] : ['{', '}']; @@ -121,34 +127,42 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D if (children.length === 0 || children.every(isEmptyNode)) { return ''; } - if (!isPreTagContent(path)) { - return concat([...trim(printChildren(path, print), isLine), hardline]); + trimChildren(node.children, path); + return group( + concat([ + ...trim( + [printChildren(path, print)], + (n) => + isLine(n) || + (typeof n === 'string' && n.trim() === '') || + // Because printChildren may append this at the end and + // may hide other lines before it + n === breakParent, + ), + hardline, + ]), + ); } else { - return concat(printChildren(path, print)); + return group(concat(path.map(print, 'children'))); } case 'Text': if (!isPreTagContent(path)) { if (isEmptyNode(node)) { - return { - /** - * Empty (whitespace-only) text nodes are collapsed into a single `line`, - * which will be rendered as a single space if this node's group fits on a - * single line. This follows how vanilla HTML is handled both by browsers and - * by Prettier core. - */ - ...line, - - /** - * A text node is considered lonely if it is in a group without other inline - * elements, such as the line breaks between otherwise consecutive HTML tags. - * Text nodes that are both empty and lonely are discarded unless they have at - * least one empty line (i.e. at least two linebreak sequences). This is to - * allow for flexible grouping of HTML tags in a particular indentation level, - * and is similar to how vanilla HTML is handled in Prettier core. - */ - keepIfLonely: /\n\r?\s*\n\r?/.test(getUnencodedText(node)), - }; + const hasWhiteSpace = + getUnencodedText(node).trim().length < getUnencodedText(node).length; + const hasOneOrMoreNewlines = /\n/.test(getUnencodedText(node)); + const hasTwoOrMoreNewlines = /\n\r?\s*\n\r?/.test(getUnencodedText(node)); + if (hasTwoOrMoreNewlines) { + return concat([hardline, hardline]); + } + if (hasOneOrMoreNewlines) { + return hardline; + } + if (hasWhiteSpace) { + return line; + } + return ''; } /** @@ -157,7 +171,7 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D * until this node's current line is out of room, at which `fill` will break at the * most convenient instance of `line`. */ - return fill(splitTextToDocs(getUnencodedText(node))); + return fill(splitTextToDocs(node)); } else { return getUnencodedText(node); } @@ -180,48 +194,145 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D // Order important: print attributes first const attributes = path.map((childPath) => childPath.call(print), 'attributes'); - let body: Doc; + const possibleThisBinding = + node.type === 'InlineComponent' && node.expression + ? concat([line, 'this=', open, printJS(path, print, 'expression'), close]) + : ''; + + if (isSelfClosingTag) { + return group( + concat([ + '<', + node.name, + + indent( + group( + concat([ + possibleThisBinding, + ...attributes, + options.svelteBracketNewLine ? dedent(line) : '', + ]), + ), + ), + + ...[options.svelteBracketNewLine ? '' : ' ', `/>`], + ]), + ); + } + + const children = node.children; + const firstChild = children[0]; + const lastChild = children[children.length - 1]; + + // Is a function which is invoked later because printChildren will manipulate child nodes + // which would wrongfully change the other checks about hugging etc done beforehand + let body: () => Doc; + + let hugContent = false; + const hugStart = shouldHugStart(node, isSupportedLanguage); + const hugEnd = shouldHugEnd(node, isSupportedLanguage); if (isEmpty) { - body = ''; + body = + isInlineElement(node) && + node.children.length && + isTextNodeStartingWithWhitespace(node.children[0]) && + !isPreTagContent(path) + ? () => line + : () => softline; + } else if (isPreTagContent(path)) { + body = () => printRaw(node, options.originalText); } else if (!isSupportedLanguage) { - body = printRaw(node, options.originalText); - } else if (isInlineElement(node) || isPreTagContent(path)) { - body = printIndentedPreservingWhitespace(path, print); + body = () => printRaw(node, options.originalText); + hugContent = true; + } else if (isInlineElement(node) && !isPreTagContent(path)) { + body = () => printChildren(path, print); + hugContent = true; } else { - body = printIndentedWithNewlines(path, print); + body = () => printChildren(path, print); } - return group( - concat([ - '<', - node.name, + const openingTag = [ + '<', + node.name, - indent( - group( - concat([ - node.type === 'InlineComponent' && node.expression - ? concat([ - line, - 'this=', - open, - printJS(path, print, 'expression'), - close, - ]) - : '', - ...attributes, - options.svelteBracketNewLine - ? dedent(isSelfClosingTag ? line : softline) - : '', - ]), - ), + indent( + group( + concat([ + possibleThisBinding, + ...attributes, + hugContent + ? '' + : options.svelteBracketNewLine && !isPreTagContent(path) + ? dedent(softline) + : '', + ]), ), + ), + ]; - ...(isSelfClosingTag - ? [options.svelteBracketNewLine ? '' : ' ', `/>`] - : ['>', body, ``]), - ]), - ); + if (hugStart && hugEnd) { + return groupConcat([ + ...openingTag, + group(indent(concat([softline, groupConcat(['>', body(), `', + ]); + } + + if (hugStart) { + return groupConcat([ + ...openingTag, + group(indent(concat([softline, groupConcat(['>', body()])]))), + softline, + ``, + ]); + } + + if (hugEnd) { + return groupConcat([ + ...openingTag, + '>', + group(indent(concat([softline, groupConcat([body(), `', + ]); + } + + if (isEmpty) { + return groupConcat([...openingTag, '>', body(), ``]); + } + + // No hugging of content means it's either a block element and/or there's whitespace at the start/end + let separatorStart: Doc = softline; + let separatorEnd: Doc = softline; + if (isPreTagContent(path)) { + separatorStart = ''; + separatorEnd = ''; + } else { + if (firstChild && firstChild.type === 'Text') { + if (isTextNodeStartingWithLinebreak(firstChild) && firstChild !== lastChild) { + separatorStart = hardline; + separatorEnd = hardline; + } else if (isInlineElement(node)) { + separatorStart = line; + } + trimTextNodeLeft(firstChild); + } + if (lastChild && lastChild.type === 'Text') { + if (isInlineElement(node)) { + separatorEnd = line; + } + trimTextNodeRight(lastChild); + } + } + + return groupConcat([ + ...openingTag, + '>', + groupConcat([indent(concat([separatorStart, body()])), separatorEnd]), + ``, + ]); } case 'Options': case 'Body': @@ -240,7 +351,7 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D case 'Identifier': return node.name; case 'AttributeShorthand': { - return node.expression.name; + return (node.expression as any).name; } case 'Attribute': { if (isOrCanBeConvertedToShorthand(node)) { @@ -272,7 +383,7 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D '{#if ', printJS(path, print, 'expression'), '}', - printIndentedWithNewlines(path, print), + printSvelteBlockChildren(path, print, options), ]; if (node.else) { @@ -295,18 +406,21 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D const ifNode = node.children[0] as IfBlockNode; const def: Doc[] = [ '{:else if ', - path.map((ifPath) => printJS(path, print, 'expression'), 'children')[0], + path.map((ifPath) => printJS(ifPath, print, 'expression'), 'children')[0], '}', - path.map((ifPath) => printIndentedWithNewlines(ifPath, print), 'children')[0], + path.map( + (ifPath) => printSvelteBlockChildren(ifPath, print, options), + 'children', + )[0], ]; if (ifNode.else) { def.push(path.map((ifPath) => ifPath.call(print, 'else'), 'children')[0]); } - return group(concat(def)); + return concat(def); } - return group(concat(['{:else}', printIndentedWithNewlines(path, print)])); + return concat(['{:else}', printSvelteBlockChildren(path, print, options)]); } case 'EachBlock': { const def: Doc[] = [ @@ -324,7 +438,7 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D def.push(' (', printJS(path, print, 'key'), ')'); } - def.push('}', printIndentedWithNewlines(path, print)); + def.push('}', printSvelteBlockChildren(path, print, options)); if (node.else) { def.push(path.call(print, 'else')); @@ -352,19 +466,19 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D '}', ]), ), - indent(path.call(print, 'then')), + path.call(print, 'then'), ); } else { block.push(group(concat(['{#await ', printJS(path, print, 'expression'), '}']))); if (hasPendingBlock) { - block.push(indent(path.call(print, 'pending'))); + block.push(path.call(print, 'pending')); } if (hasThenBlock) { block.push( group(concat(['{:then', expandNode(node.value), '}'])), - indent(path.call(print, 'then')), + path.call(print, 'then'), ); } } @@ -372,7 +486,7 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D if (hasCatchBlock) { block.push( group(concat(['{:catch', expandNode(node.error), '}'])), - indent(path.call(print, 'catch')), + path.call(print, 'catch'), ); } @@ -385,7 +499,7 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D '{#key ', printJS(path, print, 'expression'), '}', - printIndentedWithNewlines(path, print), + printSvelteBlockChildren(path, print, options), ]; def.push('{/key}'); @@ -395,11 +509,7 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D case 'ThenBlock': case 'PendingBlock': case 'CatchBlock': - return concat([ - softline, - ...trim(printChildren(path, print), isLine), - dedent(softline), - ]); + return printSvelteBlockChildren(path, print, options); case 'EventHandler': return concat([ line, @@ -530,174 +640,210 @@ function printAttributeNodeValue( } } -function printChildren(path: FastPath, print: PrintFn): Doc[] { - let childDocs: Doc[] = []; - let currentGroup: { doc: Doc; node: Node }[] = []; - // the index of the last child doc we could add a linebreak after - let lastBreakIndex = -1; +function printSvelteBlockChildren(path: FastPath, print: PrintFn, options: ParserOptions): Doc { + const node = path.getValue(); + const children = node.children; + if (!children || children.length === 0) { + return ''; + } - const isPreformat = isPreTagContent(path); + const whitespaceAtStartOfBlock = checkWhitespaceAtStartOfSvelteBlock(node, options); + const whitespaceAtEndOfBlock = checkWhitespaceAtEndOfSvelteBlock(node, options); + const startline = + whitespaceAtStartOfBlock === 'none' + ? '' + : whitespaceAtEndOfBlock === 'line' || whitespaceAtStartOfBlock === 'line' + ? hardline + : line; + const endline = + whitespaceAtEndOfBlock === 'none' + ? '' + : whitespaceAtEndOfBlock === 'line' || whitespaceAtStartOfBlock === 'line' + ? hardline + : line; + + const firstChild = children[0]; + const lastChild = children[children.length - 1]; + if (isTextNodeStartingWithWhitespace(firstChild)) { + trimTextNodeLeft(firstChild); + } + if (isTextNodeEndingWithWhitespace(lastChild)) { + trimTextNodeRight(lastChild); + } - /** - * Call when reaching a point where a linebreak is possible. Will - * put all `childDocs` since the last possible linebreak position - * into a `concat` to avoid them breaking. - */ - function linebreakPossible() { - if (lastBreakIndex >= 0 && lastBreakIndex < childDocs.length - 1) { - childDocs = childDocs - .slice(0, lastBreakIndex) - .concat(concat(childDocs.slice(lastBreakIndex))); - } + return concat([indent(concat([startline, group(printChildren(path, print))])), endline]); +} - lastBreakIndex = -1; +function printChildren(path: FastPath, print: PrintFn): Doc { + if (isPreTagContent(path)) { + return concat(path.map(print, 'children')); } - /** - * Add a document to the output. - * @param childDoc undefined means do not add anything but allow for the possibility of a linebreak here. - * @param fromNode the Node the doc was generated from. undefined if childDoc is undefined. - */ - function outputChildDoc(childDoc?: Doc, fromNode?: Node) { - if (!isPreformat) { - if (!childDoc || !fromNode || canBreakBefore(fromNode)) { - linebreakPossible(); - - const lastChild = childDocs[childDocs.length - 1]; - - // separate children by softlines, but not if the children are already lines. - // one exception: allow for a line break before "keepIfLonely" lines because they represent an empty line - if ( - childDoc != null && - !isLineDiscardedIfLonely(childDoc) && - lastChild != null && - !isLine(lastChild) - ) { - childDocs.push(softline); - } - } + const childNodes: Node[] = path + .getValue() + .children.filter((child: Node) => child.type !== 'Text' || getUnencodedText(child) !== ''); + // modifiy original array because it's accessed later through map(print, 'children', idx) + path.getValue().children = childNodes; + if (childNodes.length === 0) { + return ''; + } - if (lastBreakIndex < 0 && childDoc && fromNode && !canBreakAfter(fromNode)) { - lastBreakIndex = childDocs.length; - } + const childDocs: Doc[] = []; + let handleWhitespaceOfPrevTextNode = false; + + for (let i = 0; i < childNodes.length; i++) { + const childNode = childNodes[i]; + if (childNode.type === 'Text') { + handleTextChild(i, childNode); + } else if (isBlockElement(path, childNode)) { + handleBlockChild(i); + } else if (isInlineElement(childNode)) { + handleInlineChild(i); + } else { + childDocs.push(printChild(i)); + handleWhitespaceOfPrevTextNode = false; } + } - if (childDoc) { - childDocs.push(childDoc); - } + // If there's at least one block element and more than one node, break content + const forceBreakContent = + childNodes.length > 1 && childNodes.some((child) => isBlockElement(path, child)); + if (forceBreakContent) { + childDocs.push(breakParent); } - function lastChildDocProduced() { - // line breaks are ok after last child - outputChildDoc(); + return concat(childDocs); + + function printChild(idx: number): Doc { + return path.call(print, 'children', idx); } /** - * Sequences of inline nodes (currently, `TextNode`s and `MustacheTag`s) are collected into - * groups and printed as a single `Fill` doc so that linebreaks as a result of sibling block - * nodes (currently, all HTML elements) don't cause those inline sequences to break - * prematurely. This is particularly important for whitespace sensitivity, as it is often - * desired to have text directly wrapping a mustache tag without additional whitespace. + * Print inline child. Hug whitespace of previous text child if there was one. */ - function flush() { - for (let { doc, node } of currentGroup) { - for (const childDoc of extractOutermostNewlines(doc)) { - outputChildDoc(childDoc, node); - } + function handleInlineChild(idx: number) { + if (handleWhitespaceOfPrevTextNode) { + childDocs.push(groupConcat([line, printChild(idx)])); + } else { + childDocs.push(printChild(idx)); } - - currentGroup = []; + handleWhitespaceOfPrevTextNode = false; } - path.each((childPath) => { - const childNode = childPath.getValue() as Node; - const childDoc = childPath.call(print); + /** + * Print block element. Add softlines around it if needed + * so it breaks into a separate line if children are broken up. + * Don't add lines at the start/end if it's the first/last child because this + * kind of whitespace handling is done in the parent already. + */ + function handleBlockChild(idx: number) { + const prevChild = childNodes[idx - 1]; + if ( + prevChild && + !isBlockElement(path, prevChild) && + (prevChild.type !== 'Text' || + handleWhitespaceOfPrevTextNode || + !isTextNodeEndingWithWhitespace(prevChild)) + ) { + childDocs.push(softline); + } - if (isInlineNode(childNode)) { - currentGroup.push({ doc: childDoc, node: childNode }); - } else { - flush(); + childDocs.push(printChild(idx)); + + const nextChild = childNodes[idx + 1]; + if ( + nextChild && + (nextChild.type !== 'Text' || + // Only handle text which starts with a whitespace and has text afterwards, + // or is empty but followed by an inline element. The latter is done + // so that if the children break, the inline element afterwards is in a seperate line. + ((!isEmptyNode(nextChild) || + (childNodes[idx + 2] && isInlineElement(childNodes[idx + 2]))) && + !isTextNodeStartingWithLinebreak(nextChild))) + ) { + childDocs.push(softline); + } + handleWhitespaceOfPrevTextNode = false; + } - if (childDoc !== '') { - outputChildDoc( - isLine(childDoc) ? childDoc : concat([breakParent, childDoc]), - childNode, - ); - } + /** + * Print text child. First/last child white space handling + * is done in parent already. By defintion of the Svelte AST, + * a text node always is inbetween other tags. Add hardlines + * if the users wants to have them inbetween. + * If the text is trimmed right, toggle flag telling + * subsequent (inline)block element to alter its printing logic + * to check if they need to hug or print lines themselves. + */ + function handleTextChild(idx: number, childNode: TextNode) { + handleWhitespaceOfPrevTextNode = false; + + if (idx === 0 || idx === childNodes.length - 1) { + childDocs.push(printChild(idx)); + return; } - }, 'children'); - flush(); - lastChildDocProduced(); + const prevNode = childNodes[idx - 1]; + const nextNode = childNodes[idx + 1]; + + if ( + isTextNodeStartingWithWhitespace(childNode) && + // If node is empty, go straight through to checking the right end + !isEmptyNode(childNode) + ) { + if (isInlineElement(prevNode) && !isTextNodeStartingWithLinebreak(childNode)) { + trimTextNodeLeft(childNode); + const lastChildDoc = childDocs.pop()!; + childDocs.push(groupConcat([lastChildDoc, line])); + } - return childDocs; -} + if (isBlockElement(path, prevNode) && !isTextNodeStartingWithLinebreak(childNode)) { + trimTextNodeLeft(childNode); + } + } -/** - * Print the nodes in `path` indented and with leading and trailing newlines. - */ -function printIndentedWithNewlines(path: FastPath, print: PrintFn): Doc { - return indent( - concat([softline, ...trim(printChildren(path, print), isLine), dedent(softline)]), - ); -} + if (isTextNodeEndingWithWhitespace(childNode)) { + if (isInlineElement(nextNode) && !isTextNodeEndingWithLinebreak(childNode)) { + handleWhitespaceOfPrevTextNode = !prevNode || !isBlockElement(path, prevNode); + trimTextNodeRight(childNode); + } + if (isBlockElement(path, nextNode) && !isTextNodeEndingWithLinebreak(childNode, 2)) { + handleWhitespaceOfPrevTextNode = !prevNode || !isBlockElement(path, prevNode); + trimTextNodeRight(childNode); + } + } -/** - * Print the nodes in `path` indented but without adding any leading or trailing newlines. - */ -function printIndentedPreservingWhitespace(path: FastPath, print: PrintFn) { - return indent(concat(dedentFinalNewline(printChildren(path, print)))); + childDocs.push(printChild(idx)); + } } /** * Split the text into words separated by whitespace. Replace the whitespaces by lines, * collapsing multiple whitespaces into a single line. * - * If the text starts or ends with multiple newlines, those newlines should be "keepIfLonely" - * since we want double newlines in the output. + * If the text starts or ends with multiple newlines, two of those should be kept. */ -function splitTextToDocs(text: string): Doc[] { +function splitTextToDocs(node: TextNode): Doc[] { + const text = getUnencodedText(node); let docs: Doc[] = text.split(/[\t\n\f\r ]+/); docs = join(line, docs).parts.filter((s) => s !== ''); - // if the text starts with two newlines, the first doc is already a newline. make it "keepIfLonely" - if (text.match(/^([\t\f\r ]*\n){2}/)) { - docs[0] = keepIfLonelyLine; + if (startsWithLinebreak(text)) { + docs[0] = hardline; } - - // if the text ends with two newlines, the last doc is already a newline. make it "keepIfLonely" - if (text.match(/(\n[\t\f\r ]*){2}$/)) { - docs[docs.length - 1] = keepIfLonelyLine; + if (startsWithLinebreak(text, 2)) { + docs = [hardline, ...docs]; } - return docs; -} - -/** - * If there is a trailing newline, pull it out and put it inside a `dedent`. This is used - * when we want to preserve whitespace, but still indent the newline if there is one - * (e.g. for `1\n` the `` will be on its own line; for `1` it can't - * because it would introduce new whitespace) - */ -function dedentFinalNewline(docs: Doc[]): Doc[] { - const trimmedRight = trimRight(docs, isLine); - - if (trimmedRight) { - return [...docs, dedent(trimmedRight[trimmedRight.length - 1])]; - } else { - return docs; + if (endsWithLinebreak(text)) { + docs[docs.length - 1] = hardline; + } + if (endsWithLinebreak(text, 2)) { + docs = [...docs, hardline]; } -} - -/** - * Pull out any nested leading or trailing lines and put them at the top level. - */ -function extractOutermostNewlines(doc: Doc): Doc[] { - const leadingLines: Doc[] = trimLeft([doc], isLine) || []; - const trailingLines: Doc[] = trimRight([doc], isLine) || []; - return [...leadingLines, ...(!isEmptyDoc(doc) ? [doc] : ([] as Doc[])), ...trailingLines]; + return docs; } function printJS(path: FastPath, print: PrintFn, name?: string) { @@ -710,7 +856,7 @@ function printJS(path: FastPath, print: PrintFn, name?: string) { return path.call(print, name); } -function expandNode(node): string { +function expandNode(node: any): string { if (node === null) { return ''; } diff --git a/src/print/node-helpers.ts b/src/print/node-helpers.ts index 7252dae3..3733fcf7 100644 --- a/src/print/node-helpers.ts +++ b/src/print/node-helpers.ts @@ -10,10 +10,18 @@ import { SlotNode, TitleNode, WindowNode, + IfBlockNode, + AwaitBlockNode, + CatchBlockNode, + EachBlockNode, + ElseBlockNode, + KeyBlockNode, + PendingBlockNode, + ThenBlockNode, } from './nodes'; import { inlineElements, TagName } from '../lib/elements'; -import { FastPath } from 'prettier'; -import { isASTNode } from './helpers'; +import { FastPath, ParserOptions } from 'prettier'; +import { findLastIndex, isASTNode, isPreTagContent } from './helpers'; const unsupportedLanguages = ['coffee', 'coffeescript', 'pug', 'styl', 'stylus', 'sass']; @@ -21,6 +29,34 @@ export function isInlineElement(node: Node) { return node.type === 'Element' && inlineElements.includes(node.name as TagName); } +export function isBlockElement(path: FastPath, node: Node): node is ElementNode { + // TODO switch to a list of tags instead + return node && node.type === 'Element' && !isInlineElement(node) && !isPreTagContent(path); +} + +export function isSvelteBlock( + node: Node, +): node is + | IfBlockNode + | AwaitBlockNode + | CatchBlockNode + | EachBlockNode + | ElseBlockNode + | KeyBlockNode + | PendingBlockNode + | ThenBlockNode { + return [ + 'IfBlock', + 'AwaitBlock', + 'CatchBlock', + 'EachBlock', + 'ElseBlock', + 'KeyBlock', + 'PendingBlock', + 'ThenBlock', + ].includes(node.type); +} + export function isWhitespaceChar(ch: string) { return ' \t\n\r'.indexOf(ch) >= 0; } @@ -106,7 +142,7 @@ export function doesEmbedStartAt(position: number, path: FastPath) { return embeds.find((n) => n && n.start === position) != null; } -export function isEmptyNode(node: Node): boolean { +export function isEmptyNode(node: Node): node is TextNode { return node.type === 'Text' && getUnencodedText(node).trim() === ''; } @@ -204,3 +240,198 @@ export function getUnencodedText(node: TextNode) { // `raw` will contain HTML entities in unencoded form return node.raw || node.data; } + +export function isTextNodeStartingWithLinebreak(node: Node, nrLines = 1): node is TextNode { + return node.type === 'Text' && startsWithLinebreak(getUnencodedText(node), nrLines); +} + +export function startsWithLinebreak(text: string, nrLines = 1): boolean { + return new RegExp(`^([\\t\\f\\r ]*\\n){${nrLines}}`).test(text); +} + +export function isTextNodeEndingWithLinebreak(node: Node, nrLines = 1): node is TextNode { + return node.type === 'Text' && endsWithLinebreak(getUnencodedText(node), nrLines); +} + +export function endsWithLinebreak(text: string, nrLines = 1): boolean { + return new RegExp(`(\\n[\\t\\f\\r ]*){${nrLines}}$`).test(text); +} + +export function isTextNodeStartingWithWhitespace(node: Node): node is TextNode { + return node.type === 'Text' && /^\s/.test(getUnencodedText(node)); +} + +export function isTextNodeEndingWithWhitespace(node: Node): node is TextNode { + return node.type === 'Text' && /\s$/.test(getUnencodedText(node)); +} + +export function trimTextNodeRight(node: TextNode): void { + node.raw = node.raw && node.raw.trimRight(); + node.data = node.data && node.data.trimRight(); +} + +export function trimTextNodeLeft(node: TextNode): void { + node.raw = node.raw && node.raw.trimLeft(); + node.data = node.data && node.data.trimLeft(); +} + +/** + * Remove all leading whitespace up until the first non-empty text node, + * and all trailing whitepsace from the last non-empty text node onwards. + */ +export function trimChildren(children: Node[], path: FastPath): void { + let firstNonEmptyNode = children.findIndex( + (n) => !isEmptyNode(n) && !doesEmbedStartAt(n.end, path), + ); + firstNonEmptyNode = firstNonEmptyNode === -1 ? children.length - 1 : firstNonEmptyNode; + + let lastNonEmptyNode = findLastIndex((n, idx) => { + // Last node is ok to end and the start of an embeded region, + // if it's not a comment (which should stick to the region) + return ( + !isEmptyNode(n) && + ((idx === children.length - 1 && n.type !== 'Comment') || + !doesEmbedStartAt(n.end, path)) + ); + }, children); + lastNonEmptyNode = lastNonEmptyNode === -1 ? 0 : lastNonEmptyNode; + + for (let i = 0; i <= firstNonEmptyNode; i++) { + const n = children[i]; + if (n.type === 'Text') { + trimTextNodeLeft(n); + } + } + + for (let i = children.length - 1; i >= lastNonEmptyNode; i--) { + const n = children[i]; + if (n.type === 'Text') { + trimTextNodeRight(n); + } + } +} + +/** + * Check if given node's starg tag should hug its first child. This is the case for inline elements when there's + * no whitespace between the `>` and the first child. + */ +export function shouldHugStart(node: Node, isSupportedLanguage: boolean): boolean { + if (!isSupportedLanguage) { + return true; + } + + if (!isInlineElement(node) && !isSvelteBlock(node)) { + return false; + } + + if (!isNodeWithChildren(node)) { + return false; + } + + const children: Node[] = node.children; + if (children.length === 0) { + return true; + } + + const firstChild = children[0]; + return !isTextNodeStartingWithWhitespace(firstChild); +} + +/** + * Check if given node's end tag should hug its last child. This is the case for inline elements when there's + * no whitespace between the last child and the ` 0 && firstChild.start > parentOpeningEnd + 1) { + const textBetween = options.originalText.substring(parentOpeningEnd + 1, firstChild.start); + if (textBetween.trim() === '') { + return startsWithLinebreak(textBetween) ? 'line' : 'space'; + } + } + + return 'none'; +} + +/** + * Check for a svelte block if there's whitespace at the end and if it's a space or a line. + */ +export function checkWhitespaceAtEndOfSvelteBlock( + node: Node, + options: ParserOptions, +): 'none' | 'space' | 'line' { + if (!isSvelteBlock(node) || !isNodeWithChildren(node)) { + return 'none'; + } + + const children: Node[] = node.children; + if (children.length === 0) { + return 'none'; + } + + const lastChild = children[children.length - 1]; + if (isTextNodeEndingWithLinebreak(lastChild)) { + return 'line'; + } else if (isTextNodeEndingWithWhitespace(lastChild)) { + return 'space'; + } + + // This extra check is necessary because the Svelte AST might swallow whitespace between + // the last child and the block's ending start. + const parentClosingStart = options.originalText.indexOf('{', lastChild.end); + if (parentClosingStart > 0 && lastChild.end < parentClosingStart) { + const textBetween = options.originalText.substring(lastChild.end, parentClosingStart); + if (textBetween.trim() === '') { + return endsWithLinebreak(textBetween) ? 'line' : 'space'; + } + } + + return 'none'; +} diff --git a/test/formatting/index.ts b/test/formatting/index.ts index 10fdbf19..e07d4ff6 100644 --- a/test/formatting/index.ts +++ b/test/formatting/index.ts @@ -37,7 +37,25 @@ for (const dir of dirs) { ...options, }); - t.is(expectedOutput, actualOutput); + t.is( + expectedOutput, + actualOutput, + `Expected:\n${expectedOutput}\n\nActual:\n${actualOutput}`, + ); + + // Reprint to check that another format outputs the same code + const actualOutput2 = format(actualOutput, { + parser: 'svelte' as any, + plugins: [require.resolve('../../src')], + tabWidth: 4, + ...options, + }); + + t.is( + expectedOutput, + actualOutput2, + `Reprint failed. Expected:\n${expectedOutput}\n\nActual:\n${actualOutput2}`, + ); } finally { if (onTestCompleted) { onTestCompleted(); diff --git a/test/formatting/samples/block-element-break-children/input.html b/test/formatting/samples/block-element-break-children/input.html new file mode 100644 index 00000000..55e607ee --- /dev/null +++ b/test/formatting/samples/block-element-break-children/input.html @@ -0,0 +1,7 @@ +
asd
asd
+ +
asd
asd
+ +
asd
asd
+ +
asd
asd
diff --git a/test/formatting/samples/block-element-break-children/output.html b/test/formatting/samples/block-element-break-children/output.html new file mode 100644 index 00000000..3c701e69 --- /dev/null +++ b/test/formatting/samples/block-element-break-children/output.html @@ -0,0 +1,19 @@ +
+
asd
+ asd +
+ +
+ asd +
asd
+
+ +
+
asd
+ asd +
+ +
+ asd +
asd
+
diff --git a/test/formatting/samples/block-element-break-long-whitespace/input.html b/test/formatting/samples/block-element-break-long-whitespace/input.html new file mode 100644 index 00000000..ff4d1268 --- /dev/null +++ b/test/formatting/samples/block-element-break-long-whitespace/input.html @@ -0,0 +1 @@ +
looooooooooong
looooooooooonglooooooooooonglooooooooooonglooooooooooonglooooooooooong

hi

hi

diff --git a/test/formatting/samples/block-element-break-long-whitespace/output.html b/test/formatting/samples/block-element-break-long-whitespace/output.html new file mode 100644 index 00000000..9023503c --- /dev/null +++ b/test/formatting/samples/block-element-break-long-whitespace/output.html @@ -0,0 +1,8 @@ +
+
looooooooooong
+ looooooooooonglooooooooooonglooooooooooonglooooooooooonglooooooooooong +
+

hi

+

hi

+
+
diff --git a/test/formatting/samples/block-element-break-long/input.html b/test/formatting/samples/block-element-break-long/input.html new file mode 100644 index 00000000..f4d97577 --- /dev/null +++ b/test/formatting/samples/block-element-break-long/input.html @@ -0,0 +1 @@ +
looooooooooong
looooooooooonglooooooooooonglooooooooooonglooooooooooonglooooooooooong

hi

hi

diff --git a/test/formatting/samples/block-element-break-long/output.html b/test/formatting/samples/block-element-break-long/output.html new file mode 100644 index 00000000..9023503c --- /dev/null +++ b/test/formatting/samples/block-element-break-long/output.html @@ -0,0 +1,8 @@ +
+
looooooooooong
+ looooooooooonglooooooooooonglooooooooooonglooooooooooonglooooooooooong +
+

hi

+

hi

+
+
diff --git a/test/formatting/samples/block-element-break-subblocks/input.html b/test/formatting/samples/block-element-break-subblocks/input.html new file mode 100644 index 00000000..426dc159 --- /dev/null +++ b/test/formatting/samples/block-element-break-subblocks/input.html @@ -0,0 +1 @@ +
hi
hihi

hi

hihihi
\ No newline at end of file diff --git a/test/formatting/samples/block-element-break-subblocks/output.html b/test/formatting/samples/block-element-break-subblocks/output.html new file mode 100644 index 00000000..847cacbe --- /dev/null +++ b/test/formatting/samples/block-element-break-subblocks/output.html @@ -0,0 +1,6 @@ +
+
hi
+ hihi +

hi

+ hihihi +
diff --git a/test/formatting/samples/block-element-with-children-no-ws/input.html b/test/formatting/samples/block-element-with-children-no-ws/input.html new file mode 100644 index 00000000..e4b5bda9 --- /dev/null +++ b/test/formatting/samples/block-element-with-children-no-ws/input.html @@ -0,0 +1,3 @@ +
Foo + +
\ No newline at end of file diff --git a/test/formatting/samples/block-element-with-children-no-ws/output.html b/test/formatting/samples/block-element-with-children-no-ws/output.html new file mode 100644 index 00000000..6e4b893b --- /dev/null +++ b/test/formatting/samples/block-element-with-children-no-ws/output.html @@ -0,0 +1,5 @@ +
+ Foo + + +
diff --git a/test/formatting/samples/block-element-with-children-ws/input.html b/test/formatting/samples/block-element-with-children-ws/input.html new file mode 100644 index 00000000..d450b1e7 --- /dev/null +++ b/test/formatting/samples/block-element-with-children-ws/input.html @@ -0,0 +1,15 @@ +
+ + Foo + + + +
+ +
+ Foo + + + + +
\ No newline at end of file diff --git a/test/formatting/samples/block-element-with-children-ws/output.html b/test/formatting/samples/block-element-with-children-ws/output.html new file mode 100644 index 00000000..a109fe2c --- /dev/null +++ b/test/formatting/samples/block-element-with-children-ws/output.html @@ -0,0 +1,12 @@ +
+ Foo + + +
+ +
+ Foo + + + +
diff --git a/test/formatting/samples/do-not-add-whitespace-between-inline-elements/input.html b/test/formatting/samples/do-not-add-whitespace-between-inline-elements/input.html index a9786277..a4efd8dc 100644 --- a/test/formatting/samples/do-not-add-whitespace-between-inline-elements/input.html +++ b/test/formatting/samples/do-not-add-whitespace-between-inline-elements/input.html @@ -1 +1,5 @@ +

OrangeBananasPineapplesGrapefruitKiwi

+ +

asdOrangeBananasPineapplesGrapefruitKiwi

+

ApplesOrangeBananasPineapplesGrapefruitKiwi

diff --git a/test/formatting/samples/do-not-add-whitespace-between-inline-elements/output.html b/test/formatting/samples/do-not-add-whitespace-between-inline-elements/output.html index 190fc54a..11246913 100644 --- a/test/formatting/samples/do-not-add-whitespace-between-inline-elements/output.html +++ b/test/formatting/samples/do-not-add-whitespace-between-inline-elements/output.html @@ -1,3 +1,11 @@ +

OrangeBananasPineapplesGrapefruitKiwi

+

- ApplesOrangeBananasPineapplesGrapefruitKiwi + asdOrangeBananasPineapplesGrapefruitKiwi +

+ +

+ ApplesOrangeBananasPineapplesGrapefruitKiwi

diff --git a/test/formatting/samples/inline-element-break-children/input.html b/test/formatting/samples/inline-element-break-children/input.html new file mode 100644 index 00000000..699a8d28 --- /dev/null +++ b/test/formatting/samples/inline-element-break-children/input.html @@ -0,0 +1,7 @@ +
asd
asd
+ +asd
asd
+ +
asd
asd
+ + asd
asd
diff --git a/test/formatting/samples/inline-element-break-children/output.html b/test/formatting/samples/inline-element-break-children/output.html new file mode 100644 index 00000000..5dbe2217 --- /dev/null +++ b/test/formatting/samples/inline-element-break-children/output.html @@ -0,0 +1,19 @@ +
asd
+ asd
+ +asd +
asd
+ + +
asd
+ asd +
+ + + asd +
asd
+
diff --git a/test/formatting/samples/inline-element-break-long-whitespace/input.html b/test/formatting/samples/inline-element-break-long-whitespace/input.html new file mode 100644 index 00000000..fefb9024 --- /dev/null +++ b/test/formatting/samples/inline-element-break-long-whitespace/input.html @@ -0,0 +1 @@ + looooooooooong looooooooooonglooooooooooonglooooooooooonglooooooooooonglooooooooooong hihi looooooooooonglooooooooooonglooooooooooonglooooooooooong \ No newline at end of file diff --git a/test/formatting/samples/inline-element-break-long-whitespace/output.html b/test/formatting/samples/inline-element-break-long-whitespace/output.html new file mode 100644 index 00000000..b6af6986 --- /dev/null +++ b/test/formatting/samples/inline-element-break-long-whitespace/output.html @@ -0,0 +1,6 @@ + + looooooooooong + looooooooooonglooooooooooonglooooooooooonglooooooooooonglooooooooooong + hihi + looooooooooonglooooooooooonglooooooooooonglooooooooooong + diff --git a/test/formatting/samples/inline-element-break-long/input.html b/test/formatting/samples/inline-element-break-long/input.html new file mode 100644 index 00000000..0adaef74 --- /dev/null +++ b/test/formatting/samples/inline-element-break-long/input.html @@ -0,0 +1 @@ +looooooooooonglooooooooooonglooooooooooonglooooooooooonglooooooooooonglooooooooooonghihi \ No newline at end of file diff --git a/test/formatting/samples/inline-element-break-long/output.html b/test/formatting/samples/inline-element-break-long/output.html new file mode 100644 index 00000000..a4d094a9 --- /dev/null +++ b/test/formatting/samples/inline-element-break-long/output.html @@ -0,0 +1,6 @@ +looooooooooonglooooooooooonglooooooooooonglooooooooooonglooooooooooonglooooooooooonghihi diff --git a/test/formatting/samples/inline-element-with-children-no-ws/input.html b/test/formatting/samples/inline-element-with-children-no-ws/input.html index fb6966df..76191dad 100644 --- a/test/formatting/samples/inline-element-with-children-no-ws/input.html +++ b/test/formatting/samples/inline-element-with-children-no-ws/input.html @@ -1,3 +1,3 @@ Foo - \ No newline at end of file + diff --git a/test/formatting/samples/inline-element-with-children-no-ws/output.html b/test/formatting/samples/inline-element-with-children-no-ws/output.html index 0223be84..d895afff 100644 --- a/test/formatting/samples/inline-element-with-children-no-ws/output.html +++ b/test/formatting/samples/inline-element-with-children-no-ws/output.html @@ -1,4 +1,5 @@ -Foo - +Foo - + diff --git a/test/formatting/samples/inline-element-with-children-ws/input.html b/test/formatting/samples/inline-element-with-children-ws/input.html index 13b0fa72..b33733ba 100644 --- a/test/formatting/samples/inline-element-with-children-ws/input.html +++ b/test/formatting/samples/inline-element-with-children-ws/input.html @@ -4,4 +4,12 @@ + + + + Foo + + + + \ No newline at end of file diff --git a/test/formatting/samples/inline-element-with-children-ws/output.html b/test/formatting/samples/inline-element-with-children-ws/output.html index 2e6a55c1..10ae26bc 100644 --- a/test/formatting/samples/inline-element-with-children-ws/output.html +++ b/test/formatting/samples/inline-element-with-children-ws/output.html @@ -1,3 +1,9 @@ + + Foo + + + + Foo diff --git a/test/formatting/samples/inlineblock-element-break-subblocks/input.html b/test/formatting/samples/inlineblock-element-break-subblocks/input.html new file mode 100644 index 00000000..199ba49b --- /dev/null +++ b/test/formatting/samples/inlineblock-element-break-subblocks/input.html @@ -0,0 +1 @@ +
hi
hihi

hi

hihihi
\ No newline at end of file diff --git a/test/formatting/samples/inlineblock-element-break-subblocks/output.html b/test/formatting/samples/inlineblock-element-break-subblocks/output.html new file mode 100644 index 00000000..22fb047b --- /dev/null +++ b/test/formatting/samples/inlineblock-element-break-subblocks/output.html @@ -0,0 +1,6 @@ +
hi
+ hihi +

hi

+ hihihi
diff --git a/test/formatting/samples/no-html-whitespace-inside-inline-element/output.html b/test/formatting/samples/no-html-whitespace-inside-inline-element/output.html index 90ab3551..f24343ba 100644 --- a/test/formatting/samples/no-html-whitespace-inside-inline-element/output.html +++ b/test/formatting/samples/no-html-whitespace-inside-inline-element/output.html @@ -1,8 +1,4 @@

- Apples, - Orange, - Bananas, - Pineapples, - Grapefruit, - Kiwi + Apples, Orange, Bananas, Pineapples, + Grapefruit, Kiwi

diff --git a/test/formatting/samples/svelte-await-block-break/input.html b/test/formatting/samples/svelte-await-block-break/input.html new file mode 100644 index 00000000..538fdb96 --- /dev/null +++ b/test/formatting/samples/svelte-await-block-break/input.html @@ -0,0 +1,7 @@ +{#await bla}

loooooooooooooooooooooong

{:then blubb}

loooooooooooooooooooooong

{:catch}

loooooooooooooooooooooong

{/await} + +{#await bla}

loooooooooooooooooooooong

{:then blubb}

loooooooooooooooooooooong

{:catch}

loooooooooooooooooooooong

{/await} + +{#await bla}

loooooooooooooooooooooong

loooooooooooooooooooooong

{:then blubb}

loooooooooooooooooooooong

loooooooooooooooooooooong

{:catch}

loooooooooooooooooooooong

loooooooooooooooooooooong

{/await} + +{#await bla then blubb}

loooooooooooooooooooooong

{:catch}

loooooooooooooooooooooong

{/await} \ No newline at end of file diff --git a/test/formatting/samples/svelte-await-block-break/output.html b/test/formatting/samples/svelte-await-block-break/output.html new file mode 100644 index 00000000..0e72d8fa --- /dev/null +++ b/test/formatting/samples/svelte-await-block-break/output.html @@ -0,0 +1,24 @@ +{#await bla}

loooooooooooooooooooooong

{:then blubb}

+ loooooooooooooooooooooong +

{:catch}

loooooooooooooooooooooong

{/await} + +{#await bla} +

loooooooooooooooooooooong

+{:then blubb} +

loooooooooooooooooooooong

+{:catch} +

loooooooooooooooooooooong

+{/await} + +{#await bla}

loooooooooooooooooooooong

+

loooooooooooooooooooooong

{:then blubb}

+ loooooooooooooooooooooong +

+

loooooooooooooooooooooong

{:catch}

loooooooooooooooooooooong

+

loooooooooooooooooooooong

{/await} + +{#await bla then blubb} +

loooooooooooooooooooooong

+{:catch} +

loooooooooooooooooooooong

+{/await} diff --git a/test/formatting/samples/svelte-each-block-break/input.html b/test/formatting/samples/svelte-each-block-break/input.html new file mode 100644 index 00000000..c3bdca7f --- /dev/null +++ b/test/formatting/samples/svelte-each-block-break/input.html @@ -0,0 +1,5 @@ +{#each bla as blubb}

loooooooooooooooooooooooooooooong

{:else}

loooooooooooooooooooooooooooooong

{/each} + +{#each bla as blubb}

loooooooooooooooooooooooooooooong

{:else}

loooooooooooooooooooooooooooooong

{/each} + +{#each bla as blubb}

loooooooooooooooooooooooooooooong

loooooooooooooooooooooooooooooong

{:else}

loooooooooooooooooooooooooooooong

loooooooooooooooooooooooooooooong

{/each} \ No newline at end of file diff --git a/test/formatting/samples/svelte-each-block-break/output.html b/test/formatting/samples/svelte-each-block-break/output.html new file mode 100644 index 00000000..cbc26f2e --- /dev/null +++ b/test/formatting/samples/svelte-each-block-break/output.html @@ -0,0 +1,15 @@ +{#each bla as blubb}

loooooooooooooooooooooooooooooong

{:else}

+ loooooooooooooooooooooooooooooong +

{/each} + +{#each bla as blubb} +

loooooooooooooooooooooooooooooong

+{:else} +

loooooooooooooooooooooooooooooong

+{/each} + +{#each bla as blubb}

loooooooooooooooooooooooooooooong

+

loooooooooooooooooooooooooooooong

{:else}

+ loooooooooooooooooooooooooooooong +

+

loooooooooooooooooooooooooooooong

{/each} diff --git a/test/formatting/samples/svelte-if-block-break/input.html b/test/formatting/samples/svelte-if-block-break/input.html new file mode 100644 index 00000000..06b14442 --- /dev/null +++ b/test/formatting/samples/svelte-if-block-break/input.html @@ -0,0 +1,5 @@ +{#if bla}

loooooooooooooooooooooooooooooong

{:else if blubb}

loooooooooooooooooooooooooooooong

{/if} + +{#if bla}

loooooooooooooooooooooooooooooong

{:else if blubb}

loooooooooooooooooooooooooooooong

{/if} + +{#if bla}

loooooooooooooooooooooooooooooong

loooooooooooooooooooooooooooooong

{:else if blubb}

loooooooooooooooooooooooooooooong

loooooooooooooooooooooooooooooong

{/if} \ No newline at end of file diff --git a/test/formatting/samples/svelte-if-block-break/output.html b/test/formatting/samples/svelte-if-block-break/output.html new file mode 100644 index 00000000..2b92cae2 --- /dev/null +++ b/test/formatting/samples/svelte-if-block-break/output.html @@ -0,0 +1,15 @@ +{#if bla}

loooooooooooooooooooooooooooooong

{:else if blubb}

+ loooooooooooooooooooooooooooooong +

{/if} + +{#if bla} +

loooooooooooooooooooooooooooooong

+{:else if blubb} +

loooooooooooooooooooooooooooooong

+{/if} + +{#if bla}

loooooooooooooooooooooooooooooong

+

loooooooooooooooooooooooooooooong

{:else if blubb}

+ loooooooooooooooooooooooooooooong +

+

loooooooooooooooooooooooooooooong

{/if} diff --git a/test/formatting/samples/svelte-key-block-break/input.html b/test/formatting/samples/svelte-key-block-break/input.html new file mode 100644 index 00000000..e1ff991f --- /dev/null +++ b/test/formatting/samples/svelte-key-block-break/input.html @@ -0,0 +1,7 @@ +{#key bla}

loooooooooooooooooooooooooooooong

loooooooooooooooooooooooooooooong

{/key} + +{#key bla}

loooooooooooooooooooooooooooooong

loooooooooooooooooooooooooooooong

{/key} + +{#key bla} loooooooooooooooooooooooooooooong loooooooooooooooooooooooooooooong {/key} + +{#key bla}

loooooooooooooooooooooooooooooong

loooooooooooooooooooooooooooooong

loooooooooooooooooooooooooooooong

loooooooooooooooooooooooooooooong

{/key} \ No newline at end of file diff --git a/test/formatting/samples/svelte-key-block-break/output.html b/test/formatting/samples/svelte-key-block-break/output.html new file mode 100644 index 00000000..3d12309a --- /dev/null +++ b/test/formatting/samples/svelte-key-block-break/output.html @@ -0,0 +1,17 @@ +{#key bla}

loooooooooooooooooooooooooooooong

+

loooooooooooooooooooooooooooooong

{/key} + +{#key bla} +

loooooooooooooooooooooooooooooong

+

loooooooooooooooooooooooooooooong

+{/key} + +{#key bla} + loooooooooooooooooooooooooooooong + loooooooooooooooooooooooooooooong +{/key} + +{#key bla}

loooooooooooooooooooooooooooooong

+

loooooooooooooooooooooooooooooong

+

loooooooooooooooooooooooooooooong

+

loooooooooooooooooooooooooooooong

{/key} diff --git a/test/formatting/samples/wrap-element-attributes-and-children/output.html b/test/formatting/samples/wrap-element-attributes-and-children/output.html index 9ec7fd93..faeb6983 100644 --- a/test/formatting/samples/wrap-element-attributes-and-children/output.html +++ b/test/formatting/samples/wrap-element-attributes-and-children/output.html @@ -1,4 +1,3 @@ -{linkText} +{linkText} diff --git a/test/printer/index.ts b/test/printer/index.ts index 6f38cd9e..149debad 100644 --- a/test/printer/index.ts +++ b/test/printer/index.ts @@ -23,7 +23,7 @@ for (const file of files) { ...options, } as any); - t.is(input, actualOutput); + t.is(input, actualOutput, `Expected:\n${input}\n\nActual:\n${actualOutput}`); }); } diff --git a/test/printer/samples/await-inline.html b/test/printer/samples/await-inline.html index f654cac7..b182ea7b 100644 --- a/test/printer/samples/await-inline.html +++ b/test/printer/samples/await-inline.html @@ -1 +1,3 @@ {#await promise}loading{:then value}{value}{:catch error}{error.message}{/await} + +{#await p} l {:then v}

{v}

{:catch e} {e.m} {/await} diff --git a/test/printer/samples/element-many-attributes-bracket-new-line.html b/test/printer/samples/element-many-attributes-bracket-new-line.html new file mode 100644 index 00000000..45a445cd --- /dev/null +++ b/test/printer/samples/element-many-attributes-bracket-new-line.html @@ -0,0 +1,25 @@ +
+

asd

+
diff --git a/test/printer/samples/element-many-attributes-bracket-new-line.options.json b/test/printer/samples/element-many-attributes-bracket-new-line.options.json new file mode 100644 index 00000000..c6a7da91 --- /dev/null +++ b/test/printer/samples/element-many-attributes-bracket-new-line.options.json @@ -0,0 +1,3 @@ +{ + "svelteBracketNewLine": true +} diff --git a/test/printer/samples/element-with-several-attributes-and-mustache.html b/test/printer/samples/element-with-several-attributes-and-mustache.html index 9ec7fd93..faeb6983 100644 --- a/test/printer/samples/element-with-several-attributes-and-mustache.html +++ b/test/printer/samples/element-with-several-attributes-and-mustache.html @@ -1,4 +1,3 @@ -{linkText} +{linkText} diff --git a/test/printer/samples/empty-elements.html b/test/printer/samples/empty-elements.html new file mode 100644 index 00000000..1751c7af --- /dev/null +++ b/test/printer/samples/empty-elements.html @@ -0,0 +1,16 @@ + + +
+ + + + + + +
+
diff --git a/test/printer/samples/empty-elements.options.json b/test/printer/samples/empty-elements.options.json new file mode 100644 index 00000000..71492516 --- /dev/null +++ b/test/printer/samples/empty-elements.options.json @@ -0,0 +1,3 @@ +{ + "svelteStrictMode": true +} diff --git a/test/printer/samples/inline-blocks-nested.html b/test/printer/samples/inline-blocks-nested.html new file mode 100644 index 00000000..45d583c7 --- /dev/null +++ b/test/printer/samples/inline-blocks-nested.html @@ -0,0 +1,16 @@ + + + asd1 + + asd2 + + + + + asd1 + + asd2 + asd3 + asd4 + asd5 + diff --git a/test/printer/samples/inline-element-single-text.html b/test/printer/samples/inline-element-single-text.html new file mode 100644 index 00000000..c06c36c5 --- /dev/null +++ b/test/printer/samples/inline-element-single-text.html @@ -0,0 +1,8 @@ + hi +hi + hi +hi + {hi} +{hi} + {hi} +{hi} diff --git a/test/printer/samples/prettier-ignore-nested.html b/test/printer/samples/prettier-ignore-nested.html index 58a833ab..e862ac43 100644 --- a/test/printer/samples/prettier-ignore-nested.html +++ b/test/printer/samples/prettier-ignore-nested.html @@ -1,8 +1,8 @@
- + d + d +
diff --git a/test/printer/samples/toplevel-blocks.html b/test/printer/samples/toplevel-blocks.html new file mode 100644 index 00000000..459ed96c --- /dev/null +++ b/test/printer/samples/toplevel-blocks.html @@ -0,0 +1,10 @@ + + +
+
+ +
+ + diff --git a/test/printer/samples/toplevel-blocks.options.json b/test/printer/samples/toplevel-blocks.options.json new file mode 100644 index 00000000..734075ba --- /dev/null +++ b/test/printer/samples/toplevel-blocks.options.json @@ -0,0 +1,3 @@ +{ + "svelteSortOrder": "scripts-markup-styles" +} diff --git a/test/printer/samples/toplevel-blocks2.html b/test/printer/samples/toplevel-blocks2.html new file mode 100644 index 00000000..d0565d4c --- /dev/null +++ b/test/printer/samples/toplevel-blocks2.html @@ -0,0 +1,9 @@ + + +bla + +bla + + diff --git a/test/printer/samples/toplevel-blocks2.options.json b/test/printer/samples/toplevel-blocks2.options.json new file mode 100644 index 00000000..734075ba --- /dev/null +++ b/test/printer/samples/toplevel-blocks2.options.json @@ -0,0 +1,3 @@ +{ + "svelteSortOrder": "scripts-markup-styles" +}