})
})
+ test('v-pre', () => {
+ const ast = parse(
+ `<div v-pre :id="foo"><Comp/>{{ bar }}</div>\n` +
+ `<div :id="foo"><Comp/>{{ bar }}</div>`
+ )
+
+ const divWithPre = ast.children[0] as ElementNode
+ expect(divWithPre.props).toMatchObject([
+ {
+ type: NodeTypes.ATTRIBUTE,
+ name: `:id`,
+ value: {
+ type: NodeTypes.TEXT,
+ content: `foo`
+ },
+ loc: {
+ source: `:id="foo"`,
+ start: {
+ line: 1,
+ column: 12
+ },
+ end: {
+ line: 1,
+ column: 21
+ }
+ }
+ }
+ ])
+ expect(divWithPre.children[0]).toMatchObject({
+ type: NodeTypes.ELEMENT,
+ tagType: ElementTypes.ELEMENT,
+ tag: `Comp`
+ })
+ expect(divWithPre.children[1]).toMatchObject({
+ type: NodeTypes.TEXT,
+ content: `{{ bar }}`
+ })
+
+ // should not affect siblings after it
+ const divWithoutPre = ast.children[1] as ElementNode
+ expect(divWithoutPre.props).toMatchObject([
+ {
+ type: NodeTypes.DIRECTIVE,
+ name: `bind`,
+ arg: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ isStatic: true,
+ content: `id`
+ },
+ exp: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ isStatic: false,
+ content: `foo`
+ },
+ loc: {
+ source: `:id="foo"`,
+ start: {
+ line: 2,
+ column: 6
+ },
+ end: {
+ line: 2,
+ column: 15
+ }
+ }
+ }
+ ])
+ expect(divWithoutPre.children[0]).toMatchObject({
+ type: NodeTypes.ELEMENT,
+ tagType: ElementTypes.COMPONENT,
+ tag: `Comp`
+ })
+ expect(divWithoutPre.children[1]).toMatchObject({
+ type: NodeTypes.INTERPOLATION,
+ content: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `bar`,
+ isStatic: false
+ }
+ })
+ })
+
test('end tags are case-insensitive.', () => {
const ast = parse('<div>hello</DIV>after')
const element = ast.children[0] as ElementNode
TemplateChildNode,
InterpolationNode
} from './ast'
+import { extend } from '@vue/shared'
export interface ParserOptions {
isVoidTag?: (tag: string) => boolean // e.g. img, br, hr
line: number
column: number
maxCRNameLength: number
+ inPre: boolean
}
export function parse(content: string, options: ParserOptions = {}): RootNode {
maxCRNameLength: Object.keys(
options.namedCharacterReferences ||
defaultParserOptions.namedCharacterReferences
- ).reduce((max, name) => Math.max(max, name.length), 0)
+ ).reduce((max, name) => Math.max(max, name.length), 0),
+ inPre: false
}
}
const s = context.source
let node: TemplateChildNode | TemplateChildNode[] | undefined = undefined
- if (startsWith(s, context.options.delimiters[0])) {
+ if (!context.inPre && startsWith(s, context.options.delimiters[0])) {
// '{{'
node = parseInterpolation(context, mode)
} else if (mode === TextModes.DATA && s[0] === '<') {
__DEV__ && assert(/^<[a-z]/i.test(context.source))
// Start tag.
+ const wasInPre = context.inPre
const parent = last(ancestors)
const element = parseTag(context, TagType.Start, parent)
+ const isPreBoundary = context.inPre && !wasInPre
if (element.isSelfClosing || context.options.isVoidTag(element.tag)) {
return element
}
element.loc = getSelection(context, element.loc.start)
+
+ if (isPreBoundary) {
+ context.inPre = false
+ }
return element
}
const start = getCursor(context)
const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!
const tag = match[1]
- const props = []
const ns = context.options.getNamespace(tag, parent)
- let tagType = ElementTypes.ELEMENT
- if (tag === 'slot') tagType = ElementTypes.SLOT
- else if (tag === 'template') tagType = ElementTypes.TEMPLATE
- else if (/[A-Z-]/.test(tag)) tagType = ElementTypes.COMPONENT
-
advanceBy(context, match[0].length)
advanceSpaces(context)
- // Attributes.
- const attributeNames = new Set<string>()
- while (
- context.source.length > 0 &&
- !startsWith(context.source, '>') &&
- !startsWith(context.source, '/>')
- ) {
- if (startsWith(context.source, '/')) {
- emitError(context, ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG)
- advanceBy(context, 1)
- advanceSpaces(context)
- continue
- }
- if (type === TagType.End) {
- emitError(context, ErrorCodes.END_TAG_WITH_ATTRIBUTES)
- }
+ // save current state in case we need to re-parse attributes with v-pre
+ const cursor = getCursor(context)
+ const currentSource = context.source
- const attr = parseAttribute(context, attributeNames)
- if (type === TagType.Start) {
- props.push(attr)
- }
+ // Attributes.
+ let props = parseAttributes(context, type)
- if (/^[^\t\r\n\f />]/.test(context.source)) {
- emitError(context, ErrorCodes.MISSING_WHITESPACE_BETWEEN_ATTRIBUTES)
- }
- advanceSpaces(context)
+ // check v-pre
+ if (
+ !context.inPre &&
+ props.some(p => p.type === NodeTypes.DIRECTIVE && p.name === 'pre')
+ ) {
+ context.inPre = true
+ // reset context
+ extend(context, cursor)
+ context.source = currentSource
+ // re-parse attrs and filter out v-pre itself
+ props = parseAttributes(context, type).filter(p => p.name !== 'v-pre')
}
// Tag close.
advanceBy(context, isSelfClosing ? 2 : 1)
}
+ let tagType = ElementTypes.ELEMENT
+ if (!context.inPre) {
+ if (tag === 'slot') tagType = ElementTypes.SLOT
+ else if (tag === 'template') tagType = ElementTypes.TEMPLATE
+ else if (/[A-Z-]/.test(tag)) tagType = ElementTypes.COMPONENT
+ }
+
return {
type: NodeTypes.ELEMENT,
ns,
}
}
+function parseAttributes(
+ context: ParserContext,
+ type: TagType
+): (AttributeNode | DirectiveNode)[] {
+ const props = []
+ const attributeNames = new Set<string>()
+ while (
+ context.source.length > 0 &&
+ !startsWith(context.source, '>') &&
+ !startsWith(context.source, '/>')
+ ) {
+ if (startsWith(context.source, '/')) {
+ emitError(context, ErrorCodes.UNEXPECTED_SOLIDUS_IN_TAG)
+ advanceBy(context, 1)
+ advanceSpaces(context)
+ continue
+ }
+ if (type === TagType.End) {
+ emitError(context, ErrorCodes.END_TAG_WITH_ATTRIBUTES)
+ }
+
+ const attr = parseAttribute(context, attributeNames)
+ if (type === TagType.Start) {
+ props.push(attr)
+ }
+
+ if (/^[^\t\r\n\f />]/.test(context.source)) {
+ emitError(context, ErrorCodes.MISSING_WHITESPACE_BETWEEN_ATTRIBUTES)
+ }
+ advanceSpaces(context)
+ }
+ return props
+}
+
function parseAttribute(
context: ParserContext,
nameSet: Set<string>
}
const loc = getSelection(context, start)
- if (/^(v-|:|@|#)/.test(name)) {
+ if (!context.inPre && /^(v-|:|@|#)/.test(name)) {
const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^@|^#)([^\.]+))?(.+)?$/i.exec(
name
)!