diff --git a/lib/rules/comment-directive.js b/lib/rules/comment-directive.js index f3320bd9c..9ff9d2fdf 100644 --- a/lib/rules/comment-directive.js +++ b/lib/rules/comment-directive.js @@ -5,6 +5,12 @@ 'use strict' +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const utils = require('../utils') + /** * @typedef {object} RuleAndLocation * @property {string} RuleAndLocation.ruleId @@ -252,15 +258,7 @@ function locToKey(location) { * @returns {VElement[]} The top-level elements */ function extractTopLevelHTMLElements(documentFragment) { - return documentFragment.children.filter(isVElement) - - /** - * @param {any} e - * @returns {e is VElement} - */ - function isVElement(e) { - return e.type === 'VElement' - } + return documentFragment.children.filter(utils.isVElement) } /** * Extracts the top-level comments in document fragment. diff --git a/lib/rules/component-definition-name-casing.js b/lib/rules/component-definition-name-casing.js index 0e7e48aaa..1f2e59a85 100644 --- a/lib/rules/component-definition-name-casing.js +++ b/lib/rules/component-definition-name-casing.js @@ -97,18 +97,10 @@ module.exports = { } }), utils.executeOnVue(context, (obj) => { - const node = obj.properties.find( - /** - * @param {ObjectExpression['properties'][0]} item - * @returns {item is Property & {value: (Literal | TemplateLiteral)}} - */ - (item) => - item.type === 'Property' && - utils.getStaticPropertyName(item) === 'name' && - canConvert(item.value) - ) + const node = utils.findProperty(obj, 'name') if (!node) return + if (!canConvert(node.value)) return convertName(node.value) }) ) diff --git a/lib/rules/component-tags-order.js b/lib/rules/component-tags-order.js index 31188408e..b2a2692a8 100644 --- a/lib/rules/component-tags-order.js +++ b/lib/rules/component-tags-order.js @@ -81,13 +81,7 @@ module.exports = { function getTopLevelHTMLElements() { if (documentFragment) { - return documentFragment.children.filter( - /** - * @param {VElement | VText | VExpressionContainer} e - * @returns {e is VElement} - */ - (e) => e.type === 'VElement' - ) + return documentFragment.children.filter(utils.isVElement) } return [] } diff --git a/lib/rules/custom-event-name-casing.js b/lib/rules/custom-event-name-casing.js index 4e4f406cd..cd7f73443 100644 --- a/lib/rules/custom-event-name-casing.js +++ b/lib/rules/custom-event-name-casing.js @@ -131,14 +131,9 @@ module.exports = { const contextReferenceIds = new Set() const emitReferenceIds = new Set() if (contextParam.type === 'ObjectPattern') { - const emitProperty = contextParam.properties.find( - /** - * @param {ESNode} p - * @returns {p is AssignmentProperty} - */ - (p) => - p.type === 'Property' && - utils.getStaticPropertyName(p) === 'emit' + const emitProperty = utils.findAssignmentProperty( + contextParam, + 'emit' ) if (!emitProperty || emitProperty.value.type !== 'Identifier') { return diff --git a/lib/rules/match-component-file-name.js b/lib/rules/match-component-file-name.js index 9b43f684f..8428516fb 100644 --- a/lib/rules/match-component-file-name.js +++ b/lib/rules/match-component-file-name.js @@ -131,20 +131,12 @@ module.exports = { } }), utils.executeOnVue(context, (object) => { - const node = object.properties.find( - /** - * @param {ObjectExpression['properties'][0]} item - * @returns {item is Property & {value: (Literal | TemplateLiteral)}} - */ - (item) => - item.type === 'Property' && - utils.getStaticPropertyName(item) === 'name' && - canVerify(item.value) - ) + const node = utils.findProperty(object, 'name') componentCount++ if (!node) return + if (!canVerify(node.value)) return verifyName(node.value) }), { diff --git a/lib/rules/name-property-casing.js b/lib/rules/name-property-casing.js index 8ea8f08ed..d86435a95 100644 --- a/lib/rules/name-property-casing.js +++ b/lib/rules/name-property-casing.js @@ -41,35 +41,28 @@ module.exports = { // ---------------------------------------------------------------------- return utils.executeOnVue(context, (obj) => { - const node = obj.properties.find( - /** - * @param {ESNode} item - * @returns {item is Property & { value : Literal } } - */ - (item) => - item.type === 'Property' && - utils.getStaticPropertyName(item) === 'name' && - item.value.type === 'Literal' - ) + const node = utils.findProperty(obj, 'name') if (!node) return + const valueNode = node.value + if (valueNode.type !== 'Literal') return - if (!casing.getChecker(caseType)(`${node.value.value}`)) { - const value = casing.getExactConverter(caseType)(`${node.value.value}`) + if (!casing.getChecker(caseType)(`${valueNode.value}`)) { + const value = casing.getExactConverter(caseType)(`${valueNode.value}`) context.report({ - node: node.value, + node: valueNode, message: 'Property name "{{value}}" is not {{caseType}}.', data: { - value: `${node.value.value}`, + value: `${valueNode.value}`, caseType }, fix: (fixer) => fixer.replaceText( - node.value, + valueNode, context .getSourceCode() - .getText(node.value) - .replace(`${node.value.value}`, value) + .getText(valueNode) + .replace(`${valueNode.value}`, value) ) }) } diff --git a/lib/rules/no-arrow-functions-in-watch.js b/lib/rules/no-arrow-functions-in-watch.js index 1f9e8e767..77bb6787e 100644 --- a/lib/rules/no-arrow-functions-in-watch.js +++ b/lib/rules/no-arrow-functions-in-watch.js @@ -19,15 +19,7 @@ module.exports = { /** @param {RuleContext} context */ create(context) { return utils.executeOnVue(context, (obj) => { - const watchNode = obj.properties.find( - /** - * @param {ESNode} property - * @returns {property is Property} - */ - (property) => - property.type === 'Property' && - utils.getStaticPropertyName(property) === 'watch' - ) + const watchNode = utils.findProperty(obj, 'watch') if (watchNode == null) { return } diff --git a/lib/rules/no-boolean-default.js b/lib/rules/no-boolean-default.js index 7e9dc0a77..e278e3011 100644 --- a/lib/rules/no-boolean-default.js +++ b/lib/rules/no-boolean-default.js @@ -53,19 +53,7 @@ function getBooleanProps(props) { * @param {ObjectExpressionProp} propDef */ function getDefaultNode(propDef) { - return propDef.value.properties.find( - /** - * @param {ESNode} p - * @returns {p is Property} - */ - (p) => { - return ( - p.type === 'Property' && - p.key.type === 'Identifier' && - p.key.name === 'default' - ) - } - ) + return utils.findProperty(propDef.value, 'default') } module.exports = { diff --git a/lib/rules/no-deprecated-data-object-declaration.js b/lib/rules/no-deprecated-data-object-declaration.js index 7ac696a9e..37d280ef6 100644 --- a/lib/rules/no-deprecated-data-object-declaration.js +++ b/lib/rules/no-deprecated-data-object-declaration.js @@ -67,34 +67,29 @@ module.exports = { const sourceCode = context.getSourceCode() return utils.executeOnVue(context, (obj) => { - obj.properties - .filter( - /** - * @param {ESNode} p - * @returns {p is Property} - */ - (p) => - p.type === 'Property' && - p.key.type === 'Identifier' && - p.key.name === 'data' && - p.value.type !== 'FunctionExpression' && - p.value.type !== 'ArrowFunctionExpression' && - p.value.type !== 'Identifier' - ) - .forEach((p) => { - context.report({ - node: p, - messageId: 'objectDeclarationIsDeprecated', - fix(fixer) { - const tokens = getFirstAndLastTokens(p.value, sourceCode) + const invalidData = utils.findProperty( + obj, + 'data', + (p) => + p.value.type !== 'FunctionExpression' && + p.value.type !== 'ArrowFunctionExpression' && + p.value.type !== 'Identifier' + ) - return [ - fixer.insertTextBefore(tokens.first, 'function() {\nreturn '), - fixer.insertTextAfter(tokens.last, ';\n}') - ] - } - }) + if (invalidData) { + context.report({ + node: invalidData, + messageId: 'objectDeclarationIsDeprecated', + fix(fixer) { + const tokens = getFirstAndLastTokens(invalidData.value, sourceCode) + + return [ + fixer.insertTextBefore(tokens.first, 'function() {\nreturn '), + fixer.insertTextAfter(tokens.last, ';\n}') + ] + } }) + } }) } } diff --git a/lib/rules/no-duplicate-attr-inheritance.js b/lib/rules/no-duplicate-attr-inheritance.js index 20fb8deae..08426c352 100644 --- a/lib/rules/no-duplicate-attr-inheritance.js +++ b/lib/rules/no-duplicate-attr-inheritance.js @@ -32,12 +32,7 @@ module.exports = { return Object.assign( utils.executeOnVue(context, (node) => { - const inheritAttrsProp = node.properties.find( - /** @param {ESNode} prop @returns {prop is Property} */ - (prop) => - prop.type === 'Property' && - utils.getStaticPropertyName(prop) === 'inheritAttrs' - ) + const inheritAttrsProp = utils.findProperty(node, 'inheritAttrs') if (inheritAttrsProp && inheritAttrsProp.value.type === 'Literal') { inheritsAttrs = inheritAttrsProp.value.value diff --git a/lib/rules/no-reserved-component-names.js b/lib/rules/no-reserved-component-names.js index 925078d92..e21bf7cd8 100644 --- a/lib/rules/no-reserved-component-names.js +++ b/lib/rules/no-reserved-component-names.js @@ -180,19 +180,10 @@ module.exports = { .filter(({ name }) => reservedNames.has(name)) .forEach(({ node, name }) => report(node, name)) - const node = obj.properties.find( - /** - * @param {ObjectExpression['properties'][0]} item - * @returns {item is Property & {value: (Literal | TemplateLiteral)}} - */ - (item) => - item.type === 'Property' && - item.key.type === 'Identifier' && - item.key.name === 'name' && - canVerify(item.value) - ) + const node = utils.findProperty(obj, 'name') if (!node) return + if (!canVerify(node.value)) return reportIfInvalid(node.value) }) ) diff --git a/lib/rules/no-shared-component-data.js b/lib/rules/no-shared-component-data.js index 782e8788a..160f41ef9 100644 --- a/lib/rules/no-shared-component-data.js +++ b/lib/rules/no-shared-component-data.js @@ -57,34 +57,28 @@ module.exports = { const sourceCode = context.getSourceCode() return utils.executeOnVueComponent(context, (obj) => { - obj.properties - .filter( - /** - * @param {ESNode} p - * @returns {p is Property} - */ - (p) => - p.type === 'Property' && - p.key.type === 'Identifier' && - p.key.name === 'data' && - p.value.type !== 'FunctionExpression' && - p.value.type !== 'ArrowFunctionExpression' && - p.value.type !== 'Identifier' - ) - .forEach((p) => { - context.report({ - node: p, - message: '`data` property in component must be a function.', - fix(fixer) { - const tokens = getFirstAndLastTokens(p.value, sourceCode) + const invalidData = utils.findProperty( + obj, + 'data', + (p) => + p.value.type !== 'FunctionExpression' && + p.value.type !== 'ArrowFunctionExpression' && + p.value.type !== 'Identifier' + ) + if (invalidData) { + context.report({ + node: invalidData, + message: '`data` property in component must be a function.', + fix(fixer) { + const tokens = getFirstAndLastTokens(invalidData.value, sourceCode) - return [ - fixer.insertTextBefore(tokens.first, 'function() {\nreturn '), - fixer.insertTextAfter(tokens.last, ';\n}') - ] - } - }) + return [ + fixer.insertTextBefore(tokens.first, 'function() {\nreturn '), + fixer.insertTextAfter(tokens.last, ';\n}') + ] + } }) + } }) } } diff --git a/lib/rules/order-in-components.js b/lib/rules/order-in-components.js index 4c6692394..1a0c3a9c3 100644 --- a/lib/rules/order-in-components.js +++ b/lib/rules/order-in-components.js @@ -250,13 +250,7 @@ module.exports = { */ function checkOrder(propertiesNodes) { const properties = propertiesNodes - .filter( - /** - * @param {ASTNode} property - * @returns {property is Property} - */ - (property) => property.type === 'Property' - ) + .filter(utils.isProperty) .map((property) => { return { node: property, diff --git a/lib/rules/padding-line-between-blocks.js b/lib/rules/padding-line-between-blocks.js index 2d1802951..b9215e2c9 100644 --- a/lib/rules/padding-line-between-blocks.js +++ b/lib/rules/padding-line-between-blocks.js @@ -151,13 +151,7 @@ module.exports = { * @returns {VElement[]} */ function getTopLevelHTMLElements() { - return documentFragment.children.filter( - /** - * @param {VElement | VExpressionContainer | VText} e - * @returns {e is VElement} - */ - (e) => e.type === 'VElement' - ) + return documentFragment.children.filter(utils.isVElement) } /** diff --git a/lib/rules/require-explicit-emits.js b/lib/rules/require-explicit-emits.js index b15af3ac1..7dc1706bb 100644 --- a/lib/rules/require-explicit-emits.js +++ b/lib/rules/require-explicit-emits.js @@ -214,21 +214,19 @@ module.exports = { /** @type {Set} */ const emitReferenceIds = new Set() if (contextParam.type === 'ObjectPattern') { - const emitProperty = contextParam.properties.find( - /** - * @param {ESNode} p - * @returns {p is AssignmentProperty & { value: Identifier }} - */ - (p) => - p.type === 'Property' && - utils.getStaticPropertyName(p) === 'emit' + const emitProperty = utils.findAssignmentProperty( + contextParam, + 'emit' ) if (!emitProperty) { return } const emitParam = emitProperty.value // `setup(props, {emit})` - const variable = findVariable(context.getScope(), emitParam) + const variable = + emitParam.type === 'Identifier' + ? findVariable(context.getScope(), emitParam) + : null if (!variable) { return } @@ -361,13 +359,7 @@ function buildSuggest(object, emits, nameNode, context) { ] } - const propertyNodes = object.properties.filter( - /** - * @param {ESNode} p - * @returns {p is Property} - */ - (p) => p.type === 'Property' && p.key.type === 'Identifier' - ) + const propertyNodes = object.properties.filter(utils.isProperty) const emitsOption = propertyNodes.find( (p) => utils.getStaticPropertyName(p) === 'emits' diff --git a/lib/rules/require-name-property.js b/lib/rules/require-name-property.js index 92b7b4a7f..e8930ceb4 100644 --- a/lib/rules/require-name-property.js +++ b/lib/rules/require-name-property.js @@ -7,7 +7,7 @@ const utils = require('../utils') /** - * @param {ObjectExpression['properties'][0]} node + * @param {Property | SpreadElement} node * @returns {node is ObjectExpressionProperty} */ function isNameProperty(node) { diff --git a/lib/rules/require-prop-type-constructor.js b/lib/rules/require-prop-type-constructor.js index 410b7ade5..1104c7a33 100644 --- a/lib/rules/require-prop-type-constructor.js +++ b/lib/rules/require-prop-type-constructor.js @@ -85,15 +85,7 @@ module.exports = { ) { checkPropertyNode(prop.propName, prop.value) } else if (prop.value.type === 'ObjectExpression') { - const typeProperty = prop.value.properties.find( - /** - * @param {ESNode} prop - * @returns {prop is Property} - */ - (prop) => - prop.type === 'Property' && - utils.getStaticPropertyName(prop) === 'type' - ) + const typeProperty = utils.findProperty(prop.value, 'type') if (!typeProperty) continue diff --git a/lib/rules/require-valid-default-prop.js b/lib/rules/require-valid-default-prop.js index a6e226316..5003d20c7 100644 --- a/lib/rules/require-valid-default-prop.js +++ b/lib/rules/require-valid-default-prop.js @@ -58,7 +58,7 @@ function getTypes(node) { return node.elements .filter( /** - * @param {ArrayExpression['elements'][0]} item + * @param {Expression | SpreadElement} item * @returns {item is Identifier} */ (item) => item.type === 'Identifier' diff --git a/lib/rules/sort-keys.js b/lib/rules/sort-keys.js index 092d207c0..5e4fa3c10 100644 --- a/lib/rules/sort-keys.js +++ b/lib/rules/sort-keys.js @@ -154,15 +154,18 @@ module.exports = { /** * @typedef {object} ObjectStack - * @property {ObjectStack} upper - * @property {string | null} prevName - * @property {number} numKeys - * @property {Property | null} currentProperty - * @property {boolean} isVueObject - * @property {boolean} withinVueObject - * @property {string | null} withinVuePropName - * @property {number} withinVuePropChainLevel - * @property {boolean} ignore + * @property {ObjectStack} ObjectStack.upper + * @property {string | null} ObjectStack.prevName + * @property {number} ObjectStack.numKeys + * @property {VueState} ObjectStack.vueState + * + * @typedef {object} VueState + * @property {Property} [VueState.currentProperty] + * @property {boolean} [VueState.isVueObject] + * @property {boolean} [VueState.within] + * @property {string} [VueState.propName] + * @property {number} [VueState.chainLevel] + * @property {boolean} [VueState.ignore] */ /** @@ -171,69 +174,62 @@ module.exports = { */ let stack - /** - * @param {Property | null} prop - * @param {Expression} expr - */ - function isPropertyChain(prop, expr) { - let value = expr - while (value.parent.type === 'TSAsExpression') { - value = value.parent - } - return prop === value.parent && prop.value === value - } - return { ObjectExpression(node) { - const isVueObject = utils.getVueObjectType(context, node) != null - let withinVueObject = isVueObject - /** @type {string | null} */ - let withinVuePropName = null - let withinVuePropChainLevel = NaN - if (!isVueObject && stack) { - if (stack.withinVueObject && stack.currentProperty) { - const isChain = isPropertyChain(stack.currentProperty, node) + /** @type {VueState} */ + const vueState = {} + const upperVueState = (stack && stack.vueState) || {} + stack = { + upper: stack, + prevName: null, + numKeys: node.properties.length, + vueState + } + + vueState.isVueObject = utils.getVueObjectType(context, node) != null + if (vueState.isVueObject) { + vueState.within = vueState.isVueObject + // Ignore Vue object properties + vueState.ignore = true + } else { + if (upperVueState.within && upperVueState.currentProperty) { + const isChain = utils.isPropertyChain( + upperVueState.currentProperty, + node + ) if (isChain) { - if (stack.isVueObject) { - withinVuePropName = utils.getStaticPropertyName( - stack.currentProperty - ) - withinVuePropChainLevel = 1 + let propName + let chainLevel + if (upperVueState.isVueObject) { + propName = + utils.getStaticPropertyName(upperVueState.currentProperty) || + '' + chainLevel = 1 } else { - withinVuePropName = stack.withinVuePropName - withinVuePropChainLevel = stack.withinVuePropChainLevel + 1 + propName = upperVueState.propName + chainLevel = upperVueState.chainLevel + 1 } - withinVueObject = true - } else { - withinVueObject = false - } - } - } - - let ignore = isVueObject + vueState.propName = propName + vueState.chainLevel = chainLevel + // chaining + vueState.within = true - if (withinVueObject && withinVuePropName != null) { - if (withinVuePropChainLevel === 1) { - if (ignoreChildrenOf.includes(withinVuePropName)) { - ignore = true - } - } else if (withinVuePropChainLevel === 2) { - if (ignoreGrandchildrenOf.includes(withinVuePropName)) { - ignore = true + // Judge whether to ignore the property. + if (chainLevel === 1) { + if (ignoreChildrenOf.includes(propName)) { + vueState.ignore = true + } + } else if (chainLevel === 2) { + if (ignoreGrandchildrenOf.includes(propName)) { + vueState.ignore = true + } + } + } else { + // chaining has broken. + vueState.within = false } } } - stack = { - upper: stack, - prevName: null, - numKeys: node.properties.length, - currentProperty: null, - isVueObject, - withinVueObject, - withinVuePropName, - withinVuePropChainLevel, - ignore - } }, 'ObjectExpression:exit'() { stack = stack.upper @@ -244,9 +240,8 @@ module.exports = { } }, 'ObjectExpression > Property'(node) { - stack.currentProperty = node - - if (stack.ignore) { + stack.vueState.currentProperty = node + if (stack.vueState.ignore) { return } const prevName = stack.prevName diff --git a/lib/utils/index.js b/lib/utils/index.js index a498fb953..a6c36d00b 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -108,25 +108,6 @@ const { findVariable } = require('eslint-utils') */ const componentComments = new WeakMap() -/** - * Checks whether the given node is Property. - * @param {ObjectExpression['properties'][0]} node - * @returns {node is Property} - */ -function isProperty(node) { - return node.type === 'Property' -} - -/** - * Checks whether the given value is defined. - * @template T - * @param {T | null | undefined} v - * @returns {v is T} - */ -function isDef(v) { - return v != null -} - /** * Wrap the rule context object to override methods which access to tokens (such as getTokenAfter). * @param {RuleContext} context The rule context object. @@ -230,22 +211,15 @@ function wrapContextToOverrideReportMethodToSkipDynamicArgument(context) { // ------------------------------------------------------------------------------ module.exports = { - /** - * Checks whether the given value is defined. - * @template T - * @param {T | null | undefined} v - * @returns {v is T} - */ - isDef, /** * Register the given visitor to parser services. * If the parser service of `vue-eslint-parser` was not found, * this generates a warning. * * @param {RuleContext} context The rule context to use parser services. - * @param {Object} templateBodyVisitor The visitor to traverse the template body. - * @param {Object} [scriptVisitor] The visitor to traverse the script. - * @returns {Object} The merged visitor. + * @param {TemplateListener} templateBodyVisitor The visitor to traverse the template body. + * @param {RuleListener} [scriptVisitor] The visitor to traverse the script. + * @returns {RuleListener} The merged visitor. */ defineTemplateBodyVisitor, @@ -342,6 +316,13 @@ module.exports = { } }, + /** + * Checks whether the given value is defined. + * @template T + * @param {T | null | undefined} v + * @returns {v is T} + */ + isDef, /** * Check whether the given node is the root element or not. * @param {VElement} node The element node to check. @@ -1411,6 +1392,32 @@ module.exports = { } return dp[alen][blen] }, + /** + * Checks whether the given node is Property. + */ + isProperty, + /** + * Checks whether the given node is AssignmentProperty. + */ + isAssignmentProperty, + /** + * Checks whether the given node is VElement. + */ + isVElement, + /** + * Finds the property with the given name from the given ObjectExpression node. + */ + findProperty, + /** + * Finds the assignment property with the given name from the given ObjectPattern node. + */ + findAssignmentProperty, + /** + * Checks if the given node is a property value. + * @param {Property} prop + * @param {Expression} node + */ + isPropertyChain, /** * Unwrap typescript types like "X as F" * @template T @@ -1418,6 +1425,11 @@ module.exports = { * @return {T} */ unwrapTypes, + /** + * Unwrap AssignmentPattern like "(a = 1) => ret" + * @param { AssignmentPattern | RestElement | ArrayPattern | ObjectPattern | Identifier } node + * @return { RestElement | ArrayPattern | ObjectPattern | Identifier} + */ unwrapAssignmentPattern, /** @@ -1520,6 +1532,24 @@ module.exports = { } } +// ------------------------------------------------------------------------------ +// Standard Helpers +// ------------------------------------------------------------------------------ + +/** + * Checks whether the given value is defined. + * @template T + * @param {T | null | undefined} v + * @returns {v is T} + */ +function isDef(v) { + return v != null +} + +// ------------------------------------------------------------------------------ +// Rule Helpers +// ------------------------------------------------------------------------------ + /** * Register the given visitor to parser services. * If the parser service of `vue-eslint-parser` was not found, @@ -1549,6 +1579,110 @@ function defineTemplateBodyVisitor( ) } +/** + * @param {RuleListener} visitor + * @param {...RuleListener} visitors + * @returns {RuleListener} + */ +function compositingVisitors(visitor, ...visitors) { + for (const v of visitors) { + for (const key in v) { + if (visitor[key]) { + const o = visitor[key] + /** @param {any[]} args */ + visitor[key] = (...args) => { + // @ts-ignore + o(...args) + // @ts-ignore + v[key](...args) + } + } else { + visitor[key] = v[key] + } + } + } + return visitor +} + +// ------------------------------------------------------------------------------ +// AST Helpers +// ------------------------------------------------------------------------------ + +/** + * Finds the property with the given name from the given ObjectExpression node. + * @param {ObjectExpression} node + * @param {string} name + * @param { (p: Property) => boolean } [filter] + * @returns { (Property) | null} + */ +function findProperty(node, name, filter) { + const predicate = filter + ? /** + * @param {Property | SpreadElement} prop + * @returns {prop is Property} + */ + (prop) => + prop.type === 'Property' && + getStaticPropertyName(prop) === name && + filter(prop) + : /** + * @param {Property | SpreadElement} prop + * @returns {prop is Property} + */ + (prop) => prop.type === 'Property' && getStaticPropertyName(prop) === name + return node.properties.find(predicate) || null +} + +/** + * Finds the assignment property with the given name from the given ObjectPattern node. + * @param {ObjectPattern} node + * @param {string} name + * @param { (p: AssignmentProperty) => boolean } [filter] + * @returns { (AssignmentProperty) | null} + */ +function findAssignmentProperty(node, name, filter) { + const predicate = filter + ? /** + * @param {AssignmentProperty | RestElement} prop + * @returns {prop is AssignmentProperty} + */ + (prop) => + prop.type === 'Property' && + getStaticPropertyName(prop) === name && + filter(prop) + : /** + * @param {AssignmentProperty | RestElement} prop + * @returns {prop is AssignmentProperty} + */ + (prop) => prop.type === 'Property' && getStaticPropertyName(prop) === name + return node.properties.find(predicate) || null +} + +/** + * Checks whether the given node is Property. + * @param {Property | SpreadElement} node + * @returns {node is Property} + */ +function isProperty(node) { + return node.type === 'Property' +} +/** + * Checks whether the given node is AssignmentProperty. + * @param {AssignmentProperty | RestElement} node + * @returns {node is AssignmentProperty} + */ +function isAssignmentProperty(node) { + return node.type === 'Property' +} +/** + * Checks whether the given node is VElement. + * @param {VElement | VExpressionContainer | VText} node + * @returns {node is VElement} + */ +function isVElement(node) { + return node.type === 'VElement' +} + /** * Unwrap typescript types like "X as F" * @template T @@ -1567,6 +1701,32 @@ function unwrapTypes(node) { return node } +/** + * Gets the parent node of the given node. This method returns a value ignoring `X as F`. + * @param {Expression} node + * @returns {ASTNode} + */ +function getParent(node) { + let parent = node.parent + while (parent.type === 'TSAsExpression') { + parent = parent.parent + } + return parent +} + +/** + * Checks if the given node is a property value. + * @param {Property} prop + * @param {Expression} node + */ +function isPropertyChain(prop, node) { + let value = node + while (value.parent.type === 'TSAsExpression') { + value = value.parent + } + return prop === value.parent && prop.value === value +} + /** * Unwrap AssignmentPattern like "(a = 1) => ret" * @param { AssignmentPattern | RestElement | ArrayPattern | ObjectPattern | Identifier } node @@ -1643,6 +1803,10 @@ function getStringLiteralValue(node, stringOnly) { return null } +// ------------------------------------------------------------------------------ +// Vue Helpers +// ------------------------------------------------------------------------------ + /** * @param {string} path */ @@ -1763,33 +1927,28 @@ function getVueObjectType(context, node) { if (node.type !== 'ObjectExpression') { return null } - let parent = node.parent - while (parent && parent.type === 'TSAsExpression') { - parent = parent.parent - } - if (parent) { - if (parent.type === 'ExportDefaultDeclaration') { - // export default {} in .vue || .jsx - const filePath = context.getFilename() - if ( - isVueComponentFile(parent, filePath) && - unwrapTypes(parent.declaration) === node - ) { - return 'export' - } - } else if (parent.type === 'CallExpression') { - // Vue.component('xxx', {}) || component('xxx', {}) - if ( - isVueComponent(parent) && - unwrapTypes(parent.arguments.slice(-1)[0]) === node - ) { - return 'definition' - } - } else if (parent.type === 'NewExpression') { - // new Vue({}) - if (isVueInstance(parent) && unwrapTypes(parent.arguments[0]) === node) { - return 'instance' - } + const parent = getParent(node) + if (parent.type === 'ExportDefaultDeclaration') { + // export default {} in .vue || .jsx + const filePath = context.getFilename() + if ( + isVueComponentFile(parent, filePath) && + unwrapTypes(parent.declaration) === node + ) { + return 'export' + } + } else if (parent.type === 'CallExpression') { + // Vue.component('xxx', {}) || component('xxx', {}) + if ( + isVueComponent(parent) && + unwrapTypes(parent.arguments.slice(-1)[0]) === node + ) { + return 'definition' + } + } else if (parent.type === 'NewExpression') { + // new Vue({}) + if (isVueInstance(parent) && unwrapTypes(parent.arguments[0]) === node) { + return 'instance' } } if ( @@ -1819,28 +1978,3 @@ function getComponentComments(context) { componentComments.set(context, tokens) return tokens } - -/** - * @param {RuleListener} visitor - * @param {...RuleListener} visitors - * @returns {RuleListener} - */ -function compositingVisitors(visitor, ...visitors) { - for (const v of visitors) { - for (const key in v) { - if (visitor[key]) { - const o = visitor[key] - /** @param {any[]} args */ - visitor[key] = (...args) => { - // @ts-ignore - o(...args) - // @ts-ignore - v[key](...args) - } - } else { - visitor[key] = v[key] - } - } - } - return visitor -}