From 6b0b8609d92fd361240d1fb2e76f92b1beb59b17 Mon Sep 17 00:00:00 2001 From: tyankatsu Date: Sat, 10 Oct 2020 10:17:18 +0900 Subject: [PATCH] feat: getable methodProperties --- .tool-versions | 1 + .../no-use-computed-property-like-method.js | 186 ++++++++++++++++++ .../no-use-computed-property-like-method.js | 131 ++++++++++++ 3 files changed, 318 insertions(+) create mode 100644 .tool-versions diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 000000000..dfaa2aed4 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +nodejs 12.18.0 diff --git a/lib/rules/no-use-computed-property-like-method.js b/lib/rules/no-use-computed-property-like-method.js index fdefaf422..82761dd3b 100644 --- a/lib/rules/no-use-computed-property-like-method.js +++ b/lib/rules/no-use-computed-property-like-method.js @@ -2,3 +2,189 @@ * @author tyankatsu * 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} */ + const computedPropertiesMap = new Map() + + /** + * @typedef {object} ScopeStack + * @property {ScopeStack | null} upper + * @property {BlockStatement | Expression} body + */ + /** @type {Map} */ + const propsMap = new Map() + + /** + * @typedef {object} ScopeStack + * @property {ScopeStack | null} upper + * @property {BlockStatement | Expression} body + */ + /** @type {Map} */ + 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: + // }) + } + }) + } +} diff --git a/tests/lib/rules/no-use-computed-property-like-method.js b/tests/lib/rules/no-use-computed-property-like-method.js index fdefaf422..8b153c1ca 100644 --- a/tests/lib/rules/no-use-computed-property-like-method.js +++ b/tests/lib/rules/no-use-computed-property-like-method.js @@ -2,3 +2,134 @@ * @author tyankatsu * 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: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + } + ], + invalid: [ + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + } + ] +})