From 69b70e6421de34bf5163b0a6e200ebf57779f38a Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Thu, 17 Feb 2022 19:32:09 +0900 Subject: [PATCH] [fix]: strip leading newline after `
`

---
 src/compiler/compile/nodes/Text.ts            |  9 +++++++
 .../render_dom/wrappers/Element/index.ts      | 13 ++++++++--
 .../compile/render_dom/wrappers/Fragment.ts   |  9 +++++--
 src/compiler/utils/patterns.ts                |  1 +
 test/runtime/samples/pre-tag/.editorconfig    |  2 ++
 test/runtime/samples/pre-tag/_config.js       | 26 +++++++++----------
 test/runtime/samples/pre-tag/main.svelte      | 22 ++++++++++++++++
 .../samples/preserve-whitespaces/_config.js   | 15 +----------
 .../samples/pre-tag/.editorconfig             |  2 ++
 .../samples/pre-tag/_expected.html            | 13 ++++++++++
 .../samples/pre-tag/main.svelte               | 17 ++++++++++++
 11 files changed, 97 insertions(+), 32 deletions(-)
 create mode 100644 test/runtime/samples/pre-tag/.editorconfig
 create mode 100644 test/server-side-rendering/samples/pre-tag/.editorconfig

diff --git a/src/compiler/compile/nodes/Text.ts b/src/compiler/compile/nodes/Text.ts
index 347beb9c23..1c5a6a7584 100644
--- a/src/compiler/compile/nodes/Text.ts
+++ b/src/compiler/compile/nodes/Text.ts
@@ -49,6 +49,15 @@ export default class Text extends Node {
 		return this.within_pre();
 	}
 
+	/**
+	 * @returns If true, the leading newline character should be stripped.
+	 * @see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
+	 */
+	should_strip_leading_newline(): boolean {
+		const parent = this.parent;
+		return parent.type === 'Element' && parent.name === 'pre' && parent.children[0] === this;
+	}
+
 	within_pre(): boolean {
 		let node = this.parent;
 		while (node) {
diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts
index eb57233ed0..49797314fe 100644
--- a/src/compiler/compile/render_dom/wrappers/Element/index.ts
+++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts
@@ -12,7 +12,7 @@ import { namespaces } from '../../../../utils/namespaces';
 import AttributeWrapper from './Attribute';
 import StyleAttributeWrapper from './StyleAttribute';
 import SpreadAttributeWrapper from './SpreadAttribute';
-import { dimensions } from '../../../../utils/patterns';
+import { dimensions, start_newline } from '../../../../utils/patterns';
 import Binding from './Binding';
 import add_to_set from '../../../utils/add_to_set';
 import { add_event_handler } from '../shared/add_event_handlers';
@@ -939,7 +939,7 @@ export default class ElementWrapper extends Wrapper {
 				if (should_cache) {
 					block.chunks.update.push(b`
 							if (${block.renderer.dirty(dependencies)} && (${cached_snippet} !== (${cached_snippet} = ${snippet}))) {
-								${updater}	
+								${updater}
 							}
 					`);
 				} else {
@@ -1015,6 +1015,15 @@ function to_html(wrappers: Array`.
+					// see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
+					const first = wrapper.fragment.nodes[0];
+					if (first && first.node.type === 'Text' && start_newline.test(first.node.data)) {
+						state.quasi.value.raw += '\n';
+					}
+				}
+
 				to_html(wrapper.fragment.nodes as Array, block, literal, state);
 
 				state.quasi.value.raw += ``;
diff --git a/src/compiler/compile/render_dom/wrappers/Fragment.ts b/src/compiler/compile/render_dom/wrappers/Fragment.ts
index 87ce775ca9..7986e43c83 100644
--- a/src/compiler/compile/render_dom/wrappers/Fragment.ts
+++ b/src/compiler/compile/render_dom/wrappers/Fragment.ts
@@ -21,6 +21,7 @@ import Block from '../Block';
 import { trim_start, trim_end } from '../../../utils/trim';
 import { link } from '../../../utils/link';
 import { Identifier } from 'estree';
+import { start_newline } from '../../../utils/patterns';
 
 const wrappers = {
 	AwaitBlock,
@@ -127,8 +128,12 @@ export default class FragmentWrapper {
 		if (strip_whitespace) {
 			const first = this.nodes[0] as Text;
 
-			if (first && first.node.type === 'Text' && !first.node.keep_space()) {
-				first.data = trim_start(first.data);
+			if (first && first.node.type === 'Text') {
+				if (!first.node.keep_space()) {
+					first.data = trim_start(first.data);
+				} else if (first.node.should_strip_leading_newline()) {
+					first.data = first.data.replace(start_newline, '');
+				}
 				if (!first.data) {
 					first.var = null;
 					this.nodes.shift();
diff --git a/src/compiler/utils/patterns.ts b/src/compiler/utils/patterns.ts
index 0728035a48..071abd791f 100644
--- a/src/compiler/utils/patterns.ts
+++ b/src/compiler/utils/patterns.ts
@@ -1,5 +1,6 @@
 export const whitespace = /[ \t\r\n]/;
 export const start_whitespace = /^[ \t\r\n]*/;
 export const end_whitespace = /[ \t\r\n]*$/;
+export const start_newline = /^\r?\n/;
 
 export const dimensions = /^(?:offset|client)(?:Width|Height)$/;
diff --git a/test/runtime/samples/pre-tag/.editorconfig b/test/runtime/samples/pre-tag/.editorconfig
new file mode 100644
index 0000000000..fa948941d5
--- /dev/null
+++ b/test/runtime/samples/pre-tag/.editorconfig
@@ -0,0 +1,2 @@
+[main.svelte]
+trim_trailing_whitespace = unset
diff --git a/test/runtime/samples/pre-tag/_config.js b/test/runtime/samples/pre-tag/_config.js
index a2e8feb118..c65b9df524 100644
--- a/test/runtime/samples/pre-tag/_config.js
+++ b/test/runtime/samples/pre-tag/_config.js
@@ -6,23 +6,14 @@ export default {
 		const elementDiv = target.querySelector('#div');
 		// Test for 
 tag in non 
 tag
 		const elementDivWithPre = target.querySelector('#div-with-pre');
-
-		// There is a slight difference in innerHTML because there is a difference in HTML optimization (in jsdom)
-		// depending on how the innerHTML is set.
-		// (There is no difference in the display.)
-		// Reassign innerHTML to add the same optimizations to innerHTML.
-
-		// eslint-disable-next-line no-self-assign
-		elementPre.innerHTML = elementPre.innerHTML;
-		// eslint-disable-next-line no-self-assign
-		elementDiv.innerHTML = elementDiv.innerHTML;
-		// eslint-disable-next-line no-self-assign
-		elementDivWithPre.innerHTML = elementDivWithPre.innerHTML;
+		// Test for 
 tag with leading newline
+		const elementPreWithLeadingNewline = target.querySelector('#pre-with-leading-newline');
+		const elementPreWithoutLeadingNewline = target.querySelector('#pre-without-leading-newline');
+		const elementPreWithMultipleLeadingNewline = target.querySelector('#pre-with-multiple-leading-newlines');
 
 		assert.equal(
 			elementPre.innerHTML,
-			`
-  A
+			`  A
   B
   
     C
@@ -53,5 +44,12 @@ export default {
     F
   
` ); + assert.equal(elementPreWithLeadingNewline.children[0].innerHTML, 'leading newline'); + assert.equal(elementPreWithLeadingNewline.children[1].innerHTML, ' leading newline and spaces'); + assert.equal(elementPreWithLeadingNewline.children[2].innerHTML, '\nleading newlines'); + assert.equal(elementPreWithoutLeadingNewline.children[0].innerHTML, 'without spaces'); + assert.equal(elementPreWithoutLeadingNewline.children[1].innerHTML, ' with spaces '); + assert.equal(elementPreWithoutLeadingNewline.children[2].innerHTML, ' \nnewline after leading space'); + assert.equal(elementPreWithMultipleLeadingNewline.innerHTML, '\n\nmultiple leading newlines'); } }; diff --git a/test/runtime/samples/pre-tag/main.svelte b/test/runtime/samples/pre-tag/main.svelte index ef603b9883..6ab8b161de 100644 --- a/test/runtime/samples/pre-tag/main.svelte +++ b/test/runtime/samples/pre-tag/main.svelte @@ -32,3 +32,25 @@ F
+ +
+
+leading newline
+
+  leading newline and spaces
+
+
+leading newlines
+
+ +
+
without spaces
+
  with spaces  
+
 
+newline after leading space
+
+ +
+
+
+multiple leading newlines
diff --git a/test/runtime/samples/preserve-whitespaces/_config.js b/test/runtime/samples/preserve-whitespaces/_config.js index e0c621a18b..ffd6423911 100644 --- a/test/runtime/samples/preserve-whitespaces/_config.js +++ b/test/runtime/samples/preserve-whitespaces/_config.js @@ -10,22 +10,9 @@ export default { // Test for
 tag in non 
 tag
 		const elementDivWithPre = target.querySelector('#div-with-pre');
 
-		// There is a slight difference in innerHTML because there is a difference in HTML optimization (in jsdom)
-		// depending on how the innerHTML is set.
-		// (There is no difference in the display.)
-		// Reassign innerHTML to add the same optimizations to innerHTML.
-
-		// eslint-disable-next-line no-self-assign
-		elementPre.innerHTML = elementPre.innerHTML;
-		// eslint-disable-next-line no-self-assign
-		elementDiv.innerHTML = elementDiv.innerHTML;
-		// eslint-disable-next-line no-self-assign
-		elementDivWithPre.innerHTML = elementDivWithPre.innerHTML;
-
 		assert.equal(
 			elementPre.innerHTML,
-			`
-  A
+			`  A
   B
   
     C
diff --git a/test/server-side-rendering/samples/pre-tag/.editorconfig b/test/server-side-rendering/samples/pre-tag/.editorconfig
new file mode 100644
index 0000000000..d53d6a3faf
--- /dev/null
+++ b/test/server-side-rendering/samples/pre-tag/.editorconfig
@@ -0,0 +1,2 @@
+[{main.svelte,_expected.html}]
+trim_trailing_whitespace = unset
diff --git a/test/server-side-rendering/samples/pre-tag/_expected.html b/test/server-side-rendering/samples/pre-tag/_expected.html
index 7f88acdff4..8d8ad5497d 100644
--- a/test/server-side-rendering/samples/pre-tag/_expected.html
+++ b/test/server-side-rendering/samples/pre-tag/_expected.html
@@ -28,3 +28,16 @@
     E
     F
   
+ +
+leading newline
+
+  leading newline and spaces
+
+
+leading newlines
+ +
without spaces
+
  with spaces  
+
 
+newline after leading space
diff --git a/test/server-side-rendering/samples/pre-tag/main.svelte b/test/server-side-rendering/samples/pre-tag/main.svelte index fb240817f7..be3bf9e7ff 100644 --- a/test/server-side-rendering/samples/pre-tag/main.svelte +++ b/test/server-side-rendering/samples/pre-tag/main.svelte @@ -32,3 +32,20 @@ F
+ +
+
+leading newline
+
+  leading newline and spaces
+
+
+leading newlines
+
+ +
+
without spaces
+
  with spaces  
+
 
+newline after leading space
+