Skip to content

Commit 4d43930

Browse files
committed
feat: implement move key to template transformation
1 parent 0f52a71 commit 4d43930

File tree

4 files changed

+230
-0
lines changed

4 files changed

+230
-0
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { runTest } from '../../src/testUtils'
2+
3+
runTest(
4+
'Moves the key from the Template child node to Template',
5+
'v-for-template-key',
6+
'v-for-template-key',
7+
'vue',
8+
'vue'
9+
)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<template>
2+
<div id="app">
3+
<template v-for="item in items">
4+
<ul id="v-for-object" class="demo">
5+
<li class="example-1" v-for="value in object" v-bind:key="value.id">
6+
{{ value }}
7+
</li>
8+
</ul>
9+
<li
10+
class="example-2"
11+
:key="'bodying-id' + item.game.id + 'bodying-name'"
12+
role="presentation"
13+
></li>
14+
<div>
15+
<li
16+
class="example-1"
17+
:key="'bottom-' + item.id"
18+
role="presentation"
19+
></li>
20+
</div>
21+
</template>
22+
</div>
23+
</template>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<template>
2+
<div id="app">
3+
<template v-for="item in items" :key="'bodying-id' + item.game.id + 'bodying-name'">
4+
<ul id="v-for-object" class="demo">
5+
<li class="example-1" v-for="value in object" v-bind:key="value.id">
6+
{{ value }}
7+
</li>
8+
</ul>
9+
<li
10+
class="example-2"
11+
12+
role="presentation"
13+
></li>
14+
<div>
15+
<li
16+
class="example-1"
17+
18+
role="presentation"
19+
></li>
20+
</div>
21+
</template>
22+
</div>
23+
</template>
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import * as OperationUtils from '../src/operationUtils'
2+
import * as parser from 'vue-eslint-parser'
3+
import * as util from 'util'
4+
import type { Node } from 'vue-eslint-parser/ast/nodes'
5+
import type { Operation } from '../src/operationUtils'
6+
import type { VueASTTransformation } from '../src/wrapVueTransformation'
7+
import wrap from '../src/wrapVueTransformation'
8+
9+
let operatingParentElements: any = []
10+
11+
/**
12+
* 每一个实际的规则,需要做以下几件事:
13+
* 1、findNodes(fileInfo, ast): 寻找匹配规则的节点
14+
* 2、fix(nodes): 完善匹配节点的增删改逻辑
15+
* 3、applyFix(fileInfo, tempFixes): 执行fixer,对源码进行增删改,并返回转换后的代码
16+
* @param context
17+
*/
18+
19+
export const transformAST: VueASTTransformation = (context) => {
20+
let fixOperations: Operation[] = []
21+
const toFixNodes: Node[] = findNodes(context)
22+
toFixNodes.forEach((node) => {
23+
// fix(node) 返回的为 Operation 数组,因此用 concat 合并多个数组
24+
fixOperations = fixOperations.concat(fix(node))
25+
})
26+
return fixOperations
27+
}
28+
29+
export default wrap(transformAST)
30+
/**
31+
* 定位 slot attribute 节点
32+
*
33+
* @param context { file: FileInfo, api: API }
34+
* @param templateBody
35+
* @returns 所有的 slot attribute 节点
36+
*/
37+
function findNodes(context: any): Node[] {
38+
const { file } = context
39+
const source = file.source
40+
const options = { sourceType: 'module' }
41+
const ast = parser.parse(source, options)
42+
let toFixNodes: Node[] = []
43+
let root: Node = <Node>ast.templateBody // 强制类型转换
44+
parser.AST.traverseNodes(root, {
45+
enterNode(node: Node) {
46+
if (
47+
node.type === 'VAttribute' &&
48+
node.key.type === 'VDirectiveKey' &&
49+
node.key.name.name === 'bind' &&
50+
node.key.argument?.type === 'VIdentifier' &&
51+
node.key.argument?.name === 'key'
52+
) {
53+
toFixNodes.push(node)
54+
}
55+
},
56+
leaveNode(node: Node) {},
57+
})
58+
return toFixNodes
59+
}
60+
/**
61+
* The repair logic for
62+
* @param node The Target Node
63+
*/
64+
function fix(node: any): Operation[] {
65+
let fixOperations: Operation[] = []
66+
const target: any = node!.parent!.parent
67+
68+
// The current node has no attribute that is v-for
69+
let havaForBrother: boolean = false
70+
target.startTag.attributes
71+
.filter(
72+
(attr: any) =>
73+
attr.type === 'VAttribute' &&
74+
attr.key.type === 'VDirectiveKey' &&
75+
attr.key.name.name === 'for'
76+
)
77+
.forEach((element: any) => {
78+
havaForBrother = true
79+
})
80+
if (havaForBrother) {
81+
return fixOperations
82+
}
83+
84+
let elder: any = null
85+
let elderHasKey: any = false
86+
let tmp: any = target.parent
87+
// find template parent
88+
while (elder == null && tmp != null) {
89+
elderHasKey = false
90+
if (tmp.type != 'VElement' || tmp.name != 'template') {
91+
tmp = tmp.parent
92+
continue
93+
}
94+
95+
tmp.startTag.attributes
96+
.filter(
97+
(attr: any) =>
98+
attr.type === 'VAttribute' &&
99+
attr.key.type === 'VDirectiveKey' &&
100+
attr.key.name.name === 'bind' &&
101+
attr.key.argument?.type === 'VIdentifier' &&
102+
attr.key.argument?.name === 'key'
103+
)
104+
.forEach((element: any) => {
105+
elderHasKey = true
106+
})
107+
108+
if (elderHasKey) {
109+
break
110+
}
111+
112+
tmp.startTag.attributes
113+
.filter(
114+
(attr: any) =>
115+
attr.type === 'VAttribute' &&
116+
attr.key.type === 'VDirectiveKey' &&
117+
attr.key.name.name === 'for'
118+
)
119+
.forEach((element: any) => {
120+
elder = element
121+
})
122+
}
123+
124+
let expression: string = getExpression(node.value)
125+
126+
fixOperations.push(OperationUtils.remove(node))
127+
if (
128+
util.inspect(operatingParentElements).indexOf(util.inspect(elder.range)) ==
129+
-1
130+
) {
131+
operatingParentElements.push(elder.range)
132+
fixOperations.push(
133+
OperationUtils.insertTextAfter(elder, ' :key=' + expression)
134+
)
135+
}
136+
return fixOperations
137+
}
138+
139+
function getExpression(node: any): any {
140+
if (node.type == 'VExpressionContainer') {
141+
return '"' + getExpression(node.expression) + '"'
142+
} else if (node.type == 'BinaryExpression') {
143+
return (
144+
getExpression(node.left) +
145+
' ' +
146+
node.operator +
147+
' ' +
148+
getExpression(node.right)
149+
)
150+
} else if (node.type == 'Literal') {
151+
return "'" + node.value + "'"
152+
} else if (node.type == 'MemberExpression') {
153+
return getExpression(node.object) + '.' + node.property.name
154+
} else if (node.type == 'ObjectExpression') {
155+
let str: string = ''
156+
for (let index = 0; index < node.properties.length; index++) {
157+
if (
158+
node.properties[index].key == null ||
159+
node.properties[index].value == null
160+
) {
161+
str = str + ''
162+
} else {
163+
str =
164+
str +
165+
getExpression(node.properties[index].key) +
166+
':' +
167+
getExpression(node.properties[index].value)
168+
}
169+
}
170+
str = str.substring(0, str.length - 1)
171+
return '{' + str + '}'
172+
} else {
173+
return node.name
174+
}
175+
}

0 commit comments

Comments
 (0)