2019-10-03 01:11:07 +08:00
|
|
|
import {
|
|
|
|
SourceLocation,
|
|
|
|
Position,
|
|
|
|
ElementNode,
|
|
|
|
NodeTypes,
|
|
|
|
CallExpression,
|
|
|
|
SequenceExpression,
|
|
|
|
createSequenceExpression,
|
2019-10-03 05:18:11 +08:00
|
|
|
createCallExpression,
|
2019-10-03 11:10:41 +08:00
|
|
|
DirectiveNode,
|
|
|
|
ElementTypes,
|
|
|
|
TemplateChildNode,
|
2019-10-04 00:03:14 +08:00
|
|
|
RootNode,
|
|
|
|
ObjectExpression,
|
|
|
|
Property,
|
|
|
|
JSChildNode,
|
2019-10-06 10:47:20 +08:00
|
|
|
createObjectExpression,
|
|
|
|
SlotOutletNode,
|
2019-10-08 22:50:00 +08:00
|
|
|
TemplateNode,
|
|
|
|
BlockCodegenNode,
|
|
|
|
ElementCodegenNode,
|
|
|
|
SlotOutletCodegenNode,
|
2019-10-10 22:33:58 +08:00
|
|
|
ComponentCodegenNode,
|
|
|
|
ExpressionNode
|
2019-10-03 01:11:07 +08:00
|
|
|
} from './ast'
|
2019-10-02 23:05:56 +08:00
|
|
|
import { parse } from 'acorn'
|
2019-10-01 01:13:32 +08:00
|
|
|
import { walk } from 'estree-walker'
|
2019-10-03 01:11:07 +08:00
|
|
|
import { TransformContext } from './transform'
|
2019-10-08 22:50:00 +08:00
|
|
|
import { OPEN_BLOCK, MERGE_PROPS, RENDER_SLOT } from './runtimeHelpers'
|
2019-10-05 01:08:06 +08:00
|
|
|
import { isString, isFunction } from '@vue/shared'
|
2019-10-01 01:13:32 +08:00
|
|
|
|
|
|
|
// cache node requires
|
|
|
|
// lazy require dependencies so that they don't end up in rollup's dep graph
|
|
|
|
// and thus can be tree-shaken in browser builds.
|
2019-10-02 23:05:56 +08:00
|
|
|
let _parse: typeof parse
|
2019-10-01 01:13:32 +08:00
|
|
|
let _walk: typeof walk
|
|
|
|
|
2019-10-05 05:43:20 +08:00
|
|
|
export function loadDep(name: string) {
|
2019-10-05 01:08:06 +08:00
|
|
|
if (typeof process !== 'undefined' && isFunction(require)) {
|
|
|
|
return require(name)
|
|
|
|
} else {
|
|
|
|
// This is only used when we are building a dev-only build of the compiler
|
|
|
|
// which runs in the browser but also uses Node deps.
|
|
|
|
return (window as any)._deps[name]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-02 23:05:56 +08:00
|
|
|
export const parseJS: typeof parse = (code: string, options: any) => {
|
2019-10-01 01:13:32 +08:00
|
|
|
assert(
|
|
|
|
!__BROWSER__,
|
|
|
|
`Expression AST analysis can only be performed in non-browser builds.`
|
|
|
|
)
|
2019-10-05 01:08:06 +08:00
|
|
|
const parse = _parse || (_parse = loadDep('acorn').parse)
|
2019-10-01 01:13:32 +08:00
|
|
|
return parse(code, options)
|
|
|
|
}
|
|
|
|
|
|
|
|
export const walkJS: typeof walk = (ast, walker) => {
|
|
|
|
assert(
|
|
|
|
!__BROWSER__,
|
|
|
|
`Expression AST analysis can only be performed in non-browser builds.`
|
|
|
|
)
|
2019-10-05 01:08:06 +08:00
|
|
|
const walk = _walk || (_walk = loadDep('estree-walker').walk)
|
2019-10-01 01:13:32 +08:00
|
|
|
return walk(ast, walker)
|
|
|
|
}
|
2019-09-20 01:23:49 +08:00
|
|
|
|
2019-10-10 23:15:24 +08:00
|
|
|
const nonIdentifierRE = /^\d|[^\$\w]/
|
2019-09-25 10:39:20 +08:00
|
|
|
export const isSimpleIdentifier = (name: string): boolean =>
|
2019-10-10 23:15:24 +08:00
|
|
|
!nonIdentifierRE.test(name)
|
|
|
|
|
|
|
|
const memberExpRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\[[^\]]+\])*$/
|
|
|
|
export const isMemberExpression = (path: string): boolean =>
|
|
|
|
memberExpRE.test(path)
|
2019-09-25 10:39:20 +08:00
|
|
|
|
2019-09-20 01:23:49 +08:00
|
|
|
export function getInnerRange(
|
|
|
|
loc: SourceLocation,
|
|
|
|
offset: number,
|
|
|
|
length?: number
|
|
|
|
): SourceLocation {
|
2019-09-20 11:05:51 +08:00
|
|
|
__DEV__ && assert(offset <= loc.source.length)
|
2019-09-20 01:23:49 +08:00
|
|
|
const source = loc.source.substr(offset, length)
|
|
|
|
const newLoc: SourceLocation = {
|
|
|
|
source,
|
2019-09-20 11:05:51 +08:00
|
|
|
start: advancePositionWithClone(loc.start, loc.source, offset),
|
2019-09-20 01:23:49 +08:00
|
|
|
end: loc.end
|
|
|
|
}
|
|
|
|
|
|
|
|
if (length != null) {
|
2019-09-20 11:05:51 +08:00
|
|
|
__DEV__ && assert(offset + length <= loc.source.length)
|
|
|
|
newLoc.end = advancePositionWithClone(
|
|
|
|
loc.start,
|
|
|
|
loc.source,
|
|
|
|
offset + length
|
|
|
|
)
|
2019-09-20 01:23:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return newLoc
|
|
|
|
}
|
|
|
|
|
2019-09-20 11:05:51 +08:00
|
|
|
export function advancePositionWithClone(
|
2019-09-20 01:23:49 +08:00
|
|
|
pos: Position,
|
|
|
|
source: string,
|
2019-09-26 07:17:45 +08:00
|
|
|
numberOfCharacters: number = source.length
|
2019-09-20 01:23:49 +08:00
|
|
|
): Position {
|
2019-09-20 01:59:24 +08:00
|
|
|
return advancePositionWithMutation({ ...pos }, source, numberOfCharacters)
|
|
|
|
}
|
2019-09-20 01:23:49 +08:00
|
|
|
|
2019-09-20 01:59:24 +08:00
|
|
|
// advance by mutation without cloning (for performance reasons), since this
|
|
|
|
// gets called a lot in the parser
|
|
|
|
export function advancePositionWithMutation(
|
|
|
|
pos: Position,
|
|
|
|
source: string,
|
2019-09-26 07:17:45 +08:00
|
|
|
numberOfCharacters: number = source.length
|
2019-09-20 01:59:24 +08:00
|
|
|
): Position {
|
2019-09-20 09:18:18 +08:00
|
|
|
let linesCount = 0
|
|
|
|
let lastNewLinePos = -1
|
|
|
|
for (let i = 0; i < numberOfCharacters; i++) {
|
|
|
|
if (source.charCodeAt(i) === 10 /* newline char code */) {
|
|
|
|
linesCount++
|
|
|
|
lastNewLinePos = i
|
|
|
|
}
|
|
|
|
}
|
2019-09-20 01:23:49 +08:00
|
|
|
|
2019-09-20 01:59:24 +08:00
|
|
|
pos.offset += numberOfCharacters
|
2019-09-20 09:18:18 +08:00
|
|
|
pos.line += linesCount
|
2019-09-20 01:59:24 +08:00
|
|
|
pos.column =
|
2019-09-20 09:18:18 +08:00
|
|
|
lastNewLinePos === -1
|
2019-09-20 01:23:49 +08:00
|
|
|
? pos.column + numberOfCharacters
|
2019-09-26 07:17:45 +08:00
|
|
|
: Math.max(1, numberOfCharacters - lastNewLinePos)
|
2019-09-20 01:23:49 +08:00
|
|
|
|
2019-09-20 01:59:24 +08:00
|
|
|
return pos
|
2019-09-20 01:23:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export function assert(condition: boolean, msg?: string) {
|
2019-09-25 04:35:01 +08:00
|
|
|
/* istanbul ignore if */
|
2019-09-20 01:23:49 +08:00
|
|
|
if (!condition) {
|
2019-09-23 04:50:57 +08:00
|
|
|
throw new Error(msg || `unexpected compiler condition`)
|
2019-09-20 01:23:49 +08:00
|
|
|
}
|
|
|
|
}
|
2019-10-02 11:19:48 +08:00
|
|
|
|
2019-10-03 06:03:42 +08:00
|
|
|
export function findDir(
|
2019-10-03 05:18:11 +08:00
|
|
|
node: ElementNode,
|
2019-10-03 06:03:42 +08:00
|
|
|
name: string | RegExp,
|
|
|
|
allowEmpty: boolean = false
|
2019-10-03 05:18:11 +08:00
|
|
|
): DirectiveNode | undefined {
|
|
|
|
for (let i = 0; i < node.props.length; i++) {
|
|
|
|
const p = node.props[i]
|
|
|
|
if (
|
|
|
|
p.type === NodeTypes.DIRECTIVE &&
|
2019-10-03 06:03:42 +08:00
|
|
|
(allowEmpty || p.exp) &&
|
2019-10-03 11:10:41 +08:00
|
|
|
(isString(name) ? p.name === name : name.test(p.name))
|
2019-10-03 05:18:11 +08:00
|
|
|
) {
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-02 11:19:48 +08:00
|
|
|
export function findProp(
|
2019-10-03 05:18:11 +08:00
|
|
|
node: ElementNode,
|
2019-10-02 11:19:48 +08:00
|
|
|
name: string
|
|
|
|
): ElementNode['props'][0] | undefined {
|
2019-10-03 05:18:11 +08:00
|
|
|
for (let i = 0; i < node.props.length; i++) {
|
|
|
|
const p = node.props[i]
|
2019-10-02 11:19:48 +08:00
|
|
|
if (p.type === NodeTypes.ATTRIBUTE) {
|
|
|
|
if (p.name === name && p.value && !p.value.isEmpty) {
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
} else if (
|
|
|
|
p.arg &&
|
|
|
|
p.arg.type === NodeTypes.SIMPLE_EXPRESSION &&
|
|
|
|
p.arg.isStatic &&
|
|
|
|
p.arg.content === name &&
|
|
|
|
p.exp
|
|
|
|
) {
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-10-03 01:11:07 +08:00
|
|
|
|
|
|
|
export function createBlockExpression(
|
2019-10-08 22:50:00 +08:00
|
|
|
blockExp: BlockCodegenNode,
|
2019-10-03 01:11:07 +08:00
|
|
|
context: TransformContext
|
|
|
|
): SequenceExpression {
|
|
|
|
return createSequenceExpression([
|
|
|
|
createCallExpression(context.helper(OPEN_BLOCK)),
|
2019-10-08 22:50:00 +08:00
|
|
|
blockExp
|
2019-10-03 01:11:07 +08:00
|
|
|
])
|
|
|
|
}
|
2019-10-03 05:18:11 +08:00
|
|
|
|
2019-10-03 11:10:41 +08:00
|
|
|
export const isVSlot = (p: ElementNode['props'][0]): p is DirectiveNode =>
|
|
|
|
p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
|
|
|
|
|
|
|
|
export const isTemplateNode = (
|
|
|
|
node: RootNode | TemplateChildNode
|
2019-10-06 10:47:20 +08:00
|
|
|
): node is TemplateNode =>
|
2019-10-03 11:10:41 +08:00
|
|
|
node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.TEMPLATE
|
2019-10-04 00:03:14 +08:00
|
|
|
|
|
|
|
export const isSlotOutlet = (
|
|
|
|
node: RootNode | TemplateChildNode
|
2019-10-06 10:47:20 +08:00
|
|
|
): node is SlotOutletNode =>
|
2019-10-04 00:03:14 +08:00
|
|
|
node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT
|
|
|
|
|
|
|
|
export function injectProp(
|
2019-10-08 22:50:00 +08:00
|
|
|
node: ElementCodegenNode | ComponentCodegenNode | SlotOutletCodegenNode,
|
2019-10-04 00:03:14 +08:00
|
|
|
prop: Property,
|
|
|
|
context: TransformContext
|
2019-10-08 22:50:00 +08:00
|
|
|
) {
|
|
|
|
let propsWithInjection: ObjectExpression | CallExpression
|
|
|
|
const props =
|
|
|
|
node.callee === RENDER_SLOT ? node.arguments[2] : node.arguments[1]
|
|
|
|
if (props == null || isString(props)) {
|
|
|
|
propsWithInjection = createObjectExpression([prop])
|
2019-10-04 00:03:14 +08:00
|
|
|
} else if (props.type === NodeTypes.JS_CALL_EXPRESSION) {
|
|
|
|
// merged props... add ours
|
|
|
|
// only inject key to object literal if it's the first argument so that
|
|
|
|
// if doesn't override user provided keys
|
|
|
|
const first = props.arguments[0] as string | JSChildNode
|
|
|
|
if (!isString(first) && first.type === NodeTypes.JS_OBJECT_EXPRESSION) {
|
|
|
|
first.properties.unshift(prop)
|
|
|
|
} else {
|
|
|
|
props.arguments.unshift(createObjectExpression([prop]))
|
|
|
|
}
|
2019-10-08 22:50:00 +08:00
|
|
|
propsWithInjection = props
|
2019-10-04 00:03:14 +08:00
|
|
|
} else if (props.type === NodeTypes.JS_OBJECT_EXPRESSION) {
|
|
|
|
props.properties.unshift(prop)
|
2019-10-08 22:50:00 +08:00
|
|
|
propsWithInjection = props
|
2019-10-04 00:03:14 +08:00
|
|
|
} else {
|
|
|
|
// single v-bind with expression, return a merged replacement
|
2019-10-08 22:50:00 +08:00
|
|
|
propsWithInjection = createCallExpression(context.helper(MERGE_PROPS), [
|
2019-10-04 00:03:14 +08:00
|
|
|
createObjectExpression([prop]),
|
|
|
|
props
|
|
|
|
])
|
|
|
|
}
|
2019-10-08 22:50:00 +08:00
|
|
|
if (node.callee === RENDER_SLOT) {
|
|
|
|
node.arguments[2] = propsWithInjection
|
|
|
|
} else {
|
|
|
|
node.arguments[1] = propsWithInjection
|
|
|
|
}
|
2019-10-04 00:03:14 +08:00
|
|
|
}
|
2019-10-06 05:18:25 +08:00
|
|
|
|
|
|
|
export function toValidAssetId(
|
|
|
|
name: string,
|
|
|
|
type: 'component' | 'directive'
|
|
|
|
): string {
|
2019-10-11 01:55:26 +08:00
|
|
|
return `_${type}_${name.replace(/[^\w]/g, '_')}`
|
2019-10-06 05:18:25 +08:00
|
|
|
}
|
2019-10-10 22:33:58 +08:00
|
|
|
|
|
|
|
export function isEmptyExpression(node: ExpressionNode) {
|
|
|
|
return node.type === NodeTypes.SIMPLE_EXPRESSION && !node.content.trim()
|
|
|
|
}
|