| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- import {
- SourceLocation,
- Position,
- ElementNode,
- NodeTypes,
- CallExpression,
- createCallExpression,
- DirectiveNode,
- ElementTypes,
- TemplateChildNode,
- RootNode,
- ObjectExpression,
- Property,
- JSChildNode,
- createObjectExpression,
- SlotOutletNode,
- TemplateNode,
- RenderSlotCall,
- ExpressionNode,
- IfBranchNode,
- TextNode,
- InterpolationNode,
- VNodeCall,
- SimpleExpressionNode
- } from './ast'
- import { TransformContext } from './transform'
- import {
- MERGE_PROPS,
- TELEPORT,
- SUSPENSE,
- KEEP_ALIVE,
- BASE_TRANSITION
- } from './runtimeHelpers'
- import { isString, isObject, hyphenate, extend } from '@vue/shared'
- export const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
- p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic
- export const isBuiltInType = (tag: string, expected: string): boolean =>
- tag === expected || tag === hyphenate(expected)
- export function isCoreComponent(tag: string): symbol | void {
- if (isBuiltInType(tag, 'Teleport')) {
- return TELEPORT
- } else if (isBuiltInType(tag, 'Suspense')) {
- return SUSPENSE
- } else if (isBuiltInType(tag, 'KeepAlive')) {
- return KEEP_ALIVE
- } else if (isBuiltInType(tag, 'BaseTransition')) {
- return BASE_TRANSITION
- }
- }
- const nonIdentifierRE = /^\d|[^\$\w]/
- export const isSimpleIdentifier = (name: string): boolean =>
- !nonIdentifierRE.test(name)
- const memberExpRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\[[^\]]+\])*$/
- export const isMemberExpression = (path: string): boolean => {
- if (!path) return false
- return memberExpRE.test(path.trim())
- }
- export function getInnerRange(
- loc: SourceLocation,
- offset: number,
- length?: number
- ): SourceLocation {
- __TEST__ && assert(offset <= loc.source.length)
- const source = loc.source.substr(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,
- numberOfCharacters: number = source.length
- ): Position {
- return advancePositionWithMutation(
- extend({}, pos),
- source,
- numberOfCharacters
- )
- }
- // advance by mutation without cloning (for performance reasons), since this
- // gets called a lot in the parser
- export function advancePositionWithMutation(
- pos: Position,
- source: string,
- numberOfCharacters: number = source.length
- ): Position {
- let linesCount = 0
- let lastNewLinePos = -1
- for (let i = 0; i < numberOfCharacters; i++) {
- if (source.charCodeAt(i) === 10 /* newline char code */) {
- linesCount++
- lastNewLinePos = i
- }
- }
- pos.offset += numberOfCharacters
- pos.line += linesCount
- pos.column =
- lastNewLinePos === -1
- ? pos.column + numberOfCharacters
- : numberOfCharacters - lastNewLinePos
- return pos
- }
- export function assert(condition: boolean, msg?: string) {
- /* istanbul ignore if */
- if (!condition) {
- throw new Error(msg || `unexpected compiler condition`)
- }
- }
- export function findDir(
- node: ElementNode,
- name: string | RegExp,
- allowEmpty: boolean = false
- ): DirectiveNode | undefined {
- for (let i = 0; i < node.props.length; i++) {
- const p = node.props[i]
- if (
- p.type === NodeTypes.DIRECTIVE &&
- (allowEmpty || p.exp) &&
- (isString(name) ? p.name === name : name.test(p.name))
- ) {
- return p
- }
- }
- }
- export function findProp(
- node: ElementNode,
- name: string,
- dynamicOnly: boolean = false,
- allowEmpty: boolean = false
- ): ElementNode['props'][0] | undefined {
- for (let i = 0; i < node.props.length; i++) {
- const p = node.props[i]
- if (p.type === NodeTypes.ATTRIBUTE) {
- if (dynamicOnly) continue
- if (p.name === name && (p.value || allowEmpty)) {
- return p
- }
- } else if (p.name === 'bind' && p.exp && isBindKey(p.arg, name)) {
- return p
- }
- }
- }
- export function isBindKey(arg: DirectiveNode['arg'], name: string): boolean {
- return !!(arg && isStaticExp(arg) && arg.content === name)
- }
- export function hasDynamicKeyVBind(node: ElementNode): boolean {
- return node.props.some(
- p =>
- p.type === NodeTypes.DIRECTIVE &&
- p.name === 'bind' &&
- (!p.arg || // v-bind="obj"
- p.arg.type !== NodeTypes.SIMPLE_EXPRESSION || // v-bind:[_ctx.foo]
- !p.arg.isStatic) // v-bind:[foo]
- )
- }
- export function isText(
- node: TemplateChildNode
- ): node is TextNode | InterpolationNode {
- return node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT
- }
- export function isVSlot(p: ElementNode['props'][0]): p is DirectiveNode {
- return p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
- }
- export function isTemplateNode(
- node: RootNode | TemplateChildNode
- ): node is TemplateNode {
- return (
- node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.TEMPLATE
- )
- }
- export function isSlotOutlet(
- node: RootNode | TemplateChildNode
- ): node is SlotOutletNode {
- return node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT
- }
- export function injectProp(
- node: VNodeCall | RenderSlotCall,
- prop: Property,
- context: TransformContext
- ) {
- let propsWithInjection: ObjectExpression | CallExpression
- const props =
- node.type === NodeTypes.VNODE_CALL ? node.props : node.arguments[2]
- if (props == null || isString(props)) {
- propsWithInjection = createObjectExpression([prop])
- } 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]))
- }
- propsWithInjection = props
- } else if (props.type === NodeTypes.JS_OBJECT_EXPRESSION) {
- let alreadyExists = false
- // check existing key to avoid overriding user provided keys
- if (prop.key.type === NodeTypes.SIMPLE_EXPRESSION) {
- const propKeyName = prop.key.content
- alreadyExists = props.properties.some(
- p =>
- p.key.type === NodeTypes.SIMPLE_EXPRESSION &&
- p.key.content === propKeyName
- )
- }
- if (!alreadyExists) {
- props.properties.unshift(prop)
- }
- propsWithInjection = props
- } else {
- // single v-bind with expression, return a merged replacement
- propsWithInjection = createCallExpression(context.helper(MERGE_PROPS), [
- createObjectExpression([prop]),
- props
- ])
- }
- if (node.type === NodeTypes.VNODE_CALL) {
- node.props = propsWithInjection
- } else {
- node.arguments[2] = propsWithInjection
- }
- }
- export function toValidAssetId(
- name: string,
- type: 'component' | 'directive'
- ): string {
- return `_${type}_${name.replace(/[^\w]/g, '_')}`
- }
- // Check if a node contains expressions that reference current context scope ids
- export function hasScopeRef(
- node: TemplateChildNode | IfBranchNode | ExpressionNode | undefined,
- ids: TransformContext['identifiers']
- ): boolean {
- if (!node || Object.keys(ids).length === 0) {
- return false
- }
- switch (node.type) {
- case NodeTypes.ELEMENT:
- for (let i = 0; i < node.props.length; i++) {
- const p = node.props[i]
- if (
- p.type === NodeTypes.DIRECTIVE &&
- (hasScopeRef(p.arg, ids) || hasScopeRef(p.exp, ids))
- ) {
- return true
- }
- }
- return node.children.some(c => hasScopeRef(c, ids))
- case NodeTypes.FOR:
- if (hasScopeRef(node.source, ids)) {
- return true
- }
- return node.children.some(c => hasScopeRef(c, ids))
- case NodeTypes.IF:
- return node.branches.some(b => hasScopeRef(b, ids))
- case NodeTypes.IF_BRANCH:
- if (hasScopeRef(node.condition, ids)) {
- return true
- }
- return node.children.some(c => hasScopeRef(c, ids))
- case NodeTypes.SIMPLE_EXPRESSION:
- return (
- !node.isStatic &&
- isSimpleIdentifier(node.content) &&
- !!ids[node.content]
- )
- case NodeTypes.COMPOUND_EXPRESSION:
- return node.children.some(c => isObject(c) && hasScopeRef(c, ids))
- case NodeTypes.INTERPOLATION:
- case NodeTypes.TEXT_CALL:
- return hasScopeRef(node.content, ids)
- case NodeTypes.TEXT:
- case NodeTypes.COMMENT:
- return false
- default:
- if (__DEV__) {
- const exhaustiveCheck: never = node
- exhaustiveCheck
- }
- return false
- }
- }
|