From: Evan You Date: Wed, 9 Oct 2019 20:00:08 +0000 (-0400) Subject: feat(compiler): implement support for v-pre X-Git-Tag: v3.0.0-alpha.0~530 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=5dfb271551912b5080a49b719805cb3706b8a3ed;p=thirdparty%2Fvuejs%2Fcore.git feat(compiler): implement support for v-pre --- diff --git a/packages/compiler-core/__tests__/parse.spec.ts b/packages/compiler-core/__tests__/parse.spec.ts index 4df608b64f..0d3fab3161 100644 --- a/packages/compiler-core/__tests__/parse.spec.ts +++ b/packages/compiler-core/__tests__/parse.spec.ts @@ -1278,6 +1278,88 @@ describe('compiler: parse', () => { }) }) + test('v-pre', () => { + const ast = parse( + `
{{ bar }}
\n` + + `
{{ bar }}
` + ) + + 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('
hello
after') const element = ast.children[0] as ElementNode diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index cbb382c329..019eddb498 100644 --- a/packages/compiler-core/src/parse.ts +++ b/packages/compiler-core/src/parse.ts @@ -26,6 +26,7 @@ import { TemplateChildNode, InterpolationNode } from './ast' +import { extend } from '@vue/shared' export interface ParserOptions { isVoidTag?: (tag: string) => boolean // e.g. img, br, hr @@ -74,6 +75,7 @@ interface ParserContext { line: number column: number maxCRNameLength: number + inPre: boolean } export function parse(content: string, options: ParserOptions = {}): RootNode { @@ -109,7 +111,8 @@ function createParserContext( 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 } } @@ -127,7 +130,7 @@ function parseChildren( 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] === '<') { @@ -325,8 +328,10 @@ function parseElement( __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 @@ -354,6 +359,10 @@ function parseElement( } element.loc = getSelection(context, element.loc.start) + + if (isPreBoundary) { + context.inPre = false + } return element } @@ -380,43 +389,29 @@ function parseTag( 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() - 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. @@ -431,6 +426,13 @@ function parseTag( 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, @@ -444,6 +446,40 @@ function parseTag( } } +function parseAttributes( + context: ParserContext, + type: TagType +): (AttributeNode | DirectiveNode)[] { + const props = [] + const attributeNames = new Set() + 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 @@ -497,7 +533,7 @@ function parseAttribute( } const loc = getSelection(context, start) - if (/^(v-|:|@|#)/.test(name)) { + if (!context.inPre && /^(v-|:|@|#)/.test(name)) { const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^@|^#)([^\.]+))?(.+)?$/i.exec( name )!