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

Issue:
`vue/component-name-in-template-casing`: ignore `<component>` built-in element
<!--
  Before proposing rule changes, please make sure it hasn't been posted already.
  You can see all open propositions here:
  https://github.com/vuejs/eslint-plugin-vue/issues?q=is%3Aopen+is%3Aissue+label%3A%22new+rule+proposition%22
-->

**What rule do you want to change?**

- `vue/component-name-in-template-casing`

**Does this change cause the rule to produce more or fewer warnings?**

It will provide one fewer warning/error, because it will handle an edge-case.

**How will the change be implemented? (New option, new default behavior, etc.)?**

The change will treat `<component>` as an element, not a component.

The `vue/component-name-in-template-casing` rule enforces pascal-cased component names. This will also enforce `<component>` to be written as `<Component>`, which is not correct. Technically, `<component>` is not a component, but an element.

**Please provide some example code that this change will affect:**

<!-- Put your code examples here -->
The following code will be valid:
```vue
<template>
  <component is="div" />
</template>
```

**What does the rule currently do for this code?**

It will enforce `<component>` to be pascal-cased as `<Component>`:

```vue
<template>
  <Component is="div" />
</template>
```

**What will the rule do after it's changed?**

- Keep `<component>` as-is.

**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->


As the [Vue docs state](https://vuejs.org/api/built-in-special-elements.html):

> [!NOTE]
> `<component>`, `<slot>` and `<template>` are component-like features and part of the template syntax. They are not true components and are compiled away during template compilation. As such, they are conventionally written with lowercase in templates.

The issue came up working with [`@antfu/eslint-config`](https://github.com/antfu/eslint-config). 
Related PR:
- https://github.com/antfu/eslint-config/pull/291


Discussion:

Patch:
diff --git a/lib/rules/component-name-in-template-casing.js b/lib/rules/component-name-in-template-casing.js
index fc1849b18..487af7714 100644
--- a/lib/rules/component-name-in-template-casing.js
+++ b/lib/rules/component-name-in-template-casing.js
@@ -123,7 +123,8 @@ module.exports = {
       if (
         (!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) ||
         utils.isHtmlWellKnownElementName(node.rawName) ||
-        utils.isSvgWellKnownElementName(node.rawName)
+        utils.isSvgWellKnownElementName(node.rawName) ||
+        utils.isVueBuiltInElementName(node.rawName)
       ) {
         return false
       }
diff --git a/lib/utils/index.js b/lib/utils/index.js
index 8702c6260..22119ae98 100644
--- a/lib/utils/index.js
+++ b/lib/utils/index.js
@@ -54,6 +54,7 @@ const VUE2_BUILTIN_COMPONENT_NAMES = new Set(
 const VUE3_BUILTIN_COMPONENT_NAMES = new Set(
   require('./vue3-builtin-components')
 )
+const VUE_BUILTIN_ELEMENT_NAMES = new Set(require('./vue-builtin-elements'))
 const path = require('path')
 const vueEslintParser = require('vue-eslint-parser')
 const { traverseNodes, getFallbackKeys, NS } = vueEslintParser.AST
@@ -867,6 +868,15 @@ module.exports = {
     )
   },
 
+  /**
+   * Check whether the given name is Vue builtin element name or not.
+   * @param {string} name The name to check.
+   * @returns {boolean} `true` if the name is a builtin Vue element name
+   */
+  isVueBuiltInElementName(name) {
+    return VUE_BUILTIN_ELEMENT_NAMES.has(name.toLowerCase())
+  },
+
   /**
    * Check whether the given name is Vue builtin directive name or not.
    * @param {string} name The name to check.
diff --git a/lib/utils/vue-builtin-elements.js b/lib/utils/vue-builtin-elements.js
new file mode 100644
index 000000000..70ddb6b7d
--- /dev/null
+++ b/lib/utils/vue-builtin-elements.js
@@ -0,0 +1,1 @@
+module.exports = ['template', 'slot', 'component']


Test patch:
diff --git a/tests/lib/rules/component-name-in-template-casing.js b/tests/lib/rules/component-name-in-template-casing.js
index 3aa4ee43d..992063808 100644
--- a/tests/lib/rules/component-name-in-template-casing.js
+++ b/tests/lib/rules/component-name-in-template-casing.js
@@ -86,6 +86,10 @@ tester.run('component-name-in-template-casing', rule, {
       code: '<template><circle cx="0" cy="0" :d="radius"></template>',
       options: ['PascalCase', { registeredComponentsOnly: false }]
     },
+    {
+      code: '<template><component is="div" /></template>',
+      options: ['PascalCase', { registeredComponentsOnly: false }]
+    },
 
     // kebab-case
     {
@@ -108,6 +112,10 @@ tester.run('component-name-in-template-casing', rule, {
       code: '<template><math><mspace/></math></template>',
       options: ['kebab-case', { registeredComponentsOnly: false }]
     },
+    {
+      code: '<template><component is="div" /></template>',
+      options: ['kebab-case', { registeredComponentsOnly: false }]
+    },
 
     // ignores
     {
@@ -859,7 +867,7 @@ tester.run('component-name-in-template-casing', rule, {
       `,
       output: `
         <template>
-          <Component />
+          <component />
           <Suspense />
           <Teleport />
           <ClientOnly />
@@ -868,7 +876,6 @@ tester.run('component-name-in-template-casing', rule, {
       `,
       options: ['PascalCase', { registeredComponentsOnly: false }],
       errors: [
-        'Component name "component" is not PascalCase.',
         'Component name "suspense" is not PascalCase.',
         'Component name "teleport" is not PascalCase.',
         'Component name "client-only" is not PascalCase.',
@@ -1025,7 +1032,7 @@ tester.run('component-name-in-template-casing', rule, {
                 <HelloWorld3 />
                 <HelloWorld4 />
                 <HelloWorld5 />
-                <Component />
+                <component />
               </template>
             `,
             errors: [
@@ -1058,11 +1065,6 @@ tester.run('component-name-in-template-casing', rule, {
                 message: 'Component name "hello-world5" is not PascalCase.',
                 line: 18,
                 column: 17
-              },
-              {
-                message: 'Component name "component" is not PascalCase.',
-                line: 19,
-                column: 17
               }
             ]
           }