Skip to content

Commit

Permalink
feat: getable methodProperties
Browse files Browse the repository at this point in the history
  • Loading branch information
tyankatsu0105 committed Oct 10, 2020
1 parent 5d98e10 commit d1fe2a1
Show file tree
Hide file tree
Showing 3 changed files with 318 additions and 0 deletions.
1 change: 1 addition & 0 deletions .tool-versions
@@ -0,0 +1 @@
nodejs 12.18.0
186 changes: 186 additions & 0 deletions lib/rules/no-use-computed-property-like-method.js
Expand Up @@ -2,3 +2,189 @@
* @author tyankatsu <https://github.com/tyankatsu0105>
* See LICENSE file in root directory for full license.
*/
'use strict'

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

const {
defineVueVisitor,
getComputedProperties,
getComponentProps,

isProperty,
getStaticPropertyName,
unwrapTypes
} = require('../utils')

/**
* @typedef {import('../utils').ComponentComputedProperty} ComponentComputedProperty
* @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp
*/

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

/**
* @typedef { {key: string | null, value: BlockStatement | null} } ComponentMethodProperty
*/

/**
* Get all method by looking at all component's properties
* @param {ObjectExpression} componentObject Object with component definition
* @return {ComponentMethodProperty[]} Array of methods in format: [{key: String, value: ASTNode}]
*/
const getMethodProperties = (componentObject) => {
const methodsNode = componentObject.properties.find(
/**
* @param {ESNode} property
* @returns {property is (Property & { key: Identifier & {name: 'method'}, value: ObjectExpression })}
*/
(property) => {
return (
property.type === 'Property' &&
property.key.type === 'Identifier' &&
property.key.name === 'methods' &&
property.value.type === 'ObjectExpression'
)
}
)

if (!methodsNode) {
return []
}

return methodsNode.value.properties.filter(isProperty).map((method) => {
const key = getStaticPropertyName(method)
/** @type {Expression} */
const propValue = unwrapTypes(method.value)
/** @type {BlockStatement | null} */
let value = null

if (propValue.type === 'FunctionExpression') {
value = propValue.body
} else if (propValue.type === 'ObjectExpression') {
const get = propValue.properties.find(
/**
* @param {ESNode} p
* @returns { p is (Property & { value: FunctionExpression }) }
*/
(p) =>
p.type === 'Property' &&
p.key.type === 'Identifier' &&
p.key.name === 'get' &&
p.value.type === 'FunctionExpression'
)
value = get ? get.value.body : null
}

return { key, value }
})
}

module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce',
categories: undefined,
url:
'https://eslint.vuejs.org/rules/no-use-computed-property-like-method.html'
},
fixable: null,
schema: [],
messages: {
unexpected: 'Unexpected multiple objects. Merge objects.'
}
},
/** @param {RuleContext} context */
create(context) {
/**
* @typedef {object} ScopeStack
* @property {ScopeStack | null} upper
* @property {BlockStatement | Expression} body
*/
/** @type {Map<ObjectExpression, ComponentComputedProperty[]>} */
const computedPropertiesMap = new Map()

/**
* @typedef {object} ScopeStack
* @property {ScopeStack | null} upper
* @property {BlockStatement | Expression} body
*/
/** @type {Map<ObjectExpression, ComponentObjectProp[]>} */
const propsMap = new Map()

/**
* @typedef {object} ScopeStack
* @property {ScopeStack | null} upper
* @property {BlockStatement | Expression} body
*/
/** @type {Map<ObjectExpression, ComponentMthodProperty[]>} */
const methodPropertiesMap = new Map()
return defineVueVisitor(context, {
onVueObjectEnter(node) {
computedPropertiesMap.set(node, getComputedProperties(node))
propsMap.set(node, getComponentProps(node))
methodPropertiesMap.set(node, getMethodProperties(node))
},

/** @param {MemberExpression} node */
'MemberExpression[object.type="ThisExpression"]'(
node,
{ node: vueNode }
) {
if (node.property.type !== 'Identifier') return

const computedProperties = computedPropertiesMap
.get(vueNode)
.map((item) => item.key)

const methodProperties = methodPropertiesMap
.get(vueNode)
.map((item) => item.key)

/**
* propsProperties that excluded when type is array, and props property type is `Function`
*/
const propsProperties = propsMap.get(vueNode).reduce((acc, current) => {
// ignore `props: ['props1', 'props2']`
if (current.type === 'array') return acc

current.value.properties.reduce((accProperties, property) => {
// ignore `type: Function`
if (
property.key.name === 'type' &&
property.value.name === 'Function'
)
return accProperties

accProperties.push(property)
return accProperties
}, [])

acc.push(current.propName)
return acc
}, [])

const properties = [
...computedProperties,
...methodProperties,
...propsProperties
]

console.log(properties)

// if (!computedProperties.includes(node.property.name)) return

// context.report({
// node: node.property,
// loc: node.property.loc,
// messageId:
// })
}
})
}
}
131 changes: 131 additions & 0 deletions tests/lib/rules/no-use-computed-property-like-method.js
Expand Up @@ -2,3 +2,134 @@
* @author tyankatsu <https://github.com/tyankatsu0105>
* See LICENSE file in root directory for full license.
*/

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

const RuleTester = require('eslint').RuleTester
const rule = require('../../../lib/rules/no-use-computed-property-like-method')

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------

const tester = new RuleTester({
parser: require.resolve('vue-eslint-parser'),
parserOptions: { ecmaVersion: 2015, sourceType: 'module' }
})

tester.run('no-use-computed-property-like-method', rule, {
valid: [
{
filename: 'test.vue',
code: `
<script>
export default {
computed: {
name() {
return 'name';
}
},
methods: {
getName() {
return this.name
}
},
}
</script>
`
},
{
filename: 'test.vue',
code: `
<script>
export default {
props: {
name: {
type: String
},
},
computed: {
isExpectedName() {
return this.name === 'name';
}
},
methods: {
getName() {
return this.isExpectedName
}
},
}
</script>
`
},
{
filename: 'test.vue',
code: `
<script>
export default {
computed: {
name() {
return 'name';
},
isExpectedName() {
return this.name === 'name';
}
},
methods: {
getName() {
return this.isExpectedName
}
},
}
</script>
`
}
],
invalid: [
{
filename: 'test.vue',
code: `
<script>
export default {
computed: {
name() {
return 'name';
}
},
methods: {
getName() {
return this.name()
}
}
}
</script>
`
},
{
filename: 'test.vue',
code: `
<script>
export default {
props: {
name: {
type: String
},
},
computed: {
isExpectedName() {
return this.name === 'name';
}
},
methods: {
getName() {
return this.isExpectedName()
}
}
}
</script>
`
}
]
})

0 comments on commit d1fe2a1

Please sign in to comment.