diff --git a/packages/compiler-sfc/__tests__/parse.spec.ts b/packages/compiler-sfc/__tests__/parse.spec.ts new file mode 100644 index 00000000..14cb9dcc --- /dev/null +++ b/packages/compiler-sfc/__tests__/parse.spec.ts @@ -0,0 +1,21 @@ +import { parse } from '../src' +import { mockWarn } from '@vue/runtime-test' + +describe('compiler:sfc', () => { + mockWarn() + describe('error', () => { + test('should only allow single template element', () => { + parse(``) + expect( + `Single file component can contain only one template element` + ).toHaveBeenWarned() + }) + + test('should only allow single script element', () => { + parse(``) + expect( + `Single file component can contain only one script element` + ).toHaveBeenWarned() + }) + }) +}) diff --git a/packages/compiler-sfc/src/parse.ts b/packages/compiler-sfc/src/parse.ts index 9cfded62..de8945dd 100644 --- a/packages/compiler-sfc/src/parse.ts +++ b/packages/compiler-sfc/src/parse.ts @@ -7,6 +7,7 @@ import { SourceLocation } from '@vue/compiler-core' import { RawSourceMap } from 'source-map' +import { generateCodeFrame } from '@vue/shared' export interface SFCParseOptions { needMap?: boolean @@ -78,14 +79,14 @@ export function parse( if (!sfc.template) { sfc.template = createBlock(node) as SFCTemplateBlock } else { - // TODO warn duplicate template + warnDuplicateBlock(source, filename, node) } break case 'script': if (!sfc.script) { sfc.script = createBlock(node) as SFCScriptBlock } else { - // TODO warn duplicate script + warnDuplicateBlock(source, filename, node) } break case 'style': @@ -105,6 +106,24 @@ export function parse( return sfc } +function warnDuplicateBlock( + source: string, + filename: string, + node: ElementNode +) { + const codeFrame = generateCodeFrame( + 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 + } element (${location}):\n\n${codeFrame}` + ) +} + function createBlock(node: ElementNode): SFCBlock { const type = node.tag const text = node.children[0] as TextNode