refactor(compiler-sfc): move sfc parse errors into return result
Also warn against `<script setup src>` usage
This commit is contained in:
parent
fcbefdb769
commit
03f924e48a
@ -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)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -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(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user