]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor: swap to new template parser
authorEvan You <yyx990803@gmail.com>
Fri, 17 Nov 2023 06:17:30 +0000 (14:17 +0800)
committerEvan You <yyx990803@gmail.com>
Sat, 25 Nov 2023 08:18:29 +0000 (16:18 +0800)
- get rid of SourceLocation.source for memory efficiency
- move source location generation logic transform phase into the parser
  itself so that SourceLocation.source is no longer needed
  - move v-for expression parsing into the parser itself
  - added nameLoc on AttributeNode for use in transformElement

Tests are not passing yet.

25 files changed:
packages/compiler-core/__tests__/codegen.spec.ts
packages/compiler-core/__tests__/parse.spec.ts
packages/compiler-core/__tests__/transform.spec.ts
packages/compiler-core/__tests__/transforms/vFor.spec.ts
packages/compiler-core/__tests__/transforms/vIf.spec.ts
packages/compiler-core/__tests__/utils.spec.ts
packages/compiler-core/src/ast.ts
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/compat/compatConfig.ts
packages/compiler-core/src/compile.ts
packages/compiler-core/src/index.ts
packages/compiler-core/src/options.ts
packages/compiler-core/src/parse.ts [deleted file]
packages/compiler-core/src/parser/index.ts
packages/compiler-core/src/transforms/transformElement.ts
packages/compiler-core/src/transforms/transformExpression.ts
packages/compiler-core/src/transforms/vFor.ts
packages/compiler-core/src/transforms/vModel.ts
packages/compiler-core/src/transforms/vSlot.ts
packages/compiler-core/src/utils.ts
packages/compiler-dom/src/parserOptions.ts
packages/compiler-dom/src/transforms/Transition.ts
packages/compiler-sfc/src/parse.ts
packages/compiler-ssr/src/transforms/ssrTransformElement.ts
packages/vue/src/index.ts

index 802df84b35b47284d50231dee92cf646a131ba9b..d18d6cb380f769fd6f205b88bd6160d8eda773a4 100644 (file)
@@ -40,6 +40,7 @@ import { PatchFlags } from '@vue/shared'
 function createRoot(options: Partial<RootNode> = {}): RootNode {
   return {
     type: NodeTypes.ROOT,
+    source: '',
     children: [],
     helpers: new Set(),
     components: [],
index b43679614c5cc7f0f0024e13e85de43b9e52b3c7..3ce27a1f30584fc6324b78d729d8f6ffebe03fd4 100644 (file)
@@ -1,5 +1,4 @@
 import { ParserOptions } from '../src/options'
-import { TextModes } from '../src/parse'
 import { ErrorCodes } from '../src/errors'
 import {
   CommentNode,
@@ -1913,35 +1912,38 @@ describe('compiler: parse', () => {
   })
 
   test.skip('parse with correct location info', () => {
+    const fooSrc = `foo
+  is `
+    const barSrc = `{{ bar }}`
+    const butSrc = ` but `
+    const bazSrc = `{{ baz }}`
     const [foo, bar, but, baz] = baseParse(
-      `
-foo
- is {{ bar }} but {{ baz }}`.trim()
+      fooSrc + barSrc + butSrc + bazSrc
     ).children
 
     let offset = 0
     expect(foo.loc.start).toEqual({ line: 1, column: 1, offset })
-    offset += foo.loc.source.length
+    offset += fooSrc.length
     expect(foo.loc.end).toEqual({ line: 2, column: 5, offset })
 
     expect(bar.loc.start).toEqual({ line: 2, column: 5, offset })
     const barInner = (bar as InterpolationNode).content
     offset += 3
     expect(barInner.loc.start).toEqual({ line: 2, column: 8, offset })
-    offset += barInner.loc.source.length
+    offset += 3
     expect(barInner.loc.end).toEqual({ line: 2, column: 11, offset })
     offset += 3
     expect(bar.loc.end).toEqual({ line: 2, column: 14, offset })
 
     expect(but.loc.start).toEqual({ line: 2, column: 14, offset })
-    offset += but.loc.source.length
+    offset += butSrc.length
     expect(but.loc.end).toEqual({ line: 2, column: 19, offset })
 
     expect(baz.loc.start).toEqual({ line: 2, column: 19, offset })
     const bazInner = (baz as InterpolationNode).content
     offset += 3
     expect(bazInner.loc.start).toEqual({ line: 2, column: 22, offset })
-    offset += bazInner.loc.source.length
+    offset += 3
     expect(bazInner.loc.end).toEqual({ line: 2, column: 25, offset })
     offset += 3
     expect(baz.loc.end).toEqual({ line: 2, column: 28, offset })
@@ -2073,8 +2075,7 @@ foo
 
     test.skip('should NOT condense whitespaces in RCDATA text mode', () => {
       const ast = baseParse(`<textarea>Text:\n   foo</textarea>`, {
-        getTextMode: ({ tag }) =>
-          tag === 'textarea' ? TextModes.RCDATA : TextModes.DATA
+        parseMode: 'html'
       })
       const preElement = ast.children[0] as ElementNode
       expect(preElement.children).toHaveLength(1)
@@ -3069,24 +3070,7 @@ foo
             () => {
               const spy = vi.fn()
               const ast = baseParse(code, {
-                getNamespace: (tag, parent) => {
-                  const ns = parent ? parent.ns : Namespaces.HTML
-                  if (ns === Namespaces.HTML) {
-                    if (tag === 'svg') {
-                      return (Namespaces.HTML + 1) as any
-                    }
-                  }
-                  return ns
-                },
-                getTextMode: ({ tag }) => {
-                  if (tag === 'textarea') {
-                    return TextModes.RCDATA
-                  }
-                  if (tag === 'script') {
-                    return TextModes.RAWTEXT
-                  }
-                  return TextModes.DATA
-                },
+                parseMode: 'html',
                 ...options,
                 onError: spy
               })
index 042865efb4eb5e03404c2e0b12482d05b0a3324f..f0e91108e43ec8da5ab3ef364cb07d330ef99aee 100644 (file)
@@ -1,4 +1,4 @@
-import { baseParse } from '../src/parse'
+import { baseParse } from '../src/parser'
 import { transform, NodeTransform } from '../src/transform'
 import {
   ElementNode,
index 60a9378ebbd2a8ca7abf7927e10cd3e876b1abad..c5132b68dc8942a29df5955001ca163143b909cc 100644 (file)
@@ -1,4 +1,4 @@
-import { baseParse as parse } from '../../src/parse'
+import { baseParse as parse } from '../../src/parser'
 import { transform } from '../../src/transform'
 import { transformIf } from '../../src/transforms/vIf'
 import { transformFor } from '../../src/transforms/vFor'
index 5bc9bedd53bedef209e5bcc39eadbb55b035db9a..39fa13f00cf52ab6e49e955d3d88f6f8dd452c7c 100644 (file)
@@ -1,4 +1,4 @@
-import { baseParse as parse } from '../../src/parse'
+import { baseParse as parse } from '../../src/parser'
 import { transform } from '../../src/transform'
 import { transformIf } from '../../src/transforms/vIf'
 import { transformElement } from '../../src/transforms/transformElement'
index 45fa46fea7a60d28a367fe5c4f1a886280eb530e..7baa1ae89d7f257deb87dd10e3afed1f687cf521 100644 (file)
@@ -1,7 +1,6 @@
 import { TransformContext } from '../src'
 import { Position } from '../src/ast'
 import {
-  getInnerRange,
   advancePositionWithClone,
   isMemberExpressionNode,
   isMemberExpressionBrowser,
@@ -41,32 +40,6 @@ describe('advancePositionWithClone', () => {
   })
 })
 
-describe('getInnerRange', () => {
-  const loc1 = {
-    source: 'foo\nbar\nbaz',
-    start: p(1, 1, 0),
-    end: p(3, 3, 11)
-  }
-
-  test('at start', () => {
-    const loc2 = getInnerRange(loc1, 0, 4)
-    expect(loc2.start).toEqual(loc1.start)
-    expect(loc2.end.column).toBe(1)
-    expect(loc2.end.line).toBe(2)
-    expect(loc2.end.offset).toBe(4)
-  })
-
-  test('in between', () => {
-    const loc2 = getInnerRange(loc1, 4, 3)
-    expect(loc2.start.column).toBe(1)
-    expect(loc2.start.line).toBe(2)
-    expect(loc2.start.offset).toBe(4)
-    expect(loc2.end.column).toBe(4)
-    expect(loc2.end.line).toBe(2)
-    expect(loc2.end.offset).toBe(7)
-  })
-})
-
 describe('isMemberExpression', () => {
   function commonAssertions(fn: (str: string) => boolean) {
     // should work
index 8c3a18ec4dbf80c4129df66b3762f172d636fdfd..cc581fff13a9e8d18393e97eb42268454c240d51 100644 (file)
@@ -1,5 +1,4 @@
 import { isString } from '@vue/shared'
-import { ForParseResult } from './transforms/vFor'
 import {
   RENDER_SLOT,
   CREATE_SLOTS,
@@ -76,7 +75,6 @@ export interface Node {
 export interface SourceLocation {
   start: Position
   end: Position
-  source: string
 }
 
 export interface Position {
@@ -102,6 +100,7 @@ export type TemplateChildNode =
 
 export interface RootNode extends Node {
   type: NodeTypes.ROOT
+  source: string
   children: TemplateChildNode[]
   helpers: Set<symbol>
   components: string[]
@@ -182,20 +181,33 @@ export interface CommentNode extends Node {
 export interface AttributeNode extends Node {
   type: NodeTypes.ATTRIBUTE
   name: string
+  nameLoc: SourceLocation
   value: TextNode | undefined
 }
 
 export interface DirectiveNode extends Node {
   type: NodeTypes.DIRECTIVE
+  /**
+   * the normalized name without prefix or shorthands, e.g. "bind", "on"
+   */
   name: string
+  /**
+   * the raw attribute name, preserving shorthand, and including arg & modifiers
+   * this is only used during parse.
+   */
+  rawName?: string
   exp: ExpressionNode | undefined
+  /**
+   * the raw expression as a string
+   * only required on directives parsed from templates
+   */
+  rawExp?: string
   arg: ExpressionNode | undefined
   modifiers: string[]
-  raw?: string
   /**
    * optional property to cache the expression parse result for v-for
    */
-  parseResult?: ForParseResult
+  forParseResult?: ForParseResult
 }
 
 /**
@@ -277,6 +289,14 @@ export interface ForNode extends Node {
   codegenNode?: ForCodegenNode
 }
 
+export interface ForParseResult {
+  source: ExpressionNode
+  value: ExpressionNode | undefined
+  key: ExpressionNode | undefined
+  index: ExpressionNode | undefined
+  finalized: boolean
+}
+
 export interface TextCallNode extends Node {
   type: NodeTypes.TEXT_CALL
   content: TextNode | InterpolationNode | CompoundExpressionNode
@@ -548,17 +568,17 @@ export interface ForIteratorExpression extends FunctionExpression {
 // associated with template nodes, so their source locations are just a stub.
 // Container types like CompoundExpression also don't need a real location.
 export const locStub: SourceLocation = {
-  source: '',
   start: { line: 1, column: 1, offset: 0 },
   end: { line: 1, column: 1, offset: 0 }
 }
 
 export function createRoot(
   children: TemplateChildNode[],
-  loc = locStub
+  source = ''
 ): RootNode {
   return {
     type: NodeTypes.ROOT,
+    source,
     children,
     helpers: new Set(),
     components: [],
@@ -568,7 +588,7 @@ export function createRoot(
     cached: 0,
     temps: 0,
     codegenNode: undefined,
-    loc
+    loc: locStub
   }
 }
 
index ceae499822f78d529e476ceb02ba44b73e51bdc0..fb3040dd944c14c84311c1305a94da1d2d5b7e3e 100644 (file)
@@ -116,7 +116,7 @@ function createCodegenContext(
     ssr,
     isTS,
     inSSR,
-    source: ast.loc.source,
+    source: ast.source,
     code: ``,
     column: 1,
     line: 1,
index dcb304263b4feffe23d8fa8d84b441b68e0fa2d2..b643e801117b6ca06b6a0b4dd6b8beb77f51a509 100644 (file)
@@ -1,5 +1,6 @@
 import { SourceLocation } from '../ast'
 import { CompilerError } from '../errors'
+// @ts-expect-error TODO
 import { ParserContext } from '../parse'
 import { TransformContext } from '../transform'
 
index 01cb560cc8963f2695c1c6f41d96e74dfecd16fc..c2aa07346a67be71626a434bd9f5d0ffc1411fb1 100644 (file)
@@ -1,5 +1,5 @@
 import { CompilerOptions } from './options'
-import { baseParse } from './parse'
+import { baseParse } from './parser/index'
 import { transform, NodeTransform, DirectiveTransform } from './transform'
 import { generate, CodegenResult } from './codegen'
 import { RootNode } from './ast'
index c88844a5c06216577ae6711525b5eeedb204ee2f..74ca59e69eee733e73dd5db360ca5ef3e2bc6811 100644 (file)
@@ -10,7 +10,7 @@ export {
   type BindingMetadata,
   BindingTypes
 } from './options'
-export { baseParse, TextModes } from './parse'
+export { baseParse } from './parser'
 export {
   transform,
   type TransformContext,
@@ -70,5 +70,3 @@ export {
   warnDeprecation,
   CompilerDeprecationTypes
 } from './compat/compatConfig'
-
-export { baseParse as newParse } from './parser/index'
index 3ab42625ea212e64ec3a5cfc407db8ce5092f2e9..491d1eafaabf2076ceeb5d6a23711c6148db8048 100644 (file)
@@ -1,5 +1,4 @@
 import { ElementNode, Namespace, TemplateChildNode, ParentNode } from './ast'
-import { TextModes } from './parse'
 import { CompilerError } from './errors'
 import {
   NodeTransform,
@@ -42,13 +41,6 @@ export interface ParserOptions
    * Get tag namespace
    */
   getNamespace?: (tag: string, parent: ElementNode | undefined) => Namespace
-  /**
-   * Get text parsing mode for this element
-   */
-  getTextMode?: (
-    node: ElementNode,
-    parent: ElementNode | undefined
-  ) => TextModes
   /**
    * @default ['{{', '}}']
    */
diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts
deleted file mode 100644 (file)
index d9cdb4e..0000000
+++ /dev/null
@@ -1,1190 +0,0 @@
-import { ErrorHandlingOptions, ParserOptions } from './options'
-import { NO, isArray, makeMap, extend } from '@vue/shared'
-import {
-  ErrorCodes,
-  createCompilerError,
-  defaultOnError,
-  defaultOnWarn
-} from './errors'
-import {
-  assert,
-  advancePositionWithMutation,
-  advancePositionWithClone,
-  isCoreComponent,
-  isStaticArgOf
-} from './utils'
-import {
-  Namespaces,
-  AttributeNode,
-  CommentNode,
-  DirectiveNode,
-  ElementNode,
-  ElementTypes,
-  ExpressionNode,
-  NodeTypes,
-  Position,
-  RootNode,
-  SourceLocation,
-  TextNode,
-  TemplateChildNode,
-  InterpolationNode,
-  createRoot,
-  ConstantTypes
-} from './ast'
-import {
-  checkCompatEnabled,
-  CompilerCompatOptions,
-  CompilerDeprecationTypes,
-  isCompatEnabled,
-  warnDeprecation
-} from './compat/compatConfig'
-
-type OptionalOptions =
-  | 'parseMode'
-  | 'whitespace'
-  | 'isNativeTag'
-  | 'isBuiltInComponent'
-  | keyof CompilerCompatOptions
-type MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &
-  Pick<ParserOptions, OptionalOptions>
-type AttributeValue =
-  | {
-      content: string
-      isQuoted: boolean
-      loc: SourceLocation
-    }
-  | undefined
-
-// The default decoder only provides escapes for characters reserved as part of
-// the template syntax, and is only used if the custom renderer did not provide
-// a platform-specific decoder.
-const decodeRE = /&(gt|lt|amp|apos|quot);/g
-const decodeMap: Record<string, string> = {
-  gt: '>',
-  lt: '<',
-  amp: '&',
-  apos: "'",
-  quot: '"'
-}
-
-export const defaultParserOptions: MergedParserOptions = {
-  delimiters: [`{{`, `}}`],
-  getNamespace: () => Namespaces.HTML,
-  getTextMode: () => TextModes.DATA,
-  isVoidTag: NO,
-  isPreTag: NO,
-  isCustomElement: NO,
-  decodeEntities: (rawText: string): string =>
-    rawText.replace(decodeRE, (_, p1) => decodeMap[p1]),
-  onError: defaultOnError,
-  onWarn: defaultOnWarn,
-  comments: __DEV__
-}
-
-export const enum TextModes {
-  //          | Elements | Entities | End sign              | Inside of
-  DATA, //    | âœ”        | âœ”        | End tags of ancestors |
-  RCDATA, //  | âœ˜        | âœ”        | End tag of the parent | <textarea>
-  RAWTEXT, // | âœ˜        | âœ˜        | End tag of the parent | <style>,<script>
-  CDATA,
-  ATTRIBUTE_VALUE
-}
-
-export interface ParserContext {
-  options: MergedParserOptions
-  readonly originalSource: string
-  source: string
-  offset: number
-  line: number
-  column: number
-  inPre: boolean // HTML <pre> tag, preserve whitespaces
-  inVPre: boolean // v-pre, do not process directives and interpolations
-  onWarn: NonNullable<ErrorHandlingOptions['onWarn']>
-}
-
-export function baseParse(
-  content: string,
-  options: ParserOptions = {}
-): RootNode {
-  const context = createParserContext(content, options)
-  const start = getCursor(context)
-  return createRoot(
-    parseChildren(context, TextModes.DATA, []),
-    getSelection(context, start)
-  )
-}
-
-function createParserContext(
-  content: string,
-  rawOptions: ParserOptions
-): ParserContext {
-  const options = extend({}, defaultParserOptions)
-
-  let key: keyof ParserOptions
-  for (key in rawOptions) {
-    // @ts-ignore
-    options[key] =
-      rawOptions[key] === undefined
-        ? defaultParserOptions[key]
-        : rawOptions[key]
-  }
-  return {
-    options,
-    column: 1,
-    line: 1,
-    offset: 0,
-    originalSource: content,
-    source: content,
-    inPre: false,
-    inVPre: false,
-    onWarn: options.onWarn
-  }
-}
-
-function parseChildren(
-  context: ParserContext,
-  mode: TextModes,
-  ancestors: ElementNode[]
-): TemplateChildNode[] {
-  const parent = last(ancestors)
-  const ns = parent ? parent.ns : Namespaces.HTML
-  const nodes: TemplateChildNode[] = []
-
-  while (!isEnd(context, mode, ancestors)) {
-    __TEST__ && assert(context.source.length > 0)
-    const s = context.source
-    let node: TemplateChildNode | TemplateChildNode[] | undefined = undefined
-
-    if (mode === TextModes.DATA || mode === TextModes.RCDATA) {
-      if (!context.inVPre && startsWith(s, context.options.delimiters[0])) {
-        // '{{'
-        node = parseInterpolation(context, mode)
-      } else if (mode === TextModes.DATA && s[0] === '<') {
-        // https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
-        if (s.length === 1) {
-          emitError(context, ErrorCodes.EOF_BEFORE_TAG_NAME, 1)
-        } else if (s[1] === '!') {
-          // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state
-          if (startsWith(s, '<!--')) {
-            node = parseComment(context)
-          } else if (startsWith(s, '<!DOCTYPE')) {
-            // Ignore DOCTYPE by a limitation.
-            node = parseBogusComment(context)
-          } else if (startsWith(s, '<![CDATA[')) {
-            if (ns !== Namespaces.HTML) {
-              node = parseCDATA(context, ancestors)
-            } else {
-              emitError(context, ErrorCodes.CDATA_IN_HTML_CONTENT)
-              node = parseBogusComment(context)
-            }
-          } else {
-            emitError(context, ErrorCodes.INCORRECTLY_OPENED_COMMENT)
-            node = parseBogusComment(context)
-          }
-        } else if (s[1] === '/') {
-          // https://html.spec.whatwg.org/multipage/parsing.html#end-tag-open-state
-          if (s.length === 2) {
-            emitError(context, ErrorCodes.EOF_BEFORE_TAG_NAME, 2)
-          } else if (s[2] === '>') {
-            emitError(context, ErrorCodes.MISSING_END_TAG_NAME, 2)
-            advanceBy(context, 3)
-            continue
-          } else if (/[a-z]/i.test(s[2])) {
-            emitError(context, ErrorCodes.X_INVALID_END_TAG)
-            parseTag(context, TagType.End, parent)
-            continue
-          } else {
-            emitError(
-              context,
-              ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,
-              2
-            )
-            node = parseBogusComment(context)
-          }
-        } else if (/[a-z]/i.test(s[1])) {
-          node = parseElement(context, ancestors)
-
-          // 2.x <template> with no directive compat
-          if (
-            __COMPAT__ &&
-            isCompatEnabled(
-              CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE,
-              context
-            ) &&
-            node &&
-            node.tag === 'template' &&
-            !node.props.some(
-              p =>
-                p.type === NodeTypes.DIRECTIVE &&
-                isSpecialTemplateDirective(p.name)
-            )
-          ) {
-            __DEV__ &&
-              warnDeprecation(
-                CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE,
-                context,
-                node.loc
-              )
-            node = node.children
-          }
-        } else if (s[1] === '?') {
-          emitError(
-            context,
-            ErrorCodes.UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME,
-            1
-          )
-          node = parseBogusComment(context)
-        } else {
-          emitError(context, ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME, 1)
-        }
-      }
-    }
-    if (!node) {
-      node = parseText(context, mode)
-    }
-
-    if (isArray(node)) {
-      for (let i = 0; i < node.length; i++) {
-        pushNode(nodes, node[i])
-      }
-    } else {
-      pushNode(nodes, node)
-    }
-  }
-
-  // Whitespace handling strategy like v2
-  let removedWhitespace = false
-  if (mode !== TextModes.RAWTEXT && mode !== TextModes.RCDATA) {
-    const shouldCondense = context.options.whitespace !== 'preserve'
-    for (let i = 0; i < nodes.length; i++) {
-      const node = nodes[i]
-      if (node.type === NodeTypes.TEXT) {
-        if (!context.inPre) {
-          if (!/[^\t\r\n\f ]/.test(node.content)) {
-            const prev = nodes[i - 1]
-            const next = nodes[i + 1]
-            // Remove if:
-            // - the whitespace is the first or last node, or:
-            // - (condense mode) the whitespace is between twos comments, or:
-            // - (condense mode) the whitespace is between comment and element, or:
-            // - (condense mode) the whitespace is between two elements AND contains newline
-            if (
-              !prev ||
-              !next ||
-              (shouldCondense &&
-                ((prev.type === NodeTypes.COMMENT &&
-                  next.type === NodeTypes.COMMENT) ||
-                  (prev.type === NodeTypes.COMMENT &&
-                    next.type === NodeTypes.ELEMENT) ||
-                  (prev.type === NodeTypes.ELEMENT &&
-                    next.type === NodeTypes.COMMENT) ||
-                  (prev.type === NodeTypes.ELEMENT &&
-                    next.type === NodeTypes.ELEMENT &&
-                    /[\r\n]/.test(node.content))))
-            ) {
-              removedWhitespace = true
-              nodes[i] = null as any
-            } else {
-              // Otherwise, the whitespace is condensed into a single space
-              node.content = ' '
-            }
-          } else if (shouldCondense) {
-            // in condense mode, consecutive whitespaces in text are condensed
-            // down to a single space.
-            node.content = node.content.replace(/[\t\r\n\f ]+/g, ' ')
-          }
-        } else {
-          // #6410 normalize windows newlines in <pre>:
-          // in SSR, browsers normalize server-rendered \r\n into a single \n
-          // in the DOM
-          node.content = node.content.replace(/\r\n/g, '\n')
-        }
-      }
-      // Remove comment nodes if desired by configuration.
-      else if (node.type === NodeTypes.COMMENT && !context.options.comments) {
-        removedWhitespace = true
-        nodes[i] = null as any
-      }
-    }
-    if (context.inPre && parent && context.options.isPreTag(parent.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
-}
-
-function pushNode(nodes: TemplateChildNode[], node: TemplateChildNode): void {
-  if (node.type === NodeTypes.TEXT) {
-    const prev = last(nodes)
-    // Merge if both this and the previous node are text and those are
-    // consecutive. This happens for cases like "a < b".
-    if (
-      prev &&
-      prev.type === NodeTypes.TEXT &&
-      prev.loc.end.offset === node.loc.start.offset
-    ) {
-      prev.content += node.content
-      prev.loc.end = node.loc.end
-      prev.loc.source += node.loc.source
-      return
-    }
-  }
-
-  nodes.push(node)
-}
-
-function parseCDATA(
-  context: ParserContext,
-  ancestors: ElementNode[]
-): TemplateChildNode[] {
-  __TEST__ &&
-    assert(last(ancestors) == null || last(ancestors)!.ns !== Namespaces.HTML)
-  __TEST__ && assert(startsWith(context.source, '<![CDATA['))
-
-  advanceBy(context, 9)
-  const nodes = parseChildren(context, TextModes.CDATA, ancestors)
-  if (context.source.length === 0) {
-    emitError(context, ErrorCodes.EOF_IN_CDATA)
-  } else {
-    __TEST__ && assert(startsWith(context.source, ']]>'))
-    advanceBy(context, 3)
-  }
-
-  return nodes
-}
-
-function parseComment(context: ParserContext): CommentNode {
-  __TEST__ && assert(startsWith(context.source, '<!--'))
-
-  const start = getCursor(context)
-  let content: string
-
-  // Regular comment.
-  const match = /--(\!)?>/.exec(context.source)
-  if (!match) {
-    content = context.source.slice(4)
-    advanceBy(context, context.source.length)
-    emitError(context, ErrorCodes.EOF_IN_COMMENT)
-  } else {
-    if (match.index <= 3) {
-      emitError(context, ErrorCodes.ABRUPT_CLOSING_OF_EMPTY_COMMENT)
-    }
-    if (match[1]) {
-      emitError(context, ErrorCodes.INCORRECTLY_CLOSED_COMMENT)
-    }
-    content = context.source.slice(4, match.index)
-
-    // Advancing with reporting nested comments.
-    const s = context.source.slice(0, match.index)
-    let prevIndex = 1,
-      nestedIndex = 0
-    while ((nestedIndex = s.indexOf('<!--', prevIndex)) !== -1) {
-      advanceBy(context, nestedIndex - prevIndex + 1)
-      if (nestedIndex + 4 < s.length) {
-        emitError(context, ErrorCodes.NESTED_COMMENT)
-      }
-      prevIndex = nestedIndex + 1
-    }
-    advanceBy(context, match.index + match[0].length - prevIndex + 1)
-  }
-
-  return {
-    type: NodeTypes.COMMENT,
-    content,
-    loc: getSelection(context, start)
-  }
-}
-
-function parseBogusComment(context: ParserContext): CommentNode | undefined {
-  __TEST__ && assert(/^<(?:[\!\?]|\/[^a-z>])/i.test(context.source))
-
-  const start = getCursor(context)
-  const contentStart = context.source[1] === '?' ? 1 : 2
-  let content: string
-
-  const closeIndex = context.source.indexOf('>')
-  if (closeIndex === -1) {
-    content = context.source.slice(contentStart)
-    advanceBy(context, context.source.length)
-  } else {
-    content = context.source.slice(contentStart, closeIndex)
-    advanceBy(context, closeIndex + 1)
-  }
-
-  return {
-    type: NodeTypes.COMMENT,
-    content,
-    loc: getSelection(context, start)
-  }
-}
-
-function parseElement(
-  context: ParserContext,
-  ancestors: ElementNode[]
-): ElementNode | undefined {
-  __TEST__ && assert(/^<[a-z]/i.test(context.source))
-
-  // Start tag.
-  const wasInPre = context.inPre
-  const wasInVPre = context.inVPre
-  const parent = last(ancestors)
-  const element = parseTag(context, TagType.Start, parent)
-  const isPreBoundary = context.inPre && !wasInPre
-  const isVPreBoundary = context.inVPre && !wasInVPre
-
-  if (element.isSelfClosing || context.options.isVoidTag(element.tag)) {
-    // #4030 self-closing <pre> tag
-    if (isPreBoundary) {
-      context.inPre = false
-    }
-    if (isVPreBoundary) {
-      context.inVPre = false
-    }
-    return element
-  }
-
-  // Children.
-  ancestors.push(element)
-  const mode = context.options.getTextMode(element, parent)
-  const children = parseChildren(context, mode, ancestors)
-  ancestors.pop()
-
-  // 2.x inline-template compat
-  if (__COMPAT__) {
-    const inlineTemplateProp = element.props.find(
-      p => p.type === NodeTypes.ATTRIBUTE && p.name === 'inline-template'
-    ) as AttributeNode
-    if (
-      inlineTemplateProp &&
-      checkCompatEnabled(
-        CompilerDeprecationTypes.COMPILER_INLINE_TEMPLATE,
-        context,
-        inlineTemplateProp.loc
-      )
-    ) {
-      const loc = getSelection(context, element.loc.end)
-      inlineTemplateProp.value = {
-        type: NodeTypes.TEXT,
-        content: loc.source,
-        loc
-      }
-    }
-  }
-
-  element.children = children
-
-  // End tag.
-  if (startsWithEndTagOpen(context.source, element.tag)) {
-    parseTag(context, TagType.End, parent)
-  } else {
-    emitError(context, ErrorCodes.X_MISSING_END_TAG, 0, element.loc.start)
-    if (context.source.length === 0 && element.tag.toLowerCase() === 'script') {
-      const first = children[0]
-      if (first && startsWith(first.loc.source, '<!--')) {
-        emitError(context, ErrorCodes.EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT)
-      }
-    }
-  }
-
-  element.loc = getSelection(context, element.loc.start)
-
-  if (isPreBoundary) {
-    context.inPre = false
-  }
-  if (isVPreBoundary) {
-    context.inVPre = false
-  }
-  return element
-}
-
-const enum TagType {
-  Start,
-  End
-}
-
-const isSpecialTemplateDirective = /*#__PURE__*/ makeMap(
-  `if,else,else-if,for,slot`
-)
-
-/**
- * Parse a tag (E.g. `<div id=a>`) with that type (start tag or end tag).
- */
-function parseTag(
-  context: ParserContext,
-  type: TagType.Start,
-  parent: ElementNode | undefined
-): ElementNode
-function parseTag(
-  context: ParserContext,
-  type: TagType.End,
-  parent: ElementNode | undefined
-): void
-function parseTag(
-  context: ParserContext,
-  type: TagType,
-  parent: ElementNode | undefined
-): ElementNode | undefined {
-  __TEST__ && assert(/^<\/?[a-z]/i.test(context.source))
-  __TEST__ &&
-    assert(
-      type === (startsWith(context.source, '</') ? TagType.End : TagType.Start)
-    )
-
-  // Tag open.
-  const start = getCursor(context)
-  const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!
-  const tag = match[1]
-  const ns = context.options.getNamespace(tag, parent)
-
-  advanceBy(context, match[0].length)
-  advanceSpaces(context)
-
-  // save current state in case we need to re-parse attributes with v-pre
-  const cursor = getCursor(context)
-  const currentSource = context.source
-
-  // check <pre> tag
-  if (context.options.isPreTag(tag)) {
-    context.inPre = true
-  }
-
-  // Attributes.
-  let props = parseAttributes(context, type)
-
-  // check v-pre
-  if (
-    type === TagType.Start &&
-    !context.inVPre &&
-    props.some(p => p.type === NodeTypes.DIRECTIVE && p.name === 'pre')
-  ) {
-    context.inVPre = 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.
-  let isSelfClosing = false
-  if (context.source.length === 0) {
-    emitError(context, ErrorCodes.EOF_IN_TAG)
-  } else {
-    isSelfClosing = startsWith(context.source, '/>')
-    if (type === TagType.End && isSelfClosing) {
-      emitError(context, ErrorCodes.END_TAG_WITH_TRAILING_SOLIDUS)
-    }
-    advanceBy(context, isSelfClosing ? 2 : 1)
-  }
-
-  if (type === TagType.End) {
-    return
-  }
-
-  // 2.x deprecation checks
-  if (
-    __COMPAT__ &&
-    __DEV__ &&
-    isCompatEnabled(
-      CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE,
-      context
-    )
-  ) {
-    let hasIf = false
-    let hasFor = false
-    for (let i = 0; i < props.length; i++) {
-      const p = props[i]
-      if (p.type === NodeTypes.DIRECTIVE) {
-        if (p.name === 'if') {
-          hasIf = true
-        } else if (p.name === 'for') {
-          hasFor = true
-        }
-      }
-      if (hasIf && hasFor) {
-        warnDeprecation(
-          CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE,
-          context,
-          getSelection(context, start)
-        )
-        break
-      }
-    }
-  }
-
-  let tagType = ElementTypes.ELEMENT
-  if (!context.inVPre) {
-    if (tag === 'slot') {
-      tagType = ElementTypes.SLOT
-    } else if (tag === 'template') {
-      if (
-        props.some(
-          p =>
-            p.type === NodeTypes.DIRECTIVE && isSpecialTemplateDirective(p.name)
-        )
-      ) {
-        tagType = ElementTypes.TEMPLATE
-      }
-    } else if (isComponent(tag, props, context)) {
-      tagType = ElementTypes.COMPONENT
-    }
-  }
-
-  return {
-    type: NodeTypes.ELEMENT,
-    ns,
-    tag,
-    tagType,
-    props,
-    isSelfClosing,
-    children: [],
-    loc: getSelection(context, start),
-    codegenNode: undefined // to be created during transform phase
-  }
-}
-
-function isComponent(
-  tag: string,
-  props: (AttributeNode | DirectiveNode)[],
-  context: ParserContext
-) {
-  const options = context.options
-  if (options.isCustomElement(tag)) {
-    return false
-  }
-  if (
-    tag === 'component' ||
-    /^[A-Z]/.test(tag) ||
-    isCoreComponent(tag) ||
-    (options.isBuiltInComponent && options.isBuiltInComponent(tag)) ||
-    (options.isNativeTag && !options.isNativeTag(tag))
-  ) {
-    return true
-  }
-  // at this point the tag should be a native tag, but check for potential "is"
-  // casting
-  for (let i = 0; i < props.length; i++) {
-    const p = props[i]
-    if (p.type === NodeTypes.ATTRIBUTE) {
-      if (p.name === 'is' && p.value) {
-        if (p.value.content.startsWith('vue:')) {
-          return true
-        } else if (
-          __COMPAT__ &&
-          checkCompatEnabled(
-            CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
-            context,
-            p.loc
-          )
-        ) {
-          return true
-        }
-      }
-    } else {
-      // directive
-      // v-is (TODO: remove in 3.4)
-      if (p.name === 'is') {
-        return true
-      } else if (
-        // :is on plain element - only treat as component in compat mode
-        p.name === 'bind' &&
-        isStaticArgOf(p.arg, 'is') &&
-        __COMPAT__ &&
-        checkCompatEnabled(
-          CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
-          context,
-          p.loc
-        )
-      ) {
-        return true
-      }
-    }
-  }
-}
-
-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)
-
-    // Trim whitespace between class
-    // https://github.com/vuejs/core/issues/4251
-    if (
-      attr.type === NodeTypes.ATTRIBUTE &&
-      attr.value &&
-      attr.name === 'class'
-    ) {
-      attr.value.content = attr.value.content.replace(/\s+/g, ' ').trim()
-    }
-
-    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>
-): AttributeNode | DirectiveNode {
-  __TEST__ && assert(/^[^\t\r\n\f />]/.test(context.source))
-
-  // Name.
-  const start = getCursor(context)
-  const match = /^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(context.source)!
-  const name = match[0]
-
-  if (nameSet.has(name)) {
-    emitError(context, ErrorCodes.DUPLICATE_ATTRIBUTE)
-  }
-  nameSet.add(name)
-
-  if (name[0] === '=') {
-    emitError(context, ErrorCodes.UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME)
-  }
-  {
-    const pattern = /["'<]/g
-    let m: RegExpExecArray | null
-    while ((m = pattern.exec(name))) {
-      emitError(
-        context,
-        ErrorCodes.UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME,
-        m.index
-      )
-    }
-  }
-
-  advanceBy(context, name.length)
-
-  // Value
-  let value: AttributeValue = undefined
-
-  if (/^[\t\r\n\f ]*=/.test(context.source)) {
-    advanceSpaces(context)
-    advanceBy(context, 1)
-    advanceSpaces(context)
-    value = parseAttributeValue(context)
-    if (!value) {
-      emitError(context, ErrorCodes.MISSING_ATTRIBUTE_VALUE)
-    }
-  }
-  const loc = getSelection(context, start)
-
-  if (!context.inVPre && /^(v-[A-Za-z0-9-]|:|\.|@|#)/.test(name)) {
-    const match =
-      /(?:^v-([a-z0-9-]+))?(?:(?::|^\.|^@|^#)(\[[^\]]+\]|[^\.]+))?(.+)?$/i.exec(
-        name
-      )!
-
-    let isPropShorthand = startsWith(name, '.')
-    let dirName =
-      match[1] ||
-      (isPropShorthand || startsWith(name, ':')
-        ? 'bind'
-        : startsWith(name, '@')
-          ? 'on'
-          : 'slot')
-    let arg: ExpressionNode | undefined
-
-    if (match[2]) {
-      const isSlot = dirName === 'slot'
-      const startOffset = name.lastIndexOf(
-        match[2],
-        name.length - (match[3]?.length || 0)
-      )
-      const loc = getSelection(
-        context,
-        getNewPosition(context, start, startOffset),
-        getNewPosition(
-          context,
-          start,
-          startOffset + match[2].length + ((isSlot && match[3]) || '').length
-        )
-      )
-      let content = match[2]
-      let isStatic = true
-
-      if (content.startsWith('[')) {
-        isStatic = false
-
-        if (!content.endsWith(']')) {
-          emitError(
-            context,
-            ErrorCodes.X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END
-          )
-          content = content.slice(1)
-        } else {
-          content = content.slice(1, content.length - 1)
-        }
-      } else if (isSlot) {
-        // #1241 special case for v-slot: vuetify relies extensively on slot
-        // names containing dots. v-slot doesn't have any modifiers and Vue 2.x
-        // supports such usage so we are keeping it consistent with 2.x.
-        content += match[3] || ''
-      }
-
-      arg = {
-        type: NodeTypes.SIMPLE_EXPRESSION,
-        content,
-        isStatic,
-        constType: isStatic
-          ? ConstantTypes.CAN_STRINGIFY
-          : ConstantTypes.NOT_CONSTANT,
-        loc
-      }
-    }
-
-    if (value && value.isQuoted) {
-      const valueLoc = value.loc
-      valueLoc.start.offset++
-      valueLoc.start.column++
-      valueLoc.end = advancePositionWithClone(valueLoc.start, value.content)
-      valueLoc.source = valueLoc.source.slice(1, -1)
-    }
-
-    const modifiers = match[3] ? match[3].slice(1).split('.') : []
-    if (isPropShorthand) modifiers.push('prop')
-
-    // 2.x compat v-bind:foo.sync -> v-model:foo
-    if (__COMPAT__ && dirName === 'bind' && arg) {
-      if (
-        modifiers.includes('sync') &&
-        checkCompatEnabled(
-          CompilerDeprecationTypes.COMPILER_V_BIND_SYNC,
-          context,
-          loc,
-          arg.loc.source
-        )
-      ) {
-        dirName = 'model'
-        modifiers.splice(modifiers.indexOf('sync'), 1)
-      }
-
-      if (__DEV__ && modifiers.includes('prop')) {
-        checkCompatEnabled(
-          CompilerDeprecationTypes.COMPILER_V_BIND_PROP,
-          context,
-          loc
-        )
-      }
-    }
-
-    return {
-      type: NodeTypes.DIRECTIVE,
-      name: dirName,
-      exp: value && {
-        type: NodeTypes.SIMPLE_EXPRESSION,
-        content: value.content,
-        isStatic: false,
-        // Treat as non-constant by default. This can be potentially set to
-        // other values by `transformExpression` to make it eligible for hoisting.
-        constType: ConstantTypes.NOT_CONSTANT,
-        loc: value.loc
-      },
-      arg,
-      modifiers,
-      loc
-    }
-  }
-
-  // missing directive name or illegal directive name
-  if (!context.inVPre && startsWith(name, 'v-')) {
-    emitError(context, ErrorCodes.X_MISSING_DIRECTIVE_NAME)
-  }
-
-  return {
-    type: NodeTypes.ATTRIBUTE,
-    name,
-    value: value && {
-      type: NodeTypes.TEXT,
-      content: value.content,
-      loc: value.loc
-    },
-    loc
-  }
-}
-
-function parseAttributeValue(context: ParserContext): AttributeValue {
-  const start = getCursor(context)
-  let content: string
-
-  const quote = context.source[0]
-  const isQuoted = quote === `"` || quote === `'`
-  if (isQuoted) {
-    // Quoted value.
-    advanceBy(context, 1)
-
-    const endIndex = context.source.indexOf(quote)
-    if (endIndex === -1) {
-      content = parseTextData(
-        context,
-        context.source.length,
-        TextModes.ATTRIBUTE_VALUE
-      )
-    } else {
-      content = parseTextData(context, endIndex, TextModes.ATTRIBUTE_VALUE)
-      advanceBy(context, 1)
-    }
-  } else {
-    // Unquoted
-    const match = /^[^\t\r\n\f >]+/.exec(context.source)
-    if (!match) {
-      return undefined
-    }
-    const unexpectedChars = /["'<=`]/g
-    let m: RegExpExecArray | null
-    while ((m = unexpectedChars.exec(match[0]))) {
-      emitError(
-        context,
-        ErrorCodes.UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE,
-        m.index
-      )
-    }
-    content = parseTextData(context, match[0].length, TextModes.ATTRIBUTE_VALUE)
-  }
-
-  return { content, isQuoted, loc: getSelection(context, start) }
-}
-
-function parseInterpolation(
-  context: ParserContext,
-  mode: TextModes
-): InterpolationNode | undefined {
-  const [open, close] = context.options.delimiters
-  __TEST__ && assert(startsWith(context.source, open))
-
-  const closeIndex = context.source.indexOf(close, open.length)
-  if (closeIndex === -1) {
-    emitError(context, ErrorCodes.X_MISSING_INTERPOLATION_END)
-    return undefined
-  }
-
-  const start = getCursor(context)
-  advanceBy(context, open.length)
-  const innerStart = getCursor(context)
-  const innerEnd = getCursor(context)
-  const rawContentLength = closeIndex - open.length
-  const rawContent = context.source.slice(0, rawContentLength)
-  const preTrimContent = parseTextData(context, rawContentLength, mode)
-  const content = preTrimContent.trim()
-  const startOffset = preTrimContent.indexOf(content)
-  if (startOffset > 0) {
-    advancePositionWithMutation(innerStart, rawContent, startOffset)
-  }
-  const endOffset =
-    rawContentLength - (preTrimContent.length - content.length - startOffset)
-  advancePositionWithMutation(innerEnd, rawContent, endOffset)
-  advanceBy(context, close.length)
-
-  return {
-    type: NodeTypes.INTERPOLATION,
-    content: {
-      type: NodeTypes.SIMPLE_EXPRESSION,
-      isStatic: false,
-      // Set `isConstant` to false by default and will decide in transformExpression
-      constType: ConstantTypes.NOT_CONSTANT,
-      content,
-      loc: getSelection(context, innerStart, innerEnd)
-    },
-    loc: getSelection(context, start)
-  }
-}
-
-function parseText(context: ParserContext, mode: TextModes): TextNode {
-  __TEST__ && assert(context.source.length > 0)
-
-  const endTokens =
-    mode === TextModes.CDATA ? [']]>'] : ['<', context.options.delimiters[0]]
-
-  let endIndex = context.source.length
-  for (let i = 0; i < endTokens.length; i++) {
-    const index = context.source.indexOf(endTokens[i], 1)
-    if (index !== -1 && endIndex > index) {
-      endIndex = index
-    }
-  }
-
-  __TEST__ && assert(endIndex > 0)
-
-  const start = getCursor(context)
-  const content = parseTextData(context, endIndex, mode)
-
-  return {
-    type: NodeTypes.TEXT,
-    content,
-    loc: getSelection(context, start)
-  }
-}
-
-/**
- * Get text data with a given length from the current location.
- * This translates HTML entities in the text data.
- */
-function parseTextData(
-  context: ParserContext,
-  length: number,
-  mode: TextModes
-): string {
-  const rawText = context.source.slice(0, length)
-  advanceBy(context, length)
-  if (
-    mode === TextModes.RAWTEXT ||
-    mode === TextModes.CDATA ||
-    !rawText.includes('&')
-  ) {
-    return rawText
-  } else {
-    // DATA or RCDATA containing "&". Entity decoding is required.
-    return context.options.decodeEntities(
-      rawText,
-      mode === TextModes.ATTRIBUTE_VALUE
-    )
-  }
-}
-
-function getCursor(context: ParserContext): Position {
-  const { column, line, offset } = context
-  return { column, line, offset }
-}
-
-function getSelection(
-  context: ParserContext,
-  start: Position,
-  end?: Position
-): SourceLocation {
-  end = end || getCursor(context)
-  return {
-    start,
-    end,
-    source: context.originalSource.slice(start.offset, end.offset)
-  }
-}
-
-function last<T>(xs: T[]): T | undefined {
-  return xs[xs.length - 1]
-}
-
-function startsWith(source: string, searchString: string): boolean {
-  return source.startsWith(searchString)
-}
-
-function advanceBy(context: ParserContext, numberOfCharacters: number): void {
-  const { source } = context
-  __TEST__ && assert(numberOfCharacters <= source.length)
-  advancePositionWithMutation(context, source, numberOfCharacters)
-  context.source = source.slice(numberOfCharacters)
-}
-
-function advanceSpaces(context: ParserContext): void {
-  const match = /^[\t\r\n\f ]+/.exec(context.source)
-  if (match) {
-    advanceBy(context, match[0].length)
-  }
-}
-
-function getNewPosition(
-  context: ParserContext,
-  start: Position,
-  numberOfCharacters: number
-): Position {
-  return advancePositionWithClone(
-    start,
-    context.originalSource.slice(start.offset, numberOfCharacters),
-    numberOfCharacters
-  )
-}
-
-function emitError(
-  context: ParserContext,
-  code: ErrorCodes,
-  offset?: number,
-  loc: Position = getCursor(context)
-): void {
-  if (offset) {
-    loc.offset += offset
-    loc.column += offset
-  }
-  context.options.onError(
-    createCompilerError(code, {
-      start: loc,
-      end: loc,
-      source: ''
-    })
-  )
-}
-
-function isEnd(
-  context: ParserContext,
-  mode: TextModes,
-  ancestors: ElementNode[]
-): boolean {
-  const s = context.source
-
-  switch (mode) {
-    case TextModes.DATA:
-      if (startsWith(s, '</')) {
-        // TODO: probably bad performance
-        for (let i = ancestors.length - 1; i >= 0; --i) {
-          if (startsWithEndTagOpen(s, ancestors[i].tag)) {
-            return true
-          }
-        }
-      }
-      break
-
-    case TextModes.RCDATA:
-    case TextModes.RAWTEXT: {
-      const parent = last(ancestors)
-      if (parent && startsWithEndTagOpen(s, parent.tag)) {
-        return true
-      }
-      break
-    }
-
-    case TextModes.CDATA:
-      if (startsWith(s, ']]>')) {
-        return true
-      }
-      break
-  }
-
-  return !s
-}
-
-function startsWithEndTagOpen(source: string, tag: string): boolean {
-  return (
-    startsWith(source, '</') &&
-    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase() &&
-    /[\t\r\n\f />]/.test(source[2 + tag.length] || '>')
-  )
-}
index 7447f535045bd427864ac1f83440635883d9c821..02443992e6c55c03b219e1ce4acec597408262c1 100644 (file)
@@ -5,13 +5,15 @@ import {
   DirectiveNode,
   ElementNode,
   ElementTypes,
+  ForParseResult,
   Namespaces,
   NodeTypes,
   RootNode,
   SimpleExpressionNode,
   SourceLocation,
   TemplateChildNode,
-  createRoot
+  createRoot,
+  createSimpleExpression
 } from '../ast'
 import { ParserOptions } from '../options'
 import Tokenizer, {
@@ -24,13 +26,12 @@ import Tokenizer, {
 import { CompilerCompatOptions } from '../compat/compatConfig'
 import { NO, extend } from '@vue/shared'
 import { defaultOnError, defaultOnWarn } from '../errors'
-import { isCoreComponent } from '../utils'
+import { forAliasRE, isCoreComponent } from '../utils'
 
 type OptionalOptions =
   | 'whitespace'
   | 'isNativeTag'
   | 'isBuiltInComponent'
-  | 'getTextMode'
   | keyof CompilerCompatOptions
 
 type MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &
@@ -64,7 +65,7 @@ export const defaultParserOptions: MergedParserOptions = {
 }
 
 let currentOptions: MergedParserOptions = defaultParserOptions
-let currentRoot: RootNode = createRoot([])
+let currentRoot: RootNode | null = null
 
 // parser state
 let currentInput = ''
@@ -102,14 +103,11 @@ const tokenizer = new Tokenizer(stack, {
     }
     addNode({
       type: NodeTypes.INTERPOLATION,
-      content: {
-        type: NodeTypes.SIMPLE_EXPRESSION,
-        isStatic: false,
-        // Set `isConstant` to false by default and will decide in transformExpression
-        constType: ConstantTypes.NOT_CONSTANT,
-        content: getSlice(innerStart, innerEnd),
-        loc: getLoc(innerStart, innerEnd)
-      },
+      content: createSimpleExpression(
+        getSlice(innerStart, innerEnd),
+        false,
+        getLoc(innerStart, innerEnd)
+      ),
       loc: getLoc(start, end)
     })
   },
@@ -123,12 +121,7 @@ const tokenizer = new Tokenizer(stack, {
       tagType: ElementTypes.ELEMENT, // will be refined on tag close
       props: [],
       children: [],
-      loc: {
-        start: tokenizer.getPos(start - 1),
-        // @ts-expect-error to be attached on tag close
-        end: undefined,
-        source: ''
-      },
+      loc: getLoc(start - 1),
       codegenNode: undefined
     }
     currentAttrs.clear()
@@ -159,6 +152,7 @@ const tokenizer = new Tokenizer(stack, {
     currentProp = {
       type: NodeTypes.ATTRIBUTE,
       name: getSlice(start, end),
+      nameLoc: getLoc(start, end),
       value: undefined,
       loc: getLoc(start)
     }
@@ -170,6 +164,7 @@ const tokenizer = new Tokenizer(stack, {
       currentProp = {
         type: NodeTypes.ATTRIBUTE,
         name: raw,
+        nameLoc: getLoc(start, end),
         value: undefined,
         loc: getLoc(start)
       }
@@ -185,7 +180,7 @@ const tokenizer = new Tokenizer(stack, {
       currentProp = {
         type: NodeTypes.DIRECTIVE,
         name,
-        raw,
+        rawName: raw,
         exp: undefined,
         arg: undefined,
         modifiers: [],
@@ -209,17 +204,15 @@ const tokenizer = new Tokenizer(stack, {
     const arg = getSlice(start, end)
     if (inVPre) {
       ;(currentProp as AttributeNode).name += arg
+      ;(currentProp as AttributeNode).nameLoc.end = tokenizer.getPos(end)
     } else {
       const isStatic = arg[0] !== `[`
-      ;(currentProp as DirectiveNode).arg = {
-        type: NodeTypes.SIMPLE_EXPRESSION,
-        content: arg,
+      ;(currentProp as DirectiveNode).arg = createSimpleExpression(
+        arg,
         isStatic,
-        constType: isStatic
-          ? ConstantTypes.CAN_STRINGIFY
-          : ConstantTypes.NOT_CONSTANT,
-        loc: getLoc(start, end)
-      }
+        getLoc(start, end),
+        isStatic ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT
+      )
     }
   },
 
@@ -227,6 +220,7 @@ const tokenizer = new Tokenizer(stack, {
     const mod = getSlice(start, end)
     if (inVPre) {
       ;(currentProp as AttributeNode).name += '.' + mod
+      ;(currentProp as AttributeNode).nameLoc.end = tokenizer.getPos(end)
     } else {
       ;(currentProp as DirectiveNode).modifiers.push(mod)
     }
@@ -247,7 +241,7 @@ const tokenizer = new Tokenizer(stack, {
     const start = currentProp!.loc.start.offset
     const name = getSlice(start, end)
     if (currentProp!.type === NodeTypes.DIRECTIVE) {
-      currentProp!.raw = name
+      currentProp!.rawName = name
     }
     if (currentAttrs.has(name)) {
       currentProp = null
@@ -273,15 +267,14 @@ const tokenizer = new Tokenizer(stack, {
           }
         } else {
           // directive
-          currentProp.exp = {
-            type: NodeTypes.SIMPLE_EXPRESSION,
-            content: currentAttrValue,
-            isStatic: false,
-            // Treat as non-constant by default. This can be potentially set
-            // to other values by `transformExpression` to make it eligible
-            // for hoisting.
-            constType: ConstantTypes.NOT_CONSTANT,
-            loc: getLoc(currentAttrStartIndex, currentAttrEndIndex)
+          currentProp.rawExp = currentAttrValue
+          currentProp.exp = createSimpleExpression(
+            currentAttrValue,
+            false,
+            getLoc(currentAttrStartIndex, currentAttrEndIndex)
+          )
+          if (currentProp.name === 'for') {
+            currentProp.forParseResult = parseForExpression(currentProp.exp)
           }
         }
       }
@@ -319,6 +312,73 @@ const tokenizer = new Tokenizer(stack, {
   }
 })
 
+// This regex doesn't cover the case if key or index aliases have destructuring,
+// but those do not make sense in the first place, so this works in practice.
+const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
+const stripParensRE = /^\(|\)$/g
+
+function parseForExpression(
+  input: SimpleExpressionNode
+): ForParseResult | undefined {
+  const loc = input.loc
+  const exp = input.content
+  const inMatch = exp.match(forAliasRE)
+  if (!inMatch) return
+
+  const [, LHS, RHS] = inMatch
+
+  const createAliasExpression = (content: string, offset: number) => {
+    const start = loc.start.offset + offset
+    const end = start + content.length
+    return createSimpleExpression(content, false, getLoc(start, end))
+  }
+
+  const result: ForParseResult = {
+    source: createAliasExpression(RHS.trim(), exp.indexOf(RHS, LHS.length)),
+    value: undefined,
+    key: undefined,
+    index: undefined,
+    finalized: false
+  }
+
+  let valueContent = LHS.trim().replace(stripParensRE, '').trim()
+  const trimmedOffset = LHS.indexOf(valueContent)
+
+  const iteratorMatch = valueContent.match(forIteratorRE)
+  if (iteratorMatch) {
+    valueContent = valueContent.replace(forIteratorRE, '').trim()
+
+    const keyContent = iteratorMatch[1].trim()
+    let keyOffset: number | undefined
+    if (keyContent) {
+      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length)
+      result.key = createAliasExpression(keyContent, keyOffset)
+    }
+
+    if (iteratorMatch[2]) {
+      const indexContent = iteratorMatch[2].trim()
+
+      if (indexContent) {
+        result.index = createAliasExpression(
+          indexContent,
+          exp.indexOf(
+            indexContent,
+            result.key
+              ? keyOffset! + keyContent.length
+              : trimmedOffset + valueContent.length
+          )
+        )
+      }
+    }
+  }
+
+  if (valueContent) {
+    result.value = createAliasExpression(valueContent, trimmedOffset)
+  }
+
+  return result
+}
+
 function getSlice(start: number, end: number) {
   return currentInput.slice(start, end)
 }
@@ -356,11 +416,7 @@ function onText(content: string, start: number, end: number) {
     parent.children.push({
       type: NodeTypes.TEXT,
       content,
-      loc: {
-        start: tokenizer.getPos(start),
-        end: tokenizer.getPos(end),
-        source: ''
-      }
+      loc: getLoc(start, end)
     })
   }
 }
@@ -403,7 +459,7 @@ function isFragmentTemplate({ tag, props }: ElementNode): boolean {
     for (let i = 0; i < props.length; i++) {
       if (
         props[i].type === NodeTypes.DIRECTIVE &&
-        specialTemplateDir.has(props[i].name)
+        specialTemplateDir.has((props[i] as DirectiveNode).name)
       ) {
         return true
       }
@@ -571,7 +627,11 @@ function getLoc(start: number, end?: number): SourceLocation {
 function dirToAttr(dir: DirectiveNode): AttributeNode {
   const attr: AttributeNode = {
     type: NodeTypes.ATTRIBUTE,
-    name: dir.raw!,
+    name: dir.rawName!,
+    nameLoc: getLoc(
+      dir.loc.start.offset,
+      dir.loc.start.offset + dir.rawName!.length
+    ),
     value: undefined,
     loc: dir.loc
   }
@@ -622,9 +682,9 @@ export function baseParse(input: string, options?: ParserOptions): RootNode {
     tokenizer.delimiterClose = toCharCodes(delimiters[1])
   }
 
-  const root = (currentRoot = createRoot([]))
+  const root = (currentRoot = createRoot([], input))
   tokenizer.parse(currentInput)
-  root.loc.end = tokenizer.getPos(input.length)
   root.children = condenseWhitespace(root.children)
+  currentRoot = null
   return root
 }
index fd61f011051e82539552f151539b5ab3471166a5..1d4d2f1f134ef0874f13badee0ac801c7e9ea2c4 100644 (file)
@@ -49,7 +49,6 @@ import {
   GUARD_REACTIVE_PROPS
 } from '../runtimeHelpers'
 import {
-  getInnerRange,
   toValidAssetId,
   findProp,
   isCoreComponent,
@@ -160,8 +159,7 @@ export const transformElement: NodeTransform = (node, context) => {
           context.onError(
             createCompilerError(ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN, {
               start: node.children[0].loc.start,
-              end: node.children[node.children.length - 1].loc.end,
-              source: ''
+              end: node.children[node.children.length - 1].loc.end
             })
           )
         }
@@ -489,7 +487,7 @@ export function buildProps(
     // static attribute
     const prop = props[i]
     if (prop.type === NodeTypes.ATTRIBUTE) {
-      const { loc, name, value } = prop
+      const { loc, name, nameLoc, value } = prop
       let isStatic = true
       if (name === 'ref') {
         hasRef = true
@@ -536,11 +534,7 @@ export function buildProps(
       }
       properties.push(
         createObjectProperty(
-          createSimpleExpression(
-            name,
-            true,
-            getInnerRange(loc, 0, name.length)
-          ),
+          createSimpleExpression(name, true, nameLoc),
           createSimpleExpression(
             value ? value.content : '',
             isStatic,
index 52d1fb42a1354402fd1d2e5f86a3681b92b475b8..22d7a43ed8f6543ff923e7c0a686f72f058980f8 100644 (file)
@@ -336,7 +336,6 @@ export function processExpression(
         id.name,
         false,
         {
-          source,
           start: advancePositionWithClone(node.loc.start, source, start),
           end: advancePositionWithClone(node.loc.start, source, end)
         },
index 3f725b71b2b4820fcdb518b8d2d0c1cc81c48845..89340a6aa3283af4348c400409081a5adfe39b4b 100644 (file)
@@ -6,7 +6,6 @@ import {
   NodeTypes,
   ExpressionNode,
   createSimpleExpression,
-  SourceLocation,
   SimpleExpressionNode,
   createCallExpression,
   createFunctionExpression,
@@ -28,17 +27,16 @@ import {
   createBlockStatement,
   createCompoundExpression,
   getVNodeBlockHelper,
-  getVNodeHelper
+  getVNodeHelper,
+  ForParseResult
 } from '../ast'
 import { createCompilerError, ErrorCodes } from '../errors'
 import {
-  getInnerRange,
   findProp,
   isTemplateNode,
   isSlotOutlet,
   injectProp,
-  findDir,
-  forAliasRE
+  findDir
 } from '../utils'
 import {
   RENDER_LIST,
@@ -256,12 +254,7 @@ export function processFor(
     return
   }
 
-  const parseResult = parseForExpression(
-    // can only be simple expression because vFor transform is applied
-    // before expression transform.
-    dir.exp as SimpleExpressionNode,
-    context
-  )
+  const parseResult = dir.forParseResult
 
   if (!parseResult) {
     context.onError(
@@ -270,6 +263,8 @@ export function processFor(
     return
   }
 
+  finalizeForParseResult(parseResult, context)
+
   const { addIdentifiers, removeIdentifiers, scopes } = context
   const { source, value, key, index } = parseResult
 
@@ -309,128 +304,50 @@ export function processFor(
   }
 }
 
-// This regex doesn't cover the case if key or index aliases have destructuring,
-// but those do not make sense in the first place, so this works in practice.
-const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
-const stripParensRE = /^\(|\)$/g
-
-export interface ForParseResult {
-  source: ExpressionNode
-  value: ExpressionNode | undefined
-  key: ExpressionNode | undefined
-  index: ExpressionNode | undefined
-}
-
-export function parseForExpression(
-  input: SimpleExpressionNode,
+export function finalizeForParseResult(
+  result: ForParseResult,
   context: TransformContext
-): ForParseResult | undefined {
-  const loc = input.loc
-  const exp = input.content
-  const inMatch = exp.match(forAliasRE)
-  if (!inMatch) return
-
-  const [, LHS, RHS] = inMatch
+) {
+  if (result.finalized) return
 
-  const result: ForParseResult = {
-    source: createAliasExpression(
-      loc,
-      RHS.trim(),
-      exp.indexOf(RHS, LHS.length)
-    ),
-    value: undefined,
-    key: undefined,
-    index: undefined
-  }
   if (!__BROWSER__ && context.prefixIdentifiers) {
     result.source = processExpression(
       result.source as SimpleExpressionNode,
       context
     )
-  }
-  if (__DEV__ && __BROWSER__) {
-    validateBrowserExpression(result.source as SimpleExpressionNode, context)
-  }
-
-  let valueContent = LHS.trim().replace(stripParensRE, '').trim()
-  const trimmedOffset = LHS.indexOf(valueContent)
-
-  const iteratorMatch = valueContent.match(forIteratorRE)
-  if (iteratorMatch) {
-    valueContent = valueContent.replace(forIteratorRE, '').trim()
-
-    const keyContent = iteratorMatch[1].trim()
-    let keyOffset: number | undefined
-    if (keyContent) {
-      keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length)
-      result.key = createAliasExpression(loc, keyContent, keyOffset)
-      if (!__BROWSER__ && context.prefixIdentifiers) {
-        result.key = processExpression(result.key, context, true)
-      }
-      if (__DEV__ && __BROWSER__) {
-        validateBrowserExpression(
-          result.key as SimpleExpressionNode,
-          context,
-          true
-        )
-      }
+    if (result.key) {
+      result.key = processExpression(
+        result.key as SimpleExpressionNode,
+        context,
+        true
+      )
     }
-
-    if (iteratorMatch[2]) {
-      const indexContent = iteratorMatch[2].trim()
-
-      if (indexContent) {
-        result.index = createAliasExpression(
-          loc,
-          indexContent,
-          exp.indexOf(
-            indexContent,
-            result.key
-              ? keyOffset! + keyContent.length
-              : trimmedOffset + valueContent.length
-          )
-        )
-        if (!__BROWSER__ && context.prefixIdentifiers) {
-          result.index = processExpression(result.index, context, true)
-        }
-        if (__DEV__ && __BROWSER__) {
-          validateBrowserExpression(
-            result.index as SimpleExpressionNode,
-            context,
-            true
-          )
-        }
-      }
+    if (result.index) {
+      result.index = processExpression(
+        result.index as SimpleExpressionNode,
+        context,
+        true
+      )
     }
   }
-
-  if (valueContent) {
-    result.value = createAliasExpression(loc, valueContent, trimmedOffset)
-    if (!__BROWSER__ && context.prefixIdentifiers) {
-      result.value = processExpression(result.value, context, true)
+  if (__DEV__ && __BROWSER__) {
+    validateBrowserExpression(result.source as SimpleExpressionNode, context)
+    if (result.key) {
+      validateBrowserExpression(
+        result.key as SimpleExpressionNode,
+        context,
+        true
+      )
     }
-    if (__DEV__ && __BROWSER__) {
+    if (result.index) {
       validateBrowserExpression(
-        result.value as SimpleExpressionNode,
+        result.index as SimpleExpressionNode,
         context,
         true
       )
     }
   }
-
-  return result
-}
-
-function createAliasExpression(
-  range: SourceLocation,
-  content: string,
-  offset: number
-): SimpleExpressionNode {
-  return createSimpleExpression(
-    content,
-    false,
-    getInnerRange(range, offset, content.length)
-  )
+  result.finalized = true
 }
 
 export function createForLoopParams(
index 3f13f32709837bdc4099ff31a6a48d16cc56e3a6..3e0e1be29cdfb1a0594d80fa30034daf6b78a83e 100644 (file)
@@ -29,7 +29,9 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
     return createTransformProps()
   }
 
-  const rawExp = exp.loc.source
+  // we assume v-model directives are always parsed
+  // (not artificially created by a transform)
+  const rawExp = dir.rawExp!
   const expString =
     exp.type === NodeTypes.SIMPLE_EXPRESSION ? exp.content : rawExp
 
index 156a8461abcc9a73887683fc3e356d02ba9c2910..fac861b20b26ef91ae39bd895984548ef9db3a40 100644 (file)
@@ -14,7 +14,6 @@ import {
   SourceLocation,
   createConditionalExpression,
   ConditionalExpression,
-  SimpleExpressionNode,
   FunctionExpression,
   CallExpression,
   createCallExpression,
@@ -32,7 +31,7 @@ import {
   isStaticExp
 } from '../utils'
 import { CREATE_SLOTS, RENDER_LIST, WITH_CTX } from '../runtimeHelpers'
-import { parseForExpression, createForLoopParams } from './vFor'
+import { createForLoopParams, finalizeForParseResult } from './vFor'
 import { SlotFlags, slotFlagsText } from '@vue/shared'
 
 const defaultFallback = createSimpleExpression(`undefined`, false)
@@ -78,11 +77,9 @@ export const trackVForSlotScopes: NodeTransform = (node, context) => {
     node.props.some(isVSlot) &&
     (vFor = findDir(node, 'for'))
   ) {
-    const result = (vFor.parseResult = parseForExpression(
-      vFor.exp as SimpleExpressionNode,
-      context
-    ))
+    const result = vFor.forParseResult
     if (result) {
+      finalizeForParseResult(result, context)
       const { value, key, index } = result
       const { addIdentifiers, removeIdentifiers } = context
       value && addIdentifiers(value)
@@ -266,10 +263,9 @@ export function buildSlots(
       }
     } else if (vFor) {
       hasDynamicSlots = true
-      const parseResult =
-        vFor.parseResult ||
-        parseForExpression(vFor.exp as SimpleExpressionNode, context)
+      const parseResult = vFor.forParseResult
       if (parseResult) {
+        finalizeForParseResult(parseResult, context)
         // Render the dynamic slots as an array and add it to the createSlot()
         // args. The runtime knows how to handle it appropriately.
         dynamicSlots.push(
index ddb4b7f8d51247f25a6722ac568fde3bfdb18724..7e3eb46a81675b179891b67d8843773051d224df 100644 (file)
@@ -1,5 +1,4 @@
 import {
-  SourceLocation,
   Position,
   ElementNode,
   NodeTypes,
@@ -176,31 +175,6 @@ export const isMemberExpression = __BROWSER__
   ? isMemberExpressionBrowser
   : isMemberExpressionNode
 
-export function getInnerRange(
-  loc: SourceLocation,
-  offset: number,
-  length: number
-): SourceLocation {
-  __TEST__ && assert(offset <= loc.source.length)
-  const source = loc.source.slice(offset, offset + length)
-  const newLoc: SourceLocation = {
-    source,
-    start: advancePositionWithClone(loc.start, loc.source, offset),
-    end: loc.end
-  }
-
-  if (length != null) {
-    __TEST__ && assert(offset + length <= loc.source.length)
-    newLoc.end = advancePositionWithClone(
-      loc.start,
-      loc.source,
-      offset + length
-    )
-  }
-
-  return newLoc
-}
-
 export function advancePositionWithClone(
   pos: Position,
   source: string,
index 67b8f10b02e2050b153fe26a6be6f8238f7b09fd..3a298835d06e161b06f9bfe4bb8438ee57fd2993 100644 (file)
@@ -1,9 +1,4 @@
-import {
-  TextModes,
-  ParserOptions,
-  ElementNode,
-  NodeTypes
-} from '@vue/compiler-core'
+import { ParserOptions, ElementNode, NodeTypes } from '@vue/compiler-core'
 import { isVoidTag, isHTMLTag, isSVGTag } from '@vue/shared'
 import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
 import { decodeHtml } from './decodeHtml'
@@ -16,6 +11,7 @@ export const enum DOMNamespaces {
 }
 
 export const parserOptions: ParserOptions = {
+  parseMode: 'html',
   isVoidTag,
   isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag),
   isPreTag: tag => tag === 'pre',
@@ -75,18 +71,5 @@ export const parserOptions: ParserOptions = {
       }
     }
     return ns
-  },
-
-  // https://html.spec.whatwg.org/multipage/parsing.html#parsing-html-fragments
-  getTextMode({ tag, ns }: ElementNode): TextModes {
-    if (ns === DOMNamespaces.HTML) {
-      if (tag === 'textarea' || tag === 'title') {
-        return TextModes.RCDATA
-      }
-      if (tag === 'style' || tag === 'script') {
-        return TextModes.RAWTEXT
-      }
-    }
-    return TextModes.DATA
   }
 }
index 56d05ef03c12938cc7381edb5785cc3b3cd90ba0..d9e6b988d77296eb8413811b2f6d998f0e760bc5 100644 (file)
@@ -27,8 +27,7 @@ export const transformTransition: NodeTransform = (node, context) => {
               DOMErrorCodes.X_TRANSITION_INVALID_CHILDREN,
               {
                 start: node.children[0].loc.start,
-                end: node.children[node.children.length - 1].loc.end,
-                source: ''
+                end: node.children[node.children.length - 1].loc.end
               }
             )
           )
@@ -43,6 +42,7 @@ export const transformTransition: NodeTransform = (node, context) => {
               node.props.push({
                 type: NodeTypes.ATTRIBUTE,
                 name: 'persisted',
+                nameLoc: node.loc,
                 value: undefined,
                 loc: node.loc
               })
index da8cf6d866948ced76f02aae18463d0f5d092833..3612d5a393e9b8316597de7309197f1c9d3bc1eb 100644 (file)
@@ -3,7 +3,6 @@ import {
   ElementNode,
   SourceLocation,
   CompilerError,
-  TextModes,
   BindingMetadata
 } from '@vue/compiler-core'
 import * as CompilerDOM from '@vue/compiler-dom'
@@ -128,31 +127,7 @@ export function parse(
 
   const errors: (CompilerError | SyntaxError)[] = []
   const ast = compiler.parse(source, {
-    // there are no components at SFC parsing level
-    isNativeTag: () => true,
-    // preserve all whitespaces
-    isPreTag: () => true,
-    getTextMode: ({ tag, props }, parent) => {
-      // all top level elements except <template> are parsed as raw text
-      // containers
-      if (
-        (!parent && tag !== 'template') ||
-        // <template lang="xxx"> should also be treated as raw text
-        (tag === 'template' &&
-          props.some(
-            p =>
-              p.type === NodeTypes.ATTRIBUTE &&
-              p.name === 'lang' &&
-              p.value &&
-              p.value.content &&
-              p.value.content !== 'html'
-          ))
-      ) {
-        return TextModes.RAWTEXT
-      } else {
-        return TextModes.DATA
-      }
-    },
+    parseMode: 'sfc',
     onError: e => {
       errors.push(e)
     }
@@ -188,7 +163,9 @@ export function parse(
                 `difference from stateful ones. Just use a normal <template> ` +
                 `instead.`
             ) as CompilerError
-            err.loc = node.props.find(p => p.name === 'functional')!.loc
+            err.loc = node.props.find(
+              p => p.type === NodeTypes.ATTRIBUTE && p.name === 'functional'
+            )!.loc
             errors.push(err)
           }
         } else {
@@ -314,7 +291,7 @@ function createBlock(
     end = node.children[node.children.length - 1].loc.end
     content = source.slice(start.offset, end.offset)
   } else {
-    const offset = node.loc.source.indexOf(`</`)
+    const offset = source.indexOf(`</`, start.offset)
     if (offset > -1) {
       start = {
         line: start.line,
@@ -341,18 +318,19 @@ function createBlock(
   }
   node.props.forEach(p => {
     if (p.type === NodeTypes.ATTRIBUTE) {
-      attrs[p.name] = p.value ? p.value.content || true : true
-      if (p.name === 'lang') {
+      const name = p.name
+      attrs[name] = p.value ? p.value.content || true : true
+      if (name === 'lang') {
         block.lang = p.value && p.value.content
-      } else if (p.name === 'src') {
+      } else if (name === 'src') {
         block.src = p.value && p.value.content
       } else if (type === 'style') {
-        if (p.name === 'scoped') {
+        if (name === 'scoped') {
           ;(block as SFCStyleBlock).scoped = true
-        } else if (p.name === 'module') {
-          ;(block as SFCStyleBlock).module = attrs[p.name]
+        } else if (name === 'module') {
+          ;(block as SFCStyleBlock).module = attrs[name]
         }
-      } else if (type === 'script' && p.name === 'setup') {
+      } else if (type === 'script' && name === 'setup') {
         ;(block as SFCScriptBlock).setup = attrs.setup
       }
     }
index 2c42a83ba3708bf639f9ce78273abc907158550a..19259f36f2dcaca1fd83e5880be26d39e2a68f5c 100644 (file)
@@ -292,14 +292,15 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
         }
       } else {
         // special case: value on <textarea>
-        if (node.tag === 'textarea' && prop.name === 'value' && prop.value) {
+        const name = prop.name
+        if (node.tag === 'textarea' && name === 'value' && prop.value) {
           rawChildrenMap.set(node, escapeHtml(prop.value.content))
         } else if (!needMergeProps) {
-          if (prop.name === 'key' || prop.name === 'ref') {
+          if (name === 'key' || name === 'ref') {
             continue
           }
           // static prop
-          if (prop.name === 'class' && prop.value) {
+          if (name === 'class' && prop.value) {
             staticClassBinding = JSON.stringify(prop.value.content)
           }
           openTag.push(
index c00103bfee4df4a321afc63be90d670e7447c581..8215be7476ea1d7936d9c80129fca9edae26246d 100644 (file)
@@ -91,5 +91,3 @@ registerRuntimeCompiler(compileToFunction)
 
 export { compileToFunction as compile }
 export * from '@vue/runtime-dom'
-
-export { newParse } from '@vue/compiler-dom'