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

Issue:
disallow importing macros: defineProps etc
It it worth tracking approved but closed #1906 as a new issue here?

Example playground that includes [vue/prefer-import-from-vue rule](https://eslint.vuejs.org/rules/prefer-import-from-vue.html) enabled: [link](https://ota-meshi.github.io/eslint-plugin-vue-demo/#eJxNzsEKgzAMxvFXCTltsOG9dGWPsAfIRTRKQdPSRmGUvvsq7uAx/D/4peAQRkaDVnmNS6/sSADs6HdXCkwhQK22O04S211GNg/JR4XMukVYeplfhJoJW/NrDEmhwMiTF/6kEDNUmFJYgXDfmJDk0mw50IYZyJq8zCTV3e6HeCoOHxj7lDmhKU1pD7/1G/msT86LF+3+i1p/8rNMrg==)

This playground code triggers a compiler message about the macro import:

```
[@vue/compiler-sfc] `defineProps` is a compiler macro and no longer needs to be imported.
```

> **Please describe what the rule should do:** It should warn if defineProps, defineEmits and similar are imported from vue (autofixable)
> 
> **What category should the rule belong to?**
> 
>   [x] Enforces code style (layout)
>   [ ] Warns about a potential error (problem)
>   [ ] Suggests an alternate way of doing something (suggestion)
>   [ ] Other (please specify:)
> 
> **Provide 2-3 code examples that this rule should warn about:**
> 
> bad:
> 
> ```
> <script setup>
> import { defineProps } from '@vue/runtime-dom';
> 
> defineProps<{}>();
> </script>
> ```
> 
> good:
> 
> ```
> <script setup>
> defineProps<{}>();
> </script>
> ```
> 
> good:
> 
> ```
> <script setup>
> import { defineProps } from 'my-lib';
> 
> defineProps('whatever');
> ```
> 
> **Additional context** Useful when `define*` are added to eslint globals, there's no need to import them manually. If project was using imports and then dev switched to globals then autofix would just automatically remove unnecessary imports



Pull request:
feat: add `vue/no-import-compiler-macros` rule
resolve #2437

Patch:
diff --git a/docs/rules/index.md b/docs/rules/index.md
index f170f803..4fea4705 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -233,6 +233,7 @@ For example:
 | [vue/no-deprecated-model-definition] | disallow deprecated `model` definition (in Vue.js 3.0.0+) | :bulb: | :warning: |
 | [vue/no-duplicate-attr-inheritance] | enforce `inheritAttrs` to be set to `false` when using `v-bind="$attrs"` |  | :hammer: |
 | [vue/no-empty-component-block] | disallow the `<template>` `<script>` `<style>` block to be empty | :wrench: | :hammer: |
+| [vue/no-import-compiler-macros] | disallow importing Vue compiler macros | :wrench: | :warning: |
 | [vue/no-multiple-objects-in-class] | disallow passing multiple objects in an array to class |  | :hammer: |
 | [vue/no-potential-component-option-typo] | disallow a potential typo in your component property | :bulb: | :hammer: |
 | [vue/no-ref-object-reactivity-loss] | disallow usages of ref objects that can lead to loss of reactivity |  | :warning: |
@@ -467,6 +468,7 @@ The following rules extend the rules provided by ESLint itself and apply them to
 [vue/no-expose-after-await]: ./no-expose-after-await.md
 [vue/no-extra-parens]: ./no-extra-parens.md
 [vue/no-implicit-coercion]: ./no-implicit-coercion.md
+[vue/no-import-compiler-macros]: ./no-import-compiler-macros.md
 [vue/no-invalid-model-keys]: ./no-invalid-model-keys.md
 [vue/no-irregular-whitespace]: ./no-irregular-whitespace.md
 [vue/no-lifecycle-after-await]: ./no-lifecycle-after-await.md
diff --git a/docs/rules/no-implicit-coercion.md b/docs/rules/no-implicit-coercion.md
index d069f282..22698320 100644
--- a/docs/rules/no-implicit-coercion.md
+++ b/docs/rules/no-implicit-coercion.md
@@ -3,13 +3,13 @@ pageClass: rule-details
 sidebarDepth: 0
 title: vue/no-implicit-coercion
 description: Disallow shorthand type conversions in `<template>`
+since: v9.33.0
 ---
 
 # vue/no-implicit-coercion
 
 > Disallow shorthand type conversions in `<template>`
 
-- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> _**This rule has not been released yet.**_ </badge>
 - :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
 
 This rule is the same rule as core [no-implicit-coercion] rule but it applies to the expressions in `<template>`.
@@ -20,6 +20,10 @@ This rule is the same rule as core [no-implicit-coercion] rule but it applies to
 
 [no-implicit-coercion]: https://eslint.org/docs/rules/no-implicit-coercion
 
+## :rocket: Version
+
+This rule was introduced in eslint-plugin-vue v9.33.0
+
 ## :mag: Implementation
 
 - [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-implicit-coercion.js)
diff --git a/docs/rules/no-import-compiler-macros.md b/docs/rules/no-import-compiler-macros.md
new file mode 100644
index 00000000..ed968daa
--- /dev/null
+++ b/docs/rules/no-import-compiler-macros.md
@@ -0,0 +1,54 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-import-compiler-macros
+description: disallow importing Vue compiler macros
+---
+
+# vue/no-import-compiler-macros
+
+> disallow importing Vue compiler macros
+
+- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> _**This rule has not been released yet.**_ </badge>
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
+
+## :book: Rule Details
+
+This rule disallow importing vue compiler macros.
+
+<eslint-code-block fix :rules="{'vue/no-import-compiler-macros': ['error']}">
+
+```vue
+<script setup>
+/* ✗ BAD */
+import { defineProps, withDefaults } from 'vue'
+</script>
+```
+
+</eslint-code-block>
+
+<eslint-code-block fix :rules="{'vue/no-import-compiler-macros': ['error']}">
+
+```vue
+<script setup>
+/* ✓ GOOD */
+import { ref } from 'vue'
+</script>
+```
+
+</eslint-code-block>
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [defineProps() & defineEmits()]
+
+[defineProps() & defineEmits()]: https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-import-compiler-macros.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-import-compiler-macros.js)
diff --git a/lib/index.js b/lib/index.js
index ecb8ce5e..7b407efe 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -127,6 +127,7 @@ const plugin = {
     'no-expose-after-await': require('./rules/no-expose-after-await'),
     'no-extra-parens': require('./rules/no-extra-parens'),
     'no-implicit-coercion': require('./rules/no-implicit-coercion'),
+    'no-import-compiler-macros': require('./rules/no-import-compiler-macros'),
     'no-invalid-model-keys': require('./rules/no-invalid-model-keys'),
     'no-irregular-whitespace': require('./rules/no-irregular-whitespace'),
     'no-lifecycle-after-await': require('./rules/no-lifecycle-after-await'),
diff --git a/lib/rules/no-import-compiler-macros.js b/lib/rules/no-import-compiler-macros.js
new file mode 100644
index 00000000..693a9f33
--- /dev/null
+++ b/lib/rules/no-import-compiler-macros.js
@@ -0,0 +1,101 @@
+/**
+ * @author Wayne Zhang
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const COMPILER_MACROS = new Set([
+  'defineProps',
+  'defineEmits',
+  'defineExpose',
+  'withDefaults',
+  'defineModel',
+  'defineOptions',
+  'defineSlots'
+])
+
+const VUE_MODULES = new Set(['@vue/runtime-core', '@vue/runtime-dom', 'vue'])
+
+/**
+ * @param {Token} node
+ */
+function isComma(node) {
+  return node.type === 'Punctuator' && node.value === ','
+}
+
+module.exports = {
+  meta: {
+    type: 'problem',
+    docs: {
+      description: 'disallow importing Vue compiler macros',
+      categories: undefined,
+      url: 'https://eslint.vuejs.org/rules/no-import-compiler-macros.html'
+    },
+    fixable: 'code',
+    schema: [],
+    messages: {
+      noImportCompilerMacros:
+        "'{{name}}' is a compiler macro and doesn't need to be imported."
+    }
+  },
+  /**
+   * @param {RuleContext} context
+   * @returns {RuleListener}
+   */
+  create(context) {
+    const sourceCode = context.getSourceCode()
+
+    return {
+      ImportDeclaration(node) {
+        if (node.specifiers.length === 0 || !VUE_MODULES.has(node.source.value))
+          return
+
+        for (const specifier of node.specifiers) {
+          if (
+            specifier.type !== 'ImportSpecifier' ||
+            !COMPILER_MACROS.has(specifier.imported.name)
+          ) {
+            continue
+          }
+
+          context.report({
+            node: specifier,
+            messageId: 'noImportCompilerMacros',
+            data: {
+              name: specifier.imported.name
+            },
+            fix: (fixer) => {
+              const isOnlySpecifier = node.specifiers.length === 1
+              const isLastSpecifier =
+                specifier === node.specifiers[node.specifiers.length - 1]
+
+              if (isOnlySpecifier) {
+                return fixer.remove(node)
+              } else if (isLastSpecifier) {
+                const precedingComma = sourceCode.getTokenBefore(
+                  specifier,
+                  isComma
+                )
+                return fixer.removeRange([
+                  precedingComma ? precedingComma.range[0] : specifier.range[0],
+                  specifier.range[1]
+                ])
+              } else {
+                const subsequentComma = sourceCode.getTokenAfter(
+                  specifier,
+                  isComma
+                )
+                return fixer.removeRange([
+                  specifier.range[0],
+                  subsequentComma
+                    ? subsequentComma.range[1]
+                    : specifier.range[1]
+                ])
+              }
+            }
+          })
+        }
+      }
+    }
+  }
+}
diff --git a/lib/utils/ts-utils/ts-types.js b/lib/utils/ts-utils/ts-types.js
index 2fe354c2..7280c876 100644
--- a/lib/utils/ts-utils/ts-types.js
+++ b/lib/utils/ts-utils/ts-types.js
@@ -202,6 +202,18 @@ function inferRuntimeTypeInternal(type, services) {
   const { checker } = services
   /** @type {Set<string>} */
   const types = new Set()
+
+  // handle generic parameter types
+  if (type.isTypeParameter()) {
+    const constraint = type.getConstraint()
+    if (constraint) {
+      for (const t of inferRuntimeTypeInternal(constraint, services)) {
+        types.add(t)
+      }
+    }
+    return [...types]
+  }
+
   for (const targetType of iterateTypes(checker.getNonNullableType(type))) {
     if (
       isAny(targetType) ||
diff --git a/package.json b/package.json
index 8d9c25a6..e33574b9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint-plugin-vue",
-  "version": "9.32.0",
+  "version": "9.33.0",
   "description": "Official ESLint plugin for Vue.js",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",


Test patch:
diff --git a/tests/lib/rules/no-import-compiler-macros.js b/tests/lib/rules/no-import-compiler-macros.js
new file mode 100644
index 00000000..d74a268a
--- /dev/null
+++ b/tests/lib/rules/no-import-compiler-macros.js
@@ -0,0 +1,201 @@
+/**
+ * @author Wayne Zhang
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('../../eslint-compat').RuleTester
+const rule = require('../../../lib/rules/no-import-compiler-macros')
+
+const tester = new RuleTester({
+  languageOptions: {
+    parser: require('vue-eslint-parser'),
+    ecmaVersion: 2020,
+    sourceType: 'module'
+  }
+})
+
+tester.run('no-import-compiler-macros', rule, {
+  valid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup>
+      import { ref, computed } from 'vue'
+      import { someFunction } from '@vue/runtime-core'
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      import { defineProps } from 'some-other-package'
+      </script>
+      `
+    }
+  ],
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup>
+      import { defineProps } from 'vue'
+      </script>
+      `,
+      output: `
+      <script setup>
+      
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'noImportCompilerMacros',
+          data: {
+            name: 'defineProps'
+          },
+          line: 3,
+          column: 16
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup>
+      import {
+        ref,
+        defineProps
+      } from 'vue'
+      </script>
+      `,
+      output: `
+      <script setup>
+      import {
+        ref
+      } from 'vue'
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'noImportCompilerMacros',
+          data: {
+            name: 'defineProps'
+          },
+          line: 5,
+          column: 9
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup>
+      import { ref, defineProps } from 'vue'
+      import { defineEmits, computed } from '@vue/runtime-core'
+      import { defineExpose, watch, withDefaults } from '@vue/runtime-dom'
+      </script>
+      `,
+      output: `
+      <script setup>
+      import { ref } from 'vue'
+      import {  computed } from '@vue/runtime-core'
+      import {  watch } from '@vue/runtime-dom'
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'noImportCompilerMacros',
+          data: {
+            name: 'defineProps'
+          },
+          line: 3,
+          column: 21
+        },
+        {
+          messageId: 'noImportCompilerMacros',
+          data: {
+            name: 'defineEmits'
+          },
+          line: 4,
+          column: 16
+        },
+        {
+          messageId: 'noImportCompilerMacros',
+          data: {
+            name: 'defineExpose'
+          },
+          line: 5,
+          column: 16
+        },
+        {
+          messageId: 'noImportCompilerMacros',
+          data: {
+            name: 'withDefaults'
+          },
+          line: 5,
+          column: 37
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup>
+      import { defineModel, defineOptions } from 'vue'
+      </script>
+      `,
+      output: `
+      <script setup>
+      import {  defineOptions } from 'vue'
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'noImportCompilerMacros',
+          data: {
+            name: 'defineModel'
+          },
+          line: 3,
+          column: 16
+        },
+        {
+          messageId: 'noImportCompilerMacros',
+          data: {
+            name: 'defineOptions'
+          },
+          line: 3,
+          column: 29
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup lang="ts">
+      import { ref as refFoo, defineSlots as defineSlotsFoo, type computed } from '@vue/runtime-core'
+      </script>
+      `,
+      output: `
+      <script setup lang="ts">
+      import { ref as refFoo,  type computed } from '@vue/runtime-core'
+      </script>
+      `,
+      languageOptions: {
+        parserOptions: {
+          parser: require.resolve('@typescript-eslint/parser')
+        }
+      },
+      errors: [
+        {
+          messageId: 'noImportCompilerMacros',
+          data: {
+            name: 'defineSlots'
+          },
+          line: 3,
+          column: 31
+        }
+      ]
+    }
+  ]
+})
diff --git a/tests/lib/rules/require-valid-default-prop.js b/tests/lib/rules/require-valid-default-prop.js
index a724d36f..103b6b7d 100644
--- a/tests/lib/rules/require-valid-default-prop.js
+++ b/tests/lib/rules/require-valid-default-prop.js
@@ -222,8 +222,7 @@ ruleTester.run('require-valid-default-prop', rule, {
       `,
       languageOptions: {
         parser: require('@typescript-eslint/parser'),
-        ecmaVersion: 6,
-        sourceType: 'module'
+        ...languageOptions
       }
     },
     {
@@ -332,6 +331,17 @@ ruleTester.run('require-valid-default-prop', rule, {
       languageOptions: {
         parser: require('vue-eslint-parser')
       }
+    },
+    {
+      // https://github.com/vuejs/eslint-plugin-vue/issues/2692
+      filename: 'test.vue',
+      code: `
+      <script setup lang="ts">
+      type MaybeString<T extends number> = T | \`\${T}\`
+      const { foo = 1 } = defineProps<{ foo: MaybeString<1, 2>}>()
+      </script>
+      `,
+      ...getTypeScriptFixtureTestOptions()
     }
   ],
 
@@ -1201,6 +1211,22 @@ ruleTester.run('require-valid-default-prop', rule, {
           line: 3
         }
       ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup lang="ts">
+      type MaybeString<T extends string | number> = \`\${T}\`
+      const { foo = 1 } = defineProps<{ foo: MaybeString<1, 2>}>()
+      </script>
+      `,
+      errors: [
+        {
+          message: "Type of the default value for 'foo' prop must be a string.",
+          line: 4
+        }
+      ],
+      ...getTypeScriptFixtureTestOptions()
     }
   ]
 })