private readonly decodeEntities: boolean
private readonly entityDecoder: EntityDecoder
+ public line = 1
+ public column = 1
+ public startLine = 1
+ public startColumn = 1
+
constructor(
{ decodeEntities = true }: { decodeEntities?: boolean },
private readonly cbs: Callbacks
public reset(): void {
this.state = State.Text
this.buffer = ''
- this.sectionStart = 0
+ this.recordStart(0)
this.index = 0
+ this.line = 1
+ this.column = 1
+ this.startLine = 1
+ this.startColumn = 1
this.baseState = State.Text
this.currentSequence = undefined!
}
+ private recordStart(start = this.index) {
+ this.sectionStart = start
+ this.startLine = this.line
+ this.startColumn = this.column + (start - this.index)
+ }
+
private stateText(c: number): void {
if (
c === CharCodes.Lt ||
this.cbs.ontext(this.sectionStart, this.index)
}
this.state = State.BeforeTagName
- this.sectionStart = this.index
+ this.recordStart()
} else if (this.decodeEntities && c === CharCodes.Amp) {
this.startEntity()
}
}
this.isSpecial = false
- this.sectionStart = endOfText + 2 // Skip over the `</`
+ this.recordStart(endOfText + 2) // Skip over the `</`
this.stateInClosingTagName(c)
return // We are done; skip the rest of the function.
}
this.state = State.InCommentLike
this.currentSequence = Sequences.CdataEnd
this.sequenceIndex = 0
- this.sectionStart = this.index + 1
+ this.recordStart(this.index + 1)
}
} else {
this.sequenceIndex = 0
}
this.sequenceIndex = 0
- this.sectionStart = this.index + 1
+ this.recordStart(this.index + 1)
this.state = State.Text
}
} else if (this.sequenceIndex === 0) {
private stateBeforeTagName(c: number): void {
if (c === CharCodes.ExclamationMark) {
this.state = State.BeforeDeclaration
- this.sectionStart = this.index + 1
+ this.recordStart(this.index + 1)
} else if (c === CharCodes.Questionmark) {
this.state = State.InProcessingInstruction
- this.sectionStart = this.index + 1
+ this.recordStart(this.index + 1)
} else if (this.isTagStartChar(c)) {
const lower = c | 0x20
- this.sectionStart = this.index
+ this.recordStart()
if (lower === Sequences.TitleEnd[2]) {
this.startSpecial(Sequences.TitleEnd, 3)
} else {
private stateInTagName(c: number): void {
if (isEndOfTagSection(c)) {
this.cbs.onopentagname(this.sectionStart, this.index)
- this.sectionStart = -1
+ this.recordStart(-1)
this.state = State.BeforeAttributeName
this.stateBeforeAttributeName(c)
}
this.state = this.isTagStartChar(c)
? State.InClosingTagName
: State.InSpecialComment
- this.sectionStart = this.index
+ this.recordStart()
}
}
private stateInClosingTagName(c: number): void {
if (c === CharCodes.Gt || isWhitespace(c)) {
this.cbs.onclosetag(this.sectionStart, this.index)
- this.sectionStart = -1
+ this.recordStart(-1)
this.state = State.AfterClosingTagName
this.stateAfterClosingTagName(c)
}
// Skip everything until ">"
if (c === CharCodes.Gt || this.fastForwardTo(CharCodes.Gt)) {
this.state = State.Text
- this.sectionStart = this.index + 1
+ this.recordStart(this.index + 1)
}
}
private stateBeforeAttributeName(c: number): void {
} else {
this.state = State.Text
}
- this.sectionStart = this.index + 1
+ this.recordStart(this.index + 1)
} else if (c === CharCodes.Slash) {
this.state = State.InSelfClosingTag
} else if (!isWhitespace(c)) {
this.state = State.InAttributeName
- this.sectionStart = this.index
+ this.recordStart()
}
}
private stateInSelfClosingTag(c: number): void {
if (c === CharCodes.Gt) {
this.cbs.onselfclosingtag(this.index)
this.state = State.Text
- this.sectionStart = this.index + 1
+ this.recordStart(this.index + 1)
this.isSpecial = false // Reset special state, in case of self-closing special tags
} else if (!isWhitespace(c)) {
this.state = State.BeforeAttributeName
private stateInAttributeName(c: number): void {
if (c === CharCodes.Eq || isEndOfTagSection(c)) {
this.cbs.onattribname(this.sectionStart, this.index)
- this.sectionStart = this.index
+ this.recordStart()
this.state = State.AfterAttributeName
this.stateAfterAttributeName(c)
}
this.state = State.BeforeAttributeValue
} else if (c === CharCodes.Slash || c === CharCodes.Gt) {
this.cbs.onattribend(QuoteType.NoValue, this.sectionStart)
- this.sectionStart = -1
+ this.recordStart(-1)
this.state = State.BeforeAttributeName
this.stateBeforeAttributeName(c)
} else if (!isWhitespace(c)) {
this.cbs.onattribend(QuoteType.NoValue, this.sectionStart)
this.state = State.InAttributeName
- this.sectionStart = this.index
+ this.recordStart()
}
}
private stateBeforeAttributeValue(c: number): void {
if (c === CharCodes.DoubleQuote) {
this.state = State.InAttributeValueDq
- this.sectionStart = this.index + 1
+ this.recordStart(this.index + 1)
} else if (c === CharCodes.SingleQuote) {
this.state = State.InAttributeValueSq
- this.sectionStart = this.index + 1
+ this.recordStart(this.index + 1)
} else if (!isWhitespace(c)) {
- this.sectionStart = this.index
+ this.recordStart()
this.state = State.InAttributeValueNq
this.stateInAttributeValueNoQuotes(c) // Reconsume token
}
private handleInAttributeValue(c: number, quote: number) {
if (c === quote || (!this.decodeEntities && this.fastForwardTo(quote))) {
this.cbs.onattribdata(this.sectionStart, this.index)
- this.sectionStart = -1
+ this.recordStart(-1)
this.cbs.onattribend(
quote === CharCodes.DoubleQuote ? QuoteType.Double : QuoteType.Single,
this.index + 1
private stateInAttributeValueNoQuotes(c: number): void {
if (isWhitespace(c) || c === CharCodes.Gt) {
this.cbs.onattribdata(this.sectionStart, this.index)
- this.sectionStart = -1
+ this.recordStart(-1)
this.cbs.onattribend(QuoteType.Unquoted, this.index)
this.state = State.BeforeAttributeName
this.stateBeforeAttributeName(c)
if (c === CharCodes.Gt || this.fastForwardTo(CharCodes.Gt)) {
this.cbs.ondeclaration(this.sectionStart, this.index)
this.state = State.Text
- this.sectionStart = this.index + 1
+ this.recordStart(this.index + 1)
}
}
private stateInProcessingInstruction(c: number): void {
if (c === CharCodes.Gt || this.fastForwardTo(CharCodes.Gt)) {
this.cbs.onprocessinginstruction(this.sectionStart, this.index)
this.state = State.Text
- this.sectionStart = this.index + 1
+ this.recordStart(this.index + 1)
}
}
private stateBeforeComment(c: number): void {
this.currentSequence = Sequences.CommentEnd
// Allow short comments (eg. <!-->)
this.sequenceIndex = 2
- this.sectionStart = this.index + 1
+ this.recordStart(this.index + 1)
} else {
this.state = State.InDeclaration
}
if (c === CharCodes.Gt || this.fastForwardTo(CharCodes.Gt)) {
this.cbs.oncomment(this.sectionStart, this.index, 0)
this.state = State.Text
- this.sectionStart = this.index + 1
+ this.recordStart(this.index + 1)
}
}
private stateBeforeSpecialS(c: number): void {
}
}
this.index++
+ // line / column handling
+ if (c === CharCodes.NewLine) {
+ this.line++
+ this.column = 1
+ } else {
+ this.column++
+ }
}
this.cleanup()
this.finish()
(this.state === State.InSpecialTag && this.sequenceIndex === 0)
) {
this.cbs.ontext(this.sectionStart, this.index)
- this.sectionStart = this.index
+ this.recordStart()
} else if (
this.state === State.InAttributeValueDq ||
this.state === State.InAttributeValueSq ||
this.state === State.InAttributeValueNq
) {
this.cbs.onattribdata(this.sectionStart, this.index)
- this.sectionStart = this.index
+ this.recordStart()
}
}
}
if (this.sectionStart < this.entityStart) {
this.cbs.onattribdata(this.sectionStart, this.entityStart)
}
- this.sectionStart = this.entityStart + consumed
+ this.recordStart(this.entityStart + consumed)
this.index = this.sectionStart - 1
this.cbs.onattribentity(cp)
if (this.sectionStart < this.entityStart) {
this.cbs.ontext(this.sectionStart, this.entityStart)
}
- this.sectionStart = this.entityStart + consumed
+ this.recordStart(this.entityStart + consumed)
this.index = this.sectionStart - 1
this.cbs.ontextentity(cp, this.sectionStart)
{ decodeEntities: true },
{
ontext(start, end) {
- const content = getSlice(start, end)
- endIndex = end - 1
- onText(content)
- startIndex = end
+ onText(getSlice(start, end), start, end)
},
ontextentity(cp, end) {
- endIndex = end - 1
- onText(fromCodePoint(cp))
- startIndex = end
+ onText(fromCodePoint(cp), end - 1, end)
},
onopentagname(start, end) {
onattribentity(codepoint) {
attribvalue += fromCodePoint(codepoint)
},
- onattribend(quote, end) {
+ onattribend(_quote, end) {
endIndex = end
if (attribs && !hasOwn(attribs, attribname)) {
// TODO gen attributes AST nodes
tagname = ''
}
-function onText(content: string) {
+function onText(content: string, start: number, end: number) {
const parent = getParent()
const lastNode = parent.children[parent.children.length - 1]
if (lastNode?.type === NodeTypes.TEXT) {
parent.children.push({
type: NodeTypes.TEXT,
content,
- // @ts-ignore TODO
- loc: {}
+ loc: {
+ start: {
+ offset: start,
+ line: tokenizer.startLine,
+ column: tokenizer.startColumn
+ },
+ end: { offset: end, line: tokenizer.line, column: tokenizer.column },
+ source: content
+ }
})
}
}
// TODO props
props: [],
children: [],
- // @ts-ignore TODO
- loc: {},
+ loc: {
+ // @ts-expect-error TODO
+ start: {},
+ // @ts-expect-error TODO
+ end: { offset: endIndex },
+ source: ''
+ },
codegenNode: undefined
}
addNode(el)
function onCloseTag() {
const el = elementStack.pop()!
// whitepsace management
- const nodes = el.children
+ el.children = condenseWhitespace(el.children)
+}
+
+const windowsNewlineRE = /\r\n/g
+const consecutiveWhitespaceRE = /[\t\r\n\f ]+/g
+const nonWhitespaceRE = /[^\t\r\n\f ]/
+
+function isEmptyText(content: string) {
+ return !nonWhitespaceRE.test(content)
+}
+
+function condenseWhitespace(nodes: TemplateChildNode[]): TemplateChildNode[] {
const shouldCondense = currentOptions.whitespace !== 'preserve'
let removedWhitespace = false
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
if (node.type === NodeTypes.TEXT) {
if (!inPre) {
- if (!/[^\t\r\n\f ]/.test(node.content)) {
+ if (isEmptyText(node.content)) {
const prev = nodes[i - 1]
const next = nodes[i + 1]
// Remove if:
} else if (shouldCondense) {
// in condense mode, consecutive whitespaces in text are condensed
// down to a single space.
- node.content = node.content.replace(/[\t\r\n\f ]+/g, ' ')
+ node.content = node.content.replace(consecutiveWhitespaceRE, ' ')
}
} else {
// #6410 normalize windows newlines in <pre>:
// in SSR, browsers normalize server-rendered \r\n into a single \n
// in the DOM
- node.content = node.content.replace(/\r\n/g, '\n')
+ node.content = node.content.replace(windowsNewlineRE, '\n')
}
}
}
- if (removedWhitespace) {
- el.children = nodes.filter(Boolean)
- }
+ return removedWhitespace ? nodes.filter(Boolean) : nodes
}
function addNode(node: TemplateChildNode) {
options: ParserOptions = {}
): RootNode {
reset()
- currentInput = input.trim()
+ currentInput = input
currentOptions = options
htmlMode = !!options.htmlMode
const root = (currentRoot = createRoot([]))
tokenizer.parse(currentInput)
- // temp hack for ts
- console.log(endIndex)
+ root.children = condenseWhitespace(root.children)
return root
}