eslint-plugin-mpx 0.2.8 → 0.2.10-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/configs/composition-api-essential.js +6 -2
- package/lib/configs/mpx-essential.js +1 -2
- package/lib/index.js +5 -1
- package/lib/rules/no-deprecated-lifecycle.js +46 -0
- package/lib/rules/no-deprecated-mpx-createfunction.js +46 -0
- package/lib/rules/no-deprecated-watch-second-param.js +40 -0
- package/lib/rules/valid-initdata.js +270 -0
- package/lib/rules/valid-setup-define-expose.js +2 -3
- package/lib/rules/valid-wx-model.js +1 -1
- package/package.json +2 -2
- package/lib/rules/valid-wx-for.js +0 -189
|
@@ -12,8 +12,12 @@ module.exports = {
|
|
|
12
12
|
'mpx/valid-wx-if': 'error',
|
|
13
13
|
'mpx/valid-wx-else': 'error',
|
|
14
14
|
'mpx/valid-wx-elif': 'error',
|
|
15
|
-
|
|
15
|
+
'mpx/valid-wx-model': 'error',
|
|
16
16
|
// 'mpx/script-setup-uses-vars': 'error',
|
|
17
|
-
'mpx/valid-
|
|
17
|
+
'mpx/valid-initdata': 'error',
|
|
18
|
+
'mpx/valid-setup-define-expose': 'error',
|
|
19
|
+
'mpx/no-deprecated-mpx-createfunction': 'error',
|
|
20
|
+
'mpx/no-deprecated-watch-second-param': 'error',
|
|
21
|
+
'mpx/no-deprecated-lifecycle': 'error'
|
|
18
22
|
}
|
|
19
23
|
}
|
|
@@ -18,8 +18,7 @@ module.exports = {
|
|
|
18
18
|
'mpx/valid-wx-if': 'error',
|
|
19
19
|
'mpx/valid-wx-else': 'error',
|
|
20
20
|
'mpx/valid-wx-elif': 'error',
|
|
21
|
-
|
|
22
|
-
// 'mpx/valid-wx-model': 'error',
|
|
21
|
+
'mpx/valid-wx-model': 'error',
|
|
23
22
|
'mpx/valid-swiper-item-style': 'error',
|
|
24
23
|
'mpx/valid-wx-key': 'error',
|
|
25
24
|
'mpx/valid-attribute-value': 'error',
|
package/lib/index.js
CHANGED
|
@@ -31,7 +31,11 @@ module.exports = {
|
|
|
31
31
|
'valid-attribute-value': require('./rules/valid-attribute-value'),
|
|
32
32
|
'valid-template-quote': require('./rules/valid-template-quote'),
|
|
33
33
|
'valid-component-range': require('./rules/valid-component-range'),
|
|
34
|
-
'valid-setup-define-expose': require('./rules/valid-setup-define-expose')
|
|
34
|
+
'valid-setup-define-expose': require('./rules/valid-setup-define-expose'),
|
|
35
|
+
'valid-initdata': require('./rules/valid-initdata'),
|
|
36
|
+
'no-deprecated-lifecycle': require('./rules/no-deprecated-lifecycle'),
|
|
37
|
+
'no-deprecated-mpx-createfunction': require('./rules/no-deprecated-mpx-createfunction'),
|
|
38
|
+
'no-deprecated-watch-second-param': require('./rules/no-deprecated-watch-second-param')
|
|
35
39
|
},
|
|
36
40
|
configs: {
|
|
37
41
|
base: require('./configs/base'),
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author pagnkelly
|
|
3
|
+
* See LICENSE file in root directory for full license.
|
|
4
|
+
*/
|
|
5
|
+
'use strict'
|
|
6
|
+
|
|
7
|
+
const utils = require('../utils')
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
meta: {
|
|
11
|
+
type: 'problem',
|
|
12
|
+
docs: {
|
|
13
|
+
description: 'pageShow/pageHide废弃的生命周期',
|
|
14
|
+
categories: ['composition-api-essential'],
|
|
15
|
+
url: 'https://mpx-ecology.github.io/eslint-plugin-mpx/rules/no-deprecated-lifecycle.html'
|
|
16
|
+
},
|
|
17
|
+
fixable: 'code',
|
|
18
|
+
schema: [],
|
|
19
|
+
messages: {
|
|
20
|
+
deprecatedPageShow:
|
|
21
|
+
'The `pageShow` lifecycle hook is deprecated. Use `pageLifetimes.show` instead.',
|
|
22
|
+
deprecatedPageHide:
|
|
23
|
+
'The `pageHide` lifecycle hook is deprecated. Use `pageLifetimes.hide` instead.'
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
/** @param {RuleContext} context */
|
|
27
|
+
create(context) {
|
|
28
|
+
return utils.executeOnMpx(context, (obj) => {
|
|
29
|
+
const pageShow = utils.findProperty(obj, 'pageShow')
|
|
30
|
+
if (pageShow) {
|
|
31
|
+
context.report({
|
|
32
|
+
node: pageShow.key,
|
|
33
|
+
messageId: 'deprecatedPageShow'
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const pageHide = utils.findProperty(obj, 'pageHide')
|
|
38
|
+
if (pageHide) {
|
|
39
|
+
context.report({
|
|
40
|
+
node: pageHide.key,
|
|
41
|
+
messageId: 'deprecatedPageHide'
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* @author pagnkelly
|
|
4
|
+
* See LICENSE file in root directory for full license.
|
|
5
|
+
*/
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
meta: {
|
|
10
|
+
type: 'problem',
|
|
11
|
+
docs: {
|
|
12
|
+
description: 'mpx.create*调用方式已经被废弃',
|
|
13
|
+
categories: ['composition-api-essential'],
|
|
14
|
+
url: 'https://mpx-ecology.github.io/eslint-plugin-mpx/rules/no-deprecated-mpx-createfunction'
|
|
15
|
+
},
|
|
16
|
+
fixable: 'code',
|
|
17
|
+
schema: []
|
|
18
|
+
},
|
|
19
|
+
/** @param {RuleContext} context */
|
|
20
|
+
create(context) {
|
|
21
|
+
return {
|
|
22
|
+
/** @param {import("mpx-eslint-parser/ast").ESLintStatement} node */
|
|
23
|
+
ExpressionStatement(node) {
|
|
24
|
+
if (
|
|
25
|
+
node.expression.callee &&
|
|
26
|
+
node.expression.callee.object &&
|
|
27
|
+
node.expression.callee.object.name === 'mpx' &&
|
|
28
|
+
node.expression.callee.property &&
|
|
29
|
+
[
|
|
30
|
+
'createApp',
|
|
31
|
+
'createStore',
|
|
32
|
+
'createPage',
|
|
33
|
+
'createComponent'
|
|
34
|
+
].includes(node.expression.callee.property.name)
|
|
35
|
+
) {
|
|
36
|
+
context.report({
|
|
37
|
+
node,
|
|
38
|
+
message:
|
|
39
|
+
'The Mpx object of default export is no longer attached to the {{name}} runtime method.',
|
|
40
|
+
data: { name: node.expression.callee.property.name }
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* @author pagnkelly
|
|
4
|
+
* See LICENSE file in root directory for full license.
|
|
5
|
+
*/
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
meta: {
|
|
10
|
+
type: 'problem',
|
|
11
|
+
docs: {
|
|
12
|
+
description: 'watch第二个参数统一为函数,不再提供对象',
|
|
13
|
+
categories: ['composition-api-essential'],
|
|
14
|
+
url: 'https://mpx-ecology.github.io/eslint-plugin-mpx/rules/no-deprecated-watch-second-param'
|
|
15
|
+
},
|
|
16
|
+
fixable: 'code',
|
|
17
|
+
schema: []
|
|
18
|
+
},
|
|
19
|
+
/** @param {RuleContext} context */
|
|
20
|
+
create(context) {
|
|
21
|
+
return {
|
|
22
|
+
/** @param {import("mpx-eslint-parser/ast").ESLintStatement} node */
|
|
23
|
+
ExpressionStatement(node) {
|
|
24
|
+
if (
|
|
25
|
+
node.expression &&
|
|
26
|
+
node.expression.type === 'CallExpression' &&
|
|
27
|
+
node.expression.callee.name === 'watch' &&
|
|
28
|
+
node.expression.arguments[1] &&
|
|
29
|
+
node.expression.arguments[1].type === 'ObjectExpression'
|
|
30
|
+
) {
|
|
31
|
+
context.report({
|
|
32
|
+
node,
|
|
33
|
+
message:
|
|
34
|
+
'The watch API no longer accepts the second parameter as an object with the handler attribute.'
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview initData检测
|
|
4
|
+
* @author jvzuojing
|
|
5
|
+
*/
|
|
6
|
+
'use strict'
|
|
7
|
+
|
|
8
|
+
//------------------------------------------------------------------------------
|
|
9
|
+
// Rule Definition
|
|
10
|
+
//------------------------------------------------------------------------------
|
|
11
|
+
const utils = require('../utils')
|
|
12
|
+
function commonFunction(value, list) {
|
|
13
|
+
const props = value.properties
|
|
14
|
+
props.forEach((item) => {
|
|
15
|
+
if (
|
|
16
|
+
item.type === 'SpreadElement' &&
|
|
17
|
+
item.argument &&
|
|
18
|
+
item.argument.arguments &&
|
|
19
|
+
item.argument.arguments[0] &&
|
|
20
|
+
item.argument.arguments[0].elements
|
|
21
|
+
) {
|
|
22
|
+
const args = item.argument.arguments[0].elements
|
|
23
|
+
args.forEach((item) => {
|
|
24
|
+
list.add(item.value)
|
|
25
|
+
})
|
|
26
|
+
} else {
|
|
27
|
+
list.add(item.key.name)
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
function handleInitData(current, propsSet, parentName) {
|
|
32
|
+
current.forEach((item) => {
|
|
33
|
+
const currentName = item.key.name
|
|
34
|
+
if (currentName) {
|
|
35
|
+
let name = currentName
|
|
36
|
+
if (parentName) {
|
|
37
|
+
name = `${parentName}.${currentName}`
|
|
38
|
+
}
|
|
39
|
+
propsSet.add(name)
|
|
40
|
+
}
|
|
41
|
+
if (item.value.type === 'ObjectExpression' && item.value.properties) {
|
|
42
|
+
let pre = currentName
|
|
43
|
+
if (parentName) {
|
|
44
|
+
pre = `${parentName}.${currentName}`
|
|
45
|
+
}
|
|
46
|
+
handleInitData(item.value.properties, propsSet, pre)
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function checkInInitData(
|
|
52
|
+
computedSet,
|
|
53
|
+
propsSet,
|
|
54
|
+
context,
|
|
55
|
+
node,
|
|
56
|
+
nodeName,
|
|
57
|
+
hasInitData
|
|
58
|
+
) {
|
|
59
|
+
if (hasInitData === 1 && nodeName) {
|
|
60
|
+
// 属性值存在 || 说明有兜底,跳过检测
|
|
61
|
+
if (nodeName.includes('||')) return
|
|
62
|
+
// 检测当前属性值在initData 和 computedSet 导出情况,computedSet 返回不会解构出来,做一个兼容
|
|
63
|
+
if (!propsSet.has(nodeName) && computedSet.has(nodeName.split('.')[0])) {
|
|
64
|
+
context.report({
|
|
65
|
+
node,
|
|
66
|
+
messageId: 'unexpected',
|
|
67
|
+
data: {
|
|
68
|
+
name: nodeName
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function checkInitData(
|
|
75
|
+
computedSet,
|
|
76
|
+
propsSet,
|
|
77
|
+
hasInitData,
|
|
78
|
+
context,
|
|
79
|
+
node,
|
|
80
|
+
nodeName
|
|
81
|
+
) {
|
|
82
|
+
if (
|
|
83
|
+
computedSet.has(nodeName) &&
|
|
84
|
+
!propsSet.has(nodeName) &&
|
|
85
|
+
hasInitData === 2
|
|
86
|
+
) {
|
|
87
|
+
context.report({
|
|
88
|
+
node,
|
|
89
|
+
messageId: 'missingValue',
|
|
90
|
+
data: {
|
|
91
|
+
name: nodeName
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
97
|
+
module.exports = {
|
|
98
|
+
meta: {
|
|
99
|
+
type: 'suggestion', // `problem`, `suggestion`, or `layout`
|
|
100
|
+
docs: {
|
|
101
|
+
description: 'initData检测',
|
|
102
|
+
recommended: false,
|
|
103
|
+
url: null // URL to the documentation page for this rule
|
|
104
|
+
},
|
|
105
|
+
fixable: null, // Or `code` or `whitespace`
|
|
106
|
+
schema: [], // Add a schema if the rule has options
|
|
107
|
+
messages: {
|
|
108
|
+
missingValue:
|
|
109
|
+
"Missing 'initData' but the data {{name}} used for property.",
|
|
110
|
+
unexpected:
|
|
111
|
+
"The data '{{name}}' isn't expose in initData but used for property."
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
create(context) {
|
|
115
|
+
// 收集一下initData的属性
|
|
116
|
+
const propsSet = new Set([])
|
|
117
|
+
// 收集computed属性
|
|
118
|
+
const computedSet = new Set([])
|
|
119
|
+
// 忽略的标签类型
|
|
120
|
+
const ignoreElementTypes = [
|
|
121
|
+
'view',
|
|
122
|
+
'text',
|
|
123
|
+
'image',
|
|
124
|
+
'audio',
|
|
125
|
+
'video',
|
|
126
|
+
'button',
|
|
127
|
+
'input',
|
|
128
|
+
'navigator',
|
|
129
|
+
'icon',
|
|
130
|
+
'picker',
|
|
131
|
+
'picker-item',
|
|
132
|
+
'block',
|
|
133
|
+
'scroll-view',
|
|
134
|
+
'swiper',
|
|
135
|
+
'swiper-item',
|
|
136
|
+
'label',
|
|
137
|
+
'form',
|
|
138
|
+
'checkbox',
|
|
139
|
+
'checkbox-group',
|
|
140
|
+
'radio',
|
|
141
|
+
'radio-group',
|
|
142
|
+
'switch'
|
|
143
|
+
]
|
|
144
|
+
const ignoreAttributeTypes = [
|
|
145
|
+
'wx:if',
|
|
146
|
+
'wx:else',
|
|
147
|
+
'bindtap',
|
|
148
|
+
'catchtap',
|
|
149
|
+
'wx:for',
|
|
150
|
+
'wx:ref',
|
|
151
|
+
'wx:key',
|
|
152
|
+
'wx:for-item',
|
|
153
|
+
'class',
|
|
154
|
+
'style',
|
|
155
|
+
'hover-class',
|
|
156
|
+
'src'
|
|
157
|
+
]
|
|
158
|
+
// 全局是否有initData 初始值:0 ,存在: 1,不存在: 2
|
|
159
|
+
let hasInitData = 0
|
|
160
|
+
return utils.defineTemplateBodyVisitor(
|
|
161
|
+
context,
|
|
162
|
+
{
|
|
163
|
+
// VExpressionContainer(node) {},
|
|
164
|
+
VAttribute(node) {
|
|
165
|
+
const parent = node.parent
|
|
166
|
+
if (!ignoreElementTypes.includes(parent.name)) {
|
|
167
|
+
if (
|
|
168
|
+
!ignoreAttributeTypes.includes(node.key.name) &&
|
|
169
|
+
node.value &&
|
|
170
|
+
node.value.value &&
|
|
171
|
+
node.value.value.startsWith('{{')
|
|
172
|
+
) {
|
|
173
|
+
const reg = /(?<={{).*?(?=}})/
|
|
174
|
+
checkInInitData(
|
|
175
|
+
computedSet,
|
|
176
|
+
propsSet,
|
|
177
|
+
context,
|
|
178
|
+
node,
|
|
179
|
+
node.value.value.match(reg)[0],
|
|
180
|
+
hasInitData
|
|
181
|
+
)
|
|
182
|
+
checkInitData(
|
|
183
|
+
computedSet,
|
|
184
|
+
propsSet,
|
|
185
|
+
hasInitData,
|
|
186
|
+
context,
|
|
187
|
+
node,
|
|
188
|
+
node.value.value.match(reg)[0]
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
Program(node) {
|
|
196
|
+
if (!utils.isScriptSetup(context)) return
|
|
197
|
+
const body = node.body
|
|
198
|
+
if (body) {
|
|
199
|
+
body.forEach((item) => {
|
|
200
|
+
if (
|
|
201
|
+
item.type === 'ExpressionStatement' &&
|
|
202
|
+
item.expression &&
|
|
203
|
+
item.expression.type === 'CallExpression' &&
|
|
204
|
+
item.expression.callee.name === 'defineOptions' &&
|
|
205
|
+
item.expression.arguments
|
|
206
|
+
) {
|
|
207
|
+
item.expression.arguments.forEach((sub) => {
|
|
208
|
+
if (sub.type === 'ObjectExpression' && sub.properties) {
|
|
209
|
+
sub.properties.forEach((val) => {
|
|
210
|
+
if (val.key.name === 'initData') {
|
|
211
|
+
hasInitData = 1
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
if (hasInitData === 0) {
|
|
217
|
+
hasInitData = 2
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
hasInitData = 2
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
CallExpression(node) {
|
|
226
|
+
if (utils.isScriptSetup(context)) {
|
|
227
|
+
// setup 直接收集 defineExpose 的值
|
|
228
|
+
if (node.callee.name === 'defineExpose') {
|
|
229
|
+
commonFunction(node.arguments[0], computedSet)
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
if (node.callee.name !== 'createComponent') return
|
|
233
|
+
const properties = node.arguments[0].properties
|
|
234
|
+
if (!properties) return
|
|
235
|
+
properties.forEach((element) => {
|
|
236
|
+
if (element.key.name === 'initData') {
|
|
237
|
+
hasInitData = 1
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
if (hasInitData === 0) {
|
|
241
|
+
hasInitData = 2
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
ObjectExpression(node) {
|
|
246
|
+
const parent = node.parent
|
|
247
|
+
if (parent && parent.type === 'CallExpression') {
|
|
248
|
+
if (parent.callee.name === 'defineOptions') {
|
|
249
|
+
if (node.properties && Array.isArray(node.properties)) {
|
|
250
|
+
node.properties.forEach((item) => {
|
|
251
|
+
if (item.key.name === 'initData') {
|
|
252
|
+
handleInitData(item.value.properties, propsSet)
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
Property(node) {
|
|
260
|
+
if (node.key.name === 'computed') {
|
|
261
|
+
commonFunction(node.value, computedSet)
|
|
262
|
+
}
|
|
263
|
+
if (node.key.name === 'initData') {
|
|
264
|
+
handleInitData(node.value.properties, propsSet)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
@@ -61,7 +61,7 @@ function handleProperties(properties, exposeSet, scriptVariableNames) {
|
|
|
61
61
|
/**
|
|
62
62
|
* @param {String} name - 展开节点
|
|
63
63
|
* @param {Set<String>} exposeSet 存储定义的expose变量
|
|
64
|
-
* @param {
|
|
64
|
+
* @param {any} scriptVariableNames 用于解析定义的展开的expose变量
|
|
65
65
|
*/
|
|
66
66
|
function handleIdentifier(name, exposeSet, scriptVariableNames) {
|
|
67
67
|
const props = scriptVariableNames[name]
|
|
@@ -77,10 +77,9 @@ module.exports = {
|
|
|
77
77
|
docs: {
|
|
78
78
|
description:
|
|
79
79
|
'prevent `<script setup>` variables used in `<template>` to be marked as unused', // eslint-disable-line eslint-plugin/require-meta-docs-description
|
|
80
|
-
categories:
|
|
80
|
+
categories: ['composition-api-essential'],
|
|
81
81
|
url: 'https://eslint.vuejs.org/rules/script-setup-uses-vars.html'
|
|
82
82
|
},
|
|
83
|
-
categories: ['composition-api-essential'],
|
|
84
83
|
schema: [],
|
|
85
84
|
messages: {
|
|
86
85
|
unexpected: "The variable '{{name}}' isn't expose in setup scripts."
|
|
@@ -33,7 +33,7 @@ module.exports = {
|
|
|
33
33
|
type: 'problem',
|
|
34
34
|
docs: {
|
|
35
35
|
description: 'enforce valid `wx:model` directives',
|
|
36
|
-
categories: [],
|
|
36
|
+
categories: ['mpx-essential'],
|
|
37
37
|
url: 'https://mpx-ecology.github.io/eslint-plugin-mpx/rules/valid-wx-model.html'
|
|
38
38
|
},
|
|
39
39
|
fixable: null,
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-mpx",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10-beta",
|
|
4
4
|
"description": "Official ESLint plugin for Mpx.js",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"start": "npm run test:base -- --watch --growl",
|
|
8
8
|
"test": "mocha \"tests/lib/**/*.js\" --reporter dot",
|
|
9
|
-
"test:only": "mocha \"tests/lib/rules/valid-
|
|
9
|
+
"test:only": "mocha \"tests/lib/rules/valid-initdata.js\" --reporter dot",
|
|
10
10
|
"debug": "mocha --inspect \"tests/lib/**/*.js\" --reporter dot --timeout 60000",
|
|
11
11
|
"cover": "npm run cover:test && npm run cover:report",
|
|
12
12
|
"cover:test": "nyc npm run test -- --timeout 60000",
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @author pagnkelly
|
|
3
|
-
* @copyright 2020 pagnkelly. All rights reserved.
|
|
4
|
-
* See LICENSE file in root directory for full license.
|
|
5
|
-
*/
|
|
6
|
-
'use strict'
|
|
7
|
-
|
|
8
|
-
// ------------------------------------------------------------------------------
|
|
9
|
-
// Requirements
|
|
10
|
-
// ------------------------------------------------------------------------------
|
|
11
|
-
|
|
12
|
-
const utils = require('../utils')
|
|
13
|
-
|
|
14
|
-
// ------------------------------------------------------------------------------
|
|
15
|
-
// Helpers
|
|
16
|
-
// ------------------------------------------------------------------------------
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Check whether the given attribute is using the variables which are defined by `wx:for` directives.
|
|
20
|
-
* @param {VDirective} vFor The attribute node of `wx:for` to check.
|
|
21
|
-
* @param {VDirective} vBindKey The attribute node of `wx:bind:key` to check.
|
|
22
|
-
* @returns {boolean} `true` if the node is using the variables which are defined by `wx:for` directives.
|
|
23
|
-
*/
|
|
24
|
-
function isUsingIterationVar(vFor, vBindKey) {
|
|
25
|
-
if (vBindKey.value == null) {
|
|
26
|
-
return false
|
|
27
|
-
}
|
|
28
|
-
const references = vBindKey.value.references
|
|
29
|
-
return references.some((reference) =>
|
|
30
|
-
['item', '*this'].includes(reference.id.name)
|
|
31
|
-
)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Check the child element in tempalte wx:for about `wx:bind:key` attributes.
|
|
36
|
-
* @param {RuleContext} context The rule context to report.
|
|
37
|
-
* @param {VDirective} vFor The attribute node of `wx:for` to check.
|
|
38
|
-
* @param {VElement} child The child node to check.
|
|
39
|
-
*/
|
|
40
|
-
function checkChildKey(context, vFor, child) {
|
|
41
|
-
const childFor = utils.getDirective(child, 'for')
|
|
42
|
-
// if child has wx:for, check if parent iterator is used in wx:for
|
|
43
|
-
if (childFor != null) {
|
|
44
|
-
const childForRefs = (childFor.value && childFor.value.references) || []
|
|
45
|
-
const variables = vFor.parent.parent.variables
|
|
46
|
-
const usedInFor = childForRefs.some((cref) =>
|
|
47
|
-
variables.some(
|
|
48
|
-
(variable) =>
|
|
49
|
-
cref.id.name === variable.id.name && variable.kind === 'wx:for'
|
|
50
|
-
)
|
|
51
|
-
)
|
|
52
|
-
// if parent iterator is used, skip other checks
|
|
53
|
-
// iterator usage will be checked later by child wx:for
|
|
54
|
-
if (usedInFor) {
|
|
55
|
-
return
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
// otherwise, check if parent iterator is directly used in child's key
|
|
59
|
-
checkKey(context, vFor, child)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Check the given element about `wx:bind:key` attributes.
|
|
64
|
-
* @param {RuleContext} context The rule context to report.
|
|
65
|
-
* @param {VDirective} vFor The attribute node of `wx:for` to check.
|
|
66
|
-
* @param {VElement} element The element node to check.
|
|
67
|
-
*/
|
|
68
|
-
function checkKey(context, vFor, element) {
|
|
69
|
-
if (element.name === 'template') {
|
|
70
|
-
for (const child of element.children) {
|
|
71
|
-
if (child.type === 'VElement') {
|
|
72
|
-
checkChildKey(context, vFor, child)
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const vBindKey = utils.getDirective(element, 'key')
|
|
79
|
-
if (utils.isCustomComponent(element) && vBindKey == null) {
|
|
80
|
-
context.report({
|
|
81
|
-
node: element.startTag,
|
|
82
|
-
loc: element.startTag.loc,
|
|
83
|
-
message: "Custom elements in iteration require 'wx:key' directives."
|
|
84
|
-
})
|
|
85
|
-
}
|
|
86
|
-
if (vBindKey != null && !isUsingIterationVar(vFor, vBindKey)) {
|
|
87
|
-
context.report({
|
|
88
|
-
node: vBindKey,
|
|
89
|
-
loc: vBindKey.loc,
|
|
90
|
-
message:
|
|
91
|
-
"Expected 'wx:key' directive to use the variables which are defined by the 'wx:for' directive."
|
|
92
|
-
})
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// ------------------------------------------------------------------------------
|
|
97
|
-
// Rule Definition
|
|
98
|
-
// ------------------------------------------------------------------------------
|
|
99
|
-
|
|
100
|
-
module.exports = {
|
|
101
|
-
meta: {
|
|
102
|
-
type: 'problem',
|
|
103
|
-
docs: {
|
|
104
|
-
description: 'enforce valid `wx:for` directives',
|
|
105
|
-
categories: [],
|
|
106
|
-
url: 'https://mpx-ecology.github.io/eslint-plugin-mpx/rules/valid-wx-for.html'
|
|
107
|
-
},
|
|
108
|
-
fixable: null,
|
|
109
|
-
schema: []
|
|
110
|
-
},
|
|
111
|
-
/** @param {RuleContext} context */
|
|
112
|
-
create(context) {
|
|
113
|
-
const sourceCode = context.getSourceCode()
|
|
114
|
-
|
|
115
|
-
return utils.defineTemplateBodyVisitor(context, {
|
|
116
|
-
/** @param {VDirective} node */
|
|
117
|
-
"VAttribute[directive=true][key.name.name='for']"(node) {
|
|
118
|
-
const element = node.parent.parent
|
|
119
|
-
|
|
120
|
-
checkKey(context, node, element)
|
|
121
|
-
|
|
122
|
-
if (node.key.argument) {
|
|
123
|
-
context.report({
|
|
124
|
-
node,
|
|
125
|
-
loc: node.loc,
|
|
126
|
-
message: "'wx:for' directives require no argument."
|
|
127
|
-
})
|
|
128
|
-
}
|
|
129
|
-
if (node.key.modifiers.length > 0) {
|
|
130
|
-
context.report({
|
|
131
|
-
node,
|
|
132
|
-
loc: node.loc,
|
|
133
|
-
message: "'wx:for' directives require no modifier."
|
|
134
|
-
})
|
|
135
|
-
}
|
|
136
|
-
if (!node.value || utils.isEmptyValueDirective(node, context)) {
|
|
137
|
-
context.report({
|
|
138
|
-
node,
|
|
139
|
-
loc: node.loc,
|
|
140
|
-
message: "'wx:for' directives require that attribute value."
|
|
141
|
-
})
|
|
142
|
-
return
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const expr = node.value.expression
|
|
146
|
-
if (expr == null) {
|
|
147
|
-
return
|
|
148
|
-
}
|
|
149
|
-
if (expr.type !== 'VForExpression') {
|
|
150
|
-
context.report({
|
|
151
|
-
node: node.value,
|
|
152
|
-
loc: node.value.loc,
|
|
153
|
-
message:
|
|
154
|
-
"'wx:for' directives require the special syntax '<alias> in <expression>'."
|
|
155
|
-
})
|
|
156
|
-
return
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const lhs = expr.left
|
|
160
|
-
const value = lhs[0]
|
|
161
|
-
const key = lhs[1]
|
|
162
|
-
const index = lhs[2]
|
|
163
|
-
|
|
164
|
-
if (value === null) {
|
|
165
|
-
context.report({
|
|
166
|
-
node: expr,
|
|
167
|
-
message: "Invalid alias ''."
|
|
168
|
-
})
|
|
169
|
-
}
|
|
170
|
-
if (key !== undefined && (!key || key.type !== 'Identifier')) {
|
|
171
|
-
context.report({
|
|
172
|
-
node: key || expr,
|
|
173
|
-
loc: key && key.loc,
|
|
174
|
-
message: "Invalid alias '{{text}}'.",
|
|
175
|
-
data: { text: key ? sourceCode.getText(key) : '' }
|
|
176
|
-
})
|
|
177
|
-
}
|
|
178
|
-
if (index !== undefined && (!index || index.type !== 'Identifier')) {
|
|
179
|
-
context.report({
|
|
180
|
-
node: index || expr,
|
|
181
|
-
loc: index && index.loc,
|
|
182
|
-
message: "Invalid alias '{{text}}'.",
|
|
183
|
-
data: { text: index ? sourceCode.getText(index) : '' }
|
|
184
|
-
})
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
})
|
|
188
|
-
}
|
|
189
|
-
}
|