Repository: vuejs/eslint-plugin-vue (JavaScript)

Issue:
Exception thrown by require-default-prop when encountering spread operator
<!--
  Before posting the issue, please confirm that the problem you're getting
  is not related with your code editor configuration.
  To make sure it's not, run: yarn eslint src/your-file.vue
-->

**Tell us about your environment**	
* **ESLint version: 6.8.0**	
* **eslint-plugin-vue version: 6.1.2**	
* **Node version: 10.16.3**	

Package.json:
```json
{
  "scripts": {
    "lint": "eslint . --ext .js,.vue"
  },
  "dependencies": {
    "babel-eslint": "^10.0.3",
    "eslint": "^6.8.0",
    "eslint-plugin-vue": "^6.1.2",
    "vue-eslint-parser": "^7.0.0"
  }
}

```

**Please show your full configuration:**
<!-- Paste content of your .eslintrc file -->
```json
module.exports = {
  extends: ["plugin:vue/recommended"]
};

```

**What did you do?**	
<!-- Please include the actual source code causing the issue. -->
```vue
<script>
const commonProps = {
  background: String
};

export default {
  name: "test",
  props: {
    background: {
      ...commonProps.background,
      validator: data => {
        return ["bg-2", "bg"].includes(data);
      }
    }
  }
};
</script>

```

**What did you expect to happen?**
Tests to run successfully

**What actually happened?**
<!-- Please include the actual, raw output from ESLint. -->

require-default-prop encountered an error parsing the file.

```bash
$ eslint . --ext .js,.vue
TypeError: Cannot read property 'type' of undefined
Occurred while linting /eslint-test/Test.vue:6
    at value.type.ObjectExpression.value.properties.find.p (eslint-plugin-vue/lib/rules/require-default-prop.js:116:17)
    at Array.find (<anonymous>)
    at isBooleanProp (eslint-plugin-vue/lib/rules/require-default-prop.js:115:26)
    at props.filter.prop (eslint-plugin-vue/lib/rules/require-default-prop.js:129:36)
    at Array.filter (<anonymous>)
    at excludeBooleanProps (eslint-plugin-vue/lib/rules/require-default-prop.js:129:20)
    at utils.executeOnVue (eslint-plugin-vue/lib/rules/require-default-prop.js:141:29)
    at ExportDefaultDeclaration:exit (eslint-plugin-vue/lib/utils/index.js:628:9)
    at listeners.(anonymous function).forEach.listener (eslint/lib/linter/safe-emitter.js:45:58)
    at Array.forEach (<anonymous>)
error Command failed with exit code 2.
```


Discussion:
Thank you for this issue.

I will check this bug and fix it.



Patch:
diff --git a/lib/rules/require-default-prop.js b/lib/rules/require-default-prop.js
index 91dd90840..3f6f18613 100644
--- a/lib/rules/require-default-prop.js
+++ b/lib/rules/require-default-prop.js
@@ -4,6 +4,16 @@
  */
 'use strict'
 
+/**
+ * @typedef {import('vue-eslint-parser').AST.ESLintExpression} Expression
+ * @typedef {import('vue-eslint-parser').AST.ESLintObjectExpression} ObjectExpression
+ * @typedef {import('vue-eslint-parser').AST.ESLintPattern} Pattern
+ */
+/**
+ * @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp
+ * @typedef {ComponentObjectProp & { value: ObjectExpression} } ComponentObjectPropObject
+ */
+
 const utils = require('../utils')
 
 const NATIVE_TYPES = new Set([
@@ -39,14 +49,14 @@ module.exports = {
 
     /**
      * Checks if the passed prop is required
-     * @param {Property} prop - Property AST node for a single prop
+     * @param {ComponentObjectPropObject} prop - Property AST node for a single prop
      * @return {boolean}
      */
     function propIsRequired (prop) {
       const propRequiredNode = prop.value.properties
         .find(p =>
           p.type === 'Property' &&
-          p.key.name === 'required' &&
+          utils.getStaticPropertyName(p) === 'required' &&
           p.value.type === 'Literal' &&
           p.value.value === true
         )
@@ -56,14 +66,13 @@ module.exports = {
 
     /**
      * Checks if the passed prop has a default value
-     * @param {Property} prop - Property AST node for a single prop
+     * @param {ComponentObjectPropObject} prop - Property AST node for a single prop
      * @return {boolean}
      */
     function propHasDefault (prop) {
       const propDefaultNode = prop.value.properties
         .find(p =>
-          p.key &&
-          (p.key.name === 'default' || p.key.value === 'default')
+          p.type === 'Property' && utils.getStaticPropertyName(p) === 'default'
         )
 
       return Boolean(propDefaultNode)
@@ -71,23 +80,24 @@ module.exports = {
 
     /**
      * Finds all props that don't have a default value set
-     * @param {Array} props - Vue component's "props" node
-     * @return {Array} Array of props without "default" value
+     * @param {ComponentObjectProp[]} props - Vue component's "props" node
+     * @return {ComponentObjectProp[]} Array of props without "default" value
      */
     function findPropsWithoutDefaultValue (props) {
       return props
         .filter(prop => {
           if (prop.value.type !== 'ObjectExpression') {
-            return (prop.value.type !== 'CallExpression' && prop.value.type !== 'Identifier') || NATIVE_TYPES.has(prop.value.name)
+            return (prop.value.type !== 'CallExpression' && prop.value.type !== 'Identifier') ||
+              (prop.value.type === 'Identifier' && NATIVE_TYPES.has(prop.value.name))
           }
 
-          return !propIsRequired(prop) && !propHasDefault(prop)
+          return !propIsRequired(/** @type {ComponentObjectPropObject} */(prop)) && !propHasDefault(/** @type {ComponentObjectPropObject} */(prop))
         })
     }
 
     /**
      * Detects whether given value node is a Boolean type
-     * @param {Node} value
+     * @param {Expression | Pattern} value
      * @return {Boolean}
      */
     function isValueNodeOfBooleanType (value) {
@@ -104,7 +114,7 @@ module.exports = {
 
     /**
      * Detects whether given prop node is a Boolean
-     * @param {Node} prop
+     * @param {ComponentObjectProp} prop
      * @return {Boolean}
      */
     function isBooleanProp (prop) {
@@ -112,7 +122,8 @@ module.exports = {
 
       return isValueNodeOfBooleanType(value) || (
         value.type === 'ObjectExpression' &&
-        value.properties.find(p =>
+        value.properties.some(p =>
+          p.type === 'Property' &&
           p.key.type === 'Identifier' &&
           p.key.name === 'type' &&
           isValueNodeOfBooleanType(p.value)
@@ -122,8 +133,8 @@ module.exports = {
 
     /**
      * Excludes purely Boolean props from the Array
-     * @param {Array} props - Array with props
-     * @return {Array}
+     * @param {ComponentObjectProp[]} props - Array with props
+     * @return {ComponentObjectProp[]}
      */
     function excludeBooleanProps (props) {
       return props.filter(prop => !isBooleanProp(prop))
@@ -135,9 +146,9 @@ module.exports = {
 
     return utils.executeOnVue(context, (obj) => {
       const props = utils.getComponentProps(obj)
-        .filter(prop => prop.key && prop.value && !prop.node.shorthand)
+        .filter(prop => prop.key && prop.value && !(prop.node.type === 'Property' && prop.node.shorthand))
 
-      const propsWithoutDefault = findPropsWithoutDefaultValue(props)
+      const propsWithoutDefault = findPropsWithoutDefaultValue(/** @type {ComponentObjectProp[]} */(props))
       const propsToReport = excludeBooleanProps(propsWithoutDefault)
 
       for (const prop of propsToReport) {
diff --git a/lib/utils/index.js b/lib/utils/index.js
index 184ebbe3f..4b87728a0 100644
--- a/lib/utils/index.js
+++ b/lib/utils/index.js
@@ -5,6 +5,23 @@
  */
 'use strict'
 
+/**
+ * @typedef {import('vue-eslint-parser').AST.ESLintArrayExpression} ArrayExpression
+ * @typedef {import('vue-eslint-parser').AST.ESLintExpression} Expression
+ * @typedef {import('vue-eslint-parser').AST.ESLintIdentifier} Identifier
+ * @typedef {import('vue-eslint-parser').AST.ESLintLiteral} Literal
+ * @typedef {import('vue-eslint-parser').AST.ESLintMemberExpression} MemberExpression
+ * @typedef {import('vue-eslint-parser').AST.ESLintMethodDefinition} MethodDefinition
+ * @typedef {import('vue-eslint-parser').AST.ESLintObjectExpression} ObjectExpression
+ * @typedef {import('vue-eslint-parser').AST.ESLintProperty} Property
+ * @typedef {import('vue-eslint-parser').AST.ESLintTemplateLiteral} TemplateLiteral
+ */
+
+/**
+ * @typedef { {key: Literal | null, value: null, node: ArrayExpression['elements'][0], propName: string} } ComponentArrayProp
+ * @typedef { {key: Property['key'], value: Property['value'], node: Property, propName: string} } ComponentObjectProp
+ */
+
 // ------------------------------------------------------------------------------
 // Helpers
 // ------------------------------------------------------------------------------
@@ -382,7 +399,7 @@ module.exports = {
 
   /**
    * Gets the property name of a given node.
-   * @param {ASTNode} node - The node to get.
+   * @param {Property|MethodDefinition|MemberExpression|Literal|TemplateLiteral|Identifier} node - The node to get.
    * @return {string|null} The property name if static. Otherwise, null.
    */
   getStaticPropertyName (node) {
@@ -425,7 +442,7 @@ module.exports = {
   /**
    * Get all props by looking at all component's properties
    * @param {ObjectExpression} componentObject Object with component definition
-   * @return {Array} Array of component props in format: [{key?: String, value?: ASTNode, node: ASTNod}]
+   * @return {(ComponentArrayProp | ComponentObjectProp)[]} Array of component props in format: [{key?: String, value?: ASTNode, node: ASTNod}]
    */
   getComponentProps (componentObject) {
     const propsNode = componentObject.properties
@@ -844,8 +861,9 @@ module.exports = {
 
   /**
    * Unwrap typescript types like "X as F"
-   * @param {ASTNode} node
-   * @return {ASTNode}
+   * @template T
+   * @param {T} node
+   * @return {T}
    */
   unwrapTypes (node) {
     return node.type === 'TSAsExpression' ? node.expression : node


Test patch:
diff --git a/tests/lib/rules/require-default-prop.js b/tests/lib/rules/require-default-prop.js
index 62ee53342..b544921bd 100644
--- a/tests/lib/rules/require-default-prop.js
+++ b/tests/lib/rules/require-default-prop.js
@@ -153,6 +153,33 @@ ruleTester.run('require-default-prop', rule, {
           }
         }
       `
+    },
+    {
+      // https://github.com/vuejs/eslint-plugin-vue/issues/1040
+      filename: 'destructuring-test.vue',
+      code: `
+        export default {
+          props: {
+            foo: {
+              ...foo,
+              default: 0
+            },
+          }
+        }
+      `
+    },
+    {
+      filename: 'unknown-prop-details-test.vue',
+      code: `
+        export default {
+          props: {
+            foo: {
+              [bar]: true,
+              default: 0
+            },
+          }
+        }
+      `
     }
   ],
 
@@ -282,6 +309,33 @@ ruleTester.run('require-default-prop', rule, {
         message: `Prop '[baz.baz]' requires default value to be set.`,
         line: 6
       }]
+    },
+    {
+      // https://github.com/vuejs/eslint-plugin-vue/issues/1040
+      filename: 'destructuring-test.vue',
+      code: `
+        export default {
+          props: {
+            foo: {
+              ...foo
+            },
+          }
+        }
+      `,
+      errors: ['Prop \'foo\' requires default value to be set.']
+    },
+    {
+      filename: 'unknown-prop-details-test.vue',
+      code: `
+        export default {
+          props: {
+            foo: {
+              [bar]: true
+            },
+          }
+        }
+      `,
+      errors: ['Prop \'foo\' requires default value to be set.']
     }
   ]
 })