feat(compiler-core): add parser transform for v-for directive (#65)

* feat(compiler-core): add parser transform for v-for directive

* fix: Include source location for expressions

* chore: remove comment

* refactor(compiler-core): extract hepler functions to utils
This commit is contained in:
Rahul Kadyan 2019-09-19 22:53:49 +05:30 committed by Evan You
parent bbb57c26a2
commit 10c1a2b332
7 changed files with 751 additions and 21 deletions

View File

@ -0,0 +1,480 @@
import { parse } from '../../src/parse'
import { transform } from '../../src/transform'
import { transformFor } from '../../src/directives/vFor'
import { ForNode, NodeTypes } from '../../src/ast'
import { ErrorCodes } from '../../src/errors'
describe('v-for', () => {
test('number expression', () => {
const node = parse('<span v-for="index in 5" />')
transform(node, { transforms: [transformFor] })
expect(node.children.length).toBe(1)
const forNode = node.children[0] as ForNode
expect(forNode.type).toBe(NodeTypes.FOR)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
expect(forNode.valueAlias!.content).toBe('index')
expect(forNode.source.content).toBe('5')
})
test('value', () => {
const node = parse('<span v-for="(item) in items" />')
transform(node, { transforms: [transformFor] })
expect(node.children.length).toBe(1)
const forNode = node.children[0] as ForNode
expect(forNode.type).toBe(NodeTypes.FOR)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
expect(forNode.valueAlias!.content).toBe('item')
expect(forNode.source.content).toBe('items')
})
test('object de-structured value', () => {
const node = parse('<span v-for="({ id, value }) in items" />')
transform(node, { transforms: [transformFor] })
expect(node.children.length).toBe(1)
const forNode = node.children[0] as ForNode
expect(forNode.type).toBe(NodeTypes.FOR)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
expect(forNode.valueAlias!.content).toBe('{ id, value }')
expect(forNode.source.content).toBe('items')
})
test('array de-structured value', () => {
const node = parse('<span v-for="([ id, value ]) in items" />')
transform(node, { transforms: [transformFor] })
expect(node.children.length).toBe(1)
const forNode = node.children[0] as ForNode
expect(forNode.type).toBe(NodeTypes.FOR)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
expect(forNode.valueAlias!.content).toBe('[ id, value ]')
expect(forNode.source.content).toBe('items')
})
test('value and key', () => {
const node = parse('<span v-for="(item, key) in items" />')
transform(node, { transforms: [transformFor] })
expect(node.children.length).toBe(1)
const forNode = node.children[0] as ForNode
expect(forNode.keyAlias).not.toBeUndefined()
expect(forNode.keyAlias!.content).toBe('key')
expect(forNode.objectIndexAlias).toBeUndefined()
expect(forNode.valueAlias!.content).toBe('item')
expect(forNode.source.content).toBe('items')
})
test('value, key and index', () => {
const node = parse('<span v-for="(value, key, index) in items" />')
transform(node, { transforms: [transformFor] })
expect(node.children.length).toBe(1)
const forNode = node.children[0] as ForNode
expect(forNode.type).toBe(NodeTypes.FOR)
expect(forNode.keyAlias).not.toBeUndefined()
expect(forNode.keyAlias!.content).toBe('key')
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.valueAlias!.content).toBe('value')
expect(forNode.source.content).toBe('items')
})
test('skipped key', () => {
const node = parse('<span v-for="(value,,index) in items" />')
transform(node, { transforms: [transformFor] })
expect(node.children.length).toBe(1)
const forNode = node.children[0] as ForNode
expect(forNode.type).toBe(NodeTypes.FOR)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.valueAlias!.content).toBe('value')
expect(forNode.source.content).toBe('items')
})
test('skipped value and key', () => {
const node = parse('<span v-for="(,,index) in items" />')
transform(node, { transforms: [transformFor] })
expect(node.children.length).toBe(1)
const forNode = node.children[0] as ForNode
expect(forNode.type).toBe(NodeTypes.FOR)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.valueAlias).toBeUndefined()
expect(forNode.source.content).toBe('items')
})
test('unbracketed value', () => {
const node = parse('<span v-for="item in items" />')
transform(node, { transforms: [transformFor] })
expect(node.children.length).toBe(1)
const forNode = node.children[0] as ForNode
expect(forNode.type).toBe(NodeTypes.FOR)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).toBeUndefined()
expect(forNode.valueAlias!.content).toBe('item')
expect(forNode.source.content).toBe('items')
})
test('unbracketed value and key', () => {
const node = parse('<span v-for="item, key in items" />')
transform(node, { transforms: [transformFor] })
expect(node.children.length).toBe(1)
const forNode = node.children[0] as ForNode
expect(forNode.type).toBe(NodeTypes.FOR)
expect(forNode.keyAlias).not.toBeUndefined()
expect(forNode.keyAlias!.content).toBe('key')
expect(forNode.objectIndexAlias).toBeUndefined()
expect(forNode.valueAlias!.content).toBe('item')
expect(forNode.source.content).toBe('items')
})
test('unbracketed value, key and index', () => {
const node = parse('<span v-for="value, key, index in items" />')
transform(node, { transforms: [transformFor] })
expect(node.children.length).toBe(1)
const forNode = node.children[0] as ForNode
expect(forNode.type).toBe(NodeTypes.FOR)
expect(forNode.keyAlias).not.toBeUndefined()
expect(forNode.keyAlias!.content).toBe('key')
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.valueAlias!.content).toBe('value')
expect(forNode.source.content).toBe('items')
})
test('unbracketed skipped key', () => {
const node = parse('<span v-for="value, , index in items" />')
transform(node, { transforms: [transformFor] })
expect(node.children.length).toBe(1)
const forNode = node.children[0] as ForNode
expect(forNode.type).toBe(NodeTypes.FOR)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.valueAlias!.content).toBe('value')
expect(forNode.source.content).toBe('items')
})
test('unbracketed skipped value and key', () => {
const node = parse('<span v-for=", , index in items" />')
transform(node, { transforms: [transformFor] })
expect(node.children.length).toBe(1)
const forNode = node.children[0] as ForNode
expect(forNode.type).toBe(NodeTypes.FOR)
expect(forNode.keyAlias).toBeUndefined()
expect(forNode.objectIndexAlias).not.toBeUndefined()
expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.valueAlias).toBeUndefined()
expect(forNode.source.content).toBe('items')
})
test('missing expression', () => {
const node = parse('<span v-for />')
const onError = jest.fn()
transform(node, { transforms: [transformFor], onError })
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
code: ErrorCodes.X_FOR_NO_EXPRESSION
})
)
})
test('empty expression', () => {
const node = parse('<span v-for="" />')
const onError = jest.fn()
transform(node, { transforms: [transformFor], onError })
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
code: ErrorCodes.X_FOR_MALFORMED_EXPRESSION
})
)
})
test('invalid expression', () => {
const node = parse('<span v-for="items" />')
const onError = jest.fn()
transform(node, { transforms: [transformFor], onError })
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
code: ErrorCodes.X_FOR_MALFORMED_EXPRESSION
})
)
})
test('missing source', () => {
const node = parse('<span v-for="item in" />')
const onError = jest.fn()
transform(node, { transforms: [transformFor], onError })
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
code: ErrorCodes.X_FOR_MALFORMED_EXPRESSION
})
)
})
test('missing value', () => {
const node = parse('<span v-for="in items" />')
const onError = jest.fn()
transform(node, { transforms: [transformFor], onError })
expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
code: ErrorCodes.X_FOR_MALFORMED_EXPRESSION
})
)
})
describe('source location', () => {
test('value & source', () => {
const source = '<span v-for="item in items" />'
const node = parse(source)
transform(node, { transforms: [transformFor] })
expect(node.children.length).toBe(1)
const forNode = node.children[0] as ForNode
expect(forNode.type).toBe(NodeTypes.FOR)
expect(forNode.valueAlias!.content).toBe('item')
expect(forNode.valueAlias!.loc.start.offset).toBe(
source.indexOf('item') - 1
)
expect(forNode.valueAlias!.loc.start.line).toBe(1)
expect(forNode.valueAlias!.loc.start.column).toBe(source.indexOf('item'))
expect(forNode.valueAlias!.loc.end.line).toBe(1)
expect(forNode.valueAlias!.loc.end.column).toBe(
source.indexOf('item') + 4
)
expect(forNode.source.content).toBe('items')
expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1)
expect(forNode.source.loc.start.line).toBe(1)
expect(forNode.source.loc.start.column).toBe(source.indexOf('items'))
expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5)
})
test('bracketed value', () => {
const source = '<span v-for="( item ) in items" />'
const node = parse(source)
transform(node, { transforms: [transformFor] })
expect(node.children.length).toBe(1)
const forNode = node.children[0] as ForNode
expect(forNode.type).toBe(NodeTypes.FOR)
expect(forNode.valueAlias!.content).toBe('item')
expect(forNode.valueAlias!.loc.start.offset).toBe(
source.indexOf('item') - 1
)
expect(forNode.valueAlias!.loc.start.line).toBe(1)
expect(forNode.valueAlias!.loc.start.column).toBe(source.indexOf('item'))
expect(forNode.valueAlias!.loc.end.line).toBe(1)
expect(forNode.valueAlias!.loc.end.column).toBe(
source.indexOf('item') + 4
)
expect(forNode.source.content).toBe('items')
expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1)
expect(forNode.source.loc.start.line).toBe(1)
expect(forNode.source.loc.start.column).toBe(source.indexOf('items'))
expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5)
})
test('de-structured value', () => {
const source = '<span v-for="( { id, key })in items" />'
const node = parse(source)
transform(node, { transforms: [transformFor] })
expect(node.children.length).toBe(1)
const forNode = node.children[0] as ForNode
expect(forNode.type).toBe(NodeTypes.FOR)
expect(forNode.valueAlias!.content).toBe('{ id, key }')
expect(forNode.valueAlias!.loc.start.offset).toBe(
source.indexOf('{ id, key }') - 1
)
expect(forNode.valueAlias!.loc.start.line).toBe(1)
expect(forNode.valueAlias!.loc.start.column).toBe(
source.indexOf('{ id, key }')
)
expect(forNode.valueAlias!.loc.end.line).toBe(1)
expect(forNode.valueAlias!.loc.end.column).toBe(
source.indexOf('{ id, key }') + '{ id, key }'.length
)
expect(forNode.source.content).toBe('items')
expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1)
expect(forNode.source.loc.start.line).toBe(1)
expect(forNode.source.loc.start.column).toBe(source.indexOf('items'))
expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5)
})
test('bracketed value, key, index', () => {
const source = '<span v-for="( item, key, index ) in items" />'
const node = parse(source)
transform(node, { transforms: [transformFor] })
expect(node.children.length).toBe(1)
const forNode = node.children[0] as ForNode
expect(forNode.type).toBe(NodeTypes.FOR)
expect(forNode.valueAlias!.content).toBe('item')
expect(forNode.valueAlias!.loc.start.offset).toBe(
source.indexOf('item') - 1
)
expect(forNode.valueAlias!.loc.start.line).toBe(1)
expect(forNode.valueAlias!.loc.start.column).toBe(source.indexOf('item'))
expect(forNode.valueAlias!.loc.end.line).toBe(1)
expect(forNode.valueAlias!.loc.end.column).toBe(
source.indexOf('item') + 4
)
expect(forNode.keyAlias!.content).toBe('key')
expect(forNode.keyAlias!.loc.start.offset).toBe(source.indexOf('key') - 1)
expect(forNode.keyAlias!.loc.start.line).toBe(1)
expect(forNode.keyAlias!.loc.start.column).toBe(source.indexOf('key'))
expect(forNode.keyAlias!.loc.end.line).toBe(1)
expect(forNode.keyAlias!.loc.end.column).toBe(source.indexOf('key') + 3)
expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.objectIndexAlias!.loc.start.offset).toBe(
source.indexOf('index') - 1
)
expect(forNode.objectIndexAlias!.loc.start.line).toBe(1)
expect(forNode.objectIndexAlias!.loc.start.column).toBe(
source.indexOf('index')
)
expect(forNode.objectIndexAlias!.loc.end.line).toBe(1)
expect(forNode.objectIndexAlias!.loc.end.column).toBe(
source.indexOf('index') + 5
)
expect(forNode.source.content).toBe('items')
expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1)
expect(forNode.source.loc.start.line).toBe(1)
expect(forNode.source.loc.start.column).toBe(source.indexOf('items'))
expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5)
})
test('skipped key', () => {
const source = '<span v-for="( item,, index ) in items" />'
const node = parse(source)
transform(node, { transforms: [transformFor] })
expect(node.children.length).toBe(1)
const forNode = node.children[0] as ForNode
expect(forNode.type).toBe(NodeTypes.FOR)
expect(forNode.valueAlias!.content).toBe('item')
expect(forNode.valueAlias!.loc.start.offset).toBe(
source.indexOf('item') - 1
)
expect(forNode.valueAlias!.loc.start.line).toBe(1)
expect(forNode.valueAlias!.loc.start.column).toBe(source.indexOf('item'))
expect(forNode.valueAlias!.loc.end.line).toBe(1)
expect(forNode.valueAlias!.loc.end.column).toBe(
source.indexOf('item') + 4
)
expect(forNode.objectIndexAlias!.content).toBe('index')
expect(forNode.objectIndexAlias!.loc.start.offset).toBe(
source.indexOf('index') - 1
)
expect(forNode.objectIndexAlias!.loc.start.line).toBe(1)
expect(forNode.objectIndexAlias!.loc.start.column).toBe(
source.indexOf('index')
)
expect(forNode.objectIndexAlias!.loc.end.line).toBe(1)
expect(forNode.objectIndexAlias!.loc.end.column).toBe(
source.indexOf('index') + 5
)
expect(forNode.source.content).toBe('items')
expect(forNode.source.loc.start.offset).toBe(source.indexOf('items') - 1)
expect(forNode.source.loc.start.line).toBe(1)
expect(forNode.source.loc.start.column).toBe(source.indexOf('items'))
expect(forNode.source.loc.end.line).toBe(1)
expect(forNode.source.loc.end.column).toBe(source.indexOf('items') + 5)
})
})
})

View File

@ -0,0 +1,69 @@
import { Position } from '../src/ast'
import { getInnerRange, advancePositionBy } from '../src/utils'
function p(line: number, column: number, offset: number): Position {
return { column, line, offset }
}
describe('advancePositionBy', () => {
test('same line', () => {
const pos = p(1, 1, 0)
const newPos = advancePositionBy(pos, 'foo\nbar', 2)
expect(newPos.column).toBe(3)
expect(newPos.line).toBe(1)
expect(newPos.offset).toBe(2)
})
test('same line', () => {
const pos = p(1, 1, 0)
const newPos = advancePositionBy(pos, 'foo\nbar', 4)
expect(newPos.column).toBe(1)
expect(newPos.line).toBe(2)
expect(newPos.offset).toBe(4)
})
test('multiple lines', () => {
const pos = p(1, 1, 0)
const newPos = advancePositionBy(pos, 'foo\nbar\nbaz', 10)
expect(newPos.column).toBe(2)
expect(newPos.line).toBe(3)
expect(newPos.offset).toBe(10)
})
})
describe('getInnerRange', () => {
const loc1 = {
source: 'foo\nbar\nbaz',
start: p(1, 1, 0),
end: p(3, 3, 11)
}
test('at start', () => {
const loc2 = getInnerRange(loc1, 0, 4)
expect(loc2.start).toEqual(loc1.start)
expect(loc2.end.column).toBe(1)
expect(loc2.end.line).toBe(2)
expect(loc2.end.offset).toBe(4)
})
test('at end', () => {
const loc2 = getInnerRange(loc1, 4)
expect(loc2.start.column).toBe(1)
expect(loc2.start.line).toBe(2)
expect(loc2.start.offset).toBe(4)
expect(loc2.end).toEqual(loc1.end)
})
test('in between', () => {
const loc2 = getInnerRange(loc1, 4, 3)
expect(loc2.start.column).toBe(1)
expect(loc2.start.line).toBe(2)
expect(loc2.start.offset).toBe(4)
expect(loc2.end.column).toBe(3)
expect(loc2.end.line).toBe(2)
expect(loc2.end.offset).toBe(7)
})
})

View File

@ -102,9 +102,9 @@ export interface IfBranchNode extends Node {
export interface ForNode extends Node {
type: NodeTypes.FOR
source: ExpressionNode
valueAlias: ExpressionNode
keyAlias: ExpressionNode
objectIndexAlias: ExpressionNode
valueAlias: ExpressionNode | undefined
keyAlias: ExpressionNode | undefined
objectIndexAlias: ExpressionNode | undefined
children: ChildNode[]
}

View File

@ -1 +1,137 @@
// TODO
import { createDirectiveTransform, TransformContext } from '../transform'
import { NodeTypes, ExpressionNode, Node, SourceLocation } from '../ast'
import { createCompilerError, ErrorCodes } from '../errors'
import { getInnerRange } from '../utils'
const forAliasRE = /([\s\S]*?)(?:(?<=\))|\s+)(?:in|of)\s+([\s\S]*)/
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
const stripParensRE = /^\(|\)$/g
export const transformFor = createDirectiveTransform(
'for',
(node, dir, context) => {
if (dir.exp) {
const aliases = parseAliasExpressions(dir.exp.content)
if (aliases) {
context.replaceNode({
type: NodeTypes.FOR,
loc: node.loc,
source: createExpression(aliases.source, dir.exp, context),
valueAlias: aliases.value
? createExpression(aliases.value, dir.exp, context)
: undefined,
keyAlias: aliases.key
? createExpression(aliases.key, dir.exp, context)
: undefined,
objectIndexAlias: aliases.index
? createExpression(aliases.index, dir.exp, context)
: undefined,
children: [node]
})
} else {
context.onError(
createCompilerError(
ErrorCodes.X_FOR_MALFORMED_EXPRESSION,
dir.loc.start
)
)
}
} else {
context.onError(
createCompilerError(ErrorCodes.X_FOR_NO_EXPRESSION, dir.loc.start)
)
}
}
)
function createExpression(
alias: AliasExpression,
node: Node,
context: TransformContext
): ExpressionNode {
const loc: SourceLocation = getInnerRange(
node.loc,
alias.offset,
alias.content.length
)
return {
type: NodeTypes.EXPRESSION,
loc: loc,
content: alias.content,
isStatic: false
}
}
interface AliasExpression {
offset: number
content: string
}
interface AliasExpressions {
source: AliasExpression
value: AliasExpression | undefined
key: AliasExpression | undefined
index: AliasExpression | undefined
}
function parseAliasExpressions(source: string): null | AliasExpressions {
const inMatch = source.match(forAliasRE)
if (!inMatch) return null
const [, LHS, RHS] = inMatch
const result: AliasExpressions = {
source: {
offset: source.indexOf(RHS, LHS.length),
content: RHS.trim()
},
value: undefined,
key: undefined,
index: undefined
}
let valueContent = LHS.trim()
.replace(stripParensRE, '')
.trim()
const trimmedOffset = LHS.indexOf(valueContent)
const iteratorMatch = valueContent.match(forIteratorRE)
if (iteratorMatch) {
valueContent = valueContent.replace(forIteratorRE, '').trim()
const keyContent = iteratorMatch[1].trim()
if (keyContent) {
result.key = {
offset: source.indexOf(keyContent, trimmedOffset + valueContent.length),
content: keyContent
}
}
if (iteratorMatch[2]) {
const indexContent = iteratorMatch[2].trim()
if (indexContent) {
result.index = {
offset: source.indexOf(
indexContent,
result.key
? result.key.offset + result.key.content.length
: trimmedOffset + valueContent.length
),
content: indexContent
}
}
}
}
if (valueContent) {
result.value = {
offset: trimmedOffset,
content: valueContent
}
}
return result
}

View File

@ -59,7 +59,9 @@ export const enum ErrorCodes {
// transform errors
X_ELSE_IF_NO_ADJACENT_IF,
X_ELSE_NO_ADJACENT_IF
X_ELSE_NO_ADJACENT_IF,
X_FOR_NO_EXPRESSION,
X_FOR_MALFORMED_EXPRESSION
}
export const errorMessages: { [code: number]: string } = {
@ -116,5 +118,7 @@ export const errorMessages: { [code: number]: string } = {
// transform errors
[ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF]: `v-else-if has no adjacent v-if`,
[ErrorCodes.X_ELSE_NO_ADJACENT_IF]: `v-else has no adjacent v-if`
[ErrorCodes.X_ELSE_NO_ADJACENT_IF]: `v-else has no adjacent v-if`,
[ErrorCodes.X_FOR_NO_EXPRESSION]: `v-for has no expression`,
[ErrorCodes.X_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression`
}

View File

@ -15,6 +15,7 @@ import {
TextNode,
ChildNode
} from './ast'
import { assert, advancePositionBy } from './utils'
export interface ParserOptions {
isVoidTag?: (tag: string) => boolean // e.g. img, br, hr
@ -795,17 +796,13 @@ function startsWith(source: string, searchString: string): boolean {
function advanceBy(context: ParserContext, numberOfCharacters: number): void {
__DEV__ && assert(numberOfCharacters <= context.source.length)
const { column, source } = context
const str = source.slice(0, numberOfCharacters)
const lines = str.split(/\r?\n/)
const { source } = context
const pos = advancePositionBy(context, source, numberOfCharacters)
context.source = source.slice(numberOfCharacters)
context.offset += numberOfCharacters
context.line += lines.length - 1
context.column =
lines.length === 1
? column + numberOfCharacters
: Math.max(1, lines.pop()!.length)
context.offset = pos.offset
context.line = pos.line
context.column = pos.column
}
function advanceSpaces(context: ParserContext): void {
@ -899,12 +896,6 @@ function startsWithEndTagOpen(source: string, tag: string): boolean {
)
}
function assert(condition: boolean, msg?: string) {
if (!condition) {
throw new Error(msg || `unexpected parser condition`)
}
}
// https://html.spec.whatwg.org/multipage/parsing.html#numeric-character-reference-end-state
const CCR_REPLACEMENTS: { [key: number]: number | undefined } = {
0x80: 0x20ac,

View File

@ -0,0 +1,50 @@
import { SourceLocation, Position } from './ast'
export function getInnerRange(
loc: SourceLocation,
offset: number,
length?: number
): SourceLocation {
const source = loc.source.substr(offset, length)
const newLoc: SourceLocation = {
source,
start: advancePositionBy(loc.start, loc.source, offset),
end: loc.end
}
if (length != null) {
newLoc.end = advancePositionBy(loc.start, loc.source, offset + length)
}
return newLoc
}
export function advancePositionBy(
pos: Position,
source: string,
numberOfCharacters: number
): Position {
__DEV__ && assert(numberOfCharacters <= source.length)
const newPosition = {
...pos
}
const str = source.slice(0, numberOfCharacters)
const lines = str.split(/\r?\n/)
newPosition.offset += numberOfCharacters
newPosition.line += lines.length - 1
newPosition.column =
lines.length === 1
? pos.column + numberOfCharacters
: Math.max(1, lines.pop()!.length)
return newPosition
}
export function assert(condition: boolean, msg?: string) {
if (!condition) {
throw new Error(msg || `unexpected parser condition`)
}
}