|
1 | | -import { transformVModel as _transformVModel } from '@vue/compiler-vapor' |
2 | | -import { resolveDirectiveNode, resolveNode } from '../utils' |
| 1 | +import { |
| 2 | + createCompilerError, |
| 3 | + createDOMCompilerError, |
| 4 | + createSimpleExpression, |
| 5 | + DOMErrorCodes, |
| 6 | + ErrorCodes, |
| 7 | + isMemberExpression, |
| 8 | +} from '@vue/compiler-dom' |
| 9 | +import { IRNodeTypes, type DirectiveIRNode } from '../ir' |
| 10 | +import { |
| 11 | + findProp, |
| 12 | + getText, |
| 13 | + isJSXComponent, |
| 14 | + resolveDirective, |
| 15 | + resolveLocation, |
| 16 | +} from '../utils' |
3 | 17 | import type { DirectiveTransform } from '../transform' |
| 18 | +import type { JSXElement } from '@babel/types' |
4 | 19 |
|
5 | | -export const transformVModel: DirectiveTransform = (dir, node, context) => { |
6 | | - return _transformVModel( |
7 | | - resolveDirectiveNode(dir, context), |
8 | | - resolveNode(node, context), |
9 | | - context as any, |
| 20 | +export const transformVModel: DirectiveTransform = (_dir, node, context) => { |
| 21 | + const dir = resolveDirective(_dir, context) |
| 22 | + const { exp, arg } = dir |
| 23 | + if (!exp) { |
| 24 | + context.options.onError( |
| 25 | + createCompilerError(ErrorCodes.X_V_MODEL_NO_EXPRESSION, dir.loc), |
| 26 | + ) |
| 27 | + return |
| 28 | + } |
| 29 | + |
| 30 | + const expString = exp.content |
| 31 | + if (!expString.trim() || !isMemberExpression(exp, context.options)) { |
| 32 | + context.options.onError( |
| 33 | + createCompilerError(ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION, exp.loc), |
| 34 | + ) |
| 35 | + return |
| 36 | + } |
| 37 | + |
| 38 | + const isComponent = isJSXComponent(node) |
| 39 | + if (isComponent) { |
| 40 | + return { |
| 41 | + key: arg ? arg : createSimpleExpression('modelValue', true), |
| 42 | + value: exp, |
| 43 | + model: true, |
| 44 | + modelModifiers: dir.modifiers.map((m) => m.content), |
| 45 | + } |
| 46 | + } |
| 47 | + |
| 48 | + if (dir.arg) |
| 49 | + context.options.onError( |
| 50 | + createDOMCompilerError( |
| 51 | + DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT, |
| 52 | + dir.arg.loc, |
| 53 | + ), |
| 54 | + ) |
| 55 | + const tag = getText(node.openingElement.name, context) |
| 56 | + const isCustomElement = context.options.isCustomElement(tag) |
| 57 | + let modelType: DirectiveIRNode['modelType'] | undefined = 'text' |
| 58 | + // TODO let runtimeDirective: VaporHelper | undefined = 'vModelText' |
| 59 | + if ( |
| 60 | + tag === 'input' || |
| 61 | + tag === 'textarea' || |
| 62 | + tag === 'select' || |
| 63 | + isCustomElement |
| 64 | + ) { |
| 65 | + if (tag === 'input' || isCustomElement) { |
| 66 | + const type = findProp(node, 'type') |
| 67 | + if (type?.value) { |
| 68 | + if (type.value.type === 'JSXExpressionContainer') { |
| 69 | + // type={foo} |
| 70 | + modelType = 'dynamic' |
| 71 | + } else if (type.value.type === 'StringLiteral') { |
| 72 | + switch (type.value.value) { |
| 73 | + case 'radio': |
| 74 | + modelType = 'radio' |
| 75 | + break |
| 76 | + case 'checkbox': |
| 77 | + modelType = 'checkbox' |
| 78 | + break |
| 79 | + case 'file': |
| 80 | + modelType = undefined |
| 81 | + context.options.onError( |
| 82 | + createDOMCompilerError( |
| 83 | + DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT, |
| 84 | + dir.loc, |
| 85 | + ), |
| 86 | + ) |
| 87 | + break |
| 88 | + default: |
| 89 | + // text type |
| 90 | + checkDuplicatedValue() |
| 91 | + break |
| 92 | + } |
| 93 | + } |
| 94 | + } else if (hasDynamicKeyVBind(node)) { |
| 95 | + // element has bindings with dynamic keys, which can possibly contain |
| 96 | + // "type". |
| 97 | + modelType = 'dynamic' |
| 98 | + } else { |
| 99 | + // text type |
| 100 | + checkDuplicatedValue() |
| 101 | + } |
| 102 | + } else if (tag === 'select') { |
| 103 | + modelType = 'select' |
| 104 | + } else { |
| 105 | + // textarea |
| 106 | + checkDuplicatedValue() |
| 107 | + } |
| 108 | + } else { |
| 109 | + context.options.onError( |
| 110 | + createDOMCompilerError( |
| 111 | + DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT, |
| 112 | + dir.loc, |
| 113 | + ), |
| 114 | + ) |
| 115 | + } |
| 116 | + |
| 117 | + if (modelType) |
| 118 | + context.registerOperation({ |
| 119 | + type: IRNodeTypes.DIRECTIVE, |
| 120 | + element: context.reference(), |
| 121 | + dir, |
| 122 | + name: 'model', |
| 123 | + modelType, |
| 124 | + builtin: true, |
| 125 | + }) |
| 126 | + |
| 127 | + function checkDuplicatedValue() { |
| 128 | + const value = findProp(node, 'value') |
| 129 | + if (value && value.value?.type !== 'StringLiteral') { |
| 130 | + context.options.onError( |
| 131 | + createDOMCompilerError( |
| 132 | + DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE, |
| 133 | + resolveLocation(value.loc, context), |
| 134 | + ), |
| 135 | + ) |
| 136 | + } |
| 137 | + } |
| 138 | +} |
| 139 | + |
| 140 | +function hasDynamicKeyVBind(node: JSXElement): boolean { |
| 141 | + return node.openingElement.attributes.some( |
| 142 | + (p) => |
| 143 | + p.type === 'JSXSpreadAttribute' || |
| 144 | + (p.type === 'JSXAttribute' && |
| 145 | + p.name.type === 'JSXNamespacedName' && |
| 146 | + !p.name.namespace.name.startsWith('v-')), |
10 | 147 | ) |
11 | 148 | } |
0 commit comments