From a7f95ce329171c3a492586ca23957027f5cf34dc Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Wed, 15 Jul 2020 11:39:46 +0900 Subject: [PATCH] Fixed `vue/no-unused-properties`, `vue/require-valid-default-prop`, `vue/require-default-prop` and `vue/no-multiple-objects-in-class` rules crash on sparse arrays. (#1242) --- lib/rules/no-multiple-objects-in-class.js | 2 +- lib/rules/require-default-prop.js | 22 +++-- lib/rules/require-prop-type-constructor.js | 6 +- lib/rules/require-valid-default-prop.js | 4 +- lib/utils/index.js | 85 +++++++++---------- .../lib/rules/no-multiple-objects-in-class.js | 11 +++ tests/lib/rules/no-unused-properties.js | 15 ++++ tests/lib/rules/require-default-prop.js | 15 ++++ tests/lib/rules/require-valid-default-prop.js | 14 +++ .../util-types/ast/es-ast.ts | 2 +- 10 files changed, 119 insertions(+), 57 deletions(-) diff --git a/lib/rules/no-multiple-objects-in-class.js b/lib/rules/no-multiple-objects-in-class.js index f2a170641..cf930ebec 100644 --- a/lib/rules/no-multiple-objects-in-class.js +++ b/lib/rules/no-multiple-objects-in-class.js @@ -21,7 +21,7 @@ const { defineTemplateBodyVisitor } = require('../utils') */ function countObjectExpression(node) { return node.value.expression.elements.filter( - (element) => element.type === 'ObjectExpression' + (element) => element && element.type === 'ObjectExpression' ).length } diff --git a/lib/rules/require-default-prop.js b/lib/rules/require-default-prop.js index ff014ca8f..ec73e369b 100644 --- a/lib/rules/require-default-prop.js +++ b/lib/rules/require-default-prop.js @@ -10,6 +10,7 @@ */ const utils = require('../utils') +const { isDef } = require('../utils') const NATIVE_TYPES = new Set([ 'String', @@ -99,16 +100,21 @@ module.exports = { /** * Detects whether given value node is a Boolean type * @param {Expression | Pattern} value - * @return {Boolean} + * @return {boolean} */ function isValueNodeOfBooleanType(value) { - return ( - (value.type === 'Identifier' && value.name === 'Boolean') || - (value.type === 'ArrayExpression' && - value.elements.length === 1 && - value.elements[0].type === 'Identifier' && - value.elements[0].name === 'Boolean') - ) + if (value.type === 'Identifier' && value.name === 'Boolean') { + return true + } + if (value.type === 'ArrayExpression') { + const elements = value.elements.filter(isDef) + return ( + elements.length === 1 && + elements[0].type === 'Identifier' && + elements[0].name === 'Boolean' + ) + } + return false } /** diff --git a/lib/rules/require-prop-type-constructor.js b/lib/rules/require-prop-type-constructor.js index 1104c7a33..0f94f0f2c 100644 --- a/lib/rules/require-prop-type-constructor.js +++ b/lib/rules/require-prop-type-constructor.js @@ -5,6 +5,7 @@ 'use strict' const utils = require('../utils') +const { isDef } = require('../utils') // ------------------------------------------------------------------------------ // Rule Definition @@ -49,10 +50,11 @@ module.exports = { */ function checkPropertyNode(propName, node) { /** @type {ESNode[]} */ - const nodes = node.type === 'ArrayExpression' ? node.elements : [node] + const nodes = + node.type === 'ArrayExpression' ? node.elements.filter(isDef) : [node] nodes - .filter((prop) => prop && isForbiddenType(prop)) + .filter((prop) => isForbiddenType(prop)) .forEach((prop) => context.report({ node: prop, diff --git a/lib/rules/require-valid-default-prop.js b/lib/rules/require-valid-default-prop.js index a6b2ca95e..a0444db08 100644 --- a/lib/rules/require-valid-default-prop.js +++ b/lib/rules/require-valid-default-prop.js @@ -58,10 +58,10 @@ function getTypes(node) { return node.elements .filter( /** - * @param {Expression | SpreadElement} item + * @param {Expression | SpreadElement | null} item * @returns {item is Identifier} */ - (item) => item.type === 'Identifier' + (item) => item != null && item.type === 'Identifier' ) .map((item) => item.name) } diff --git a/lib/utils/index.js b/lib/utils/index.js index 085c596ad..0a6f7bf33 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -737,29 +737,27 @@ module.exports = { } }) } else { - return propsNode.value.elements - .filter((prop) => prop) - .map((prop) => { - if (prop.type === 'Literal' || prop.type === 'TemplateLiteral') { - const propName = getStringLiteralValue(prop) - if (propName != null) { - return { - type: 'array', - key: prop, - propName, - value: null, - node: prop - } + return propsNode.value.elements.filter(isDef).map((prop) => { + if (prop.type === 'Literal' || prop.type === 'TemplateLiteral') { + const propName = getStringLiteralValue(prop) + if (propName != null) { + return { + type: 'array', + key: prop, + propName, + value: null, + node: prop } } - return { - type: 'array', - key: null, - propName: null, - value: null, - node: prop - } - }) + } + return { + type: 'array', + key: null, + propName: null, + value: null, + node: prop + } + }) } }, @@ -810,29 +808,27 @@ module.exports = { } }) } else { - return emitsNode.value.elements - .filter((prop) => prop) - .map((prop) => { - if (prop.type === 'Literal' || prop.type === 'TemplateLiteral') { - const emitName = getStringLiteralValue(prop) - if (emitName != null) { - return { - type: 'array', - key: prop, - emitName, - value: null, - node: prop - } + return emitsNode.value.elements.filter(isDef).map((prop) => { + if (prop.type === 'Literal' || prop.type === 'TemplateLiteral') { + const emitName = getStringLiteralValue(prop) + if (emitName != null) { + return { + type: 'array', + key: prop, + emitName, + value: null, + node: prop } } - return { - type: 'array', - key: null, - emitName: null, - value: null, - node: prop - } - }) + } + return { + type: 'array', + key: null, + emitName: null, + value: null, + node: prop + } + }) } }, @@ -1109,7 +1105,10 @@ module.exports = { */ *iterateArrayExpression(node, groupName) { for (const item of node.elements) { - if (item.type === 'Literal' || item.type === 'TemplateLiteral') { + if ( + item && + (item.type === 'Literal' || item.type === 'TemplateLiteral') + ) { const name = getStringLiteralValue(item) if (name) { yield { type: 'array', name, groupName, node: item } diff --git a/tests/lib/rules/no-multiple-objects-in-class.js b/tests/lib/rules/no-multiple-objects-in-class.js index 45f1b721d..64ce82b0a 100644 --- a/tests/lib/rules/no-multiple-objects-in-class.js +++ b/tests/lib/rules/no-multiple-objects-in-class.js @@ -43,6 +43,17 @@ ruleTester.run('no-multiple-objects-in-class', rule, { type: 'VAttribute' } ] + }, + + // sparse array + { + code: ``, + errors: [ + { + message: 'Unexpected multiple objects. Merge objects.', + type: 'VAttribute' + } + ] } ] }) diff --git a/tests/lib/rules/no-unused-properties.js b/tests/lib/rules/no-unused-properties.js index c5796e752..355fb8df7 100644 --- a/tests/lib/rules/no-unused-properties.js +++ b/tests/lib/rules/no-unused-properties.js @@ -981,6 +981,21 @@ tester.run('no-unused-properties', rule, { } `, options: [{ groups: ['props', 'setup'] }] + }, + + // sparse array + { + filename: 'test.vue', + code: ` + + + ` } ], diff --git a/tests/lib/rules/require-default-prop.js b/tests/lib/rules/require-default-prop.js index ce292774e..cdc0f4170 100644 --- a/tests/lib/rules/require-default-prop.js +++ b/tests/lib/rules/require-default-prop.js @@ -179,6 +179,21 @@ ruleTester.run('require-default-prop', rule, { } } ` + }, + + // sparse array + { + filename: 'test.vue', + code: ` + export default { + props: { + a: { + type: [,Boolean] + }, + b: [,Boolean], + } + } + ` } ], diff --git a/tests/lib/rules/require-valid-default-prop.js b/tests/lib/rules/require-valid-default-prop.js index 9f8ff7275..2617e1460 100644 --- a/tests/lib/rules/require-valid-default-prop.js +++ b/tests/lib/rules/require-valid-default-prop.js @@ -181,6 +181,20 @@ ruleTester.run('require-valid-default-prop', rule, { } }`, parserOptions + }, + + // sparse array + { + filename: 'test.vue', + code: `export default { + props: { + foo: { + type: [,Object, Number], + default: 10 + } + } + }`, + parserOptions } ], diff --git a/typings/eslint-plugin-vue/util-types/ast/es-ast.ts b/typings/eslint-plugin-vue/util-types/ast/es-ast.ts index 701de116d..035c113f4 100644 --- a/typings/eslint-plugin-vue/util-types/ast/es-ast.ts +++ b/typings/eslint-plugin-vue/util-types/ast/es-ast.ts @@ -298,7 +298,7 @@ export interface ThisExpression extends HasParentNode { } export interface ArrayExpression extends HasParentNode { type: 'ArrayExpression' - elements: (Expression | SpreadElement)[] + elements: (Expression | SpreadElement | null)[] } export interface ObjectExpression extends HasParentNode { type: 'ObjectExpression'