]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor: expose parse in compiler-dom, improve sfc parse error handling
authorEvan You <yyx990803@gmail.com>
Mon, 23 Dec 2019 00:44:21 +0000 (19:44 -0500)
committerEvan You <yyx990803@gmail.com>
Mon, 23 Dec 2019 00:44:21 +0000 (19:44 -0500)
33 files changed:
packages/compiler-core/__tests__/parse.spec.ts
packages/compiler-core/__tests__/transform.spec.ts
packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts
packages/compiler-core/__tests__/transforms/transformElement.spec.ts
packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts
packages/compiler-core/__tests__/transforms/transformSlotOutlet.spec.ts
packages/compiler-core/__tests__/transforms/transformText.spec.ts
packages/compiler-core/__tests__/transforms/vBind.spec.ts
packages/compiler-core/__tests__/transforms/vFor.spec.ts
packages/compiler-core/__tests__/transforms/vIf.spec.ts
packages/compiler-core/__tests__/transforms/vModel.spec.ts
packages/compiler-core/__tests__/transforms/vOn.spec.ts
packages/compiler-core/__tests__/transforms/vOnce.spec.ts
packages/compiler-core/__tests__/transforms/vSlot.spec.ts
packages/compiler-core/src/compile.ts
packages/compiler-core/src/index.ts
packages/compiler-core/src/parse.ts
packages/compiler-dom/__tests__/parse.spec.ts
packages/compiler-dom/__tests__/transforms/transformStyle.spec.ts
packages/compiler-dom/__tests__/transforms/vCloak.spec.ts
packages/compiler-dom/__tests__/transforms/vHtml.spec.ts
packages/compiler-dom/__tests__/transforms/vModel.spec.ts
packages/compiler-dom/__tests__/transforms/vOn.spec.ts
packages/compiler-dom/__tests__/transforms/vShow.spec.ts
packages/compiler-dom/__tests__/transforms/vText.spec.ts
packages/compiler-dom/src/index.ts
packages/compiler-sfc/__tests__/compileTemplate.spec.ts
packages/compiler-sfc/__tests__/parse.spec.ts
packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts
packages/compiler-sfc/__tests__/templateTransformSrcset.spec.ts
packages/compiler-sfc/src/compileTemplate.ts
packages/compiler-sfc/src/index.ts
packages/compiler-sfc/src/parse.ts

index 72885c224acbaf230ab4daabbefd64dc7dc02697..037e9377f819cdedc9cd73238b73d8a9fcac43bf 100644 (file)
@@ -1,5 +1,5 @@
 import { ParserOptions } from '../src/options'
-import { parse, TextModes } from '../src/parse'
+import { baseParse, TextModes } from '../src/parse'
 import { ErrorCodes } from '../src/errors'
 import {
   CommentNode,
@@ -16,7 +16,7 @@ import {
 describe('compiler: parse', () => {
   describe('Text', () => {
     test('simple text', () => {
-      const ast = parse('some text')
+      const ast = baseParse('some text')
       const text = ast.children[0] as TextNode
 
       expect(text).toStrictEqual({
@@ -31,7 +31,7 @@ describe('compiler: parse', () => {
     })
 
     test('simple text with invalid end tag', () => {
-      const ast = parse('some text</div>', {
+      const ast = baseParse('some text</div>', {
         onError: () => {}
       })
       const text = ast.children[0] as TextNode
@@ -48,7 +48,7 @@ describe('compiler: parse', () => {
     })
 
     test('text with interpolation', () => {
-      const ast = parse('some {{ foo + bar }} text')
+      const ast = baseParse('some {{ foo + bar }} text')
       const text1 = ast.children[0] as TextNode
       const text2 = ast.children[2] as TextNode
 
@@ -73,7 +73,7 @@ describe('compiler: parse', () => {
     })
 
     test('text with interpolation which has `<`', () => {
-      const ast = parse('some {{ a<b && c>d }} text')
+      const ast = baseParse('some {{ a<b && c>d }} text')
       const text1 = ast.children[0] as TextNode
       const text2 = ast.children[2] as TextNode
 
@@ -98,7 +98,7 @@ describe('compiler: parse', () => {
     })
 
     test('text with mix of tags and interpolations', () => {
-      const ast = parse('some <span>{{ foo < bar + foo }} text</span>')
+      const ast = baseParse('some <span>{{ foo < bar + foo }} text</span>')
       const text1 = ast.children[0] as TextNode
       const text2 = (ast.children[1] as ElementNode).children![1] as TextNode
 
@@ -123,7 +123,7 @@ describe('compiler: parse', () => {
     })
 
     test('lonly "<" don\'t separate nodes', () => {
-      const ast = parse('a < b', {
+      const ast = baseParse('a < b', {
         onError: err => {
           if (err.code !== ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME) {
             throw err
@@ -144,7 +144,7 @@ describe('compiler: parse', () => {
     })
 
     test('lonly "{{" don\'t separate nodes', () => {
-      const ast = parse('a {{ b', {
+      const ast = baseParse('a {{ b', {
         onError: error => {
           if (error.code !== ErrorCodes.X_MISSING_INTERPOLATION_END) {
             throw error
@@ -166,7 +166,7 @@ describe('compiler: parse', () => {
 
     test('HTML entities compatibility in text (https://html.spec.whatwg.org/multipage/parsing.html#named-character-reference-state).', () => {
       const spy = jest.fn()
-      const ast = parse('&ampersand;', {
+      const ast = baseParse('&ampersand;', {
         namedCharacterReferences: { amp: '&' },
         onError: spy
       })
@@ -195,7 +195,7 @@ describe('compiler: parse', () => {
 
     test('HTML entities compatibility in attribute (https://html.spec.whatwg.org/multipage/parsing.html#named-character-reference-state).', () => {
       const spy = jest.fn()
-      const ast = parse(
+      const ast = baseParse(
         '<div a="&ampersand;" b="&amp;ersand;" c="&amp!"></div>',
         {
           namedCharacterReferences: { amp: '&', 'amp;': '&' },
@@ -248,7 +248,7 @@ describe('compiler: parse', () => {
 
     test('Some control character reference should be replaced.', () => {
       const spy = jest.fn()
-      const ast = parse('&#x86;', { onError: spy })
+      const ast = baseParse('&#x86;', { onError: spy })
       const text = ast.children[0] as TextNode
 
       expect(text).toStrictEqual({
@@ -275,7 +275,7 @@ describe('compiler: parse', () => {
 
   describe('Interpolation', () => {
     test('simple interpolation', () => {
-      const ast = parse('{{message}}')
+      const ast = baseParse('{{message}}')
       const interpolation = ast.children[0] as InterpolationNode
 
       expect(interpolation).toStrictEqual({
@@ -300,7 +300,7 @@ describe('compiler: parse', () => {
     })
 
     test('it can have tag-like notation', () => {
-      const ast = parse('{{ a<b }}')
+      const ast = baseParse('{{ a<b }}')
       const interpolation = ast.children[0] as InterpolationNode
 
       expect(interpolation).toStrictEqual({
@@ -325,7 +325,7 @@ describe('compiler: parse', () => {
     })
 
     test('it can have tag-like notation (2)', () => {
-      const ast = parse('{{ a<b }}{{ c>d }}')
+      const ast = baseParse('{{ a<b }}{{ c>d }}')
       const interpolation1 = ast.children[0] as InterpolationNode
       const interpolation2 = ast.children[1] as InterpolationNode
 
@@ -371,7 +371,7 @@ describe('compiler: parse', () => {
     })
 
     test('it can have tag-like notation (3)', () => {
-      const ast = parse('<div>{{ "</div>" }}</div>')
+      const ast = baseParse('<div>{{ "</div>" }}</div>')
       const element = ast.children[0] as ElementNode
       const interpolation = element.children[0] as InterpolationNode
 
@@ -398,7 +398,7 @@ describe('compiler: parse', () => {
     })
 
     test('custom delimiters', () => {
-      const ast = parse('<p>{msg}</p>', {
+      const ast = baseParse('<p>{msg}</p>', {
         delimiters: ['{', '}']
       })
       const element = ast.children[0] as ElementNode
@@ -428,7 +428,7 @@ describe('compiler: parse', () => {
 
   describe('Comment', () => {
     test('empty comment', () => {
-      const ast = parse('<!---->')
+      const ast = baseParse('<!---->')
       const comment = ast.children[0] as CommentNode
 
       expect(comment).toStrictEqual({
@@ -443,7 +443,7 @@ describe('compiler: parse', () => {
     })
 
     test('simple comment', () => {
-      const ast = parse('<!--abc-->')
+      const ast = baseParse('<!--abc-->')
       const comment = ast.children[0] as CommentNode
 
       expect(comment).toStrictEqual({
@@ -458,7 +458,7 @@ describe('compiler: parse', () => {
     })
 
     test('two comments', () => {
-      const ast = parse('<!--abc--><!--def-->')
+      const ast = baseParse('<!--abc--><!--def-->')
       const comment1 = ast.children[0] as CommentNode
       const comment2 = ast.children[1] as CommentNode
 
@@ -485,7 +485,7 @@ describe('compiler: parse', () => {
 
   describe('Element', () => {
     test('simple div', () => {
-      const ast = parse('<div>hello</div>')
+      const ast = baseParse('<div>hello</div>')
       const element = ast.children[0] as ElementNode
 
       expect(element).toStrictEqual({
@@ -516,7 +516,7 @@ describe('compiler: parse', () => {
     })
 
     test('empty', () => {
-      const ast = parse('<div></div>')
+      const ast = baseParse('<div></div>')
       const element = ast.children[0] as ElementNode
 
       expect(element).toStrictEqual({
@@ -538,7 +538,7 @@ describe('compiler: parse', () => {
     })
 
     test('self closing', () => {
-      const ast = parse('<div/>after')
+      const ast = baseParse('<div/>after')
       const element = ast.children[0] as ElementNode
 
       expect(element).toStrictEqual({
@@ -560,7 +560,7 @@ describe('compiler: parse', () => {
     })
 
     test('void element', () => {
-      const ast = parse('<img>after', {
+      const ast = baseParse('<img>after', {
         isVoidTag: tag => tag === 'img'
       })
       const element = ast.children[0] as ElementNode
@@ -584,7 +584,7 @@ describe('compiler: parse', () => {
     })
 
     test('native element with `isNativeTag`', () => {
-      const ast = parse('<div></div><comp></comp><Comp></Comp>', {
+      const ast = baseParse('<div></div><comp></comp><Comp></Comp>', {
         isNativeTag: tag => tag === 'div'
       })
 
@@ -608,7 +608,7 @@ describe('compiler: parse', () => {
     })
 
     test('native element without `isNativeTag`', () => {
-      const ast = parse('<div></div><comp></comp><Comp></Comp>')
+      const ast = baseParse('<div></div><comp></comp><Comp></Comp>')
 
       expect(ast.children[0]).toMatchObject({
         type: NodeTypes.ELEMENT,
@@ -630,7 +630,7 @@ describe('compiler: parse', () => {
     })
 
     test('custom element', () => {
-      const ast = parse('<div></div><comp></comp>', {
+      const ast = baseParse('<div></div><comp></comp>', {
         isNativeTag: tag => tag === 'div',
         isCustomElement: tag => tag === 'comp'
       })
@@ -649,7 +649,7 @@ describe('compiler: parse', () => {
     })
 
     test('attribute with no value', () => {
-      const ast = parse('<div id></div>')
+      const ast = baseParse('<div id></div>')
       const element = ast.children[0] as ElementNode
 
       expect(element).toStrictEqual({
@@ -682,7 +682,7 @@ describe('compiler: parse', () => {
     })
 
     test('attribute with empty value, double quote', () => {
-      const ast = parse('<div id=""></div>')
+      const ast = baseParse('<div id=""></div>')
       const element = ast.children[0] as ElementNode
 
       expect(element).toStrictEqual({
@@ -723,7 +723,7 @@ describe('compiler: parse', () => {
     })
 
     test('attribute with empty value, single quote', () => {
-      const ast = parse("<div id=''></div>")
+      const ast = baseParse("<div id=''></div>")
       const element = ast.children[0] as ElementNode
 
       expect(element).toStrictEqual({
@@ -764,7 +764,7 @@ describe('compiler: parse', () => {
     })
 
     test('attribute with value, double quote', () => {
-      const ast = parse('<div id=">\'"></div>')
+      const ast = baseParse('<div id=">\'"></div>')
       const element = ast.children[0] as ElementNode
 
       expect(element).toStrictEqual({
@@ -805,7 +805,7 @@ describe('compiler: parse', () => {
     })
 
     test('attribute with value, single quote', () => {
-      const ast = parse("<div id='>\"'></div>")
+      const ast = baseParse("<div id='>\"'></div>")
       const element = ast.children[0] as ElementNode
 
       expect(element).toStrictEqual({
@@ -846,7 +846,7 @@ describe('compiler: parse', () => {
     })
 
     test('attribute with value, unquoted', () => {
-      const ast = parse('<div id=a/></div>')
+      const ast = baseParse('<div id=a/></div>')
       const element = ast.children[0] as ElementNode
 
       expect(element).toStrictEqual({
@@ -887,7 +887,7 @@ describe('compiler: parse', () => {
     })
 
     test('multiple attributes', () => {
-      const ast = parse('<div id=a class="c" inert style=\'\'></div>')
+      const ast = baseParse('<div id=a class="c" inert style=\'\'></div>')
       const element = ast.children[0] as ElementNode
 
       expect(element).toStrictEqual({
@@ -974,7 +974,7 @@ describe('compiler: parse', () => {
     })
 
     test('directive with no value', () => {
-      const ast = parse('<div v-if/>')
+      const ast = baseParse('<div v-if/>')
       const directive = (ast.children[0] as ElementNode).props[0]
 
       expect(directive).toStrictEqual({
@@ -992,7 +992,7 @@ describe('compiler: parse', () => {
     })
 
     test('directive with value', () => {
-      const ast = parse('<div v-if="a"/>')
+      const ast = baseParse('<div v-if="a"/>')
       const directive = (ast.children[0] as ElementNode).props[0]
 
       expect(directive).toStrictEqual({
@@ -1020,7 +1020,7 @@ describe('compiler: parse', () => {
     })
 
     test('directive with argument', () => {
-      const ast = parse('<div v-on:click/>')
+      const ast = baseParse('<div v-on:click/>')
       const directive = (ast.children[0] as ElementNode).props[0]
 
       expect(directive).toStrictEqual({
@@ -1057,7 +1057,7 @@ describe('compiler: parse', () => {
     })
 
     test('directive with a modifier', () => {
-      const ast = parse('<div v-on.enter/>')
+      const ast = baseParse('<div v-on.enter/>')
       const directive = (ast.children[0] as ElementNode).props[0]
 
       expect(directive).toStrictEqual({
@@ -1075,7 +1075,7 @@ describe('compiler: parse', () => {
     })
 
     test('directive with two modifiers', () => {
-      const ast = parse('<div v-on.enter.exact/>')
+      const ast = baseParse('<div v-on.enter.exact/>')
       const directive = (ast.children[0] as ElementNode).props[0]
 
       expect(directive).toStrictEqual({
@@ -1093,7 +1093,7 @@ describe('compiler: parse', () => {
     })
 
     test('directive with argument and modifiers', () => {
-      const ast = parse('<div v-on:click.enter.exact/>')
+      const ast = baseParse('<div v-on:click.enter.exact/>')
       const directive = (ast.children[0] as ElementNode).props[0]
 
       expect(directive).toStrictEqual({
@@ -1130,7 +1130,7 @@ describe('compiler: parse', () => {
     })
 
     test('v-bind shorthand', () => {
-      const ast = parse('<div :a=b />')
+      const ast = baseParse('<div :a=b />')
       const directive = (ast.children[0] as ElementNode).props[0]
 
       expect(directive).toStrictEqual({
@@ -1178,7 +1178,7 @@ describe('compiler: parse', () => {
     })
 
     test('v-bind shorthand with modifier', () => {
-      const ast = parse('<div :a.sync=b />')
+      const ast = baseParse('<div :a.sync=b />')
       const directive = (ast.children[0] as ElementNode).props[0]
 
       expect(directive).toStrictEqual({
@@ -1226,7 +1226,7 @@ describe('compiler: parse', () => {
     })
 
     test('v-on shorthand', () => {
-      const ast = parse('<div @a=b />')
+      const ast = baseParse('<div @a=b />')
       const directive = (ast.children[0] as ElementNode).props[0]
 
       expect(directive).toStrictEqual({
@@ -1274,7 +1274,7 @@ describe('compiler: parse', () => {
     })
 
     test('v-on shorthand with modifier', () => {
-      const ast = parse('<div @a.enter=b />')
+      const ast = baseParse('<div @a.enter=b />')
       const directive = (ast.children[0] as ElementNode).props[0]
 
       expect(directive).toStrictEqual({
@@ -1322,7 +1322,7 @@ describe('compiler: parse', () => {
     })
 
     test('v-slot shorthand', () => {
-      const ast = parse('<Comp #a="{ b }" />')
+      const ast = baseParse('<Comp #a="{ b }" />')
       const directive = (ast.children[0] as ElementNode).props[0]
 
       expect(directive).toStrictEqual({
@@ -1369,7 +1369,7 @@ describe('compiler: parse', () => {
     })
 
     test('v-pre', () => {
-      const ast = parse(
+      const ast = baseParse(
         `<div v-pre :id="foo"><Comp/>{{ bar }}</div>\n` +
           `<div :id="foo"><Comp/>{{ bar }}</div>`
       )
@@ -1451,7 +1451,7 @@ describe('compiler: parse', () => {
     })
 
     test('end tags are case-insensitive.', () => {
-      const ast = parse('<div>hello</DIV>after')
+      const ast = baseParse('<div>hello</DIV>after')
       const element = ast.children[0] as ElementNode
       const text = element.children[0] as TextNode
 
@@ -1468,14 +1468,14 @@ describe('compiler: parse', () => {
   })
 
   test('self closing single tag', () => {
-    const ast = parse('<div :class="{ some: condition }" />')
+    const ast = baseParse('<div :class="{ some: condition }" />')
 
     expect(ast.children).toHaveLength(1)
     expect(ast.children[0]).toMatchObject({ tag: 'div' })
   })
 
   test('self closing multiple tag', () => {
-    const ast = parse(
+    const ast = baseParse(
       `<div :class="{ some: condition }" />\n` +
         `<p v-bind:style="{ color: 'red' }"/>`
     )
@@ -1488,7 +1488,7 @@ describe('compiler: parse', () => {
   })
 
   test('valid html', () => {
-    const ast = parse(
+    const ast = baseParse(
       `<div :class="{ some: condition }">\n` +
         `  <p v-bind:style="{ color: 'red' }"/>\n` +
         `  <!-- a comment with <html> inside it -->\n` +
@@ -1513,11 +1513,11 @@ describe('compiler: parse', () => {
 
   test('invalid html', () => {
     expect(() => {
-      parse(`<div>\n<span>\n</div>\n</span>`)
+      baseParse(`<div>\n<span>\n</div>\n</span>`)
     }).toThrow('Element is missing end tag.')
 
     const spy = jest.fn()
-    const ast = parse(`<div>\n<span>\n</div>\n</span>`, {
+    const ast = baseParse(`<div>\n<span>\n</div>\n</span>`, {
       onError: spy
     })
 
@@ -1552,7 +1552,7 @@ describe('compiler: parse', () => {
   })
 
   test('parse with correct location info', () => {
-    const [foo, bar, but, baz] = parse(
+    const [foo, bar, but, baz] = baseParse(
       `
 foo
  is {{ bar }} but {{ baz }}`.trim()
@@ -1588,7 +1588,7 @@ foo
 
   describe('namedCharacterReferences option', () => {
     test('use the given map', () => {
-      const ast: any = parse('&amp;&cups;', {
+      const ast: any = baseParse('&amp;&cups;', {
         namedCharacterReferences: {
           'cups;': '\u222A\uFE00' // UNION with serifs
         },
@@ -1603,18 +1603,18 @@ foo
 
   describe('whitespace management', () => {
     it('should remove whitespaces at start/end inside an element', () => {
-      const ast = parse(`<div>   <span/>    </div>`)
+      const ast = baseParse(`<div>   <span/>    </div>`)
       expect((ast.children[0] as ElementNode).children.length).toBe(1)
     })
 
     it('should remove whitespaces w/ newline between elements', () => {
-      const ast = parse(`<div/> \n <div/> \n <div/>`)
+      const ast = baseParse(`<div/> \n <div/> \n <div/>`)
       expect(ast.children.length).toBe(3)
       expect(ast.children.every(c => c.type === NodeTypes.ELEMENT)).toBe(true)
     })
 
     it('should remove whitespaces adjacent to comments', () => {
-      const ast = parse(`<div/> \n <!--foo--> <div/>`)
+      const ast = baseParse(`<div/> \n <!--foo--> <div/>`)
       expect(ast.children.length).toBe(3)
       expect(ast.children[0].type).toBe(NodeTypes.ELEMENT)
       expect(ast.children[1].type).toBe(NodeTypes.COMMENT)
@@ -1622,7 +1622,7 @@ foo
     })
 
     it('should remove whitespaces w/ newline between comments and elements', () => {
-      const ast = parse(`<div/> \n <!--foo--> \n <div/>`)
+      const ast = baseParse(`<div/> \n <!--foo--> \n <div/>`)
       expect(ast.children.length).toBe(3)
       expect(ast.children[0].type).toBe(NodeTypes.ELEMENT)
       expect(ast.children[1].type).toBe(NodeTypes.COMMENT)
@@ -1630,7 +1630,7 @@ foo
     })
 
     it('should NOT remove whitespaces w/ newline between interpolations', () => {
-      const ast = parse(`{{ foo }} \n {{ bar }}`)
+      const ast = baseParse(`{{ foo }} \n {{ bar }}`)
       expect(ast.children.length).toBe(3)
       expect(ast.children[0].type).toBe(NodeTypes.INTERPOLATION)
       expect(ast.children[1]).toMatchObject({
@@ -1641,7 +1641,7 @@ foo
     })
 
     it('should NOT remove whitespaces w/o newline between elements', () => {
-      const ast = parse(`<div/> <div/> <div/>`)
+      const ast = baseParse(`<div/> <div/> <div/>`)
       expect(ast.children.length).toBe(5)
       expect(ast.children.map(c => c.type)).toMatchObject([
         NodeTypes.ELEMENT,
@@ -1653,7 +1653,7 @@ foo
     })
 
     it('should condense consecutive whitespaces in text', () => {
-      const ast = parse(`   foo  \n    bar     baz     `)
+      const ast = baseParse(`   foo  \n    bar     baz     `)
       expect((ast.children[0] as TextNode).content).toBe(` foo bar baz `)
     })
   })
@@ -2716,7 +2716,7 @@ foo
             ),
             () => {
               const spy = jest.fn()
-              const ast = parse(code, {
+              const ast = baseParse(code, {
                 getNamespace: (tag, parent) => {
                   const ns = parent ? parent.ns : Namespaces.HTML
                   if (ns === Namespaces.HTML) {
index fb67a5d1ff9d8aaebf3ceab7e705f0d11343b627..2e1c1e803c44e32567a5e475b9da038435eb4217 100644 (file)
@@ -1,4 +1,4 @@
-import { parse } from '../src/parse'
+import { baseParse } from '../src/parse'
 import { transform, NodeTransform } from '../src/transform'
 import {
   ElementNode,
@@ -26,7 +26,7 @@ import { PatchFlags } from '@vue/shared'
 
 describe('compiler: transform', () => {
   test('context state', () => {
-    const ast = parse(`<div>hello {{ world }}</div>`)
+    const ast = baseParse(`<div>hello {{ world }}</div>`)
 
     // manually store call arguments because context is mutable and shared
     // across calls
@@ -72,7 +72,7 @@ describe('compiler: transform', () => {
   })
 
   test('context.replaceNode', () => {
-    const ast = parse(`<div/><span/>`)
+    const ast = baseParse(`<div/><span/>`)
     const plugin: NodeTransform = (node, context) => {
       if (node.type === NodeTypes.ELEMENT && node.tag === 'div') {
         // change the node to <p>
@@ -106,7 +106,7 @@ describe('compiler: transform', () => {
   })
 
   test('context.removeNode', () => {
-    const ast = parse(`<span/><div>hello</div><span/>`)
+    const ast = baseParse(`<span/><div>hello</div><span/>`)
     const c1 = ast.children[0]
     const c2 = ast.children[2]
 
@@ -132,7 +132,7 @@ describe('compiler: transform', () => {
   })
 
   test('context.removeNode (prev sibling)', () => {
-    const ast = parse(`<span/><div/><span/>`)
+    const ast = baseParse(`<span/><div/><span/>`)
     const c1 = ast.children[0]
     const c2 = ast.children[2]
 
@@ -159,7 +159,7 @@ describe('compiler: transform', () => {
   })
 
   test('context.removeNode (next sibling)', () => {
-    const ast = parse(`<span/><div/><span/>`)
+    const ast = baseParse(`<span/><div/><span/>`)
     const c1 = ast.children[0]
     const d1 = ast.children[1]
 
@@ -186,7 +186,7 @@ describe('compiler: transform', () => {
   })
 
   test('context.hoist', () => {
-    const ast = parse(`<div :id="foo"/><div :id="bar"/>`)
+    const ast = baseParse(`<div :id="foo"/><div :id="bar"/>`)
     const hoisted: ExpressionNode[] = []
     const mock: NodeTransform = (node, context) => {
       if (node.type === NodeTypes.ELEMENT) {
@@ -204,7 +204,7 @@ describe('compiler: transform', () => {
   })
 
   test('onError option', () => {
-    const ast = parse(`<div/>`)
+    const ast = baseParse(`<div/>`)
     const loc = ast.children[0].loc
     const plugin: NodeTransform = (node, context) => {
       context.onError(
@@ -225,20 +225,20 @@ describe('compiler: transform', () => {
   })
 
   test('should inject toString helper for interpolations', () => {
-    const ast = parse(`{{ foo }}`)
+    const ast = baseParse(`{{ foo }}`)
     transform(ast, {})
     expect(ast.helpers).toContain(TO_STRING)
   })
 
   test('should inject createVNode and Comment for comments', () => {
-    const ast = parse(`<!--foo-->`)
+    const ast = baseParse(`<!--foo-->`)
     transform(ast, {})
     expect(ast.helpers).toContain(CREATE_COMMENT)
   })
 
   describe('root codegenNode', () => {
     function transformWithCodegen(template: string) {
-      const ast = parse(template)
+      const ast = baseParse(template)
       transform(ast, {
         nodeTransforms: [
           transformIf,
index b00584851fd6e553856bcfaa7f39cf7b9179ddde..3989805ee3f325b7e71e8295e8d2cc0159203a2d 100644 (file)
@@ -1,5 +1,5 @@
 import {
-  parse,
+  baseParse as parse,
   transform,
   NodeTypes,
   generate,
index 17ee4724f4d616d95c3f7f4c7abf1137be395823..92e2b3460028710c22d91f0c3b8c17d9744f631e 100644 (file)
@@ -1,4 +1,9 @@
-import { CompilerOptions, parse, transform, ErrorCodes } from '../../src'
+import {
+  CompilerOptions,
+  baseParse as parse,
+  transform,
+  ErrorCodes
+} from '../../src'
 import {
   RESOLVE_COMPONENT,
   CREATE_VNODE,
index 09f6ecbff7997e9df421a61e4327a3398f5ef1d6..6f1f280bd885a37d7dc06e67e4ae4e0efbdb23f4 100644 (file)
@@ -1,5 +1,5 @@
 import {
-  parse,
+  baseParse as parse,
   transform,
   ElementNode,
   DirectiveNode,
index 1e6a4b5418f7d075fd24cd025959457d9f759591..1bcd52d0ea9c6ea23599749eb9208f79afc1642c 100644 (file)
@@ -1,6 +1,6 @@
 import {
   CompilerOptions,
-  parse,
+  baseParse as parse,
   transform,
   ElementNode,
   NodeTypes,
index d11f4b615f2dc112a2b1ea86ea92d272a0118898..ab5341c32be0a85b6f1031fccada9d2eed5a422c 100644 (file)
@@ -1,6 +1,6 @@
 import {
   CompilerOptions,
-  parse,
+  baseParse as parse,
   transform,
   NodeTypes,
   generate,
index 2f1c9aba7ed68cc933ffdf506b63f024a1c8e585..d2fa15ef8500975997d864438ddab67bc1a5e603 100644 (file)
@@ -1,5 +1,5 @@
 import {
-  parse,
+  baseParse as parse,
   transform,
   ElementNode,
   ObjectExpression,
index 93b6bc9031901c796abf671407e0ea5a4c4975a6..05fc61470666e5d09da18101d57db9e06f2a919d 100644 (file)
@@ -1,4 +1,4 @@
-import { parse } from '../../src/parse'
+import { baseParse as parse } from '../../src/parse'
 import { transform } from '../../src/transform'
 import { transformIf } from '../../src/transforms/vIf'
 import { transformFor } from '../../src/transforms/vFor'
index 7e3470234578d6bd9782d9e25abba59abbbaa37b..f50ad66297dd2c55cdfd9b7ace24c5c6b05b37d4 100644 (file)
@@ -1,4 +1,4 @@
-import { parse } from '../../src/parse'
+import { baseParse as parse } from '../../src/parse'
 import { transform } from '../../src/transform'
 import { transformIf } from '../../src/transforms/vIf'
 import { transformElement } from '../../src/transforms/transformElement'
index c2ce8dae3ea37a3da1a4ea756998e7a2d82b325a..5f2721f8d836c4ad3b1355cd941cc4f1ccfc0b38 100644 (file)
@@ -1,5 +1,5 @@
 import {
-  parse,
+  baseParse as parse,
   transform,
   generate,
   ElementNode,
index 57b7c4f89252ff905f9c8d3a931608b4b252dd90..7dfe92a0026485d8b3f36b49dff9a82f2a4d2e6d 100644 (file)
@@ -1,5 +1,5 @@
 import {
-  parse,
+  baseParse as parse,
   transform,
   ElementNode,
   ObjectExpression,
index 5d2a2a05fc644e931a694b74c42fb27091ab4e51..1c9f9ace98215f2e23531d43199aee3c586518d3 100644 (file)
@@ -1,5 +1,5 @@
 import {
-  parse,
+  baseParse as parse,
   transform,
   NodeTypes,
   generate,
index bf48b6cbac3f6813d6fcc6148f791ff8d050125f..56be19aadcf6dbfdad139d5a68d2bd6239be2590 100644 (file)
@@ -1,6 +1,6 @@
 import {
   CompilerOptions,
-  parse,
+  baseParse as parse,
   transform,
   generate,
   ElementNode,
index d6214137c6a96385bbe72d1f6abddea1aa18adb6..7699dae8de5ba6d9168c0770dc8d126588099648 100644 (file)
@@ -1,5 +1,5 @@
 import { CompilerOptions } from './options'
-import { parse } from './parse'
+import { baseParse } from './parse'
 import { transform } from './transform'
 import { generate, CodegenResult } from './codegen'
 import { RootNode } from './ast'
@@ -43,7 +43,7 @@ export function baseCompile(
     onError(createCompilerError(ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED))
   }
 
-  const ast = isString(template) ? parse(template, options) : template
+  const ast = isString(template) ? baseParse(template, options) : template
   transform(ast, {
     ...options,
     prefixIdentifiers,
index 723fe8c3b21d01c4c9ef23cbd1fa9d556061b654..78a1dd3e3a2f6ee6a114363b14c5d9168f1551fe 100644 (file)
@@ -7,7 +7,7 @@ export {
   TransformOptions,
   CodegenOptions
 } from './options'
-export { parse, TextModes } from './parse'
+export { baseParse, TextModes } from './parse'
 export {
   transform,
   createStructuralDirectiveTransform,
index fa31036ddfe97333f4d3e03f659b9abf8e19430c..b9b37953b5e1ce9319b295f0c8df8b8e3777519e 100644 (file)
@@ -66,7 +66,10 @@ interface ParserContext {
   inPre: boolean
 }
 
-export function parse(content: string, options: ParserOptions = {}): RootNode {
+export function baseParse(
+  content: string,
+  options: ParserOptions = {}
+): RootNode {
   const context = createParserContext(content, options)
   const start = getCursor(context)
 
index 52f142e1118189acf76a851e4ec6dd7cc7dfa03a..e10cf2555937e2bc0813207b036cc10369268f75 100644 (file)
@@ -1,5 +1,5 @@
 import {
-  parse,
+  baseParse as parse,
   NodeTypes,
   ElementNode,
   TextNode,
index 5c568c2719fbe672b6d68c45a532f3ac3bc2ad90..aabe94d4cbcff6bdc112f80549ac23f653307d1c 100644 (file)
@@ -1,5 +1,5 @@
 import {
-  parse,
+  baseParse as parse,
   transform,
   CompilerOptions,
   ElementNode,
index 955dde7315947fdf591c11dbfe2fc7132f3920cb..03d7f7169f21f718842f9a1fd6d6ebf864e9ab94 100644 (file)
@@ -1,5 +1,5 @@
 import {
-  parse,
+  baseParse as parse,
   transform,
   ElementNode,
   CallExpression
index 12d3d6b32f5a07484ead31be84f5a935c8b9a16f..7d81fcb4fd69c6bc8fd931281c468008547865b5 100644 (file)
@@ -1,5 +1,5 @@
 import {
-  parse,
+  baseParse as parse,
   transform,
   PlainElementNode,
   CompilerOptions
index 393b62d915bb148ef42a96a82271d021fdb57683..a70e117adf019d874ded23fcba848b1e2c77fc89 100644 (file)
@@ -1,4 +1,9 @@
-import { parse, transform, CompilerOptions, generate } from '@vue/compiler-core'
+import {
+  baseParse as parse,
+  transform,
+  CompilerOptions,
+  generate
+} from '@vue/compiler-core'
 import { transformModel } from '../../src/transforms/vModel'
 import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
 import { DOMErrorCodes } from '../../src/errors'
index 20001b4910250c94d8c129cfa95a02e96a9715f5..806c80fed927ed7be5a634bc36d3d4284979b475 100644 (file)
@@ -1,5 +1,5 @@
 import {
-  parse,
+  baseParse as parse,
   transform,
   CompilerOptions,
   ElementNode,
index 3bac6bbe587a68b6069e89879615cc6aa9bfbaa2..3c70741cb829e7161f52e896a59af982ab07935a 100644 (file)
@@ -1,4 +1,9 @@
-import { parse, transform, generate, CompilerOptions } from '@vue/compiler-core'
+import {
+  baseParse as parse,
+  transform,
+  generate,
+  CompilerOptions
+} from '@vue/compiler-core'
 import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
 import { transformShow } from '../../src/transforms/vShow'
 import { DOMErrorCodes } from '../../src/errors'
index ef27ec509da6ddb57fc9a4625f8875363b89dc85..83f052cbd7e2a170ac7776953ae6b908dd04fd39 100644 (file)
@@ -1,5 +1,5 @@
 import {
-  parse,
+  baseParse as parse,
   transform,
   PlainElementNode,
   CompilerOptions
index 0c42dfee1478e502f3f808cc6c3d44f54d4595f6..8bbc774c240df34d5219ac1d532f0867feb38535 100644 (file)
@@ -1,8 +1,10 @@
 import {
   baseCompile,
+  baseParse,
   CompilerOptions,
   CodegenResult,
-  isBuiltInType
+  isBuiltInType,
+  ParserOptions
 } from '@vue/compiler-core'
 import { parserOptionsMinimal } from './parserOptionsMinimal'
 import { parserOptionsStandard } from './parserOptionsStandard'
@@ -15,13 +17,15 @@ import { transformOn } from './transforms/vOn'
 import { transformShow } from './transforms/vShow'
 import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
 
+const parserOptions = __BROWSER__ ? parserOptionsMinimal : parserOptionsStandard
+
 export function compile(
   template: string,
   options: CompilerOptions = {}
 ): CodegenResult {
   return baseCompile(template, {
+    ...parserOptions,
     ...options,
-    ...(__BROWSER__ ? parserOptionsMinimal : parserOptionsStandard),
     nodeTransforms: [transformStyle, ...(options.nodeTransforms || [])],
     directiveTransforms: {
       cloak: transformCloak,
@@ -42,4 +46,11 @@ export function compile(
   })
 }
 
+export function parse(template: string, options: ParserOptions = {}) {
+  return baseParse(template, {
+    ...parserOptions,
+    ...options
+  })
+}
+
 export * from '@vue/compiler-core'
index c6e0df029a9a4c28bb9dc111f1bcd4e15bb1a677..03a32ba0f66dea0b2af3d5ab7e434825115aabe6 100644 (file)
@@ -23,7 +23,7 @@ body
 </template>
 `,
     { filename: 'example.vue', sourceMap: true }
-  ).template as SFCTemplateBlock
+  ).descriptor.template as SFCTemplateBlock
 
   const result = compileTemplate({
     filename: 'example.vue',
@@ -35,10 +35,10 @@ body
 })
 
 test('warn missing preprocessor', () => {
-  const template = parse(`<template lang="unknownLang">\n</template>\n`, {
+  const template = parse(`<template lang="unknownLang">hi</template>\n`, {
     filename: 'example.vue',
     sourceMap: true
-  }).template as SFCTemplateBlock
+  }).descriptor.template as SFCTemplateBlock
 
   const result = compileTemplate({
     filename: 'example.vue',
@@ -70,10 +70,10 @@ test('source map', () => {
     `
 <template>
   <div><p>{{ render }}</p></div>
-</template>  
+</template>
 `,
     { filename: 'example.vue', sourceMap: true }
-  ).template as SFCTemplateBlock
+  ).descriptor.template as SFCTemplateBlock
 
   const result = compileTemplate({
     filename: 'example.vue',
@@ -86,7 +86,7 @@ test('source map', () => {
 test('template errors', () => {
   const result = compileTemplate({
     filename: 'example.vue',
-    source: `<div :foo 
+    source: `<div :foo
       :bar="a[" v-model="baz"/>`
   })
   expect(result.errors).toMatchSnapshot()
@@ -100,7 +100,7 @@ test('preprocessor errors', () => {
 </template>
 `,
     { filename: 'example.vue', sourceMap: true }
-  ).template as SFCTemplateBlock
+  ).descriptor.template as SFCTemplateBlock
 
   const result = compileTemplate({
     filename: 'example.vue',
index a3bd86b7b64b24984f69ed7e5c34b2e90630586d..6a4534c11c21f75ef234dbc189efcd74b5257fd2 100644 (file)
@@ -1,5 +1,6 @@
 import { parse } from '../src'
 import { mockWarn } from '@vue/runtime-test'
+import { baseParse, baseCompile } from '@vue/compiler-core'
 
 describe('compiler:sfc', () => {
   mockWarn()
@@ -7,13 +8,14 @@ describe('compiler:sfc', () => {
   describe('source map', () => {
     test('style block', () => {
       const style = parse(`<style>\n.color {\n color: red;\n }\n</style>\n`)
-        .styles[0]
+        .descriptor.styles[0]
       // TODO need to actually test this with SourceMapConsumer
       expect(style.map).not.toBeUndefined()
     })
 
     test('script block', () => {
-      const script = parse(`<script>\nconsole.log(1)\n }\n</script>\n`).script
+      const script = parse(`<script>\nconsole.log(1)\n }\n</script>\n`)
+        .descriptor.script
       // TODO need to actually test this with SourceMapConsumer
       expect(script!.map).not.toBeUndefined()
     })
@@ -30,12 +32,12 @@ export default {}
 <style>
 h1 { color: red }
 </style>`
-    const padFalse = parse(content.trim(), { pad: false })
+    const padFalse = parse(content.trim(), { pad: false }).descriptor
     expect(padFalse.template!.content).toBe('\n<div></div>\n')
     expect(padFalse.script!.content).toBe('\nexport default {}\n')
     expect(padFalse.styles[0].content).toBe('\nh1 { color: red }\n')
 
-    const padTrue = parse(content.trim(), { pad: true })
+    const padTrue = parse(content.trim(), { pad: true }).descriptor
     expect(padTrue.script!.content).toBe(
       Array(3 + 1).join('//\n') + '\nexport default {}\n'
     )
@@ -43,7 +45,7 @@ h1 { color: red }
       Array(6 + 1).join('\n') + '\nh1 { color: red }\n'
     )
 
-    const padLine = parse(content.trim(), { pad: 'line' })
+    const padLine = parse(content.trim(), { pad: 'line' }).descriptor
     expect(padLine.script!.content).toBe(
       Array(3 + 1).join('//\n') + '\nexport default {}\n'
     )
@@ -51,7 +53,7 @@ h1 { color: red }
       Array(6 + 1).join('\n') + '\nh1 { color: red }\n'
     )
 
-    const padSpace = parse(content.trim(), { pad: 'space' })
+    const padSpace = parse(content.trim(), { pad: 'space' }).descriptor
     expect(padSpace.script!.content).toBe(
       `<template>\n<div></div>\n</template>\n<script>`.replace(/./g, ' ') +
         '\nexport default {}\n'
@@ -65,13 +67,42 @@ h1 { color: red }
   })
 
   test('should ignore nodes with no content', () => {
-    expect(parse(`<template/>`).template).toBe(null)
-    expect(parse(`<script/>`).script).toBe(null)
-    expect(parse(`<style/>`).styles.length).toBe(0)
-    expect(parse(`<custom/>`).customBlocks.length).toBe(0)
+    expect(parse(`<template/>`).descriptor.template).toBe(null)
+    expect(parse(`<script/>`).descriptor.script).toBe(null)
+    expect(parse(`<style/>`).descriptor.styles.length).toBe(0)
+    expect(parse(`<custom/>`).descriptor.customBlocks.length).toBe(0)
   })
 
-  describe('error', () => {
+  test('nested templates', () => {
+    const content = `
+    <template v-if="ok">ok</template>
+    <div><div></div></div>
+    `
+    const sfc = parse(`<template>${content}</template>`).descriptor
+    expect(sfc.template!.content).toBe(content)
+  })
+
+  test('error tolerance', () => {
+    const { errors } = parse(`<template>`)
+    expect(errors.length).toBe(1)
+  })
+
+  test('should parse as DOM by default', () => {
+    const { errors } = parse(`<template><input></template>`)
+    expect(errors.length).toBe(0)
+  })
+
+  test('custom compiler', () => {
+    const { errors } = parse(`<template><input></template>`, {
+      compiler: {
+        parse: baseParse,
+        compile: baseCompile
+      }
+    })
+    expect(errors.length).toBe(1)
+  })
+
+  describe('warnings', () => {
     test('should only allow single template element', () => {
       parse(`<template><div/></template><template><div/></template>`)
       expect(
index a5e64058cd4e8e129e1e9930bd29d547e354f550..9adbd1a623372e882501945fa9e24c2dbaaf6c47 100644 (file)
@@ -1,10 +1,10 @@
-import { generate, parse, transform } from '@vue/compiler-core'
+import { generate, baseParse, transform } from '@vue/compiler-core'
 import { transformAssetUrl } from '../src/templateTransformAssetUrl'
 import { transformElement } from '../../compiler-core/src/transforms/transformElement'
 import { transformBind } from '../../compiler-core/src/transforms/vBind'
 
 function compileWithAssetUrls(template: string) {
-  const ast = parse(template)
+  const ast = baseParse(template)
   transform(ast, {
     nodeTransforms: [transformAssetUrl, transformElement],
     directiveTransforms: {
index 5aedc90f11dc879eb74e17bbba087461676c6e46..660242c931bb98d164a45c8fc522c0aff90f5d1b 100644 (file)
@@ -1,10 +1,10 @@
-import { generate, parse, transform } from '@vue/compiler-core'
+import { generate, baseParse, transform } from '@vue/compiler-core'
 import { transformSrcset } from '../src/templateTransformSrcset'
 import { transformElement } from '../../compiler-core/src/transforms/transformElement'
 import { transformBind } from '../../compiler-core/src/transforms/vBind'
 
 function compileWithSrcset(template: string) {
-  const ast = parse(template)
+  const ast = baseParse(template)
   transform(ast, {
     nodeTransforms: [transformSrcset, transformElement],
     directiveTransforms: {
index e7a4e476cb1680d4b072eb9ed1dbeeb51f260319..c6998ede02513d75984eb2f1b1ee775163b63008 100644 (file)
@@ -2,7 +2,9 @@ import {
   CompilerOptions,
   CodegenResult,
   CompilerError,
-  NodeTransform
+  NodeTransform,
+  ParserOptions,
+  RootNode
 } from '@vue/compiler-core'
 import { SourceMapConsumer, SourceMapGenerator, RawSourceMap } from 'source-map'
 import {
@@ -16,6 +18,7 @@ import consolidate from 'consolidate'
 
 export interface TemplateCompiler {
   compile(template: string, options: CompilerOptions): CodegenResult
+  parse(template: string, options: ParserOptions): RootNode
 }
 
 export interface SFCTemplateCompileResults {
index 45bdb875b9e25373cf4c55c0e6d1f148f4797844..ef839199bc6c52d8b66b2e3261aa98e50f40f5b3 100644 (file)
@@ -18,4 +18,8 @@ export {
   SFCTemplateCompileResults
 } from './compileTemplate'
 export { SFCStyleCompileOptions, SFCStyleCompileResults } from './compileStyle'
-export { CompilerOptions, generateCodeFrame } from '@vue/compiler-core'
+export {
+  CompilerOptions,
+  CompilerError,
+  generateCodeFrame
+} from '@vue/compiler-core'
index 066dcdb6600c3e20256fe3fe6e72785a462a4bf1..38c611dfb88bac386397974989791ceadc746348 100644 (file)
@@ -1,20 +1,20 @@
 import {
-  parse as baseParse,
-  TextModes,
   NodeTypes,
-  TextNode,
   ElementNode,
-  SourceLocation
+  SourceLocation,
+  CompilerError
 } from '@vue/compiler-core'
 import { RawSourceMap, SourceMapGenerator } from 'source-map'
 import LRUCache from 'lru-cache'
 import { generateCodeFrame } from '@vue/shared'
+import { TemplateCompiler } from './compileTemplate'
 
 export interface SFCParseOptions {
   filename?: string
   sourceMap?: boolean
   sourceRoot?: string
   pad?: boolean | 'line' | 'space'
+  compiler?: TemplateCompiler
 }
 
 export interface SFCBlock {
@@ -50,24 +50,32 @@ export interface SFCDescriptor {
   customBlocks: SFCBlock[]
 }
 
+export interface SFCParseResult {
+  descriptor: SFCDescriptor
+  errors: CompilerError[]
+}
+
 const SFC_CACHE_MAX_SIZE = 500
-const sourceToSFC = new LRUCache<string, SFCDescriptor>(SFC_CACHE_MAX_SIZE)
+const sourceToSFC = new LRUCache<string, SFCParseResult>(SFC_CACHE_MAX_SIZE)
+
 export function parse(
   source: string,
   {
     sourceMap = true,
     filename = 'component.vue',
     sourceRoot = '',
-    pad = false
+    pad = false,
+    compiler = require('@vue/compiler-dom')
   }: SFCParseOptions = {}
-): SFCDescriptor {
-  const sourceKey = source + sourceMap + filename + sourceRoot + pad
+): SFCParseResult {
+  const sourceKey =
+    source + sourceMap + filename + sourceRoot + pad + compiler.parse
   const cache = sourceToSFC.get(sourceKey)
   if (cache) {
     return cache
   }
 
-  const sfc: SFCDescriptor = {
+  const descriptor: SFCDescriptor = {
     filename,
     template: null,
     script: null,
@@ -75,9 +83,15 @@ export function parse(
     customBlocks: []
   }
 
-  const ast = baseParse(source, {
+  const errors: CompilerError[] = []
+  const ast = compiler.parse(source, {
+    // there are no components at SFC parsing level
     isNativeTag: () => true,
-    getTextMode: () => TextModes.RAWTEXT
+    // preserve all whitespaces
+    isPreTag: () => true,
+    onError: e => {
+      errors.push(e)
+    }
   })
 
   ast.children.forEach(node => {
@@ -89,24 +103,28 @@ export function parse(
     }
     switch (node.tag) {
       case 'template':
-        if (!sfc.template) {
-          sfc.template = createBlock(node, source, pad) as SFCTemplateBlock
+        if (!descriptor.template) {
+          descriptor.template = createBlock(
+            node,
+            source,
+            pad
+          ) as SFCTemplateBlock
         } else {
           warnDuplicateBlock(source, filename, node)
         }
         break
       case 'script':
-        if (!sfc.script) {
-          sfc.script = createBlock(node, source, pad) as SFCScriptBlock
+        if (!descriptor.script) {
+          descriptor.script = createBlock(node, source, pad) as SFCScriptBlock
         } else {
           warnDuplicateBlock(source, filename, node)
         }
         break
       case 'style':
-        sfc.styles.push(createBlock(node, source, pad) as SFCStyleBlock)
+        descriptor.styles.push(createBlock(node, source, pad) as SFCStyleBlock)
         break
       default:
-        sfc.customBlocks.push(createBlock(node, source, pad))
+        descriptor.customBlocks.push(createBlock(node, source, pad))
         break
     }
   })
@@ -123,13 +141,17 @@ export function parse(
         )
       }
     }
-    genMap(sfc.template)
-    genMap(sfc.script)
-    sfc.styles.forEach(genMap)
+    genMap(descriptor.template)
+    genMap(descriptor.script)
+    descriptor.styles.forEach(genMap)
   }
-  sourceToSFC.set(sourceKey, sfc)
 
-  return sfc
+  const result = {
+    descriptor,
+    errors
+  }
+  sourceToSFC.set(sourceKey, result)
+  return result
 }
 
 function warnDuplicateBlock(
@@ -156,12 +178,19 @@ function createBlock(
   pad: SFCParseOptions['pad']
 ): SFCBlock {
   const type = node.tag
-  const text = node.children[0] as TextNode
+  const start = node.children[0].loc.start
+  const end = node.children[node.children.length - 1].loc.end
+  const content = source.slice(start.offset, end.offset)
+  const loc = {
+    source: content,
+    start,
+    end
+  }
   const attrs: Record<string, string | true> = {}
   const block: SFCBlock = {
     type,
-    content: text.content,
-    loc: text.loc,
+    content,
+    loc,
     attrs
   }
   if (node.tag !== 'template' && pad) {