Repository: sveltejs/prettier-plugin-svelte
Pull request #147 (2020-09-25 15:45:53)

Support {#key} block
Needed for Svelte >2.28


Patch:
diff --git a/package-lock.json b/package-lock.json
index 824e21a..483011c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4366,9 +4366,9 @@
             }
         },
         "svelte": {
-            "version": "3.22.2",
-            "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.22.2.tgz",
-            "integrity": "sha512-DxumO0+vvHA6NSc2jtVty08I8lFI43q8P2zX6JxZL8J1kqK5NVjad6TRM/twhnWXC+QScnwkZ15O6X1aTsEKTA==",
+            "version": "3.28.0",
+            "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.28.0.tgz",
+            "integrity": "sha512-WJW8wD+aTmU5GUnTUjdhVF35mve2MjylubLgB6fGWoXHpYENdwcwWsWvjMQLayzMynqNH733h1Ck8wJzNR7gdQ==",
             "dev": true
         },
         "symbol-observable": {
diff --git a/package.json b/package.json
index 2dbd27b..bd85786 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,7 @@
         "rollup-plugin-commonjs": "9.2.0",
         "rollup-plugin-node-resolve": "4.0.0",
         "rollup-plugin-typescript": "1.0.0",
-        "svelte": "^3.20.0",
+        "svelte": "^3.28.0",
         "ts-node": "^7.0.1",
         "tslib": "^1.9.3",
         "typescript": "3.2.4"
diff --git a/src/print/index.ts b/src/print/index.ts
index 8de0040..80ec7e3 100644
--- a/src/print/index.ts
+++ b/src/print/index.ts
@@ -177,6 +177,8 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D
                     node.type !== 'Element' ||
                     selfClosingTags.indexOf(node.name) !== -1);
 
+            // Order important: print attributes first
+            const attributes = path.map((childPath) => childPath.call(print), 'attributes');
             let body: Doc;
 
             if (isEmpty) {
@@ -206,7 +208,7 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D
                                           close,
                                       ])
                                     : '',
-                                ...path.map((childPath) => childPath.call(print), 'attributes'),
+                                ...attributes,
                                 options.svelteBracketNewLine
                                     ? dedent(isSelfClosingTag ? line : softline)
                                     : '',
@@ -377,6 +379,18 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D
 
             return group(concat(block));
         }
+        case 'KeyBlock': {
+            const def: Doc[] = [
+                '{#key ',
+                printJS(path, print, 'expression'),
+                '}',
+                printIndentedWithNewlines(path, print),
+            ];
+
+            def.push('{/key}');
+
+            return concat([group(concat(def)), breakParent]);
+        }
         case 'ThenBlock':
         case 'PendingBlock':
         case 'CatchBlock':
@@ -439,7 +453,7 @@ export function print(path: FastPath, options: ParserOptions, print: PrintFn): D
         case 'Comment': {
             /**
              * If there is no sibling node that starts right after us but the parent indicates
-             * that there used to be, that means that node was actually an embedded `<style>` 
+             * that there used to be, that means that node was actually an embedded `<style>`
              * or `<script>` node that was cut out.
              * If so, the comment does not refer to the next line we will see.
              * The `embed` function handles printing the comment in the right place.
@@ -545,7 +559,7 @@ function printChildren(path: FastPath, print: PrintFn): Doc[] {
      */
     function outputChildDoc(childDoc?: Doc, fromNode?: Node) {
         if (!isPreformat) {
-            if ((!childDoc || !fromNode || canBreakBefore(fromNode))) {
+            if (!childDoc || !fromNode || canBreakBefore(fromNode)) {
                 linebreakPossible();
 
                 const lastChild = childDocs[childDocs.length - 1];
diff --git a/src/print/nodes.ts b/src/print/nodes.ts
index dced6e7..38861ef 100644
--- a/src/print/nodes.ts
+++ b/src/print/nodes.ts
@@ -76,6 +76,12 @@ export interface AwaitBlockNode extends BaseNode {
     catch: CatchBlockNode;
 }
 
+export interface KeyBlockNode extends BaseNode {
+    type: 'KeyBlock';
+    expression: Node;
+    children: Node[];
+}
+
 export interface ThenBlockNode extends BaseNode {
     type: 'ThenBlock';
     children: Node[];
@@ -257,6 +263,7 @@ export type Node =
     | ElseBlockNode
     | EachBlockNode
     | AwaitBlockNode
+    | KeyBlockNode
     | ThenBlockNode
     | PendingBlockNode
     | CatchBlockNode


Test patch:
diff --git a/test/formatting/index.ts b/test/formatting/index.ts
index df978bd..10fdbf1 100644
--- a/test/formatting/index.ts
+++ b/test/formatting/index.ts
@@ -2,7 +2,14 @@ import test from 'ava';
 import { readdirSync, readFileSync, existsSync } from 'fs';
 import { format } from 'prettier';
 
-const dirs = readdirSync('test/formatting/samples');
+let dirs = readdirSync('test/formatting/samples');
+const endsWithOnly = (f: string): boolean => f.endsWith('.only');
+const hasOnly = dirs.some(endsWithOnly);
+dirs = !hasOnly ? dirs : dirs.filter(endsWithOnly);
+
+if (process.env.CI && hasOnly) {
+    throw new Error('.only tests present');
+}
 
 for (const dir of dirs) {
     const input = readFileSync(`test/formatting/samples/${dir}/input.html`, 'utf-8').replace(
diff --git a/test/printer/index.ts b/test/printer/index.ts
index ac09bdb..6f38cd9 100644
--- a/test/printer/index.ts
+++ b/test/printer/index.ts
@@ -2,13 +2,20 @@ import test from 'ava';
 import { readdirSync, readFileSync, existsSync } from 'fs';
 import { format } from 'prettier';
 
-const files = readdirSync('test/printer/samples').filter(name => name.endsWith('.html'));
+let files = readdirSync('test/printer/samples').filter((name) => name.endsWith('.html'));
+const endsWithOnly = (f: string): boolean => f.endsWith('.only.html');
+const hasOnly = files.some(endsWithOnly);
+files = !hasOnly ? files : files.filter(endsWithOnly);
+
+if (process.env.CI && hasOnly) {
+    throw new Error('.only tests present');
+}
 
 for (const file of files) {
     const input = readFileSync(`test/printer/samples/${file}`, 'utf-8').replace(/\r?\n/g, '\n');
     const options = readOptions(`test/printer/samples/${file.replace('.html', '.options.json')}`);
 
-    test(`printer: ${file.slice(0, file.length - '.html'.length)}`, t => {
+    test(`printer: ${file.slice(0, file.length - '.html'.length)}`, (t) => {
         const actualOutput = format(input, {
             parser: 'svelte' as any,
             plugins: [require.resolve('../../src')],
diff --git a/test/printer/samples/element-with-class-and-comment-inside.html b/test/printer/samples/element-with-class-and-comment-inside.html
new file mode 100644
index 0000000..7529bb9
--- /dev/null
+++ b/test/printer/samples/element-with-class-and-comment-inside.html
@@ -0,0 +1,5 @@
+<div class="test">
+    <!-- c -->
+</div>
+
+<div class="test"><!-- c --></div>
diff --git a/test/printer/samples/key-block.html b/test/printer/samples/key-block.html
new file mode 100644
index 0000000..b5084ec
--- /dev/null
+++ b/test/printer/samples/key-block.html
@@ -0,0 +1,10 @@
+{#key value}
+    <p>hello</p>
+    <p>hello</p>
+{/key}
+{#key $store}
+    <p>hello</p>
+{/key}
+{#key expr.obj}
+    <p>hello</p>
+{/key}