From e9017d19d84da5f21e98352dfa7d13cc2ea69c2b Mon Sep 17 00:00:00 2001 From: CathLee <447932704@qq.com> Date: Sun, 21 Apr 2024 23:04:04 +0800 Subject: [PATCH 1/4] feat: Add 'isShouldSelfClosing' tag to identify whether it is the last element --- packages/compiler-core/src/ast.ts | 1 + packages/compiler-core/src/parser.ts | 5 ++- packages/compiler-core/src/tokenizer.ts | 45 ++++++++++++++++++- .../src/transforms/transformElement.ts | 14 +++++- 4 files changed, 60 insertions(+), 5 deletions(-) diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index 720d43cb3..2748645ca 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -70,6 +70,7 @@ export enum ElementTypes { export interface Node { type: NodeTypes loc: SourceLocation + isShouldSelfClosing?: boolean } // The node's range. The `start` is inclusive and `end` is exclusive. diff --git a/packages/compiler-core/src/parser.ts b/packages/compiler-core/src/parser.ts index da8861b92..8fe484ad8 100644 --- a/packages/compiler-core/src/parser.ts +++ b/packages/compiler-core/src/parser.ts @@ -151,7 +151,7 @@ const tokenizer = new Tokenizer(stack, { endOpenTag(end) }, - onclosetag(start, end) { + onclosetag(start, end, isLastElement) { const name = getSlice(start, end) if (!currentOptions.isVoidTag(name)) { let found = false @@ -159,6 +159,9 @@ const tokenizer = new Tokenizer(stack, { const e = stack[i] if (e.tag.toLowerCase() === name.toLowerCase()) { found = true + if (isLastElement) { + e.isShouldSelfClosing = true + } if (i > 0) { emitError(ErrorCodes.X_MISSING_END_TAG, stack[0].loc.start.offset) } diff --git a/packages/compiler-core/src/tokenizer.ts b/packages/compiler-core/src/tokenizer.ts index 561a84b5f..cb3cf394d 100644 --- a/packages/compiler-core/src/tokenizer.ts +++ b/packages/compiler-core/src/tokenizer.ts @@ -186,7 +186,7 @@ export interface Callbacks { onopentagname(start: number, endIndex: number): void onopentagend(endIndex: number): void onselfclosingtag(endIndex: number): void - onclosetag(start: number, endIndex: number): void + onclosetag(start: number, endIndex: number, isLastElement: boolean): void onattribdata(start: number, endIndex: number): void onattribentity(char: string, start: number, end: number): void @@ -246,6 +246,8 @@ export default class Tokenizer { public inVPre = false /** Record newline positions for fast line / column calculation */ private newlines: number[] = [] + // Record current stage + private currentStage = '' private readonly entityDecoder?: EntityDecoder @@ -311,6 +313,7 @@ export default class Tokenizer { if (c === CharCodes.Lt) { if (this.index > this.sectionStart) { this.cbs.ontext(this.sectionStart, this.index) + this.currentStage = 'stateText' } this.state = State.BeforeTagName this.sectionStart = this.index @@ -608,8 +611,13 @@ export default class Tokenizer { } private stateInClosingTagName(c: number): void { if (c === CharCodes.Gt || isWhitespace(c)) { - this.cbs.onclosetag(this.sectionStart, this.index) + this.cbs.onclosetag( + this.sectionStart, + this.index, + this.currentStage === 'stateInTagName', + ) this.sectionStart = -1 + this.currentStage = 'InClosingTagName' this.state = State.AfterClosingTagName this.stateAfterClosingTagName(c) } @@ -619,6 +627,7 @@ export default class Tokenizer { if (c === CharCodes.Gt) { this.state = State.Text this.sectionStart = this.index + 1 + this.currentStage = 'stateAfterClosingTagName' } } private stateBeforeAttrName(c: number): void { @@ -927,78 +936,97 @@ export default class Tokenizer { switch (this.state) { case State.Text: { this.stateText(c) + break } case State.InterpolationOpen: { this.stateInterpolationOpen(c) + this.currentStage = 'stateInterpolationOpen' break } case State.Interpolation: { this.stateInterpolation(c) + this.currentStage = 'stateInterpolation' break } case State.InterpolationClose: { this.stateInterpolationClose(c) + this.currentStage = 'stateInterpolationClose' break } case State.SpecialStartSequence: { this.stateSpecialStartSequence(c) + this.currentStage = 'stateSpecialStartSequence' break } case State.InRCDATA: { this.stateInRCDATA(c) + this.currentStage = 'stateInRCDATA' break } case State.CDATASequence: { this.stateCDATASequence(c) + this.currentStage = 'stateCDATASequence' break } case State.InAttrValueDq: { this.stateInAttrValueDoubleQuotes(c) + this.currentStage = 'stateInAttrValueDoubleQuotes' break } case State.InAttrName: { this.stateInAttrName(c) + this.currentStage = 'stateInAttrName' break } case State.InDirName: { this.stateInDirName(c) + this.currentStage = 'stateInDirName' break } case State.InDirArg: { this.stateInDirArg(c) + this.currentStage = 'stateInDirArg' break } case State.InDirDynamicArg: { this.stateInDynamicDirArg(c) + this.currentStage = 'stateInDynamicDirArg' break } case State.InDirModifier: { this.stateInDirModifier(c) + this.currentStage = 'stateInDirModifier' break } case State.InCommentLike: { this.stateInCommentLike(c) + this.currentStage = 'stateInCommentLike' break } case State.InSpecialComment: { this.stateInSpecialComment(c) + this.currentStage = 'stateInSpecialComment' break } case State.BeforeAttrName: { this.stateBeforeAttrName(c) + this.currentStage = 'stateBeforeAttrName' break } case State.InTagName: { this.stateInTagName(c) + this.currentStage = 'stateInTagName' break } case State.InSFCRootTagName: { this.stateInSFCRootTagName(c) + this.currentStage = 'stateInSFCRootTagName' break } case State.InClosingTagName: { this.stateInClosingTagName(c) + break } case State.BeforeTagName: { @@ -1007,14 +1035,17 @@ export default class Tokenizer { } case State.AfterAttrName: { this.stateAfterAttrName(c) + this.currentStage = 'stateAfterAttrName' break } case State.InAttrValueSq: { this.stateInAttrValueSingleQuotes(c) + this.currentStage = 'stateInAttrValueSingleQuotes' break } case State.BeforeAttrValue: { this.stateBeforeAttrValue(c) + this.currentStage = 'stateBeforeAttrValue' break } case State.BeforeClosingTagName: { @@ -1023,42 +1054,52 @@ export default class Tokenizer { } case State.AfterClosingTagName: { this.stateAfterClosingTagName(c) + break } case State.BeforeSpecialS: { this.stateBeforeSpecialS(c) + this.currentStage = 'stateBeforeSpecialS' break } case State.BeforeSpecialT: { this.stateBeforeSpecialT(c) + this.currentStage = 'stateBeforeSpecialT' break } case State.InAttrValueNq: { this.stateInAttrValueNoQuotes(c) + this.currentStage = 'stateInAttrValueNoQuotes' break } case State.InSelfClosingTag: { this.stateInSelfClosingTag(c) + this.currentStage = 'stateInSelfClosingTag' break } case State.InDeclaration: { this.stateInDeclaration(c) + this.currentStage = 'stateInDeclaration' break } case State.BeforeDeclaration: { this.stateBeforeDeclaration(c) + this.currentStage = 'stateBeforeDeclaration' break } case State.BeforeComment: { this.stateBeforeComment(c) + this.currentStage = 'stateBeforeComment' break } case State.InProcessingInstruction: { this.stateInProcessingInstruction(c) + this.currentStage = 'stateInProcessingInstruction' break } case State.InEntity: { this.stateInEntity() + this.currentStage = 'stateInEntity' break } } diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index 7c51bb6a5..8a5139b53 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -159,11 +159,21 @@ function transformNativeElement( } } } + const { node } = context + + if (node.isShouldSelfClosing) { + template += context.childrenTemplate.join('') + } else { + template += `>` + context.childrenTemplate.join('') + } - template += `>` + context.childrenTemplate.join('') // TODO remove unnecessary close tag, e.g. if it's the last element of the template if (!isVoidTag(tag)) { - template += `` + if (node.isShouldSelfClosing) { + template += ` />` + } else { + template += `` + } } if ( From 1bec10221571ddf911ad6e3a3232fb387a809228 Mon Sep 17 00:00:00 2001 From: CathLee <447932704@qq.com> Date: Sun, 21 Apr 2024 23:06:57 +0800 Subject: [PATCH 2/4] test:The end tag abbreviation test --- .../__snapshots__/parse.spec.ts.snap | 2 ++ .../compiler-core/__tests__/parse.spec.ts | 1 + .../__snapshots__/compile.spec.ts.snap | 30 +++++++++++++++++++ .../__tests__/abbreviation.spec.ts | 19 +++++++++++- .../compiler-vapor/__tests__/compile.spec.ts | 17 +++++++++++ 5 files changed, 68 insertions(+), 1 deletion(-) diff --git a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap index 678548e35..7f765fd68 100644 --- a/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/parse.spec.ts.snap @@ -2399,6 +2399,7 @@ exports[`compiler: parse > Errors > MISSING_END_TAG_NAME >