Skip to content

Commit c7bb4ca

Browse files
committed
feat: implement v-model transformation
1 parent 9380256 commit c7bb4ca

File tree

3 files changed

+214
-0
lines changed

3 files changed

+214
-0
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { defineInlineTest } from 'jscodeshift/src/testUtils'
2+
3+
const transform = require('../v-model')
4+
5+
defineInlineTest(
6+
transform,
7+
{},
8+
`export default {
9+
props: {
10+
value: String
11+
},
12+
model: {
13+
prop: 'title',
14+
event: 'update'
15+
},
16+
emits: ['update:modelValue'],
17+
methods: {
18+
changePageTitle(title) {
19+
this.$emit('update:modelValue', title)
20+
}
21+
}
22+
}`,
23+
`export default {
24+
props: {
25+
modelValue: String
26+
},
27+
28+
emits: ['update:modelValue'],
29+
30+
methods: {
31+
changePageTitle(title) {
32+
this.$emit('update:modelValue', title)
33+
},
34+
35+
updateTitle (title){
36+
this.$emit('update:modelValue', title)
37+
}
38+
}
39+
};`,
40+
'transformation v-mode'
41+
)
42+
43+
44+
defineInlineTest(
45+
transform,
46+
{},
47+
`export default {
48+
props: {
49+
value: String
50+
},
51+
model: {
52+
prop: 'title',
53+
event: 'change'
54+
},
55+
emits: ['update:modelValue']
56+
}`,
57+
`export default {
58+
props: {
59+
modelValue: String
60+
},
61+
emits: ['update:modelValue'],
62+
methods: {
63+
changeTitle (title){
64+
this.$emit('update:modelValue', title)
65+
}
66+
}
67+
}`,
68+
'transformation v-mode no methods option'
69+
)
70+
71+
defineInlineTest(
72+
transform,
73+
{},
74+
`export default {
75+
props: {
76+
value: String
77+
},
78+
model: {
79+
prop: 'title',
80+
event: 'change'
81+
},
82+
emits: ['update:modelValue'],
83+
methods: {
84+
change(param){
85+
this.$emit('change',param)
86+
}
87+
}
88+
}`,
89+
`export default {
90+
props: {
91+
modelValue: String
92+
},
93+
94+
emits: ['update:modelValue'],
95+
96+
methods: {
97+
change(param){
98+
changeTitle(param);
99+
},
100+
101+
changeTitle (title){
102+
this.$emit('update:modelValue', title)
103+
}
104+
}
105+
};`,
106+
'transformation v-mode with a method call'
107+
)

transformations/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const transformationMap: {
2121
'add-emit-declaration': require('./add-emit-declaration'),
2222
'global-filter': require('./global-filter'),
2323
'tree-shaking': require('./tree-shaking'),
24+
'v-model': require('./v-model'),
2425

2526
// atomic ones
2627
'remove-contextual-h-from-render': require('./remove-contextual-h-from-render'),

transformations/v-model.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import wrap from '../src/wrapAstTransformation'
2+
import type { ASTTransformation } from '../src/wrapAstTransformation'
3+
import type { ObjectProperty } from 'jscodeshift'
4+
5+
export const transformAST: ASTTransformation = ({ j, root }) => {
6+
// find model option
7+
const modelCollection = root
8+
.find(j.ObjectProperty, node => node.key.name === 'model')
9+
.filter(path => path.parent.parent.node.type == 'ExportDefaultDeclaration')
10+
if (!modelCollection.length) return
11+
12+
// find prop option which is in model
13+
const propPath = modelCollection.find(j.ObjectProperty, (node: ObjectProperty) => {
14+
// @ts-ignore
15+
return node.key.name === 'prop'
16+
}).get(0)
17+
18+
// find event option which is in model
19+
const eventPath = modelCollection.find(j.ObjectProperty, (node: ObjectProperty) => {
20+
// @ts-ignore
21+
return node.key.name === 'event'
22+
}).get(0)
23+
24+
const propName = propPath.node.value.value
25+
const propEvent = eventPath.node.value.value
26+
27+
// find the props option
28+
const propsCollections = root
29+
.find(j.ObjectProperty, node => node.key.name === 'props')
30+
.filter(path => path.parent.parent.node.type === 'ExportDefaultDeclaration')
31+
if (!propsCollections.length) return
32+
33+
// find the value which is in props
34+
const valueNode: ObjectProperty = propsCollections
35+
.find(j.ObjectProperty, node => node.key.name === 'value')
36+
.filter(path => path.parent.parent.node.key.name === 'props')
37+
.get(0)
38+
.node
39+
40+
// replace the value with modelValue
41+
// @ts-ignore
42+
valueNode?.key.name = 'modelValue'
43+
44+
// remove model option
45+
modelCollection.remove()
46+
47+
// find the methods option
48+
const methodsCollections = root
49+
.find(j.ObjectProperty, node => node.key.name === 'methods')
50+
.filter(nodePath => nodePath.parent.parent.node.type === 'ExportDefaultDeclaration')
51+
52+
const methodName = `${propEvent}${propName[0].toUpperCase().concat(propName.slice(1))}`
53+
const methodBodyNode = j(`
54+
export default {
55+
${methodName} (${propName}){
56+
this.$emit('update:modelValue', ${propName})
57+
}}`).find(j.ObjectMethod).get(0).node
58+
59+
if (!methodsCollections.length) {
60+
// method option dont exists ,push a method option
61+
propsCollections.get(0)
62+
.parent
63+
.value
64+
.properties
65+
.push(j.objectProperty(j.identifier('methods'), j.objectExpression([methodBodyNode])))
66+
} else {
67+
// method option existed ,push method body
68+
methodsCollections.get(0)
69+
.node
70+
.value
71+
.properties
72+
.push(methodBodyNode)
73+
}
74+
75+
// replace all this.emit(eventName , prop) with ${methodName}(prop)
76+
const callMethods = root.find(j.CallExpression, {
77+
callee: {
78+
type: 'MemberExpression',
79+
object: {
80+
type: 'ThisExpression'
81+
},
82+
property: {
83+
type: 'Identifier',
84+
name: '$emit'
85+
}
86+
}
87+
}).filter(nodePath => {
88+
const methodArgs = nodePath.node.arguments
89+
return methodArgs.length === 2
90+
&& methodArgs[0].type === 'StringLiteral'
91+
&& methodArgs[0].value === propEvent
92+
&& methodArgs[1].type === 'Identifier'
93+
})
94+
95+
if (callMethods.length) {
96+
// get the second param name
97+
callMethods.forEach(nodePath => {
98+
// @ts-ignore
99+
const paramName = nodePath.node.arguments[1].name
100+
nodePath.parentPath.replace(j.expressionStatement(j.callExpression(j.identifier(methodName), [j.identifier(paramName)])))
101+
})
102+
}
103+
}
104+
105+
export default wrap(transformAST)
106+
export const parser = 'babylon'

0 commit comments

Comments
 (0)