7
7
// ------------------------------------------------------------------------------
8
8
// Requirements
9
9
// ------------------------------------------------------------------------------
10
-
11
- const { defineVueVisitor , getComputedProperties } = require ( '../utils' )
10
+ const eslitUtils = require ( 'eslint-utils' )
11
+ const utils = require ( '../utils' )
12
12
13
13
/**
14
14
* @typedef {import('../utils').ComponentComputedProperty } ComponentComputedProperty
15
15
* @typedef {import('../utils').ComponentObjectProp } ComponentObjectProp
16
+ * @typedef {import('../utils').ComponentPropertyData } ComponentPropertyData
17
+ * @typedef {import('../utils').ComponentObjectPropertyData } ComponentObjectPropertyData
18
+ *
19
+ * @typedef {{[key: string]: ComponentPropertyData & { valueType: { type: string } }} } ProprtyMap
16
20
*/
17
-
18
21
// ------------------------------------------------------------------------------
19
22
// Rule Definition
20
23
// ------------------------------------------------------------------------------
21
24
25
+ function replacer ( key , value ) {
26
+ if ( key === 'parent' ) {
27
+ return undefined
28
+ }
29
+ if ( key === 'errors' && Array . isArray ( value ) ) {
30
+ return value . map ( ( e ) => ( {
31
+ message : e . message ,
32
+ index : e . index ,
33
+ lineNumber : e . lineNumber ,
34
+ column : e . column
35
+ } ) )
36
+ }
37
+ return value
38
+ }
39
+
40
+ /**
41
+ *
42
+ * @param {ComponentObjectPropertyData } property
43
+ * @return {string | null }
44
+ */
45
+ const getComponetPropsType = ( property ) => {
46
+ if ( property . property . value . type === 'ObjectExpression' ) {
47
+ const propsTypeProperty = property . property . value . properties . find (
48
+ ( property ) =>
49
+ property . type === 'Property' &&
50
+ property . key . type === 'Identifier' &&
51
+ property . key . name === 'type' &&
52
+ property . value . type === 'Identifier'
53
+ )
54
+
55
+ if ( propsTypeProperty === undefined ) return null
56
+
57
+ if (
58
+ propsTypeProperty . type === 'Property' &&
59
+ propsTypeProperty . value . type === 'Identifier'
60
+ )
61
+ return propsTypeProperty . value . name
62
+ }
63
+ return null
64
+ }
65
+
66
+ /**
67
+ *
68
+ * @param {any } obj
69
+ */
70
+ const getPrototypeType = ( obj ) =>
71
+ Object . prototype . toString . call ( obj ) . slice ( 8 , - 1 )
72
+
73
+ /**
74
+ *
75
+ * @param {Expression | Super } objectOrCallee
76
+ * @returns {string | null }
77
+ */
78
+ // const getThisMember = (objectOrCallee) => {
79
+ // if (objectOrCallee.type === 'MemberExpression') {
80
+ // if (objectOrCallee.object.type === 'Identifier') return null
81
+
82
+ // if (
83
+ // objectOrCallee.object.type === 'ThisExpression' &&
84
+ // objectOrCallee.property.type === 'Identifier'
85
+ // )
86
+ // return objectOrCallee.property.name
87
+
88
+ // if (objectOrCallee.object.type === 'MemberExpression')
89
+ // getThisMember(objectOrCallee.object)
90
+ // }
91
+
92
+ // if (objectOrCallee.type === 'CallExpression') {
93
+ // if (objectOrCallee.callee.type === 'Identifier') return null
94
+
95
+ // getThisMember(objectOrCallee.callee)
96
+ // }
97
+ // }
98
+
99
+ /**
100
+ * Get return type of property.
101
+ * @param {{ property: ComponentPropertyData, propertyMap: ProprtyMap } } args
102
+ */
103
+ const getValueType = ( { property, propertyMap } ) => {
104
+ if ( property . type === 'array' ) {
105
+ return {
106
+ type : null
107
+ }
108
+ }
109
+
110
+ if ( property . type === 'object' ) {
111
+ if ( property . groupName === 'props' ) {
112
+ return {
113
+ type : getComponetPropsType ( property )
114
+ }
115
+ }
116
+
117
+ if ( property . groupName === 'computed' || property . groupName === 'methods' ) {
118
+ if (
119
+ property . property . value . type === 'FunctionExpression' &&
120
+ property . property . value . body . type === 'BlockStatement'
121
+ ) {
122
+ const blockStatement = property . property . value . body
123
+
124
+ /**
125
+ * Only check return statement inside computed and methods
126
+ */
127
+ const returnStatement = blockStatement . body . find (
128
+ ( b ) => b . type === 'ReturnStatement'
129
+ )
130
+ if ( ! returnStatement || returnStatement . type !== 'ReturnStatement' )
131
+ return
132
+
133
+ // if (property.groupName === 'computed') {
134
+ // if (
135
+ // propertyMap &&
136
+ // propertyMap[property.name] &&
137
+ // returnStatement.argument
138
+ // ) {
139
+ // const thisMember = getThisMember(returnStatement.argument)
140
+ // return {
141
+ // type: propertyMap[thisMember].valueType.type
142
+ // }
143
+ // }
144
+ // }
145
+
146
+ /**
147
+ * TODO: consider this.xxx.xxx().xxx.xxx().xxx().....
148
+ */
149
+ if ( property . groupName === 'computed' ) {
150
+ if (
151
+ propertyMap &&
152
+ propertyMap [ property . name ] &&
153
+ returnStatement . argument &&
154
+ returnStatement . argument . type === 'MemberExpression' &&
155
+ returnStatement . argument . property . type === 'Identifier'
156
+ )
157
+ return {
158
+ type :
159
+ propertyMap [ returnStatement . argument . property . name ] . valueType
160
+ . type
161
+ }
162
+
163
+ if (
164
+ propertyMap &&
165
+ propertyMap [ property . name ] &&
166
+ returnStatement . argument &&
167
+ returnStatement . argument . type === 'CallExpression' &&
168
+ returnStatement . argument . callee . type === 'MemberExpression' &&
169
+ returnStatement . argument . callee . property . type === 'Identifier'
170
+ )
171
+ return {
172
+ type :
173
+ propertyMap [ returnStatement . argument . callee . property . name ]
174
+ . valueType . type
175
+ }
176
+ }
177
+
178
+ const evaluated = eslitUtils . getStaticValue ( returnStatement . argument )
179
+
180
+ if ( evaluated ) {
181
+ return {
182
+ type : getPrototypeType ( evaluated . value )
183
+ }
184
+ }
185
+ }
186
+ }
187
+
188
+ const evaluated = eslitUtils . getStaticValue ( property . property . value )
189
+
190
+ if ( evaluated ) {
191
+ return {
192
+ type : getPrototypeType ( evaluated . value )
193
+ }
194
+ }
195
+
196
+ return {
197
+ type : null
198
+ }
199
+ }
200
+ }
201
+
22
202
module . exports = {
23
203
meta : {
24
204
type : 'problem' ,
@@ -31,43 +211,67 @@ module.exports = {
31
211
fixable : null ,
32
212
schema : [ ] ,
33
213
messages : {
34
- unexpected : 'Does not allow to use computed with this expression .'
214
+ unexpected : 'Use {{ likeProperty }} instead of {{ likeMethod }} .'
35
215
}
36
216
} ,
37
217
/** @param {RuleContext } context */
38
218
create ( context ) {
39
- /**
40
- * @typedef {object } ScopeStack
41
- * @property {ScopeStack | null } upper
42
- * @property {BlockStatement | Expression } body
43
- */
44
- /** @type {Map<ObjectExpression, ComponentComputedProperty[]> } */
45
- const computedPropertiesMap = new Map ( )
46
-
47
- return defineVueVisitor ( context , {
219
+ const GROUP_NAMES = [ 'data' , 'props' , 'computed' , 'methods' ]
220
+ const groups = new Set ( GROUP_NAMES )
221
+
222
+ /** @type ProprtyMap */
223
+ const propertyMap = { }
224
+
225
+ /**@type ObjectExpression */
226
+ let nodeMap = { }
227
+
228
+ return utils . defineVueVisitor ( context , {
48
229
onVueObjectEnter ( node ) {
49
- computedPropertiesMap . set ( node , getComputedProperties ( node ) )
230
+ nodeMap = node
231
+ const properties = utils . iterateProperties ( node , groups )
232
+
233
+ for ( const property of properties ) {
234
+ propertyMap [ property . name ] = {
235
+ ...propertyMap [ property . name ] ,
236
+ ...property ,
237
+ valueType : getValueType ( { property } )
238
+ }
239
+ }
50
240
} ,
51
241
52
242
/** @param {MemberExpression } node */
53
- 'MemberExpression[object.type="ThisExpression"]' (
54
- node ,
55
- { node : vueNode }
56
- ) {
57
- if ( node . property . type !== 'Identifier' ) return
243
+ 'MemberExpression[object.type="ThisExpression"]' ( node ) {
58
244
if ( node . parent . type !== 'CallExpression' ) return
245
+ if ( node . property . type !== 'Identifier' ) return
246
+
247
+ const properties = utils . iterateProperties ( nodeMap , groups )
248
+
249
+ for ( const property of properties ) {
250
+ propertyMap [ property . name ] = {
251
+ ...propertyMap [ property . name ] ,
252
+ ...property ,
253
+ valueType : getValueType ( { property, propertyMap } )
254
+ }
255
+ }
59
256
60
- const computedProperties = computedPropertiesMap
61
- . get ( vueNode )
62
- . map ( ( item ) => item . key )
257
+ const thisMember = node . property . name
63
258
64
- if ( ! computedProperties . includes ( node . property . name ) ) return
259
+ if ( ! propertyMap [ thisMember ] . valueType . type ) return
65
260
66
- context . report ( {
67
- node : node . property ,
68
- loc : node . property . loc ,
69
- messageId : 'unexpected'
70
- } )
261
+ if (
262
+ propertyMap [ thisMember ] . groupName === 'computed' &&
263
+ propertyMap [ thisMember ] . valueType . type !== 'Function'
264
+ ) {
265
+ context . report ( {
266
+ node,
267
+ loc : node . loc ,
268
+ messageId : 'unexpected' ,
269
+ data : {
270
+ likeProperty : `this.${ thisMember } ` ,
271
+ likeMethod : `this.${ thisMember } ()`
272
+ }
273
+ } )
274
+ }
71
275
}
72
276
} )
73
277
}
0 commit comments