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

Issue:
`vue/no-unused-emit-declarations` would throw Error when using dynamic render
<!--
  ❗Please don't ignore this template.❗

  If you ignore it, we're just going to respond asking you to fill it out, which wastes everyone's time.
  The more relevant information you can include, the faster we can find the issue and fix it without asking you for more info.
-->

<!--
  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
-->

**Checklist**

- [x] I have tried restarting my IDE and the issue persists.
- [x] I have read the [FAQ](https://eslint.vuejs.org/user-guide/#faq) and my problem is not listed.
<!-- If you do not read the FAQ and open an issue that is listed in the FAQ, we may silently close the issue. -->

**Tell us about your environment**

- **ESLint version:** 8.56.0
- **eslint-plugin-vue version:** 9.21.0
- **Vue version:** 3.4.15
- **Node version:** 18.18.2
- **Operating System:** MacOS

**Please show your full configuration:**
<!-- Paste content of your .eslintrc file -->
```js
module.exports = {
  root: true,
  extends: ['plugin:vue/vue3-essential', 'eslint:recommended'],
  parserOptions: {
    ecmaVersion: 'latest',
  },
  rules: {
    'vue/no-unused-emit-declarations': 'error',
  },
}
```

**What did you do?**	
<!-- Please include the actual source code causing the issue. -->
```vue
<script>
import { defineComponent, h } from 'vue'

export default defineComponent({
  emits: ['update'],

  setup(_, ctx) {
    return () =>
      h(
        'button',
        {
          type: 'button',
          onClick: () => ctx.emit('update'),
        },
        'Click me!',
      )
  },
})
</script>
```

**What did you expect to happen?**

Lint works well.

**What actually happened?**
<!--
  Please include the actual, raw output from ESLint.
  If you are only looking at the results of your editor extension, also check the CLI results.
-->

```
TypeError: Cannot read properties of undefined (reading 'name')
Occurred while linting /Users/ntnyq/Desktop/github/ntnyq/repro-eslint-plugin-vue/src/VButton.vue:9
Rule: "vue/no-unused-emit-declarations"
    at isEmitVariableName (/Users/ntnyq/Desktop/github/ntnyq/repro-eslint-plugin-vue/node_modules/.pnpm/eslint-plugin-vue@9.21.0_eslint@8.56.0/node_modules/eslint-plugin-vue/lib/rules/no-unused-emit-declarations.js:66:62)
    at Object.CallExpression (/Users/ntnyq/Desktop/github/ntnyq/repro-eslint-plugin-vue/node_modules/.pnpm/eslint-plugin-vue@9.21.0_eslint@8.56.0/node_modules/eslint-plugin-vue/lib/rules/no-unused-emit-declarations.js:209:13)
    at callVisitor (/Users/ntnyq/Desktop/github/ntnyq/repro-eslint-plugin-vue/node_modules/.pnpm/eslint-plugin-vue@9.21.0_eslint@8.56.0/node_modules/eslint-plugin-vue/lib/utils/index.js:1215:21)
    at vueVisitor.<computed> (/Users/ntnyq/Desktop/github/ntnyq/repro-eslint-plugin-vue/node_modules/.pnpm/eslint-plugin-vue@9.21.0_eslint@8.56.0/node_modules/eslint-plugin-vue/lib/utils/index.js:1222:35)
    at ruleErrorHandler (/Users/ntnyq/Desktop/github/ntnyq/repro-eslint-plugin-vue/node_modules/.pnpm/eslint@8.56.0/node_modules/eslint/lib/linter/linter.js:1076:28)
    at /Users/ntnyq/Desktop/github/ntnyq/repro-eslint-plugin-vue/node_modules/.pnpm/eslint@8.56.0/node_modules/eslint/lib/linter/safe-emitter.js:45:58
    at Array.forEach (<anonymous>)
    at Object.emit (/Users/ntnyq/Desktop/github/ntnyq/repro-eslint-plugin-vue/node_modules/.pnpm/eslint@8.56.0/node_modules/eslint/lib/linter/safe-emitter.js:45:38)
    at NodeEventGenerator.applySelector (/Users/ntnyq/Desktop/github/ntnyq/repro-eslint-plugin-vue/node_modules/.pnpm/eslint@8.56.0/node_modules/eslint/lib/linter/node-event-generator.js:297:26)
    at NodeEventGenerator.applySelectors (/Users/ntnyq/Desktop/github/ntnyq/repro-eslint-plugin-vue/node_modules/.pnpm/eslint@8.56.0/node_modules/eslint/lib/linter/node-event-generator.js:326:22)
```

**Repository to reproduce this issue**
<!--
  ❗ Please share a repository that can reproduce your issue.
  ❗ If you don't share it, we just label it as requesting a repository that can reproduce your issue.
  ❗ If you don't share it for a few days, we silently close this issue.
-->

https://github.com/ntnyq-dev/repro-no-unused-emit-declarations

**Additional text**

https://github.com/vuejs/eslint-plugin-vue/pull/2340 introduced a function `isEmitVariableName`.

`emitReferenceIds` may be an empty Set.


Discussion:

Patch:
diff --git a/lib/rules/no-unused-emit-declarations.js b/lib/rules/no-unused-emit-declarations.js
index 1a88d2417..146438935 100644
--- a/lib/rules/no-unused-emit-declarations.js
+++ b/lib/rules/no-unused-emit-declarations.js
@@ -63,6 +63,7 @@ function hasReferenceId(value, setupContext) {
  * @returns {boolean}
  */
 function isEmitVariableName(name, emitReferenceIds) {
+  if (emitReferenceIds.size === 0) return false
   const emitVariable = emitReferenceIds.values().next().value.name
   return emitVariable === name
 }


Test patch:
diff --git a/tests/lib/rules/no-unused-emit-declarations.js b/tests/lib/rules/no-unused-emit-declarations.js
index 17b2f441b..cccbcbb98 100644
--- a/tests/lib/rules/no-unused-emit-declarations.js
+++ b/tests/lib/rules/no-unused-emit-declarations.js
@@ -369,6 +369,19 @@ tester.run('no-unused-emit-declarations', rule, {
       const m = defineModel('foo')
       </script>
       `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: ['foo'],
+          setup(_, ctx) {
+            return () => h('button', { onClick: () => ctx.emit('foo') })
+          }
+        }
+      </script>
+      `
     }
   ],
   invalid: [
@@ -750,6 +763,27 @@ tester.run('no-unused-emit-declarations', rule, {
           line: 3
         }
       ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        export default {
+          emits: ['foo'],
+          setup(_, ctx) {
+            return () => h('button')
+          }
+        }
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'unused',
+          line: 4,
+          column: 19,
+          endColumn: 24
+        }
+      ]
     }
   ]
 })