2
0

expression.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import {
  2. BindingTypes,
  3. NewlineType,
  4. type SourceLocation,
  5. advancePositionWithClone,
  6. isInDestructureAssignment,
  7. isStaticProperty,
  8. walkIdentifiers,
  9. } from '@vue/compiler-dom'
  10. import { isGloballyAllowed, isString, makeMap } from '@vue/shared'
  11. import type { Identifier } from '@babel/types'
  12. import type { IRExpression } from '../ir'
  13. import {
  14. type CodeFragment,
  15. type CodegenContext,
  16. buildCodeFragment,
  17. } from '../generate'
  18. import type { Node } from '@babel/types'
  19. export function genExpression(
  20. node: IRExpression,
  21. context: CodegenContext,
  22. ): CodeFragment[] {
  23. const {
  24. options: { prefixIdentifiers },
  25. } = context
  26. if (isString(node)) return [node]
  27. const { content: rawExpr, ast, isStatic, loc } = node
  28. if (isStatic) {
  29. return [[JSON.stringify(rawExpr), NewlineType.None, loc]]
  30. }
  31. if (
  32. __BROWSER__ ||
  33. !prefixIdentifiers ||
  34. !node.content.trim() ||
  35. // there was a parsing error
  36. ast === false ||
  37. isGloballyAllowed(rawExpr) ||
  38. isLiteralWhitelisted(rawExpr)
  39. ) {
  40. return [[rawExpr, NewlineType.None, loc]]
  41. }
  42. // the expression is a simple identifier
  43. if (ast === null) {
  44. return genIdentifier(rawExpr, context, loc)
  45. }
  46. const ids: Identifier[] = []
  47. const parentStackMap = new WeakMap<Identifier, Node[]>()
  48. const parentStack: Node[] = []
  49. walkIdentifiers(
  50. ast!,
  51. id => {
  52. ids.push(id)
  53. parentStackMap.set(id, parentStack.slice())
  54. },
  55. false,
  56. parentStack,
  57. )
  58. if (ids.length) {
  59. ids.sort((a, b) => a.start! - b.start!)
  60. const [frag, push] = buildCodeFragment()
  61. ids.forEach((id, i) => {
  62. // range is offset by -1 due to the wrapping parens when parsed
  63. const start = id.start! - 1
  64. const end = id.end! - 1
  65. const last = ids[i - 1]
  66. const leadingText = rawExpr.slice(last ? last.end! - 1 : 0, start)
  67. if (leadingText.length) push([leadingText, NewlineType.Unknown])
  68. const source = rawExpr.slice(start, end)
  69. const parentStack = parentStackMap.get(id)!
  70. push(
  71. ...genIdentifier(
  72. source,
  73. context,
  74. {
  75. start: advancePositionWithClone(node.loc.start, source, start),
  76. end: advancePositionWithClone(node.loc.start, source, end),
  77. source,
  78. },
  79. id,
  80. parentStack[parentStack.length - 1],
  81. parentStack,
  82. ),
  83. )
  84. if (i === ids.length - 1 && end < rawExpr.length) {
  85. push([rawExpr.slice(end), NewlineType.Unknown])
  86. }
  87. })
  88. return frag
  89. } else {
  90. return [[rawExpr, NewlineType.Unknown, loc]]
  91. }
  92. }
  93. const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
  94. function genIdentifier(
  95. raw: string,
  96. { options, vaporHelper, identifiers }: CodegenContext,
  97. loc?: SourceLocation,
  98. id?: Identifier,
  99. parent?: Node,
  100. parentStack?: Node[],
  101. ): CodeFragment[] {
  102. const { inline, bindingMetadata } = options
  103. let name: string | undefined = raw
  104. const idMap = identifiers[raw]
  105. if (idMap && idMap.length) {
  106. return [[idMap[0], NewlineType.None, loc]]
  107. }
  108. let prefix: string | undefined
  109. if (isStaticProperty(parent!) && parent.shorthand) {
  110. // property shorthand like { foo }, we need to add the key since
  111. // we rewrite the value
  112. prefix = `${raw}: `
  113. }
  114. if (inline) {
  115. switch (bindingMetadata[raw]) {
  116. case BindingTypes.SETUP_REF:
  117. name = raw = `${raw}.value`
  118. break
  119. case BindingTypes.SETUP_MAYBE_REF:
  120. // ({ x } = y)
  121. const isDestructureAssignment =
  122. parent && isInDestructureAssignment(parent, parentStack || [])
  123. // x = y
  124. const isAssignmentLVal =
  125. parent && parent.type === 'AssignmentExpression' && parent.left === id
  126. // x++
  127. const isUpdateArg =
  128. parent && parent.type === 'UpdateExpression' && parent.argument === id
  129. // const binding that may or may not be ref
  130. // if it's not a ref, then assignments don't make sense -
  131. // so we ignore the non-ref assignment case and generate code
  132. // that assumes the value to be a ref for more efficiency
  133. raw =
  134. isAssignmentLVal || isUpdateArg || isDestructureAssignment
  135. ? (name = `${raw}.value`)
  136. : `${vaporHelper('unref')}(${raw})`
  137. break
  138. }
  139. } else {
  140. raw = `_ctx.${raw}`
  141. }
  142. return [prefix, [raw, NewlineType.None, loc, name]]
  143. }