mirror of https://github.com/vuejs/core.git
refactor: swap to new template parser
- get rid of SourceLocation.source for memory efficiency - move source location generation logic transform phase into the parser itself so that SourceLocation.source is no longer needed - move v-for expression parsing into the parser itself - added nameLoc on AttributeNode for use in transformElement Tests are not passing yet.
This commit is contained in:
parent
65b44045ef
commit
a60ad9180d
|
@ -40,6 +40,7 @@ import { PatchFlags } from '@vue/shared'
|
|||
function createRoot(options: Partial<RootNode> = {}): RootNode {
|
||||
return {
|
||||
type: NodeTypes.ROOT,
|
||||
source: '',
|
||||
children: [],
|
||||
helpers: new Set(),
|
||||
components: [],
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { ParserOptions } from '../src/options'
|
||||
import { TextModes } from '../src/parse'
|
||||
import { ErrorCodes } from '../src/errors'
|
||||
import {
|
||||
CommentNode,
|
||||
|
@ -1913,35 +1912,38 @@ describe('compiler: parse', () => {
|
|||
})
|
||||
|
||||
test.skip('parse with correct location info', () => {
|
||||
const fooSrc = `foo
|
||||
is `
|
||||
const barSrc = `{{ bar }}`
|
||||
const butSrc = ` but `
|
||||
const bazSrc = `{{ baz }}`
|
||||
const [foo, bar, but, baz] = baseParse(
|
||||
`
|
||||
foo
|
||||
is {{ bar }} but {{ baz }}`.trim()
|
||||
fooSrc + barSrc + butSrc + bazSrc
|
||||
).children
|
||||
|
||||
let offset = 0
|
||||
expect(foo.loc.start).toEqual({ line: 1, column: 1, offset })
|
||||
offset += foo.loc.source.length
|
||||
offset += fooSrc.length
|
||||
expect(foo.loc.end).toEqual({ line: 2, column: 5, offset })
|
||||
|
||||
expect(bar.loc.start).toEqual({ line: 2, column: 5, offset })
|
||||
const barInner = (bar as InterpolationNode).content
|
||||
offset += 3
|
||||
expect(barInner.loc.start).toEqual({ line: 2, column: 8, offset })
|
||||
offset += barInner.loc.source.length
|
||||
offset += 3
|
||||
expect(barInner.loc.end).toEqual({ line: 2, column: 11, offset })
|
||||
offset += 3
|
||||
expect(bar.loc.end).toEqual({ line: 2, column: 14, offset })
|
||||
|
||||
expect(but.loc.start).toEqual({ line: 2, column: 14, offset })
|
||||
offset += but.loc.source.length
|
||||
offset += butSrc.length
|
||||
expect(but.loc.end).toEqual({ line: 2, column: 19, offset })
|
||||
|
||||
expect(baz.loc.start).toEqual({ line: 2, column: 19, offset })
|
||||
const bazInner = (baz as InterpolationNode).content
|
||||
offset += 3
|
||||
expect(bazInner.loc.start).toEqual({ line: 2, column: 22, offset })
|
||||
offset += bazInner.loc.source.length
|
||||
offset += 3
|
||||
expect(bazInner.loc.end).toEqual({ line: 2, column: 25, offset })
|
||||
offset += 3
|
||||
expect(baz.loc.end).toEqual({ line: 2, column: 28, offset })
|
||||
|
@ -2073,8 +2075,7 @@ foo
|
|||
|
||||
test.skip('should NOT condense whitespaces in RCDATA text mode', () => {
|
||||
const ast = baseParse(`<textarea>Text:\n foo</textarea>`, {
|
||||
getTextMode: ({ tag }) =>
|
||||
tag === 'textarea' ? TextModes.RCDATA : TextModes.DATA
|
||||
parseMode: 'html'
|
||||
})
|
||||
const preElement = ast.children[0] as ElementNode
|
||||
expect(preElement.children).toHaveLength(1)
|
||||
|
@ -3069,24 +3070,7 @@ foo
|
|||
() => {
|
||||
const spy = vi.fn()
|
||||
const ast = baseParse(code, {
|
||||
getNamespace: (tag, parent) => {
|
||||
const ns = parent ? parent.ns : Namespaces.HTML
|
||||
if (ns === Namespaces.HTML) {
|
||||
if (tag === 'svg') {
|
||||
return (Namespaces.HTML + 1) as any
|
||||
}
|
||||
}
|
||||
return ns
|
||||
},
|
||||
getTextMode: ({ tag }) => {
|
||||
if (tag === 'textarea') {
|
||||
return TextModes.RCDATA
|
||||
}
|
||||
if (tag === 'script') {
|
||||
return TextModes.RAWTEXT
|
||||
}
|
||||
return TextModes.DATA
|
||||
},
|
||||
parseMode: 'html',
|
||||
...options,
|
||||
onError: spy
|
||||
})
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { baseParse } from '../src/parse'
|
||||
import { baseParse } from '../src/parser'
|
||||
import { transform, NodeTransform } from '../src/transform'
|
||||
import {
|
||||
ElementNode,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { baseParse as parse } from '../../src/parse'
|
||||
import { baseParse as parse } from '../../src/parser'
|
||||
import { transform } from '../../src/transform'
|
||||
import { transformIf } from '../../src/transforms/vIf'
|
||||
import { transformFor } from '../../src/transforms/vFor'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { baseParse as parse } from '../../src/parse'
|
||||
import { baseParse as parse } from '../../src/parser'
|
||||
import { transform } from '../../src/transform'
|
||||
import { transformIf } from '../../src/transforms/vIf'
|
||||
import { transformElement } from '../../src/transforms/transformElement'
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { TransformContext } from '../src'
|
||||
import { Position } from '../src/ast'
|
||||
import {
|
||||
getInnerRange,
|
||||
advancePositionWithClone,
|
||||
isMemberExpressionNode,
|
||||
isMemberExpressionBrowser,
|
||||
|
@ -41,32 +40,6 @@ describe('advancePositionWithClone', () => {
|
|||
})
|
||||
})
|
||||
|
||||
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('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(4)
|
||||
expect(loc2.end.line).toBe(2)
|
||||
expect(loc2.end.offset).toBe(7)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isMemberExpression', () => {
|
||||
function commonAssertions(fn: (str: string) => boolean) {
|
||||
// should work
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { isString } from '@vue/shared'
|
||||
import { ForParseResult } from './transforms/vFor'
|
||||
import {
|
||||
RENDER_SLOT,
|
||||
CREATE_SLOTS,
|
||||
|
@ -76,7 +75,6 @@ export interface Node {
|
|||
export interface SourceLocation {
|
||||
start: Position
|
||||
end: Position
|
||||
source: string
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
|
@ -102,6 +100,7 @@ export type TemplateChildNode =
|
|||
|
||||
export interface RootNode extends Node {
|
||||
type: NodeTypes.ROOT
|
||||
source: string
|
||||
children: TemplateChildNode[]
|
||||
helpers: Set<symbol>
|
||||
components: string[]
|
||||
|
@ -182,20 +181,33 @@ export interface CommentNode extends Node {
|
|||
export interface AttributeNode extends Node {
|
||||
type: NodeTypes.ATTRIBUTE
|
||||
name: string
|
||||
nameLoc: SourceLocation
|
||||
value: TextNode | undefined
|
||||
}
|
||||
|
||||
export interface DirectiveNode extends Node {
|
||||
type: NodeTypes.DIRECTIVE
|
||||
/**
|
||||
* the normalized name without prefix or shorthands, e.g. "bind", "on"
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* the raw attribute name, preserving shorthand, and including arg & modifiers
|
||||
* this is only used during parse.
|
||||
*/
|
||||
rawName?: string
|
||||
exp: ExpressionNode | undefined
|
||||
/**
|
||||
* the raw expression as a string
|
||||
* only required on directives parsed from templates
|
||||
*/
|
||||
rawExp?: string
|
||||
arg: ExpressionNode | undefined
|
||||
modifiers: string[]
|
||||
raw?: string
|
||||
/**
|
||||
* optional property to cache the expression parse result for v-for
|
||||
*/
|
||||
parseResult?: ForParseResult
|
||||
forParseResult?: ForParseResult
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -277,6 +289,14 @@ export interface ForNode extends Node {
|
|||
codegenNode?: ForCodegenNode
|
||||
}
|
||||
|
||||
export interface ForParseResult {
|
||||
source: ExpressionNode
|
||||
value: ExpressionNode | undefined
|
||||
key: ExpressionNode | undefined
|
||||
index: ExpressionNode | undefined
|
||||
finalized: boolean
|
||||
}
|
||||
|
||||
export interface TextCallNode extends Node {
|
||||
type: NodeTypes.TEXT_CALL
|
||||
content: TextNode | InterpolationNode | CompoundExpressionNode
|
||||
|
@ -548,17 +568,17 @@ export interface ForIteratorExpression extends FunctionExpression {
|
|||
// associated with template nodes, so their source locations are just a stub.
|
||||
// Container types like CompoundExpression also don't need a real location.
|
||||
export const locStub: SourceLocation = {
|
||||
source: '',
|
||||
start: { line: 1, column: 1, offset: 0 },
|
||||
end: { line: 1, column: 1, offset: 0 }
|
||||
}
|
||||
|
||||
export function createRoot(
|
||||
children: TemplateChildNode[],
|
||||
loc = locStub
|
||||
source = ''
|
||||
): RootNode {
|
||||
return {
|
||||
type: NodeTypes.ROOT,
|
||||
source,
|
||||
children,
|
||||
helpers: new Set(),
|
||||
components: [],
|
||||
|
@ -568,7 +588,7 @@ export function createRoot(
|
|||
cached: 0,
|
||||
temps: 0,
|
||||
codegenNode: undefined,
|
||||
loc
|
||||
loc: locStub
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -116,7 +116,7 @@ function createCodegenContext(
|
|||
ssr,
|
||||
isTS,
|
||||
inSSR,
|
||||
source: ast.loc.source,
|
||||
source: ast.source,
|
||||
code: ``,
|
||||
column: 1,
|
||||
line: 1,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { SourceLocation } from '../ast'
|
||||
import { CompilerError } from '../errors'
|
||||
// @ts-expect-error TODO
|
||||
import { ParserContext } from '../parse'
|
||||
import { TransformContext } from '../transform'
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { CompilerOptions } from './options'
|
||||
import { baseParse } from './parse'
|
||||
import { baseParse } from './parser/index'
|
||||
import { transform, NodeTransform, DirectiveTransform } from './transform'
|
||||
import { generate, CodegenResult } from './codegen'
|
||||
import { RootNode } from './ast'
|
||||
|
|
|
@ -10,7 +10,7 @@ export {
|
|||
type BindingMetadata,
|
||||
BindingTypes
|
||||
} from './options'
|
||||
export { baseParse, TextModes } from './parse'
|
||||
export { baseParse } from './parser'
|
||||
export {
|
||||
transform,
|
||||
type TransformContext,
|
||||
|
@ -70,5 +70,3 @@ export {
|
|||
warnDeprecation,
|
||||
CompilerDeprecationTypes
|
||||
} from './compat/compatConfig'
|
||||
|
||||
export { baseParse as newParse } from './parser/index'
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { ElementNode, Namespace, TemplateChildNode, ParentNode } from './ast'
|
||||
import { TextModes } from './parse'
|
||||
import { CompilerError } from './errors'
|
||||
import {
|
||||
NodeTransform,
|
||||
|
@ -42,13 +41,6 @@ export interface ParserOptions
|
|||
* Get tag namespace
|
||||
*/
|
||||
getNamespace?: (tag: string, parent: ElementNode | undefined) => Namespace
|
||||
/**
|
||||
* Get text parsing mode for this element
|
||||
*/
|
||||
getTextMode?: (
|
||||
node: ElementNode,
|
||||
parent: ElementNode | undefined
|
||||
) => TextModes
|
||||
/**
|
||||
* @default ['{{', '}}']
|
||||
*/
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,13 +5,15 @@ import {
|
|||
DirectiveNode,
|
||||
ElementNode,
|
||||
ElementTypes,
|
||||
ForParseResult,
|
||||
Namespaces,
|
||||
NodeTypes,
|
||||
RootNode,
|
||||
SimpleExpressionNode,
|
||||
SourceLocation,
|
||||
TemplateChildNode,
|
||||
createRoot
|
||||
createRoot,
|
||||
createSimpleExpression
|
||||
} from '../ast'
|
||||
import { ParserOptions } from '../options'
|
||||
import Tokenizer, {
|
||||
|
@ -24,13 +26,12 @@ import Tokenizer, {
|
|||
import { CompilerCompatOptions } from '../compat/compatConfig'
|
||||
import { NO, extend } from '@vue/shared'
|
||||
import { defaultOnError, defaultOnWarn } from '../errors'
|
||||
import { isCoreComponent } from '../utils'
|
||||
import { forAliasRE, isCoreComponent } from '../utils'
|
||||
|
||||
type OptionalOptions =
|
||||
| 'whitespace'
|
||||
| 'isNativeTag'
|
||||
| 'isBuiltInComponent'
|
||||
| 'getTextMode'
|
||||
| keyof CompilerCompatOptions
|
||||
|
||||
type MergedParserOptions = Omit<Required<ParserOptions>, OptionalOptions> &
|
||||
|
@ -64,7 +65,7 @@ export const defaultParserOptions: MergedParserOptions = {
|
|||
}
|
||||
|
||||
let currentOptions: MergedParserOptions = defaultParserOptions
|
||||
let currentRoot: RootNode = createRoot([])
|
||||
let currentRoot: RootNode | null = null
|
||||
|
||||
// parser state
|
||||
let currentInput = ''
|
||||
|
@ -102,14 +103,11 @@ const tokenizer = new Tokenizer(stack, {
|
|||
}
|
||||
addNode({
|
||||
type: NodeTypes.INTERPOLATION,
|
||||
content: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
isStatic: false,
|
||||
// Set `isConstant` to false by default and will decide in transformExpression
|
||||
constType: ConstantTypes.NOT_CONSTANT,
|
||||
content: getSlice(innerStart, innerEnd),
|
||||
loc: getLoc(innerStart, innerEnd)
|
||||
},
|
||||
content: createSimpleExpression(
|
||||
getSlice(innerStart, innerEnd),
|
||||
false,
|
||||
getLoc(innerStart, innerEnd)
|
||||
),
|
||||
loc: getLoc(start, end)
|
||||
})
|
||||
},
|
||||
|
@ -123,12 +121,7 @@ const tokenizer = new Tokenizer(stack, {
|
|||
tagType: ElementTypes.ELEMENT, // will be refined on tag close
|
||||
props: [],
|
||||
children: [],
|
||||
loc: {
|
||||
start: tokenizer.getPos(start - 1),
|
||||
// @ts-expect-error to be attached on tag close
|
||||
end: undefined,
|
||||
source: ''
|
||||
},
|
||||
loc: getLoc(start - 1),
|
||||
codegenNode: undefined
|
||||
}
|
||||
currentAttrs.clear()
|
||||
|
@ -159,6 +152,7 @@ const tokenizer = new Tokenizer(stack, {
|
|||
currentProp = {
|
||||
type: NodeTypes.ATTRIBUTE,
|
||||
name: getSlice(start, end),
|
||||
nameLoc: getLoc(start, end),
|
||||
value: undefined,
|
||||
loc: getLoc(start)
|
||||
}
|
||||
|
@ -170,6 +164,7 @@ const tokenizer = new Tokenizer(stack, {
|
|||
currentProp = {
|
||||
type: NodeTypes.ATTRIBUTE,
|
||||
name: raw,
|
||||
nameLoc: getLoc(start, end),
|
||||
value: undefined,
|
||||
loc: getLoc(start)
|
||||
}
|
||||
|
@ -185,7 +180,7 @@ const tokenizer = new Tokenizer(stack, {
|
|||
currentProp = {
|
||||
type: NodeTypes.DIRECTIVE,
|
||||
name,
|
||||
raw,
|
||||
rawName: raw,
|
||||
exp: undefined,
|
||||
arg: undefined,
|
||||
modifiers: [],
|
||||
|
@ -209,17 +204,15 @@ const tokenizer = new Tokenizer(stack, {
|
|||
const arg = getSlice(start, end)
|
||||
if (inVPre) {
|
||||
;(currentProp as AttributeNode).name += arg
|
||||
;(currentProp as AttributeNode).nameLoc.end = tokenizer.getPos(end)
|
||||
} else {
|
||||
const isStatic = arg[0] !== `[`
|
||||
;(currentProp as DirectiveNode).arg = {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: arg,
|
||||
;(currentProp as DirectiveNode).arg = createSimpleExpression(
|
||||
arg,
|
||||
isStatic,
|
||||
constType: isStatic
|
||||
? ConstantTypes.CAN_STRINGIFY
|
||||
: ConstantTypes.NOT_CONSTANT,
|
||||
loc: getLoc(start, end)
|
||||
}
|
||||
getLoc(start, end),
|
||||
isStatic ? ConstantTypes.CAN_STRINGIFY : ConstantTypes.NOT_CONSTANT
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -227,6 +220,7 @@ const tokenizer = new Tokenizer(stack, {
|
|||
const mod = getSlice(start, end)
|
||||
if (inVPre) {
|
||||
;(currentProp as AttributeNode).name += '.' + mod
|
||||
;(currentProp as AttributeNode).nameLoc.end = tokenizer.getPos(end)
|
||||
} else {
|
||||
;(currentProp as DirectiveNode).modifiers.push(mod)
|
||||
}
|
||||
|
@ -247,7 +241,7 @@ const tokenizer = new Tokenizer(stack, {
|
|||
const start = currentProp!.loc.start.offset
|
||||
const name = getSlice(start, end)
|
||||
if (currentProp!.type === NodeTypes.DIRECTIVE) {
|
||||
currentProp!.raw = name
|
||||
currentProp!.rawName = name
|
||||
}
|
||||
if (currentAttrs.has(name)) {
|
||||
currentProp = null
|
||||
|
@ -273,15 +267,14 @@ const tokenizer = new Tokenizer(stack, {
|
|||
}
|
||||
} else {
|
||||
// directive
|
||||
currentProp.exp = {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: currentAttrValue,
|
||||
isStatic: false,
|
||||
// Treat as non-constant by default. This can be potentially set
|
||||
// to other values by `transformExpression` to make it eligible
|
||||
// for hoisting.
|
||||
constType: ConstantTypes.NOT_CONSTANT,
|
||||
loc: getLoc(currentAttrStartIndex, currentAttrEndIndex)
|
||||
currentProp.rawExp = currentAttrValue
|
||||
currentProp.exp = createSimpleExpression(
|
||||
currentAttrValue,
|
||||
false,
|
||||
getLoc(currentAttrStartIndex, currentAttrEndIndex)
|
||||
)
|
||||
if (currentProp.name === 'for') {
|
||||
currentProp.forParseResult = parseForExpression(currentProp.exp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -319,6 +312,73 @@ const tokenizer = new Tokenizer(stack, {
|
|||
}
|
||||
})
|
||||
|
||||
// This regex doesn't cover the case if key or index aliases have destructuring,
|
||||
// but those do not make sense in the first place, so this works in practice.
|
||||
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
|
||||
const stripParensRE = /^\(|\)$/g
|
||||
|
||||
function parseForExpression(
|
||||
input: SimpleExpressionNode
|
||||
): ForParseResult | undefined {
|
||||
const loc = input.loc
|
||||
const exp = input.content
|
||||
const inMatch = exp.match(forAliasRE)
|
||||
if (!inMatch) return
|
||||
|
||||
const [, LHS, RHS] = inMatch
|
||||
|
||||
const createAliasExpression = (content: string, offset: number) => {
|
||||
const start = loc.start.offset + offset
|
||||
const end = start + content.length
|
||||
return createSimpleExpression(content, false, getLoc(start, end))
|
||||
}
|
||||
|
||||
const result: ForParseResult = {
|
||||
source: createAliasExpression(RHS.trim(), exp.indexOf(RHS, LHS.length)),
|
||||
value: undefined,
|
||||
key: undefined,
|
||||
index: undefined,
|
||||
finalized: false
|
||||
}
|
||||
|
||||
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()
|
||||
let keyOffset: number | undefined
|
||||
if (keyContent) {
|
||||
keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length)
|
||||
result.key = createAliasExpression(keyContent, keyOffset)
|
||||
}
|
||||
|
||||
if (iteratorMatch[2]) {
|
||||
const indexContent = iteratorMatch[2].trim()
|
||||
|
||||
if (indexContent) {
|
||||
result.index = createAliasExpression(
|
||||
indexContent,
|
||||
exp.indexOf(
|
||||
indexContent,
|
||||
result.key
|
||||
? keyOffset! + keyContent.length
|
||||
: trimmedOffset + valueContent.length
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (valueContent) {
|
||||
result.value = createAliasExpression(valueContent, trimmedOffset)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function getSlice(start: number, end: number) {
|
||||
return currentInput.slice(start, end)
|
||||
}
|
||||
|
@ -356,11 +416,7 @@ function onText(content: string, start: number, end: number) {
|
|||
parent.children.push({
|
||||
type: NodeTypes.TEXT,
|
||||
content,
|
||||
loc: {
|
||||
start: tokenizer.getPos(start),
|
||||
end: tokenizer.getPos(end),
|
||||
source: ''
|
||||
}
|
||||
loc: getLoc(start, end)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -403,7 +459,7 @@ function isFragmentTemplate({ tag, props }: ElementNode): boolean {
|
|||
for (let i = 0; i < props.length; i++) {
|
||||
if (
|
||||
props[i].type === NodeTypes.DIRECTIVE &&
|
||||
specialTemplateDir.has(props[i].name)
|
||||
specialTemplateDir.has((props[i] as DirectiveNode).name)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
@ -571,7 +627,11 @@ function getLoc(start: number, end?: number): SourceLocation {
|
|||
function dirToAttr(dir: DirectiveNode): AttributeNode {
|
||||
const attr: AttributeNode = {
|
||||
type: NodeTypes.ATTRIBUTE,
|
||||
name: dir.raw!,
|
||||
name: dir.rawName!,
|
||||
nameLoc: getLoc(
|
||||
dir.loc.start.offset,
|
||||
dir.loc.start.offset + dir.rawName!.length
|
||||
),
|
||||
value: undefined,
|
||||
loc: dir.loc
|
||||
}
|
||||
|
@ -622,9 +682,9 @@ export function baseParse(input: string, options?: ParserOptions): RootNode {
|
|||
tokenizer.delimiterClose = toCharCodes(delimiters[1])
|
||||
}
|
||||
|
||||
const root = (currentRoot = createRoot([]))
|
||||
const root = (currentRoot = createRoot([], input))
|
||||
tokenizer.parse(currentInput)
|
||||
root.loc.end = tokenizer.getPos(input.length)
|
||||
root.children = condenseWhitespace(root.children)
|
||||
currentRoot = null
|
||||
return root
|
||||
}
|
||||
|
|
|
@ -49,7 +49,6 @@ import {
|
|||
GUARD_REACTIVE_PROPS
|
||||
} from '../runtimeHelpers'
|
||||
import {
|
||||
getInnerRange,
|
||||
toValidAssetId,
|
||||
findProp,
|
||||
isCoreComponent,
|
||||
|
@ -160,8 +159,7 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||
context.onError(
|
||||
createCompilerError(ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN, {
|
||||
start: node.children[0].loc.start,
|
||||
end: node.children[node.children.length - 1].loc.end,
|
||||
source: ''
|
||||
end: node.children[node.children.length - 1].loc.end
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -489,7 +487,7 @@ export function buildProps(
|
|||
// static attribute
|
||||
const prop = props[i]
|
||||
if (prop.type === NodeTypes.ATTRIBUTE) {
|
||||
const { loc, name, value } = prop
|
||||
const { loc, name, nameLoc, value } = prop
|
||||
let isStatic = true
|
||||
if (name === 'ref') {
|
||||
hasRef = true
|
||||
|
@ -536,11 +534,7 @@ export function buildProps(
|
|||
}
|
||||
properties.push(
|
||||
createObjectProperty(
|
||||
createSimpleExpression(
|
||||
name,
|
||||
true,
|
||||
getInnerRange(loc, 0, name.length)
|
||||
),
|
||||
createSimpleExpression(name, true, nameLoc),
|
||||
createSimpleExpression(
|
||||
value ? value.content : '',
|
||||
isStatic,
|
||||
|
|
|
@ -336,7 +336,6 @@ export function processExpression(
|
|||
id.name,
|
||||
false,
|
||||
{
|
||||
source,
|
||||
start: advancePositionWithClone(node.loc.start, source, start),
|
||||
end: advancePositionWithClone(node.loc.start, source, end)
|
||||
},
|
||||
|
|
|
@ -6,7 +6,6 @@ import {
|
|||
NodeTypes,
|
||||
ExpressionNode,
|
||||
createSimpleExpression,
|
||||
SourceLocation,
|
||||
SimpleExpressionNode,
|
||||
createCallExpression,
|
||||
createFunctionExpression,
|
||||
|
@ -28,17 +27,16 @@ import {
|
|||
createBlockStatement,
|
||||
createCompoundExpression,
|
||||
getVNodeBlockHelper,
|
||||
getVNodeHelper
|
||||
getVNodeHelper,
|
||||
ForParseResult
|
||||
} from '../ast'
|
||||
import { createCompilerError, ErrorCodes } from '../errors'
|
||||
import {
|
||||
getInnerRange,
|
||||
findProp,
|
||||
isTemplateNode,
|
||||
isSlotOutlet,
|
||||
injectProp,
|
||||
findDir,
|
||||
forAliasRE
|
||||
findDir
|
||||
} from '../utils'
|
||||
import {
|
||||
RENDER_LIST,
|
||||
|
@ -256,12 +254,7 @@ export function processFor(
|
|||
return
|
||||
}
|
||||
|
||||
const parseResult = parseForExpression(
|
||||
// can only be simple expression because vFor transform is applied
|
||||
// before expression transform.
|
||||
dir.exp as SimpleExpressionNode,
|
||||
context
|
||||
)
|
||||
const parseResult = dir.forParseResult
|
||||
|
||||
if (!parseResult) {
|
||||
context.onError(
|
||||
|
@ -270,6 +263,8 @@ export function processFor(
|
|||
return
|
||||
}
|
||||
|
||||
finalizeForParseResult(parseResult, context)
|
||||
|
||||
const { addIdentifiers, removeIdentifiers, scopes } = context
|
||||
const { source, value, key, index } = parseResult
|
||||
|
||||
|
@ -309,128 +304,50 @@ export function processFor(
|
|||
}
|
||||
}
|
||||
|
||||
// This regex doesn't cover the case if key or index aliases have destructuring,
|
||||
// but those do not make sense in the first place, so this works in practice.
|
||||
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
|
||||
const stripParensRE = /^\(|\)$/g
|
||||
|
||||
export interface ForParseResult {
|
||||
source: ExpressionNode
|
||||
value: ExpressionNode | undefined
|
||||
key: ExpressionNode | undefined
|
||||
index: ExpressionNode | undefined
|
||||
}
|
||||
|
||||
export function parseForExpression(
|
||||
input: SimpleExpressionNode,
|
||||
export function finalizeForParseResult(
|
||||
result: ForParseResult,
|
||||
context: TransformContext
|
||||
): ForParseResult | undefined {
|
||||
const loc = input.loc
|
||||
const exp = input.content
|
||||
const inMatch = exp.match(forAliasRE)
|
||||
if (!inMatch) return
|
||||
) {
|
||||
if (result.finalized) return
|
||||
|
||||
const [, LHS, RHS] = inMatch
|
||||
|
||||
const result: ForParseResult = {
|
||||
source: createAliasExpression(
|
||||
loc,
|
||||
RHS.trim(),
|
||||
exp.indexOf(RHS, LHS.length)
|
||||
),
|
||||
value: undefined,
|
||||
key: undefined,
|
||||
index: undefined
|
||||
}
|
||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||
result.source = processExpression(
|
||||
result.source as SimpleExpressionNode,
|
||||
context
|
||||
)
|
||||
}
|
||||
if (__DEV__ && __BROWSER__) {
|
||||
validateBrowserExpression(result.source as SimpleExpressionNode, context)
|
||||
}
|
||||
|
||||
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()
|
||||
let keyOffset: number | undefined
|
||||
if (keyContent) {
|
||||
keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length)
|
||||
result.key = createAliasExpression(loc, keyContent, keyOffset)
|
||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||
result.key = processExpression(result.key, context, true)
|
||||
}
|
||||
if (__DEV__ && __BROWSER__) {
|
||||
validateBrowserExpression(
|
||||
result.key as SimpleExpressionNode,
|
||||
context,
|
||||
true
|
||||
)
|
||||
}
|
||||
if (result.key) {
|
||||
result.key = processExpression(
|
||||
result.key as SimpleExpressionNode,
|
||||
context,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
if (iteratorMatch[2]) {
|
||||
const indexContent = iteratorMatch[2].trim()
|
||||
|
||||
if (indexContent) {
|
||||
result.index = createAliasExpression(
|
||||
loc,
|
||||
indexContent,
|
||||
exp.indexOf(
|
||||
indexContent,
|
||||
result.key
|
||||
? keyOffset! + keyContent.length
|
||||
: trimmedOffset + valueContent.length
|
||||
)
|
||||
)
|
||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||
result.index = processExpression(result.index, context, true)
|
||||
}
|
||||
if (__DEV__ && __BROWSER__) {
|
||||
validateBrowserExpression(
|
||||
result.index as SimpleExpressionNode,
|
||||
context,
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (valueContent) {
|
||||
result.value = createAliasExpression(loc, valueContent, trimmedOffset)
|
||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||
result.value = processExpression(result.value, context, true)
|
||||
}
|
||||
if (__DEV__ && __BROWSER__) {
|
||||
validateBrowserExpression(
|
||||
result.value as SimpleExpressionNode,
|
||||
if (result.index) {
|
||||
result.index = processExpression(
|
||||
result.index as SimpleExpressionNode,
|
||||
context,
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function createAliasExpression(
|
||||
range: SourceLocation,
|
||||
content: string,
|
||||
offset: number
|
||||
): SimpleExpressionNode {
|
||||
return createSimpleExpression(
|
||||
content,
|
||||
false,
|
||||
getInnerRange(range, offset, content.length)
|
||||
)
|
||||
if (__DEV__ && __BROWSER__) {
|
||||
validateBrowserExpression(result.source as SimpleExpressionNode, context)
|
||||
if (result.key) {
|
||||
validateBrowserExpression(
|
||||
result.key as SimpleExpressionNode,
|
||||
context,
|
||||
true
|
||||
)
|
||||
}
|
||||
if (result.index) {
|
||||
validateBrowserExpression(
|
||||
result.index as SimpleExpressionNode,
|
||||
context,
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
result.finalized = true
|
||||
}
|
||||
|
||||
export function createForLoopParams(
|
||||
|
|
|
@ -29,7 +29,9 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
|||
return createTransformProps()
|
||||
}
|
||||
|
||||
const rawExp = exp.loc.source
|
||||
// we assume v-model directives are always parsed
|
||||
// (not artificially created by a transform)
|
||||
const rawExp = dir.rawExp!
|
||||
const expString =
|
||||
exp.type === NodeTypes.SIMPLE_EXPRESSION ? exp.content : rawExp
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ import {
|
|||
SourceLocation,
|
||||
createConditionalExpression,
|
||||
ConditionalExpression,
|
||||
SimpleExpressionNode,
|
||||
FunctionExpression,
|
||||
CallExpression,
|
||||
createCallExpression,
|
||||
|
@ -32,7 +31,7 @@ import {
|
|||
isStaticExp
|
||||
} from '../utils'
|
||||
import { CREATE_SLOTS, RENDER_LIST, WITH_CTX } from '../runtimeHelpers'
|
||||
import { parseForExpression, createForLoopParams } from './vFor'
|
||||
import { createForLoopParams, finalizeForParseResult } from './vFor'
|
||||
import { SlotFlags, slotFlagsText } from '@vue/shared'
|
||||
|
||||
const defaultFallback = createSimpleExpression(`undefined`, false)
|
||||
|
@ -78,11 +77,9 @@ export const trackVForSlotScopes: NodeTransform = (node, context) => {
|
|||
node.props.some(isVSlot) &&
|
||||
(vFor = findDir(node, 'for'))
|
||||
) {
|
||||
const result = (vFor.parseResult = parseForExpression(
|
||||
vFor.exp as SimpleExpressionNode,
|
||||
context
|
||||
))
|
||||
const result = vFor.forParseResult
|
||||
if (result) {
|
||||
finalizeForParseResult(result, context)
|
||||
const { value, key, index } = result
|
||||
const { addIdentifiers, removeIdentifiers } = context
|
||||
value && addIdentifiers(value)
|
||||
|
@ -266,10 +263,9 @@ export function buildSlots(
|
|||
}
|
||||
} else if (vFor) {
|
||||
hasDynamicSlots = true
|
||||
const parseResult =
|
||||
vFor.parseResult ||
|
||||
parseForExpression(vFor.exp as SimpleExpressionNode, context)
|
||||
const parseResult = vFor.forParseResult
|
||||
if (parseResult) {
|
||||
finalizeForParseResult(parseResult, context)
|
||||
// Render the dynamic slots as an array and add it to the createSlot()
|
||||
// args. The runtime knows how to handle it appropriately.
|
||||
dynamicSlots.push(
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import {
|
||||
SourceLocation,
|
||||
Position,
|
||||
ElementNode,
|
||||
NodeTypes,
|
||||
|
@ -176,31 +175,6 @@ export const isMemberExpression = __BROWSER__
|
|||
? isMemberExpressionBrowser
|
||||
: isMemberExpressionNode
|
||||
|
||||
export function getInnerRange(
|
||||
loc: SourceLocation,
|
||||
offset: number,
|
||||
length: number
|
||||
): SourceLocation {
|
||||
__TEST__ && assert(offset <= loc.source.length)
|
||||
const source = loc.source.slice(offset, offset + length)
|
||||
const newLoc: SourceLocation = {
|
||||
source,
|
||||
start: advancePositionWithClone(loc.start, loc.source, offset),
|
||||
end: loc.end
|
||||
}
|
||||
|
||||
if (length != null) {
|
||||
__TEST__ && assert(offset + length <= loc.source.length)
|
||||
newLoc.end = advancePositionWithClone(
|
||||
loc.start,
|
||||
loc.source,
|
||||
offset + length
|
||||
)
|
||||
}
|
||||
|
||||
return newLoc
|
||||
}
|
||||
|
||||
export function advancePositionWithClone(
|
||||
pos: Position,
|
||||
source: string,
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
import {
|
||||
TextModes,
|
||||
ParserOptions,
|
||||
ElementNode,
|
||||
NodeTypes
|
||||
} from '@vue/compiler-core'
|
||||
import { ParserOptions, ElementNode, NodeTypes } from '@vue/compiler-core'
|
||||
import { isVoidTag, isHTMLTag, isSVGTag } from '@vue/shared'
|
||||
import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
|
||||
import { decodeHtml } from './decodeHtml'
|
||||
|
@ -16,6 +11,7 @@ export const enum DOMNamespaces {
|
|||
}
|
||||
|
||||
export const parserOptions: ParserOptions = {
|
||||
parseMode: 'html',
|
||||
isVoidTag,
|
||||
isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag),
|
||||
isPreTag: tag => tag === 'pre',
|
||||
|
@ -75,18 +71,5 @@ export const parserOptions: ParserOptions = {
|
|||
}
|
||||
}
|
||||
return ns
|
||||
},
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/parsing.html#parsing-html-fragments
|
||||
getTextMode({ tag, ns }: ElementNode): TextModes {
|
||||
if (ns === DOMNamespaces.HTML) {
|
||||
if (tag === 'textarea' || tag === 'title') {
|
||||
return TextModes.RCDATA
|
||||
}
|
||||
if (tag === 'style' || tag === 'script') {
|
||||
return TextModes.RAWTEXT
|
||||
}
|
||||
}
|
||||
return TextModes.DATA
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,8 +27,7 @@ export const transformTransition: NodeTransform = (node, context) => {
|
|||
DOMErrorCodes.X_TRANSITION_INVALID_CHILDREN,
|
||||
{
|
||||
start: node.children[0].loc.start,
|
||||
end: node.children[node.children.length - 1].loc.end,
|
||||
source: ''
|
||||
end: node.children[node.children.length - 1].loc.end
|
||||
}
|
||||
)
|
||||
)
|
||||
|
@ -43,6 +42,7 @@ export const transformTransition: NodeTransform = (node, context) => {
|
|||
node.props.push({
|
||||
type: NodeTypes.ATTRIBUTE,
|
||||
name: 'persisted',
|
||||
nameLoc: node.loc,
|
||||
value: undefined,
|
||||
loc: node.loc
|
||||
})
|
||||
|
|
|
@ -3,7 +3,6 @@ import {
|
|||
ElementNode,
|
||||
SourceLocation,
|
||||
CompilerError,
|
||||
TextModes,
|
||||
BindingMetadata
|
||||
} from '@vue/compiler-core'
|
||||
import * as CompilerDOM from '@vue/compiler-dom'
|
||||
|
@ -128,31 +127,7 @@ export function parse(
|
|||
|
||||
const errors: (CompilerError | SyntaxError)[] = []
|
||||
const ast = compiler.parse(source, {
|
||||
// there are no components at SFC parsing level
|
||||
isNativeTag: () => true,
|
||||
// preserve all whitespaces
|
||||
isPreTag: () => true,
|
||||
getTextMode: ({ tag, props }, parent) => {
|
||||
// all top level elements except <template> are parsed as raw text
|
||||
// containers
|
||||
if (
|
||||
(!parent && tag !== 'template') ||
|
||||
// <template lang="xxx"> should also be treated as raw text
|
||||
(tag === 'template' &&
|
||||
props.some(
|
||||
p =>
|
||||
p.type === NodeTypes.ATTRIBUTE &&
|
||||
p.name === 'lang' &&
|
||||
p.value &&
|
||||
p.value.content &&
|
||||
p.value.content !== 'html'
|
||||
))
|
||||
) {
|
||||
return TextModes.RAWTEXT
|
||||
} else {
|
||||
return TextModes.DATA
|
||||
}
|
||||
},
|
||||
parseMode: 'sfc',
|
||||
onError: e => {
|
||||
errors.push(e)
|
||||
}
|
||||
|
@ -188,7 +163,9 @@ export function parse(
|
|||
`difference from stateful ones. Just use a normal <template> ` +
|
||||
`instead.`
|
||||
) as CompilerError
|
||||
err.loc = node.props.find(p => p.name === 'functional')!.loc
|
||||
err.loc = node.props.find(
|
||||
p => p.type === NodeTypes.ATTRIBUTE && p.name === 'functional'
|
||||
)!.loc
|
||||
errors.push(err)
|
||||
}
|
||||
} else {
|
||||
|
@ -314,7 +291,7 @@ function createBlock(
|
|||
end = node.children[node.children.length - 1].loc.end
|
||||
content = source.slice(start.offset, end.offset)
|
||||
} else {
|
||||
const offset = node.loc.source.indexOf(`</`)
|
||||
const offset = source.indexOf(`</`, start.offset)
|
||||
if (offset > -1) {
|
||||
start = {
|
||||
line: start.line,
|
||||
|
@ -341,18 +318,19 @@ function createBlock(
|
|||
}
|
||||
node.props.forEach(p => {
|
||||
if (p.type === NodeTypes.ATTRIBUTE) {
|
||||
attrs[p.name] = p.value ? p.value.content || true : true
|
||||
if (p.name === 'lang') {
|
||||
const name = p.name
|
||||
attrs[name] = p.value ? p.value.content || true : true
|
||||
if (name === 'lang') {
|
||||
block.lang = p.value && p.value.content
|
||||
} else if (p.name === 'src') {
|
||||
} else if (name === 'src') {
|
||||
block.src = p.value && p.value.content
|
||||
} else if (type === 'style') {
|
||||
if (p.name === 'scoped') {
|
||||
if (name === 'scoped') {
|
||||
;(block as SFCStyleBlock).scoped = true
|
||||
} else if (p.name === 'module') {
|
||||
;(block as SFCStyleBlock).module = attrs[p.name]
|
||||
} else if (name === 'module') {
|
||||
;(block as SFCStyleBlock).module = attrs[name]
|
||||
}
|
||||
} else if (type === 'script' && p.name === 'setup') {
|
||||
} else if (type === 'script' && name === 'setup') {
|
||||
;(block as SFCScriptBlock).setup = attrs.setup
|
||||
}
|
||||
}
|
||||
|
|
|
@ -292,14 +292,15 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
|
|||
}
|
||||
} else {
|
||||
// special case: value on <textarea>
|
||||
if (node.tag === 'textarea' && prop.name === 'value' && prop.value) {
|
||||
const name = prop.name
|
||||
if (node.tag === 'textarea' && name === 'value' && prop.value) {
|
||||
rawChildrenMap.set(node, escapeHtml(prop.value.content))
|
||||
} else if (!needMergeProps) {
|
||||
if (prop.name === 'key' || prop.name === 'ref') {
|
||||
if (name === 'key' || name === 'ref') {
|
||||
continue
|
||||
}
|
||||
// static prop
|
||||
if (prop.name === 'class' && prop.value) {
|
||||
if (name === 'class' && prop.value) {
|
||||
staticClassBinding = JSON.stringify(prop.value.content)
|
||||
}
|
||||
openTag.push(
|
||||
|
|
|
@ -91,5 +91,3 @@ registerRuntimeCompiler(compileToFunction)
|
|||
|
||||
export { compileToFunction as compile }
|
||||
export * from '@vue/runtime-dom'
|
||||
|
||||
export { newParse } from '@vue/compiler-dom'
|
||||
|
|
Loading…
Reference in New Issue