refactor(compiler-sfc): move sfc parse errors into return result

Also warn against `<script setup src>` usage
This commit is contained in:
Evan You 2020-07-15 16:00:53 -04:00
parent fcbefdb769
commit 03f924e48a
2 changed files with 40 additions and 37 deletions

View File

@ -1,11 +1,8 @@
import { parse } from '../src' import { parse } from '../src'
import { mockWarn } from '@vue/shared'
import { baseParse, baseCompile } from '@vue/compiler-core' import { baseParse, baseCompile } from '@vue/compiler-core'
import { SourceMapConsumer } from 'source-map' import { SourceMapConsumer } from 'source-map'
describe('compiler:sfc', () => { describe('compiler:sfc', () => {
mockWarn()
describe('source map', () => { describe('source map', () => {
test('style block', () => { test('style block', () => {
// Padding determines how many blank lines will there be before the style block // Padding determines how many blank lines will there be before the style block
@ -143,36 +140,40 @@ h1 { color: red }
}) })
describe('warnings', () => { describe('warnings', () => {
function assertWarning(errors: Error[], msg: string) {
expect(errors.some(e => e.message.match(msg))).toBe(true)
}
test('should only allow single template element', () => { test('should only allow single template element', () => {
parse(`<template><div/></template><template><div/></template>`) assertWarning(
expect( parse(`<template><div/></template><template><div/></template>`).errors,
`Single file component can contain only one <template> element` `Single file component can contain only one <template> element`
).toHaveBeenWarned() )
}) })
test('should only allow single script element', () => { test('should only allow single script element', () => {
parse(`<script>console.log(1)</script><script>console.log(1)</script>`) assertWarning(
expect( parse(`<script>console.log(1)</script><script>console.log(1)</script>`)
.errors,
`Single file component can contain only one <script> element` `Single file component can contain only one <script> element`
).toHaveBeenWarned() )
}) })
test('should only allow single script setup element', () => { test('should only allow single script setup element', () => {
parse( assertWarning(
`<script setup>console.log(1)</script><script setup>console.log(1)</script>` parse(
) `<script setup>console.log(1)</script><script setup>console.log(1)</script>`
expect( ).errors,
`Single file component can contain only one <script setup> element` `Single file component can contain only one <script setup> element`
).toHaveBeenWarned() )
}) })
test('should not warn script & script setup', () => { test('should not warn script & script setup', () => {
parse(
`<script setup>console.log(1)</script><script>console.log(1)</script>`
)
expect( expect(
`Single file component can contain only one` parse(
).not.toHaveBeenWarned() `<script setup>console.log(1)</script><script>console.log(1)</script>`
).errors.length
).toBe(0)
}) })
}) })
}) })

View File

@ -8,7 +8,6 @@ import {
} from '@vue/compiler-core' } from '@vue/compiler-core'
import * as CompilerDOM from '@vue/compiler-dom' import * as CompilerDOM from '@vue/compiler-dom'
import { RawSourceMap, SourceMapGenerator } from 'source-map' import { RawSourceMap, SourceMapGenerator } from 'source-map'
import { generateCodeFrame } from '@vue/shared'
import { TemplateCompiler } from './compileTemplate' import { TemplateCompiler } from './compileTemplate'
import { compileScript, SFCScriptCompileOptions } from './compileScript' import { compileScript, SFCScriptCompileOptions } from './compileScript'
@ -61,7 +60,7 @@ export interface SFCDescriptor {
export interface SFCParseResult { export interface SFCParseResult {
descriptor: SFCDescriptor descriptor: SFCDescriptor
errors: CompilerError[] errors: (CompilerError | SyntaxError)[]
} }
const SFC_CACHE_MAX_SIZE = 500 const SFC_CACHE_MAX_SIZE = 500
@ -102,7 +101,7 @@ export function parse(
customBlocks: [] customBlocks: []
} }
const errors: CompilerError[] = [] const errors: (CompilerError | SyntaxError)[] = []
const ast = compiler.parse(source, { const ast = compiler.parse(source, {
// there are no components at SFC parsing level // there are no components at SFC parsing level
isNativeTag: () => true, isNativeTag: () => true,
@ -148,13 +147,22 @@ export function parse(
false false
) as SFCTemplateBlock ) as SFCTemplateBlock
} else { } else {
warnDuplicateBlock(source, filename, node) errors.push(createDuplicateBlockError(node))
} }
break break
case 'script': case 'script':
const block = createBlock(node, source, pad) as SFCScriptBlock const block = createBlock(node, source, pad) as SFCScriptBlock
const isSetup = !!block.attrs.setup const isSetup = !!block.attrs.setup
if (isSetup && !descriptor.scriptSetup) { if (isSetup && !descriptor.scriptSetup) {
if (block.src) {
errors.push(
new SyntaxError(
`<script setup> cannot be used with the "src" attribute since ` +
`its syntax will be ambiguous outside of the component.`
)
)
break
}
descriptor.scriptSetup = block descriptor.scriptSetup = block
break break
} }
@ -162,7 +170,7 @@ export function parse(
descriptor.script = block descriptor.script = block
break break
} }
warnDuplicateBlock(source, filename, node, isSetup) errors.push(createDuplicateBlockError(node, isSetup))
break break
case 'style': case 'style':
descriptor.styles.push(createBlock(node, source, pad) as SFCStyleBlock) descriptor.styles.push(createBlock(node, source, pad) as SFCStyleBlock)
@ -208,23 +216,17 @@ export function parse(
return result return result
} }
function warnDuplicateBlock( function createDuplicateBlockError(
source: string,
filename: string,
node: ElementNode, node: ElementNode,
isScriptSetup = false isScriptSetup = false
) { ): CompilerError {
const codeFrame = generateCodeFrame( const err = new SyntaxError(
source,
node.loc.start.offset,
node.loc.end.offset
)
const location = `${filename}:${node.loc.start.line}:${node.loc.start.column}`
console.warn(
`Single file component can contain only one <${node.tag}${ `Single file component can contain only one <${node.tag}${
isScriptSetup ? ` setup` : `` isScriptSetup ? ` setup` : ``
}> element (${location}):\n\n${codeFrame}` }> element`
) ) as CompilerError
err.loc = node.loc
return err
} }
function createBlock( function createBlock(