All files / compiler-core/src/transforms vBind.ts

97.87% Statements 92/94
96.87% Branches 31/32
100% Functions 3/3
97.87% Lines 92/94

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 1242x               2x 2x 2x 2x 2x         2x 240x 240x   240x     240x 1x       1x 1x 1x 1x 1x 1x 1x 1x 1x     1x     240x 7x   1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x   6x 6x 6x   240x 2x 2x 240x 8x 8x     240x 4x 3x 2x 3x 1x 1x 4x 1x 1x 1x 4x   238x 180x 8x 8x 180x 2x 2x 180x   238x 238x 238x 238x   2x 10x 10x 10x 10x   10x 10x 10x 10x 10x 10x   2x 10x 9x 8x 9x 1x 1x 10x 1x 1x 1x 10x  
import type { DirectiveTransform, TransformContext } from '../transform'
import {
  type DirectiveNode,
  type ExpressionNode,
  NodeTypes,
  type SimpleExpressionNode,
  createObjectProperty,
  createSimpleExpression,
} from '../ast'
import { ErrorCodes, createCompilerError } from '../errors'
import { camelize } from '@vue/shared'
import { CAMELIZE } from '../runtimeHelpers'
import { processExpression } from './transformExpression'
 
// v-bind without arg is handled directly in ./transformElements.ts due to it affecting
// codegen for the entire props object. This transform here is only for v-bind
// *with* args.
export const transformBind: DirectiveTransform = (dir, _node, context) => {
  const { modifiers, loc } = dir
  const arg = dir.arg!
 
  let { exp } = dir
 
  // handle empty expression
  if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim()) {
    if (!__BROWSER__) {
      // #10280 only error against empty expression in non-browser build
      // because :foo in in-DOM templates will be parsed into :foo="" by the
      // browser
      context.onError(
        createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc),
      )
      return {
        props: [
          createObjectProperty(arg, createSimpleExpression('', true, loc)),
        ],
      }
    } else {
      exp = undefined
    }
  }
 
  // same-name shorthand - :arg is expanded to :arg="arg"
  if (!exp) {
    if (arg.type !== NodeTypes.SIMPLE_EXPRESSION || !arg.isStatic) {
      // only simple expression is allowed for same-name shorthand
      context.onError(
        createCompilerError(
          ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT,
          arg.loc,
        ),
      )
      return {
        props: [
          createObjectProperty(arg, createSimpleExpression('', true, loc)),
        ],
      }
    }
 
    transformBindShorthand(dir, context)
    exp = dir.exp!
  }
 
  if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {
    arg.children.unshift(`(`)
    arg.children.push(`) || ""`)
  } else if (!arg.isStatic) {
    arg.content = `${arg.content} || ""`
  }
 
  // .sync is replaced by v-model:arg
  if (modifiers.some(mod => mod.content === 'camel')) {
    if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
      if (arg.isStatic) {
        arg.content = camelize(arg.content)
      } else {
        arg.content = `${context.helperString(CAMELIZE)}(${arg.content})`
      }
    } else {
      arg.children.unshift(`${context.helperString(CAMELIZE)}(`)
      arg.children.push(`)`)
    }
  }
 
  if (!context.inSSR) {
    if (modifiers.some(mod => mod.content === 'prop')) {
      injectPrefix(arg, '.')
    }
    if (modifiers.some(mod => mod.content === 'attr')) {
      injectPrefix(arg, '^')
    }
  }
 
  return {
    props: [createObjectProperty(arg, exp)],
  }
}
 
export const transformBindShorthand = (
  dir: DirectiveNode,
  context: TransformContext,
): void => {
  const arg = dir.arg!
 
  const propName = camelize((arg as SimpleExpressionNode).content)
  dir.exp = createSimpleExpression(propName, false, arg.loc)
  if (!__BROWSER__) {
    dir.exp = processExpression(dir.exp, context)
  }
}
 
const injectPrefix = (arg: ExpressionNode, prefix: string) => {
  if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
    if (arg.isStatic) {
      arg.content = prefix + arg.content
    } else {
      arg.content = `\`${prefix}\${${arg.content}}\``
    }
  } else {
    arg.children.unshift(`'${prefix}' + (`)
    arg.children.push(`)`)
  }
}