]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-dom): should ignore leading newline in <textarea> per spec
authorEvan You <evan@vuejs.org>
Fri, 13 Sep 2024 13:13:52 +0000 (21:13 +0800)
committerEvan You <evan@vuejs.org>
Fri, 13 Sep 2024 13:24:59 +0000 (21:24 +0800)
packages/compiler-core/__tests__/parse.spec.ts
packages/compiler-core/src/options.ts
packages/compiler-core/src/parser.ts
packages/compiler-dom/__tests__/parse.spec.ts
packages/compiler-dom/src/parserOptions.ts

index 77211b2f5fb55e4d3451d9b5528ebf2874b6723c..4e5a96165118ce5a001329df595722bb9687a79a 100644 (file)
@@ -2369,6 +2369,7 @@ describe('compiler: parse', () => {
     test('should remove leading newline character immediately following the pre element start tag', () => {
       const ast = parse(`<pre>\n  foo  bar  </pre>`, {
         isPreTag: tag => tag === 'pre',
+        isIgnoreNewlineTag: tag => tag === 'pre',
       })
       expect(ast.children).toHaveLength(1)
       const preElement = ast.children[0] as ElementNode
index 16b35d3ee85f8a3e9c3fed7d29c63a3a4a131c99..1de865f42ebaed3edcd63ab5500ce675b6a0cb9c 100644 (file)
@@ -52,6 +52,11 @@ export interface ParserOptions
    * e.g. elements that should preserve whitespace inside, e.g. `<pre>`
    */
   isPreTag?: (tag: string) => boolean
+  /**
+   * Elements that should ignore the first newline token per parinsg spec
+   * e.g. `<textarea>` and `<pre>`
+   */
+  isIgnoreNewlineTag?: (tag: string) => boolean
   /**
    * Platform-specific built-in components e.g. `<Transition>`
    */
index 304807d076ae5e411496306b58bf5127d8bf2fd1..0d66d2055f4ca8ff1e259bca06ec0cd18fcee559 100644 (file)
@@ -72,6 +72,7 @@ export const defaultParserOptions: MergedParserOptions = {
   getNamespace: () => Namespaces.HTML,
   isVoidTag: NO,
   isPreTag: NO,
+  isIgnoreNewlineTag: NO,
   isCustomElement: NO,
   onError: defaultOnError,
   onWarn: defaultOnWarn,
@@ -633,7 +634,7 @@ function onCloseTag(el: ElementNode, end: number, isImplied = false) {
   }
 
   // refine element type
-  const { tag, ns } = el
+  const { tag, ns, children } = el
   if (!inVPre) {
     if (tag === 'slot') {
       el.tagType = ElementTypes.SLOT
@@ -646,8 +647,18 @@ function onCloseTag(el: ElementNode, end: number, isImplied = false) {
 
   // whitespace management
   if (!tokenizer.inRCDATA) {
-    el.children = condenseWhitespace(el.children, el.tag)
+    el.children = condenseWhitespace(children, tag)
   }
+
+  if (ns === Namespaces.HTML && currentOptions.isIgnoreNewlineTag(tag)) {
+    // remove leading newline for <textarea> and <pre> per html spec
+    // https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inbody
+    const first = children[0]
+    if (first && first.type === NodeTypes.TEXT) {
+      first.content = first.content.replace(/^\r?\n/, '')
+    }
+  }
+
   if (ns === Namespaces.HTML && currentOptions.isPreTag(tag)) {
     inPre--
   }
@@ -869,14 +880,6 @@ function condenseWhitespace(
       }
     }
   }
-  if (inPre && tag && currentOptions.isPreTag(tag)) {
-    // remove leading newline per html spec
-    // https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
-    const first = nodes[0]
-    if (first && first.type === NodeTypes.TEXT) {
-      first.content = first.content.replace(/^\r?\n/, '')
-    }
-  }
   return removedWhitespace ? nodes.filter(Boolean) : nodes
 }
 
index c3cfd3249d3c7aa147db90c4e4cea03ca3900258..7418b8e33fbfbd68569f4a97cfda987ece0521ea 100644 (file)
@@ -32,6 +32,22 @@ describe('DOM parser', () => {
       })
     })
 
+    test('<textarea> should remove leading newline', () => {
+      const ast = parse('<textarea>\nhello</textarea>', parserOptions)
+      const element = ast.children[0] as ElementNode
+      const text = element.children[0] as TextNode
+      expect(element.children.length).toBe(1)
+      expect(text).toStrictEqual({
+        type: NodeTypes.TEXT,
+        content: 'hello',
+        loc: {
+          start: { offset: 10, line: 1, column: 11 },
+          end: { offset: 16, line: 2, column: 6 },
+          source: '\nhello',
+        },
+      })
+    })
+
     test('should not treat Uppercase component as special tag', () => {
       const ast = parse(
         '<TextArea>some<div>text</div>and<!--comment--></TextArea>',
index e4899a9bf214fdd8004dd4a0679c6957fbeac16e..7da13bf534d4d627e535f456859f991e799f8a35 100644 (file)
@@ -8,6 +8,7 @@ export const parserOptions: ParserOptions = {
   isVoidTag,
   isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag) || isMathMLTag(tag),
   isPreTag: tag => tag === 'pre',
+  isIgnoreNewlineTag: tag => tag === 'pre' || tag === 'textarea',
   decodeEntities: __BROWSER__ ? decodeHtmlBrowser : undefined,
 
   isBuiltInComponent: tag => {