From 059caafc13de0d23490783dc75b1c0049e5dccc0 Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 20 Nov 2023 17:38:00 +0800 Subject: [PATCH] wip: pass all compiler-sfc tests --- .../compiler-core/__tests__/parse.spec.ts | 21 +++++++ .../compiler-core/src/parser/Tokenizer.ts | 6 +- packages/compiler-core/src/parser/index.ts | 62 +++++++++++++------ packages/compiler-sfc/__tests__/parse.spec.ts | 26 ++++---- packages/compiler-sfc/src/parse.ts | 29 ++------- 5 files changed, 86 insertions(+), 58 deletions(-) diff --git a/packages/compiler-core/__tests__/parse.spec.ts b/packages/compiler-core/__tests__/parse.spec.ts index 430ee3151..7b753ff39 100644 --- a/packages/compiler-core/__tests__/parse.spec.ts +++ b/packages/compiler-core/__tests__/parse.spec.ts @@ -491,6 +491,27 @@ describe('compiler: parse', () => { }) }) + test('self-closing void element', () => { + const ast = baseParse('after', { + isVoidTag: tag => tag === 'img' + }) + const element = ast.children[0] as ElementNode + + expect(element).toStrictEqual({ + type: NodeTypes.ELEMENT, + ns: Namespaces.HTML, + tag: 'img', + tagType: ElementTypes.ELEMENT, + codegenNode: undefined, + props: [], + children: [], + loc: { + start: { offset: 0, line: 1, column: 1 }, + end: { offset: 6, line: 1, column: 7 } + } + }) + }) + test('template element with directives', () => { const ast = baseParse('') const element = ast.children[0] diff --git a/packages/compiler-core/src/parser/Tokenizer.ts b/packages/compiler-core/src/parser/Tokenizer.ts index d6fbba877..3fe2656a7 100644 --- a/packages/compiler-core/src/parser/Tokenizer.ts +++ b/packages/compiler-core/src/parser/Tokenizer.ts @@ -861,6 +861,9 @@ export default class Tokenizer { this.buffer = input while (this.index < this.buffer.length) { const c = this.buffer.charCodeAt(this.index) + if (c === CharCodes.NewLine) { + this.newlines.push(this.index) + } switch (this.state) { case State.Text: { this.stateText(c) @@ -999,9 +1002,6 @@ export default class Tokenizer { break } } - if (c === CharCodes.NewLine) { - this.newlines.push(this.index) - } this.index++ } this.cleanup() diff --git a/packages/compiler-core/src/parser/index.ts b/packages/compiler-core/src/parser/index.ts index aabaaa0e5..8bdcfb38e 100644 --- a/packages/compiler-core/src/parser/index.ts +++ b/packages/compiler-core/src/parser/index.ts @@ -24,7 +24,12 @@ import Tokenizer, { } from './Tokenizer' import { CompilerCompatOptions } from '../compat/compatConfig' import { NO, extend } from '@vue/shared' -import { defaultOnError, defaultOnWarn } from '../errors' +import { + ErrorCodes, + createCompilerError, + defaultOnError, + defaultOnWarn +} from '../errors' import { forAliasRE, isCoreComponent } from '../utils' import { decodeHTML } from 'entities/lib/decode.js' @@ -105,6 +110,11 @@ const tokenizer = new Tokenizer(stack, { onopentagname(start, end) { const name = getSlice(start, end) + // in SFC mode, root-level tags locations are for its inner content. + const startIndex = + tokenizer.mode === ParseMode.SFC && stack.length === 0 + ? end + fastForward(end, CharCodes.Gt) + 1 + : start - 1 currentElement = { type: NodeTypes.ELEMENT, tag: name, @@ -112,7 +122,7 @@ const tokenizer = new Tokenizer(stack, { tagType: ElementTypes.ELEMENT, // will be refined on tag close props: [], children: [], - loc: getLoc(start - 1), + loc: getLoc(startIndex), codegenNode: undefined } }, @@ -137,7 +147,11 @@ const tokenizer = new Tokenizer(stack, { }, onselfclosingtag(end) { - closeCurrentTag(end) + const name = currentElement!.tag + endOpenTag(end) + if (stack[0]?.tag === name) { + onCloseTag(stack.shift()!, end) + } }, onattribname(start, end) { @@ -318,6 +332,13 @@ const tokenizer = new Tokenizer(stack, { }, onend() { + if (stack.length > 0) { + // has unclosed tag + currentOptions.onError( + // TODO loc info + createCompilerError(ErrorCodes.MISSING_END_TAG_NAME) + ) + } const end = currentInput.length - 1 for (let index = 0; index < stack.length; index++) { onCloseTag(stack[index], end) @@ -421,14 +442,6 @@ function endOpenTag(end: number) { currentElement = null } -function closeCurrentTag(end: number) { - const name = currentElement!.tag - endOpenTag(end) - if (stack[0].tag === name) { - onCloseTag(stack.shift()!, end) - } -} - function onText(content: string, start: number, end: number) { if (__BROWSER__ && content.includes('&')) { // TODO do not do this in