From 70edd1c61e9c787ac15b7eaa84b3d12a16d1230f Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 15 Nov 2023 19:36:05 +0800 Subject: [PATCH] wip: check duplicated attributes --- .../compiler-core/src/parser/Tokenizer.ts | 25 +++++------ packages/compiler-core/src/parser/index.ts | 45 +++++++++++-------- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/packages/compiler-core/src/parser/Tokenizer.ts b/packages/compiler-core/src/parser/Tokenizer.ts index 1cb8ab99d..3ec0d0c8c 100644 --- a/packages/compiler-core/src/parser/Tokenizer.ts +++ b/packages/compiler-core/src/parser/Tokenizer.ts @@ -143,6 +143,7 @@ export interface Callbacks { onattribentity(codepoint: number): void onattribend(quote: QuoteType, endIndex: number): void onattribname(start: number, endIndex: number): void + onattribnameend(endIndex: number): void ondirname(start: number, endIndex: number): void ondirarg(start: number, endIndex: number): void @@ -218,7 +219,7 @@ export default class Tokenizer { * processed index, so all the newlines up to this index should have been * recorded. */ - public getPositionForIndex(index: number): Position { + public getPos(index: number): Position { let line = 1 let column = index + 1 for (let i = this.newlines.length - 1; i >= 0; i--) { @@ -513,17 +514,13 @@ export default class Tokenizer { private stateInAttributeName(c: number): void { if (c === CharCodes.Eq || isEndOfTagSection(c)) { this.cbs.onattribname(this.sectionStart, this.index) - this.sectionStart = this.index - this.state = State.AfterAttributeName - this.stateAfterAttributeName(c) + this.handleAttributeNameEnd(c) } } private stateInDirectiveName(c: number): void { if (c === CharCodes.Eq || isEndOfTagSection(c)) { this.cbs.ondirname(this.sectionStart, this.index) - this.sectionStart = this.index - this.state = State.AfterAttributeName - this.stateAfterAttributeName(c) + this.handleAttributeNameEnd(c) } else if (c === CharCodes.Colon) { this.cbs.ondirname(this.sectionStart, this.index) this.state = State.InDirectiveArg @@ -537,9 +534,7 @@ export default class Tokenizer { private stateInDirectiveArg(c: number): void { if (c === CharCodes.Eq || isEndOfTagSection(c)) { this.cbs.ondirarg(this.sectionStart, this.index) - this.sectionStart = this.index - this.state = State.AfterAttributeName - this.stateAfterAttributeName(c) + this.handleAttributeNameEnd(c) } else if (c === CharCodes.LeftSqaure) { this.state = State.InDirectiveDynamicArg } else if (c === CharCodes.Dot) { @@ -558,14 +553,18 @@ export default class Tokenizer { private stateInDirectiveModifier(c: number): void { if (c === CharCodes.Eq || isEndOfTagSection(c)) { this.cbs.ondirmodifier(this.sectionStart, this.index) - this.sectionStart = this.index - this.state = State.AfterAttributeName - this.stateAfterAttributeName(c) + this.handleAttributeNameEnd(c) } else if (c === CharCodes.Dot) { this.cbs.ondirmodifier(this.sectionStart, this.index) this.sectionStart = this.index + 1 } } + private handleAttributeNameEnd(c: number): void { + this.sectionStart = this.index + this.state = State.AfterAttributeName + this.cbs.onattribnameend(this.index) + this.stateAfterAttributeName(c) + } private stateAfterAttributeName(c: number): void { if (c === CharCodes.Eq) { this.state = State.BeforeAttributeValue diff --git a/packages/compiler-core/src/parser/index.ts b/packages/compiler-core/src/parser/index.ts index eb4cd618e..926ba94f3 100644 --- a/packages/compiler-core/src/parser/index.ts +++ b/packages/compiler-core/src/parser/index.ts @@ -116,7 +116,6 @@ const tokenizer = new Tokenizer( }, ondirname(start, end) { - // console.log('name ' + getSlice(start, end)) const raw = getSlice(start, end) const name = raw === '.' || raw === ':' @@ -135,8 +134,8 @@ const tokenizer = new Tokenizer( loc: getLoc(start) } }, + ondirarg(start, end) { - // console.log('arg ' + getSlice(start, end)) const arg = getSlice(start, end) const isStatic = arg[0] !== `[` ;(currentProp as DirectiveNode).arg = { @@ -158,19 +157,28 @@ const tokenizer = new Tokenizer( if (currentAttrStartIndex < 0) currentAttrStartIndex = start currentAttrEndIndex = end }, + onattribentity(codepoint) { currentAttrValue += fromCodePoint(codepoint) }, + + onattribnameend(end) { + // check duplicate attrs + const start = currentProp!.loc.start.offset + const name = getSlice(start, end) + if (currentAttrs.has(name)) { + currentProp = null + // TODO emit error DUPLICATE_ATTRIBUTE + throw new Error(`duplicate attr ${name}`) + } else { + currentAttrs.add(name) + } + }, + onattribend(quote, end) { - // TODO check duplicate - // if (currentAttrs.has(name)) { - // // emit error DUPLICATE_ATTRIBUTE - // } else { - // currentAttrs.add(name) - // } - if (currentElement) { + if (currentElement && currentProp) { if (currentAttrValue) { - if (currentProp!.type === NodeTypes.ATTRIBUTE) { + if (currentProp.type === NodeTypes.ATTRIBUTE) { // assign value currentProp!.value = { type: NodeTypes.TEXT, @@ -182,7 +190,7 @@ const tokenizer = new Tokenizer( } } else { // directive - currentProp!.exp = { + currentProp.exp = { type: NodeTypes.SIMPLE_EXPRESSION, content: currentAttrValue, isStatic: false, @@ -194,7 +202,7 @@ const tokenizer = new Tokenizer( } } } - currentProp!.loc.end = tokenizer.getPositionForIndex(end) + currentProp.loc.end = tokenizer.getPos(end) currentElement.props.push(currentProp!) } currentAttrValue = '' @@ -232,7 +240,7 @@ function emitOpenTag(name: string, start: number) { props: [], children: [], loc: { - start: tokenizer.getPositionForIndex(start - 1), + start: tokenizer.getPos(start - 1), // @ts-expect-error to be attached on tag close end: undefined, source: '' @@ -273,8 +281,8 @@ function onText(content: string, start: number, end: number) { type: NodeTypes.TEXT, content, loc: { - start: tokenizer.getPositionForIndex(start), - end: tokenizer.getPositionForIndex(end), + start: tokenizer.getPos(start), + end: tokenizer.getPos(end), source: content } }) @@ -287,7 +295,7 @@ function onCloseTag(el: ElementNode, end: number) { while (currentInput.charCodeAt(end + offset) !== CharCodes.Gt) { offset++ } - el.loc.end = tokenizer.getPositionForIndex(end + offset + 1) + el.loc.end = tokenizer.getPos(end + offset + 1) // whitepsace management el.children = condenseWhitespace(el.children) } @@ -388,9 +396,9 @@ function getParent() { function getLoc(start: number, end?: number): SourceLocation { return { - start: tokenizer.getPositionForIndex(start), + start: tokenizer.getPos(start), // @ts-expect-error allow late attachment - end: end && tokenizer.getPositionForIndex(end) + end: end && tokenizer.getPos(end) } } @@ -411,6 +419,7 @@ export function baseParse(input: string, options?: ParserOptions): RootNode { currentOptions = extend({}, defaultParserOptions, options) const root = (currentRoot = createRoot([])) tokenizer.parse(currentInput) + root.loc.end = tokenizer.getPos(input.length) root.children = condenseWhitespace(root.children) return root }