Skip to content

Commit 03f924e

Browse files
committed
refactor(compiler-sfc): move sfc parse errors into return result
Also warn against `<script setup src>` usage
1 parent fcbefdb commit 03f924e

File tree

2 files changed

+40
-37
lines changed

2 files changed

+40
-37
lines changed

packages/compiler-sfc/__tests__/parse.spec.ts

+20-19
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
import { parse } from '../src'
2-
import { mockWarn } from '@vue/shared'
32
import { baseParse, baseCompile } from '@vue/compiler-core'
43
import { SourceMapConsumer } from 'source-map'
54

65
describe('compiler:sfc', () => {
7-
mockWarn()
8-
96
describe('source map', () => {
107
test('style block', () => {
118
// Padding determines how many blank lines will there be before the style block
@@ -143,36 +140,40 @@ h1 { color: red }
143140
})
144141

145142
describe('warnings', () => {
143+
function assertWarning(errors: Error[], msg: string) {
144+
expect(errors.some(e => e.message.match(msg))).toBe(true)
145+
}
146+
146147
test('should only allow single template element', () => {
147-
parse(`<template><div/></template><template><div/></template>`)
148-
expect(
148+
assertWarning(
149+
parse(`<template><div/></template><template><div/></template>`).errors,
149150
`Single file component can contain only one <template> element`
150-
).toHaveBeenWarned()
151+
)
151152
})
152153

153154
test('should only allow single script element', () => {
154-
parse(`<script>console.log(1)</script><script>console.log(1)</script>`)
155-
expect(
155+
assertWarning(
156+
parse(`<script>console.log(1)</script><script>console.log(1)</script>`)
157+
.errors,
156158
`Single file component can contain only one <script> element`
157-
).toHaveBeenWarned()
159+
)
158160
})
159161

160162
test('should only allow single script setup element', () => {
161-
parse(
162-
`<script setup>console.log(1)</script><script setup>console.log(1)</script>`
163-
)
164-
expect(
163+
assertWarning(
164+
parse(
165+
`<script setup>console.log(1)</script><script setup>console.log(1)</script>`
166+
).errors,
165167
`Single file component can contain only one <script setup> element`
166-
).toHaveBeenWarned()
168+
)
167169
})
168170

169171
test('should not warn script & script setup', () => {
170-
parse(
171-
`<script setup>console.log(1)</script><script>console.log(1)</script>`
172-
)
173172
expect(
174-
`Single file component can contain only one`
175-
).not.toHaveBeenWarned()
173+
parse(
174+
`<script setup>console.log(1)</script><script>console.log(1)</script>`
175+
).errors.length
176+
).toBe(0)
176177
})
177178
})
178179
})

packages/compiler-sfc/src/parse.ts

+20-18
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
} from '@vue/compiler-core'
99
import * as CompilerDOM from '@vue/compiler-dom'
1010
import { RawSourceMap, SourceMapGenerator } from 'source-map'
11-
import { generateCodeFrame } from '@vue/shared'
1211
import { TemplateCompiler } from './compileTemplate'
1312
import { compileScript, SFCScriptCompileOptions } from './compileScript'
1413

@@ -61,7 +60,7 @@ export interface SFCDescriptor {
6160

6261
export interface SFCParseResult {
6362
descriptor: SFCDescriptor
64-
errors: CompilerError[]
63+
errors: (CompilerError | SyntaxError)[]
6564
}
6665

6766
const SFC_CACHE_MAX_SIZE = 500
@@ -102,7 +101,7 @@ export function parse(
102101
customBlocks: []
103102
}
104103

105-
const errors: CompilerError[] = []
104+
const errors: (CompilerError | SyntaxError)[] = []
106105
const ast = compiler.parse(source, {
107106
// there are no components at SFC parsing level
108107
isNativeTag: () => true,
@@ -148,21 +147,30 @@ export function parse(
148147
false
149148
) as SFCTemplateBlock
150149
} else {
151-
warnDuplicateBlock(source, filename, node)
150+
errors.push(createDuplicateBlockError(node))
152151
}
153152
break
154153
case 'script':
155154
const block = createBlock(node, source, pad) as SFCScriptBlock
156155
const isSetup = !!block.attrs.setup
157156
if (isSetup && !descriptor.scriptSetup) {
157+
if (block.src) {
158+
errors.push(
159+
new SyntaxError(
160+
`<script setup> cannot be used with the "src" attribute since ` +
161+
`its syntax will be ambiguous outside of the component.`
162+
)
163+
)
164+
break
165+
}
158166
descriptor.scriptSetup = block
159167
break
160168
}
161169
if (!isSetup && !descriptor.script) {
162170
descriptor.script = block
163171
break
164172
}
165-
warnDuplicateBlock(source, filename, node, isSetup)
173+
errors.push(createDuplicateBlockError(node, isSetup))
166174
break
167175
case 'style':
168176
descriptor.styles.push(createBlock(node, source, pad) as SFCStyleBlock)
@@ -208,23 +216,17 @@ export function parse(
208216
return result
209217
}
210218

211-
function warnDuplicateBlock(
212-
source: string,
213-
filename: string,
219+
function createDuplicateBlockError(
214220
node: ElementNode,
215221
isScriptSetup = false
216-
) {
217-
const codeFrame = generateCodeFrame(
218-
source,
219-
node.loc.start.offset,
220-
node.loc.end.offset
221-
)
222-
const location = `${filename}:${node.loc.start.line}:${node.loc.start.column}`
223-
console.warn(
222+
): CompilerError {
223+
const err = new SyntaxError(
224224
`Single file component can contain only one <${node.tag}${
225225
isScriptSetup ? ` setup` : ``
226-
}> element (${location}):\n\n${codeFrame}`
227-
)
226+
}> element`
227+
) as CompilerError
228+
err.loc = node.loc
229+
return err
228230
}
229231

230232
function createBlock(

0 commit comments

Comments
 (0)