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.
@@ -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
- // 'mpx/valid-wx-model': 'error',
15
+ 'mpx/valid-wx-model': 'error',
16
16
  // 'mpx/script-setup-uses-vars': 'error',
17
- 'mpx/valid-setup-define-expose': 'error'
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
- // 'mpx/valid-wx-for': 'error',
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 {Object} scriptVariableNames 用于解析定义的展开的expose变量
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: undefined,
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.8",
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-template-quote.js\" --reporter dot",
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
- }