]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler): implement support for v-pre
authorEvan You <yyx990803@gmail.com>
Wed, 9 Oct 2019 20:00:08 +0000 (16:00 -0400)
committerEvan You <yyx990803@gmail.com>
Wed, 9 Oct 2019 20:00:08 +0000 (16:00 -0400)
packages/compiler-core/__tests__/parse.spec.ts
packages/compiler-core/src/parse.ts

index 4df608b64f462ee5b0e8aea7ce67764c057c5579..0d3fab316182f419394c448baa6f89fed12e5a28 100644 (file)
@@ -1278,6 +1278,88 @@ describe('compiler: parse', () => {
       })
     })
 
+    test('v-pre', () => {
+      const ast = parse(
+        `<div v-pre :id="foo"><Comp/>{{ bar }}</div>\n` +
+          `<div :id="foo"><Comp/>{{ bar }}</div>`
+      )
+
+      const divWithPre = ast.children[0] as ElementNode
+      expect(divWithPre.props).toMatchObject([
+        {
+          type: NodeTypes.ATTRIBUTE,
+          name: `:id`,
+          value: {
+            type: NodeTypes.TEXT,
+            content: `foo`
+          },
+          loc: {
+            source: `:id="foo"`,
+            start: {
+              line: 1,
+              column: 12
+            },
+            end: {
+              line: 1,
+              column: 21
+            }
+          }
+        }
+      ])
+      expect(divWithPre.children[0]).toMatchObject({
+        type: NodeTypes.ELEMENT,
+        tagType: ElementTypes.ELEMENT,
+        tag: `Comp`
+      })
+      expect(divWithPre.children[1]).toMatchObject({
+        type: NodeTypes.TEXT,
+        content: `{{ bar }}`
+      })
+
+      // should not affect siblings after it
+      const divWithoutPre = ast.children[1] as ElementNode
+      expect(divWithoutPre.props).toMatchObject([
+        {
+          type: NodeTypes.DIRECTIVE,
+          name: `bind`,
+          arg: {
+            type: NodeTypes.SIMPLE_EXPRESSION,
+            isStatic: true,
+            content: `id`
+          },
+          exp: {
+            type: NodeTypes.SIMPLE_EXPRESSION,
+            isStatic: false,
+            content: `foo`
+          },
+          loc: {
+            source: `:id="foo"`,
+            start: {
+              line: 2,
+              column: 6
+            },
+            end: {
+              line: 2,
+              column: 15
+            }
+          }
+        }
+      ])
+      expect(divWithoutPre.children[0]).toMatchObject({
+        type: NodeTypes.ELEMENT,
+        tagType: ElementTypes.COMPONENT,
+        tag: `Comp`
+      })
+      expect(divWithoutPre.children[1]).toMatchObject({
+        type: NodeTypes.INTERPOLATION,
+        content: {
+          type: NodeTypes.SIMPLE_EXPRESSION,
+          content: `bar`,
+          isStatic: false
+        }
+      })
+    })
+
     test('end tags are case-insensitive.', () => {
       const ast = parse('<div>hello</DIV>after')
       const element = ast.children[0] as ElementNode
index cbb382c329a3fcfbfc7e3924150dd795544747cd..019eddb49829d8837f949c882cb5c6639a2e114b 100644 (file)
@@ -26,6 +26,7 @@ import {
   TemplateChildNode,
   InterpolationNode
 } from './ast'
+import { extend } from '@vue/shared'
 
 export interface ParserOptions {
   isVoidTag?: (tag: string) => boolean // e.g. img, br, hr
@@ -74,6 +75,7 @@ interface ParserContext {
   line: number
   column: number
   maxCRNameLength: number
+  inPre: boolean
 }
 
 export function parse(content: string, options: ParserOptions = {}): RootNode {
@@ -109,7 +111,8 @@ function createParserContext(
     maxCRNameLength: Object.keys(
       options.namedCharacterReferences ||
         defaultParserOptions.namedCharacterReferences
-    ).reduce((max, name) => Math.max(max, name.length), 0)
+    ).reduce((max, name) => Math.max(max, name.length), 0),
+    inPre: false
   }
 }
 
@@ -127,7 +130,7 @@ function parseChildren(
     const s = context.source
     let node: TemplateChildNode | TemplateChildNode[] | undefined = undefined
 
-    if (startsWith(s, context.options.delimiters[0])) {
+    if (!context.inPre && startsWith(s, context.options.delimiters[0])) {
       // '{{'
       node = parseInterpolation(context, mode)
     } else if (mode === TextModes.DATA && s[0] === '<') {
@@ -325,8 +328,10 @@ function parseElement(
   __DEV__ && assert(/^<[a-z]/i.test(context.source))
 
   // Start tag.
+  const wasInPre = context.inPre
   const parent = last(ancestors)
   const element = parseTag(context, TagType.Start, parent)
+  const isPreBoundary = context.inPre && !wasInPre
 
   if (element.isSelfClosing || context.options.isVoidTag(element.tag)) {
     return element
@@ -354,6 +359,10 @@ function parseElement(
   }
 
   element.loc = getSelection(context, element.loc.start)
+
+  if (isPreBoundary) {
+    context.inPre = false
+  }
   return element
 }
 
@@ -380,43 +389,29 @@ function parseTag(
   const start = getCursor(context)
   const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!
   const tag = match[1]
-  const props = []
   const ns = context.options.getNamespace(tag, parent)
 
-  let tagType = ElementTypes.ELEMENT
-  if (tag === 'slot') tagType = ElementTypes.SLOT
-  else if (tag === 'template') tagType = ElementTypes.TEMPLATE
-  else if (/[A-Z-]/.test(tag)) tagType = ElementTypes.COMPONENT
-
   advanceBy(context, match[0].length)
   advanceSpaces(context)
 
-  // Attributes.
-  const attributeNames = new Set<string>()
-  while (
-    context.source.length > 0 &&
-    !startsWith(context.source, '>') &&
-    !startsWith(context.source, '/>')
-  ) {
-    if (startsWith(context.source, '/')) {
-      emitError(context, ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG)
-      advanceBy(context, 1)
-      advanceSpaces(context)
-      continue
-    }
-    if (type === TagType.End) {
-      emitError(context, ErrorCodes.END_TAG_WITH_ATTRIBUTES)
-    }
+  // save current state in case we need to re-parse attributes with v-pre
+  const cursor = getCursor(context)
+  const currentSource = context.source
 
-    const attr = parseAttribute(context, attributeNames)
-    if (type === TagType.Start) {
-      props.push(attr)
-    }
+  // Attributes.
+  let props = parseAttributes(context, type)
 
-    if (/^[^\t\r\n\f />]/.test(context.source)) {
-      emitError(context, ErrorCodes.MISSING_WHITESPACE_BETWEEN_ATTRIBUTES)
-    }
-    advanceSpaces(context)
+  // check v-pre
+  if (
+    !context.inPre &&
+    props.some(p => p.type === NodeTypes.DIRECTIVE && p.name === 'pre')
+  ) {
+    context.inPre = true
+    // reset context
+    extend(context, cursor)
+    context.source = currentSource
+    // re-parse attrs and filter out v-pre itself
+    props = parseAttributes(context, type).filter(p => p.name !== 'v-pre')
   }
 
   // Tag close.
@@ -431,6 +426,13 @@ function parseTag(
     advanceBy(context, isSelfClosing ? 2 : 1)
   }
 
+  let tagType = ElementTypes.ELEMENT
+  if (!context.inPre) {
+    if (tag === 'slot') tagType = ElementTypes.SLOT
+    else if (tag === 'template') tagType = ElementTypes.TEMPLATE
+    else if (/[A-Z-]/.test(tag)) tagType = ElementTypes.COMPONENT
+  }
+
   return {
     type: NodeTypes.ELEMENT,
     ns,
@@ -444,6 +446,40 @@ function parseTag(
   }
 }
 
+function parseAttributes(
+  context: ParserContext,
+  type: TagType
+): (AttributeNode | DirectiveNode)[] {
+  const props = []
+  const attributeNames = new Set<string>()
+  while (
+    context.source.length > 0 &&
+    !startsWith(context.source, '>') &&
+    !startsWith(context.source, '/>')
+  ) {
+    if (startsWith(context.source, '/')) {
+      emitError(context, ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG)
+      advanceBy(context, 1)
+      advanceSpaces(context)
+      continue
+    }
+    if (type === TagType.End) {
+      emitError(context, ErrorCodes.END_TAG_WITH_ATTRIBUTES)
+    }
+
+    const attr = parseAttribute(context, attributeNames)
+    if (type === TagType.Start) {
+      props.push(attr)
+    }
+
+    if (/^[^\t\r\n\f />]/.test(context.source)) {
+      emitError(context, ErrorCodes.MISSING_WHITESPACE_BETWEEN_ATTRIBUTES)
+    }
+    advanceSpaces(context)
+  }
+  return props
+}
+
 function parseAttribute(
   context: ParserContext,
   nameSet: Set<string>
@@ -497,7 +533,7 @@ function parseAttribute(
   }
   const loc = getSelection(context, start)
 
-  if (/^(v-|:|@|#)/.test(name)) {
+  if (!context.inPre && /^(v-|:|@|#)/.test(name)) {
     const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^@|^#)([^\.]+))?(.+)?$/i.exec(
       name
     )!