Repository: vuejs/eslint-plugin-vue
Pull request #2162 (2023-05-13 01:43:42)

Add support for `defineOptions` to `vue/no-restricted-component-options` rule
Related to https://github.com/vuejs/eslint-plugin-vue/issues/2118


Patch:
diff --git a/docs/rules/valid-v-is.md b/docs/rules/valid-v-is.md
index 6eda3d99..0ef75aac 100644
--- a/docs/rules/valid-v-is.md
+++ b/docs/rules/valid-v-is.md
@@ -59,7 +59,7 @@ Nothing.
 
 ## :books: Further Reading
 
-- [API - v-is](https://vuejs.org/api/built-in-directives.html#v-is)
+- [API - v-is (Recent)](https://github.com/vuejs/docs/blob/8b4f11a4e94d01c7f1c91a60ceaa5b89d6b6de9f/src/api/built-in-directives.md#v-is-)
 - [API - v-is (Old)](https://github.com/vuejs/docs-next/blob/008613756c3d781128d96b64a2d27f7598f8f548/src/api/directives.md#v-is)
 
 ## :rocket: Version
diff --git a/lib/rules/component-definition-name-casing.js b/lib/rules/component-definition-name-casing.js
index e227d4b9..c3a5cbc3 100644
--- a/lib/rules/component-definition-name-casing.js
+++ b/lib/rules/component-definition-name-casing.js
@@ -77,8 +77,7 @@ module.exports = {
       }
     }
 
-    return Object.assign(
-      {},
+    return utils.compositingVisitors(
       utils.executeOnCallVueComponent(context, (node) => {
         if (node.arguments.length === 2) {
           const argument = node.arguments[0]
@@ -94,6 +93,17 @@ module.exports = {
         if (!node) return
         if (!canConvert(node.value)) return
         convertName(node.value)
+      }),
+      utils.defineScriptSetupVisitor(context, {
+        onDefineOptionsEnter(node) {
+          if (node.arguments.length === 0) return
+          const define = node.arguments[0]
+          if (define.type !== 'ObjectExpression') return
+          const nameNode = utils.findProperty(define, 'name')
+          if (!nameNode) return
+          if (!canConvert(nameNode.value)) return
+          convertName(nameNode.value)
+        }
       })
     )
   }
diff --git a/lib/rules/match-component-file-name.js b/lib/rules/match-component-file-name.js
index 70c34009..f30a6b50 100644
--- a/lib/rules/match-component-file-name.js
+++ b/lib/rules/match-component-file-name.js
@@ -119,8 +119,7 @@ module.exports = {
       }
     }
 
-    return Object.assign(
-      {},
+    return utils.compositingVisitors(
       utils.executeOnCallVueComponent(context, (node) => {
         if (node.arguments.length === 2) {
           const argument = node.arguments[0]
@@ -139,6 +138,18 @@ module.exports = {
         if (!canVerify(node.value)) return
         verifyName(node.value)
       }),
+      utils.defineScriptSetupVisitor(context, {
+        onDefineOptionsEnter(node) {
+          componentCount++
+          if (node.arguments.length === 0) return
+          const define = node.arguments[0]
+          if (define.type !== 'ObjectExpression') return
+          const nameNode = utils.findProperty(define, 'name')
+          if (!nameNode) return
+          if (!canVerify(nameNode.value)) return
+          verifyName(nameNode.value)
+        }
+      }),
       {
         'Program:exit'() {
           if (componentCount > 1) return
diff --git a/lib/rules/no-restricted-component-options.js b/lib/rules/no-restricted-component-options.js
index f50f091d..5af68053 100644
--- a/lib/rules/no-restricted-component-options.js
+++ b/lib/rules/no-restricted-component-options.js
@@ -173,13 +173,25 @@ module.exports = {
     /** @type {ParsedOption[]} */
     const options = context.options.map(parseOption)
 
-    return utils.defineVueVisitor(context, {
-      onVueObjectEnter(node) {
-        for (const option of options) {
-          verify(node, option.test, option.message)
+    return utils.compositingVisitors(
+      utils.defineVueVisitor(context, {
+        onVueObjectEnter(node) {
+          for (const option of options) {
+            verify(node, option.test, option.message)
+          }
         }
-      }
-    })
+      }),
+      utils.defineScriptSetupVisitor(context, {
+        onDefineOptionsEnter(node) {
+          if (node.arguments.length === 0) return
+          const define = node.arguments[0]
+          if (define.type !== 'ObjectExpression') return
+          for (const option of options) {
+            verify(define, option.test, option.message)
+          }
+        }
+      })
+    )
 
     /**
      * @param {ObjectExpression} node
diff --git a/lib/rules/order-in-components.js b/lib/rules/order-in-components.js
index cf9dba9d..50e27062 100644
--- a/lib/rules/order-in-components.js
+++ b/lib/rules/order-in-components.js
@@ -334,8 +334,18 @@ module.exports = {
       }
     }
 
-    return utils.executeOnVue(context, (obj) => {
-      checkOrder(obj.properties)
-    })
+    return utils.compositingVisitors(
+      utils.executeOnVue(context, (obj) => {
+        checkOrder(obj.properties)
+      }),
+      utils.defineScriptSetupVisitor(context, {
+        onDefineOptionsEnter(node) {
+          if (node.arguments.length === 0) return
+          const define = node.arguments[0]
+          if (define.type !== 'ObjectExpression') return
+          checkOrder(define.properties)
+        }
+      })
+    )
   }
 }


Test patch:
diff --git a/tests/lib/rules/component-definition-name-casing.js b/tests/lib/rules/component-definition-name-casing.js
index ef9cbde8..2349f5d3 100644
--- a/tests/lib/rules/component-definition-name-casing.js
+++ b/tests/lib/rules/component-definition-name-casing.js
@@ -145,6 +145,26 @@ ruleTester.run('component-definition-name-casing', rule, {
       filename: 'test.js',
       code: `fn1(component.data)`,
       parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `<script setup> defineOptions({}) </script>`,
+      parser: require.resolve('vue-eslint-parser'),
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `<script setup> defineOptions({name: 'FooBar'}) </script>`,
+      options: ['PascalCase'],
+      parser: require.resolve('vue-eslint-parser'),
+      parserOptions
+    },
+    {
+      filename: 'test.vue',
+      code: `<script setup> defineOptions({name: 'foo-bar'}) </script>`,
+      options: ['kebab-case'],
+      parser: require.resolve('vue-eslint-parser'),
+      parserOptions
     }
   ],
 
@@ -393,6 +413,34 @@ ruleTester.run('component-definition-name-casing', rule, {
           line: 1
         }
       ]
+    },
+    {
+      filename: 'test.vue',
+      code: `<script setup> defineOptions({name: 'foo-bar'}) </script>`,
+      output: `<script setup> defineOptions({name: 'FooBar'}) </script>`,
+      options: ['PascalCase'],
+      parser: require.resolve('vue-eslint-parser'),
+      parserOptions,
+      errors: [
+        {
+          message: 'Property name "foo-bar" is not PascalCase.',
+          line: 1
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `<script setup> defineOptions({name: 'FooBar'}) </script>`,
+      output: `<script setup> defineOptions({name: 'foo-bar'}) </script>`,
+      options: ['kebab-case'],
+      parser: require.resolve('vue-eslint-parser'),
+      parserOptions,
+      errors: [
+        {
+          message: 'Property name "FooBar" is not kebab-case.',
+          line: 1
+        }
+      ]
     }
   ]
 })
diff --git a/tests/lib/rules/match-component-file-name.js b/tests/lib/rules/match-component-file-name.js
index cbd35713..82c75ccc 100644
--- a/tests/lib/rules/match-component-file-name.js
+++ b/tests/lib/rules/match-component-file-name.js
@@ -539,6 +539,13 @@ ruleTester.run('match-component-file-name', rule, {
       filename: 'test.jsx',
       code: `fn1(component.data)`,
       parserOptions
+    },
+    {
+      filename: 'MyComponent.vue',
+      code: `<script setup> defineOptions({name: 'MyComponent'}) </script>`,
+      options: [{ extensions: ['vue'] }],
+      parser: require.resolve('vue-eslint-parser'),
+      parserOptions
     }
   ],
 
@@ -1081,6 +1088,25 @@ ruleTester.run('match-component-file-name', rule, {
           ]
         }
       ]
+    },
+    {
+      filename: 'MyComponent.vue',
+      code: `<script setup> defineOptions({name: 'CoolComponent'}) </script>`,
+      options: [{ extensions: ['vue'] }],
+      parser: require.resolve('vue-eslint-parser'),
+      parserOptions,
+      errors: [
+        {
+          message:
+            'Component name `CoolComponent` should match file name `MyComponent`.',
+          suggestions: [
+            {
+              desc: 'Rename component to match file name.',
+              output: `<script setup> defineOptions({name: 'MyComponent'}) </script>`
+            }
+          ]
+        }
+      ]
     }
   ]
 })
diff --git a/tests/lib/rules/no-restricted-component-options.js b/tests/lib/rules/no-restricted-component-options.js
index 87ff4e41..484f6700 100644
--- a/tests/lib/rules/no-restricted-component-options.js
+++ b/tests/lib/rules/no-restricted-component-options.js
@@ -101,6 +101,11 @@ tester.run('no-restricted-component-options', rule, {
       </script>
       `,
       options: [['foo', 'bar']]
+    },
+    {
+      filename: 'test.vue',
+      code: `<script setup> defineOptions({ name: 'Foo' }) </script>`,
+      options: ['Foo']
     }
   ],
   invalid: [
@@ -304,6 +309,17 @@ tester.run('no-restricted-component-options', rule, {
           line: 5
         }
       ]
+    },
+    {
+      filename: 'test.vue',
+      code: `<script setup> defineOptions({ Foo: 'Foo' }) </script>`,
+      options: ['Foo'],
+      errors: [
+        {
+          message: 'Using `Foo` is not allowed.',
+          line: 1
+        }
+      ]
     }
   ]
 })
diff --git a/tests/lib/rules/order-in-components.js b/tests/lib/rules/order-in-components.js
index 873df3b0..b12c41a3 100644
--- a/tests/lib/rules/order-in-components.js
+++ b/tests/lib/rules/order-in-components.js
@@ -165,6 +165,19 @@ ruleTester.run('order-in-components', rule, {
         new Vue()
       `,
       parserOptions: { ecmaVersion: 6 }
+    },
+    {
+      filename: 'example.vue',
+      code: `
+      <script setup>
+        defineOptions({
+          name: 'Foo',
+          inheritAttrs: true,
+        })
+      </script>
+      `,
+      parser: require.resolve('vue-eslint-parser'),
+      parserOptions
     }
   ],
 
@@ -943,6 +956,34 @@ ruleTester.run('order-in-components', rule, {
           line: 5
         }
       ]
+    },
+    {
+      filename: 'example.vue',
+      code: `
+      <script setup>
+        defineOptions({
+          inheritAttrs: true,
+          name: 'Foo',
+        })
+      </script>
+      `,
+      parser: require.resolve('vue-eslint-parser'),
+      parserOptions,
+      output: `
+      <script setup>
+        defineOptions({
+          name: 'Foo',
+          inheritAttrs: true,
+        })
+      </script>
+      `,
+      errors: [
+        {
+          message:
+            'The "name" property should be above the "inheritAttrs" property on line 4.',
+          line: 5
+        }
+      ]
     }
   ]
 })