vModel.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import {
  2. BindingTypes,
  3. DOMErrorCodes,
  4. ElementTypes,
  5. ErrorCodes,
  6. NodeTypes,
  7. createCompilerError,
  8. createDOMCompilerError,
  9. createSimpleExpression,
  10. findDir,
  11. findProp,
  12. hasDynamicKeyVBind,
  13. isMemberExpression,
  14. isStaticArgOf,
  15. } from '@vue/compiler-dom'
  16. import type { DirectiveTransform } from '../transform'
  17. import { IRNodeTypes, type VaporHelper } from '../ir'
  18. export const transformVModel: DirectiveTransform = (dir, node, context) => {
  19. const { exp, arg } = dir
  20. if (!exp) {
  21. context.options.onError(
  22. createCompilerError(ErrorCodes.X_V_MODEL_NO_EXPRESSION, dir.loc),
  23. )
  24. return
  25. }
  26. // we assume v-model directives are always parsed
  27. // (not artificially created by a transform)
  28. const rawExp = exp.loc.source
  29. // in SFC <script setup> inline mode, the exp may have been transformed into
  30. // _unref(exp)
  31. const bindingType = context.options.bindingMetadata[rawExp]
  32. // check props
  33. if (
  34. bindingType === BindingTypes.PROPS ||
  35. bindingType === BindingTypes.PROPS_ALIASED
  36. ) {
  37. context.options.onError(
  38. createCompilerError(ErrorCodes.X_V_MODEL_ON_PROPS, exp.loc),
  39. )
  40. return
  41. }
  42. const expString = exp.content
  43. const maybeRef =
  44. !__BROWSER__ &&
  45. context.options.inline &&
  46. (bindingType === BindingTypes.SETUP_LET ||
  47. bindingType === BindingTypes.SETUP_REF ||
  48. bindingType === BindingTypes.SETUP_MAYBE_REF)
  49. if (
  50. !expString.trim() ||
  51. (!isMemberExpression(expString, context.options) && !maybeRef)
  52. ) {
  53. context.options.onError(
  54. createCompilerError(ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION, exp.loc),
  55. )
  56. return
  57. }
  58. const isComponent = node.tagType === ElementTypes.COMPONENT
  59. let runtimeDirective: VaporHelper | undefined
  60. if (isComponent) {
  61. if (dir.arg)
  62. context.options.onError(
  63. createDOMCompilerError(
  64. DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT,
  65. dir.arg.loc,
  66. ),
  67. )
  68. } else {
  69. const { tag } = node
  70. const isCustomElement = context.options.isCustomElement(tag)
  71. runtimeDirective = 'vModelText'
  72. if (
  73. tag === 'input' ||
  74. tag === 'textarea' ||
  75. tag === 'select' ||
  76. isCustomElement
  77. ) {
  78. if (tag === 'input' || isCustomElement) {
  79. const type = findProp(node, 'type')
  80. if (type) {
  81. if (type.type === NodeTypes.DIRECTIVE) {
  82. // :type="foo"
  83. runtimeDirective = 'vModelDynamic'
  84. } else if (type.value) {
  85. switch (type.value.content) {
  86. case 'radio':
  87. runtimeDirective = 'vModelRadio'
  88. break
  89. case 'checkbox':
  90. runtimeDirective = 'vModelCheckbox'
  91. break
  92. case 'file':
  93. runtimeDirective = undefined
  94. context.options.onError(
  95. createDOMCompilerError(
  96. DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT,
  97. dir.loc,
  98. ),
  99. )
  100. break
  101. default:
  102. // text type
  103. __DEV__ && checkDuplicatedValue()
  104. break
  105. }
  106. }
  107. } else if (hasDynamicKeyVBind(node)) {
  108. // element has bindings with dynamic keys, which can possibly contain
  109. // "type".
  110. runtimeDirective = 'vModelDynamic'
  111. } else {
  112. // text type
  113. __DEV__ && checkDuplicatedValue()
  114. }
  115. } else if (tag === 'select') {
  116. runtimeDirective = 'vModelSelect'
  117. } else {
  118. // textarea
  119. __DEV__ && checkDuplicatedValue()
  120. }
  121. } else {
  122. context.options.onError(
  123. createDOMCompilerError(
  124. DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT,
  125. dir.loc,
  126. ),
  127. )
  128. }
  129. }
  130. context.registerOperation({
  131. type: IRNodeTypes.SET_MODEL_VALUE,
  132. element: context.reference(),
  133. key: arg || createSimpleExpression('modelValue', true),
  134. value: exp,
  135. isComponent,
  136. })
  137. if (runtimeDirective)
  138. context.registerOperation({
  139. type: IRNodeTypes.WITH_DIRECTIVE,
  140. element: context.reference(),
  141. dir,
  142. builtin: runtimeDirective,
  143. })
  144. function checkDuplicatedValue() {
  145. const value = findDir(node, 'bind')
  146. if (value && isStaticArgOf(value.arg, 'value')) {
  147. context.options.onError(
  148. createDOMCompilerError(
  149. DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE,
  150. value.loc,
  151. ),
  152. )
  153. }
  154. }
  155. }