| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- 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,
- TO_HANDLERS
- } 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 enum MemberExpLexState {
- inMemberExp,
- inBrackets,
- inString
- }
- const validFirstIdentCharRE = /[A-Za-z_$\xA0-\uFFFF]/
- const validIdentCharRE = /[\.\w$\xA0-\uFFFF]/
- const whitespaceRE = /\s+[.[]\s*|\s*[.[]\s+/g
- /**
- * Simple lexer to check if an expression is a member expression. This is
- * lax and only checks validity at the root level (i.e. does not validate exps
- * inside square brackets), but it's ok since these are only used on template
- * expressions and false positives are invalid expressions in the first place.
- */
- export const isMemberExpression = (path: string): boolean => {
- // remove whitespaces around . or [ first
- path = path.trim().replace(whitespaceRE, s => s.trim())
- let state = MemberExpLexState.inMemberExp
- let prevState = MemberExpLexState.inMemberExp
- let currentOpenBracketCount = 0
- let currentStringType: "'" | '"' | '`' | null = null
- for (let i = 0; i < path.length; i++) {
- const char = path.charAt(i)
- switch (state) {
- case MemberExpLexState.inMemberExp:
- if (char === '[') {
- prevState = state
- state = MemberExpLexState.inBrackets
- currentOpenBracketCount++
- } else if (
- !(i === 0 ? validFirstIdentCharRE : validIdentCharRE).test(char)
- ) {
- return false
- }
- break
- case MemberExpLexState.inBrackets:
- if (char === `'` || char === `"` || char === '`') {
- prevState = state
- state = MemberExpLexState.inString
- currentStringType = char
- } else if (char === `[`) {
- currentOpenBracketCount++
- } else if (char === `]`) {
- if (!--currentOpenBracketCount) {
- state = prevState
- }
- }
- break
- case MemberExpLexState.inString:
- if (char === currentStringType) {
- state = prevState
- currentStringType = null
- }
- break
- }
- }
- return !currentOpenBracketCount
- }
- 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 || allowEmpty) &&
- 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 | undefined
- 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 {
- if (props.callee === TO_HANDLERS) {
- // #2366
- propsWithInjection = createCallExpression(context.helper(MERGE_PROPS), [
- createObjectExpression([prop]),
- props
- ])
- } else {
- props.arguments.unshift(createObjectExpression([prop]))
- }
- }
- !propsWithInjection && (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' | 'filter'
- ): 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
- }
- }
|