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

Issue:
`no-reserved-component-names` crash on visiting `component.data`
<!--
  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.0
* **Node version:**	12.14.0

**Please show your full configuration:**
<!-- Paste content of your .eslintrc file -->
```js
module.exports = {
  root: true,
  parser: require.resolve('vue-eslint-parser'),
  parserOptions: {
    parser: 'babel-eslint',
  },
  plugins: ['vue'],
  rules: {
    'vue/no-reserved-component-names': 'error',
  },
};
```

**What did you do?**	
<!-- Please include the actual source code causing the issue. -->
```vue
<script>
function fn1() {}
function fn2(component) {
  fn1(component.data);
}

export default {};
</script>
```

**What did you expect to happen?**


**What actually happened?**
<!-- Please include the actual, raw output from ESLint. -->

ESLint crashed.

```text
TypeError: Cannot read property 'type' of undefined
Occurred while linting path/to/vue-file.vue:86
    at Object.unwrapTypes (<CWD>node_modules\eslint-plugin-vue\lib\utils\index.js:827:17)
    at CallExpression > MemberExpression > Identifier[name='component'] (<CWD>node_modules\eslint-plugin-vue\lib\rules\no-reserved-component-names.js:92:38)
    at listeners.(anonymous function).forEach.listener (<CWD>node_modules\eslint\lib\linter\safe-emitter.js:45:58)
    at Array.forEach (<anonymous>)
```


Pull request:
Fixed crashes of `no-reserved-component-names`, `match-component-file-name` and `component-definition-name-casing` rules.
fixed #1018

Patch:
diff --git a/lib/rules/component-definition-name-casing.js b/lib/rules/component-definition-name-casing.js
index 8860c087..9713cca2 100644
--- a/lib/rules/component-definition-name-casing.js
+++ b/lib/rules/component-definition-name-casing.js
@@ -73,21 +73,15 @@ module.exports = {
     }
 
     return Object.assign({},
-      {
-        "CallExpression > MemberExpression > Identifier[name='component']" (node) {
-          const parent = node.parent.parent
-          const calleeObject = utils.unwrapTypes(parent.callee.object)
+      utils.executeOnCallVueComponent(context, (node) => {
+        if (node.arguments.length === 2) {
+          const argument = node.arguments[0]
 
-          if (calleeObject.type === 'Identifier' && calleeObject.name === 'Vue') {
-            if (parent.arguments && parent.arguments.length === 2) {
-              const argument = parent.arguments[0]
-              if (canConvert(argument)) {
-                convertName(argument)
-              }
-            }
+          if (canConvert(argument)) {
+            convertName(argument)
           }
         }
-      },
+      }),
       utils.executeOnVue(context, (obj) => {
         const node = obj.properties
           .find(item => (
diff --git a/lib/rules/match-component-file-name.js b/lib/rules/match-component-file-name.js
index 7eac356a..ae060c44 100644
--- a/lib/rules/match-component-file-name.js
+++ b/lib/rules/match-component-file-name.js
@@ -101,21 +101,15 @@ module.exports = {
     }
 
     return Object.assign({},
-      {
-        "CallExpression > MemberExpression > Identifier[name='component']" (node) {
-          const parent = node.parent.parent
-          const calleeObject = utils.unwrapTypes(parent.callee.object)
-
-          if (calleeObject.type === 'Identifier' && calleeObject.name === 'Vue') {
-            if (parent.arguments && parent.arguments.length === 2) {
-              const argument = parent.arguments[0]
-              if (canVerify(argument)) {
-                verifyName(argument)
-              }
-            }
+      utils.executeOnCallVueComponent(context, (node) => {
+        if (node.arguments.length === 2) {
+          const argument = node.arguments[0]
+
+          if (canVerify(argument)) {
+            verifyName(argument)
           }
         }
-      },
+      }),
       utils.executeOnVue(context, (object) => {
         const node = object.properties
           .find(item => (
diff --git a/lib/rules/no-reserved-component-names.js b/lib/rules/no-reserved-component-names.js
index c08b34f5..d65a36cc 100644
--- a/lib/rules/no-reserved-component-names.js
+++ b/lib/rules/no-reserved-component-names.js
@@ -86,24 +86,15 @@ module.exports = {
     }
 
     return Object.assign({},
-      {
-        "CallExpression > MemberExpression > Identifier[name='component']" (node) {
-          const parent = node.parent.parent
-          const calleeObject = utils.unwrapTypes(parent.callee.object)
+      utils.executeOnCallVueComponent(context, (node) => {
+        if (node.arguments.length === 2) {
+          const argument = node.arguments[0]
 
-          if (calleeObject.type === 'Identifier' &&
-              calleeObject.name === 'Vue' &&
-              parent.arguments &&
-              parent.arguments.length === 2
-          ) {
-            const argument = parent.arguments[0]
-
-            if (canVerify(argument)) {
-              reportIfInvalid(argument)
-            }
+          if (canVerify(argument)) {
+            reportIfInvalid(argument)
           }
         }
-      },
+      }),
       utils.executeOnVue(context, (obj) => {
         // Report if a component has been registered locally with a reserved name.
         utils.getRegisteredComponents(obj)
diff --git a/lib/utils/index.js b/lib/utils/index.js
index 6dfb4460..184ebbe3 100644
--- a/lib/utils/index.js
+++ b/lib/utils/index.js
@@ -635,6 +635,30 @@ module.exports = {
     }
   },
 
+  /**
+   * Check call `Vue.component` and call callback.
+   * @param {RuleContext} _context The ESLint rule context object.
+   * @param {Function} cb Callback function
+   */
+  executeOnCallVueComponent (_context, cb) {
+    return {
+      "CallExpression > MemberExpression > Identifier[name='component']": (node) => {
+        const callExpr = node.parent.parent
+        const callee = callExpr.callee
+
+        if (callee.type === 'MemberExpression') {
+          const calleeObject = this.unwrapTypes(callee.object)
+
+          if (calleeObject.type === 'Identifier' &&
+            calleeObject.name === 'Vue' &&
+            callee.property === node &&
+            callExpr.arguments.length >= 1) {
+            cb(callExpr)
+          }
+        }
+      }
+    }
+  },
   /**
    * Return generator with all properties
    * @param {ASTNode} node Node to check


Test patch:
diff --git a/tests/lib/rules/component-definition-name-casing.js b/tests/lib/rules/component-definition-name-casing.js
index f1bcf538..98cb4367 100644
--- a/tests/lib/rules/component-definition-name-casing.js
+++ b/tests/lib/rules/component-definition-name-casing.js
@@ -136,6 +136,12 @@ ruleTester.run('component-definition-name-casing', rule, {
       code: `Vue.component(\`fooBar\${foo}\`, component)`,
       options: ['kebab-case'],
       parserOptions
+    },
+    // https://github.com/vuejs/eslint-plugin-vue/issues/1018
+    {
+      filename: 'test.js',
+      code: `fn1(component.data)`,
+      parserOptions
     }
   ],
 
diff --git a/tests/lib/rules/match-component-file-name.js b/tests/lib/rules/match-component-file-name.js
index 2b60db9a..d8a1b141 100644
--- a/tests/lib/rules/match-component-file-name.js
+++ b/tests/lib/rules/match-component-file-name.js
@@ -531,6 +531,12 @@ ruleTester.run('match-component-file-name', rule, {
       `,
       options: [{ shouldMatchCase: true }],
       parserOptions: jsxParserOptions
+    },
+    // https://github.com/vuejs/eslint-plugin-vue/issues/1018
+    {
+      filename: 'test.jsx',
+      code: `fn1(component.data)`,
+      parserOptions
     }
   ],
 
diff --git a/tests/lib/rules/no-reserved-component-names.js b/tests/lib/rules/no-reserved-component-names.js
index 0b5bed15..43a34f15 100644
--- a/tests/lib/rules/no-reserved-component-names.js
+++ b/tests/lib/rules/no-reserved-component-names.js
@@ -311,6 +311,12 @@ ruleTester.run('no-reserved-component-names', rule, {
       `,
       parser: require.resolve('vue-eslint-parser'),
       parserOptions
+    },
+    // https://github.com/vuejs/eslint-plugin-vue/issues/1018
+    {
+      filename: 'test.js',
+      code: `fn1(component.data)`,
+      parserOptions
     }
   ],