Skip to content

Commit d1fe2a1

Browse files
committed
feat: getable methodProperties
1 parent 5d98e10 commit d1fe2a1

File tree

3 files changed

+318
-0
lines changed

3 files changed

+318
-0
lines changed

.tool-versions

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nodejs 12.18.0

lib/rules/no-use-computed-property-like-method.js

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,189 @@
22
* @author tyankatsu <https://github.com/tyankatsu0105>
33
* See LICENSE file in root directory for full license.
44
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const {
12+
defineVueVisitor,
13+
getComputedProperties,
14+
getComponentProps,
15+
16+
isProperty,
17+
getStaticPropertyName,
18+
unwrapTypes
19+
} = require('../utils')
20+
21+
/**
22+
* @typedef {import('../utils').ComponentComputedProperty} ComponentComputedProperty
23+
* @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp
24+
*/
25+
26+
// ------------------------------------------------------------------------------
27+
// Rule Definition
28+
// ------------------------------------------------------------------------------
29+
30+
/**
31+
* @typedef { {key: string | null, value: BlockStatement | null} } ComponentMethodProperty
32+
*/
33+
34+
/**
35+
* Get all method by looking at all component's properties
36+
* @param {ObjectExpression} componentObject Object with component definition
37+
* @return {ComponentMethodProperty[]} Array of methods in format: [{key: String, value: ASTNode}]
38+
*/
39+
const getMethodProperties = (componentObject) => {
40+
const methodsNode = componentObject.properties.find(
41+
/**
42+
* @param {ESNode} property
43+
* @returns {property is (Property & { key: Identifier & {name: 'method'}, value: ObjectExpression })}
44+
*/
45+
(property) => {
46+
return (
47+
property.type === 'Property' &&
48+
property.key.type === 'Identifier' &&
49+
property.key.name === 'methods' &&
50+
property.value.type === 'ObjectExpression'
51+
)
52+
}
53+
)
54+
55+
if (!methodsNode) {
56+
return []
57+
}
58+
59+
return methodsNode.value.properties.filter(isProperty).map((method) => {
60+
const key = getStaticPropertyName(method)
61+
/** @type {Expression} */
62+
const propValue = unwrapTypes(method.value)
63+
/** @type {BlockStatement | null} */
64+
let value = null
65+
66+
if (propValue.type === 'FunctionExpression') {
67+
value = propValue.body
68+
} else if (propValue.type === 'ObjectExpression') {
69+
const get = propValue.properties.find(
70+
/**
71+
* @param {ESNode} p
72+
* @returns { p is (Property & { value: FunctionExpression }) }
73+
*/
74+
(p) =>
75+
p.type === 'Property' &&
76+
p.key.type === 'Identifier' &&
77+
p.key.name === 'get' &&
78+
p.value.type === 'FunctionExpression'
79+
)
80+
value = get ? get.value.body : null
81+
}
82+
83+
return { key, value }
84+
})
85+
}
86+
87+
module.exports = {
88+
meta: {
89+
type: 'problem',
90+
docs: {
91+
description: 'enforce',
92+
categories: undefined,
93+
url:
94+
'https://eslint.vuejs.org/rules/no-use-computed-property-like-method.html'
95+
},
96+
fixable: null,
97+
schema: [],
98+
messages: {
99+
unexpected: 'Unexpected multiple objects. Merge objects.'
100+
}
101+
},
102+
/** @param {RuleContext} context */
103+
create(context) {
104+
/**
105+
* @typedef {object} ScopeStack
106+
* @property {ScopeStack | null} upper
107+
* @property {BlockStatement | Expression} body
108+
*/
109+
/** @type {Map<ObjectExpression, ComponentComputedProperty[]>} */
110+
const computedPropertiesMap = new Map()
111+
112+
/**
113+
* @typedef {object} ScopeStack
114+
* @property {ScopeStack | null} upper
115+
* @property {BlockStatement | Expression} body
116+
*/
117+
/** @type {Map<ObjectExpression, ComponentObjectProp[]>} */
118+
const propsMap = new Map()
119+
120+
/**
121+
* @typedef {object} ScopeStack
122+
* @property {ScopeStack | null} upper
123+
* @property {BlockStatement | Expression} body
124+
*/
125+
/** @type {Map<ObjectExpression, ComponentMthodProperty[]>} */
126+
const methodPropertiesMap = new Map()
127+
return defineVueVisitor(context, {
128+
onVueObjectEnter(node) {
129+
computedPropertiesMap.set(node, getComputedProperties(node))
130+
propsMap.set(node, getComponentProps(node))
131+
methodPropertiesMap.set(node, getMethodProperties(node))
132+
},
133+
134+
/** @param {MemberExpression} node */
135+
'MemberExpression[object.type="ThisExpression"]'(
136+
node,
137+
{ node: vueNode }
138+
) {
139+
if (node.property.type !== 'Identifier') return
140+
141+
const computedProperties = computedPropertiesMap
142+
.get(vueNode)
143+
.map((item) => item.key)
144+
145+
const methodProperties = methodPropertiesMap
146+
.get(vueNode)
147+
.map((item) => item.key)
148+
149+
/**
150+
* propsProperties that excluded when type is array, and props property type is `Function`
151+
*/
152+
const propsProperties = propsMap.get(vueNode).reduce((acc, current) => {
153+
// ignore `props: ['props1', 'props2']`
154+
if (current.type === 'array') return acc
155+
156+
current.value.properties.reduce((accProperties, property) => {
157+
// ignore `type: Function`
158+
if (
159+
property.key.name === 'type' &&
160+
property.value.name === 'Function'
161+
)
162+
return accProperties
163+
164+
accProperties.push(property)
165+
return accProperties
166+
}, [])
167+
168+
acc.push(current.propName)
169+
return acc
170+
}, [])
171+
172+
const properties = [
173+
...computedProperties,
174+
...methodProperties,
175+
...propsProperties
176+
]
177+
178+
console.log(properties)
179+
180+
// if (!computedProperties.includes(node.property.name)) return
181+
182+
// context.report({
183+
// node: node.property,
184+
// loc: node.property.loc,
185+
// messageId:
186+
// })
187+
}
188+
})
189+
}
190+
}

tests/lib/rules/no-use-computed-property-like-method.js

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,134 @@
22
* @author tyankatsu <https://github.com/tyankatsu0105>
33
* See LICENSE file in root directory for full license.
44
*/
5+
6+
// ------------------------------------------------------------------------------
7+
// Requirements
8+
// ------------------------------------------------------------------------------
9+
10+
const RuleTester = require('eslint').RuleTester
11+
const rule = require('../../../lib/rules/no-use-computed-property-like-method')
12+
13+
// ------------------------------------------------------------------------------
14+
// Tests
15+
// ------------------------------------------------------------------------------
16+
17+
const tester = new RuleTester({
18+
parser: require.resolve('vue-eslint-parser'),
19+
parserOptions: { ecmaVersion: 2015, sourceType: 'module' }
20+
})
21+
22+
tester.run('no-use-computed-property-like-method', rule, {
23+
valid: [
24+
{
25+
filename: 'test.vue',
26+
code: `
27+
<script>
28+
export default {
29+
computed: {
30+
name() {
31+
return 'name';
32+
}
33+
},
34+
methods: {
35+
getName() {
36+
return this.name
37+
}
38+
},
39+
}
40+
</script>
41+
`
42+
},
43+
{
44+
filename: 'test.vue',
45+
code: `
46+
<script>
47+
export default {
48+
props: {
49+
name: {
50+
type: String
51+
},
52+
},
53+
computed: {
54+
isExpectedName() {
55+
return this.name === 'name';
56+
}
57+
},
58+
methods: {
59+
getName() {
60+
return this.isExpectedName
61+
}
62+
},
63+
}
64+
</script>
65+
`
66+
},
67+
{
68+
filename: 'test.vue',
69+
code: `
70+
<script>
71+
export default {
72+
computed: {
73+
name() {
74+
return 'name';
75+
},
76+
isExpectedName() {
77+
return this.name === 'name';
78+
}
79+
},
80+
methods: {
81+
getName() {
82+
return this.isExpectedName
83+
}
84+
},
85+
}
86+
</script>
87+
`
88+
}
89+
],
90+
invalid: [
91+
{
92+
filename: 'test.vue',
93+
code: `
94+
<script>
95+
export default {
96+
computed: {
97+
name() {
98+
return 'name';
99+
}
100+
},
101+
methods: {
102+
getName() {
103+
return this.name()
104+
}
105+
}
106+
}
107+
</script>
108+
`
109+
},
110+
{
111+
filename: 'test.vue',
112+
code: `
113+
<script>
114+
export default {
115+
props: {
116+
name: {
117+
type: String
118+
},
119+
},
120+
computed: {
121+
isExpectedName() {
122+
return this.name === 'name';
123+
}
124+
},
125+
methods: {
126+
getName() {
127+
return this.isExpectedName()
128+
}
129+
}
130+
}
131+
</script>
132+
`
133+
}
134+
]
135+
})

0 commit comments

Comments
 (0)