Repository: vuejs/eslint-plugin-vue
Pull request #2134 (2023-04-18 13:18:47)

Add support for imported types in SFC macros
This PR allows type-only defineProps and type-only defineEmits to be resolved from TypeScript type information as well. If the user is not using typescript-eslint, mark it as unknown.


Patch:
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index 2945e424..5b2d6551 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -73,7 +73,7 @@ const sidebarCategories = [
   }
 ]
 
-const categorizedRules: DefaultTheme.SidebarGroup[] = []
+const categorizedRules: DefaultTheme.SidebarItem[] = []
 for (const { title, categoryIds } of sidebarCategories) {
   const categoryRules = rules
     .filter((rule) => rule.meta.docs.categories && !rule.meta.deprecated)
@@ -84,8 +84,10 @@ for (const { title, categoryIds } of sidebarCategories) {
     )
   const children: DefaultTheme.SidebarItem[] = categoryRules
     .filter(({ ruleId }) => {
-      const exists = categorizedRules.some(({ items }) =>
-        items.some(({ text: alreadyRuleId }) => alreadyRuleId === ruleId)
+      const exists = categorizedRules.some(
+        ({ items }) =>
+          items &&
+          items.some(({ text: alreadyRuleId }) => alreadyRuleId === ruleId)
       )
       return !exists
     })
@@ -101,16 +103,16 @@ for (const { title, categoryIds } of sidebarCategories) {
   }
   categorizedRules.push({
     text: title,
-    collapsible: false,
+    collapsed: false,
     items: children
   })
 }
 
-const extraCategories: DefaultTheme.SidebarGroup[] = []
+const extraCategories: DefaultTheme.SidebarItem[] = []
 if (uncategorizedRules.length > 0) {
   extraCategories.push({
     text: 'Uncategorized',
-    collapsible: false,
+    collapsed: false,
     items: uncategorizedRules.map(({ ruleId, name }) => ({
       text: ruleId,
       link: `/rules/${name}`
@@ -120,7 +122,7 @@ if (uncategorizedRules.length > 0) {
 if (uncategorizedExtensionRule.length > 0) {
   extraCategories.push({
     text: 'Extension Rules',
-    collapsible: false,
+    collapsed: false,
     items: uncategorizedExtensionRule.map(({ ruleId, name }) => ({
       text: ruleId,
       link: `/rules/${name}`
@@ -130,7 +132,7 @@ if (uncategorizedExtensionRule.length > 0) {
 if (deprecatedRules.length > 0) {
   extraCategories.push({
     text: 'Deprecated',
-    collapsible: false,
+    collapsed: false,
     items: deprecatedRules.map(({ ruleId, name }) => ({
       text: ruleId,
       link: `/rules/${name}`
diff --git a/lib/rules/no-restricted-props.js b/lib/rules/no-restricted-props.js
index 564bf077..db673e4c 100644
--- a/lib/rules/no-restricted-props.js
+++ b/lib/rules/no-restricted-props.js
@@ -109,14 +109,17 @@ module.exports = {
               option.message ||
               `Using \`${prop.propName}\` props is not allowed.`
             context.report({
-              node: prop.key,
+              node: prop.type !== 'infer-type' ? prop.key : prop.node,
               messageId: 'restrictedProp',
               data: { message },
-              suggest: createSuggest(
-                prop.key,
-                option,
-                withDefaultsProps && withDefaultsProps[prop.propName]
-              )
+              suggest:
+                prop.type !== 'infer-type'
+                  ? createSuggest(
+                      prop.key,
+                      option,
+                      withDefaultsProps && withDefaultsProps[prop.propName]
+                    )
+                  : null
             })
             break
           }
diff --git a/lib/rules/no-unused-properties.js b/lib/rules/no-unused-properties.js
index 1421ad0b..818dcc05 100644
--- a/lib/rules/no-unused-properties.js
+++ b/lib/rules/no-unused-properties.js
@@ -30,7 +30,7 @@ const {
  * @typedef {object} ComponentNonObjectPropertyData
  * @property {string} name
  * @property {GroupName} groupName
- * @property {'array' | 'type'} type
+ * @property {'array' | 'type' | 'infer-type'} type
  * @property {ASTNode} node
  *
  * @typedef { ComponentNonObjectPropertyData | ComponentObjectPropertyData } ComponentPropertyData
@@ -423,7 +423,7 @@ module.exports = {
                 type: prop.type,
                 name: prop.propName,
                 groupName: 'props',
-                node: prop.key
+                node: prop.type !== 'infer-type' ? prop.key : prop.node
               })
             }
           }
diff --git a/lib/rules/padding-lines-in-component-definition.js b/lib/rules/padding-lines-in-component-definition.js
index b2f1a54d..1c2b6e88 100644
--- a/lib/rules/padding-lines-in-component-definition.js
+++ b/lib/rules/padding-lines-in-component-definition.js
@@ -6,6 +6,7 @@
 
 /**
  * @typedef {import('../utils').ComponentProp} ComponentProp
+ * @typedef {import('../utils').ComponentEmit} ComponentEmit
  * @typedef {import('../utils').GroupName} GroupName
  */
 
@@ -33,10 +34,19 @@ function isComma(node) {
 }
 
 /**
- * @param {string} nodeType
+ * @typedef {Exclude<ComponentProp | ComponentEmit, {type:'infer-type'}> & { node: {type: 'Property' | 'SpreadElement'} }} ValidComponentPropOrEmit
  */
-function isValidProperties(nodeType) {
-  return ['Property', 'SpreadElement'].includes(nodeType)
+/**
+ * @template {ComponentProp | ComponentEmit} T
+ * @param {T} propOrEmit
+ * @returns {propOrEmit is ValidComponentPropOrEmit & T}
+ */
+function isValidProperties(propOrEmit) {
+  return Boolean(
+    propOrEmit.type !== 'infer-type' &&
+      propOrEmit.node &&
+      ['Property', 'SpreadElement'].includes(propOrEmit.node.type)
+  )
 }
 
 /**
@@ -320,11 +330,9 @@ module.exports = {
       }),
       utils.defineScriptSetupVisitor(context, {
         onDefinePropsEnter(_, props) {
-          const propNodes = /** @type {(Property | SpreadElement)[]} */ (
-            props
-              .filter((prop) => prop.node && isValidProperties(prop.node.type))
-              .map((prop) => prop.node)
-          )
+          const propNodes = props
+            .filter(isValidProperties)
+            .map((prop) => prop.node)
 
           const withinOption = parseOption(options, OptionKeys.WithinOption)
           const propsOption = withinOption && parseOption(withinOption, 'props')
@@ -337,11 +345,9 @@ module.exports = {
           )
         },
         onDefineEmitsEnter(_, emits) {
-          const emitNodes = /** @type {(Property | SpreadElement)[]} */ (
-            emits
-              .filter((emit) => emit.node && isValidProperties(emit.node.type))
-              .map((emit) => emit.node)
-          )
+          const emitNodes = emits
+            .filter(isValidProperties)
+            .map((emit) => emit.node)
 
           const withinOption = parseOption(options, OptionKeys.WithinOption)
           const emitsOption = withinOption && parseOption(withinOption, 'emits')
diff --git a/lib/rules/prefer-prop-type-boolean-first.js b/lib/rules/prefer-prop-type-boolean-first.js
index e26f9462..aa16707c 100644
--- a/lib/rules/prefer-prop-type-boolean-first.js
+++ b/lib/rules/prefer-prop-type-boolean-first.js
@@ -67,6 +67,9 @@ module.exports = {
      * @param {import('../utils').ComponentProp} prop
      */
     function checkProperty(prop) {
+      if (prop.type !== 'object') {
+        return
+      }
       const { value } = prop
       if (!value) {
         return
diff --git a/lib/rules/require-emit-validator.js b/lib/rules/require-emit-validator.js
index 6fc439fd..8b478be9 100644
--- a/lib/rules/require-emit-validator.js
+++ b/lib/rules/require-emit-validator.js
@@ -34,24 +34,29 @@ module.exports = {
      * @param {ComponentEmit} emit
      */
     function checker(emit) {
-      if (emit.type !== 'object' && emit.type !== 'array') {
-        return
-      }
-      const { value, node, emitName } = emit
-      const hasType =
-        !!value &&
-        (value.type === 'ArrowFunctionExpression' ||
+      /** @type {Expression|null} */
+      let value = null
+      let hasType = false
+      if (emit.type === 'object') {
+        value = emit.value
+        hasType =
+          value.type === 'ArrowFunctionExpression' ||
           value.type === 'FunctionExpression' ||
           // validator may from outer scope
-          value.type === 'Identifier')
+          value.type === 'Identifier'
+      } else if (emit.type !== 'array') {
+        return
+      }
 
       if (!hasType) {
+        const { node, emitName } = emit
         const name =
           emitName ||
           (node.type === 'Identifier' && node.name) ||
           'Unknown emit'
 
         if (value && value.type === 'Literal' && value.value === null) {
+          const valueNode = value
           context.report({
             node,
             messageId: 'skipped',
@@ -59,7 +64,7 @@ module.exports = {
             suggest: [
               {
                 messageId: 'emptyValidation',
-                fix: (fixer) => fixer.replaceText(value, '() => true')
+                fix: (fixer) => fixer.replaceText(valueNode, '() => true')
               }
             ]
           })
diff --git a/lib/rules/require-explicit-emits.js b/lib/rules/require-explicit-emits.js
index 16761380..267f4e61 100644
--- a/lib/rules/require-explicit-emits.js
+++ b/lib/rules/require-explicit-emits.js
@@ -429,7 +429,10 @@ module.exports = {
 function buildSuggest(define, emits, nameWithLoc, context) {
   const emitsKind =
     define.type === 'ObjectExpression' ? '`emits` option' : '`defineEmits`'
-  const certainEmits = emits.filter((e) => e.key)
+  const certainEmits = emits.filter(
+    /** @returns {e is ComponentEmit & {type:'array'|'object'}} */
+    (e) => e.type === 'array' || e.type === 'object'
+  )
   if (certainEmits.length > 0) {
     const last = certainEmits[certainEmits.length - 1]
     return [
diff --git a/lib/rules/require-prop-comment.js b/lib/rules/require-prop-comment.js
index 8dd852cb..845bba9d 100644
--- a/lib/rules/require-prop-comment.js
+++ b/lib/rules/require-prop-comment.js
@@ -62,7 +62,7 @@ module.exports = {
      */
     function verifyProps(props) {
       for (const prop of props) {
-        if (!prop.propName) {
+        if (!prop.propName || prop.type === 'infer-type') {
           continue
         }
 
diff --git a/lib/rules/require-prop-type-constructor.js b/lib/rules/require-prop-type-constructor.js
index a708508e..45354a1d 100644
--- a/lib/rules/require-prop-type-constructor.js
+++ b/lib/rules/require-prop-type-constructor.js
@@ -79,7 +79,7 @@ module.exports = {
     /** @param {ComponentProp[]} props */
     function verifyProps(props) {
       for (const prop of props) {
-        if (!prop.value || prop.propName == null) {
+        if (prop.type !== 'object' || prop.propName == null) {
           continue
         }
         if (
diff --git a/lib/rules/require-prop-types.js b/lib/rules/require-prop-types.js
index e4d9fdcc..f603fd18 100644
--- a/lib/rules/require-prop-types.js
+++ b/lib/rules/require-prop-types.js
@@ -51,12 +51,12 @@ module.exports = {
       if (prop.type !== 'object' && prop.type !== 'array') {
         return
       }
-      const { value, node, propName } = prop
       let hasType = true
 
-      if (!value) {
+      if (prop.type === 'array') {
         hasType = false
       } else {
+        const { value } = prop
         switch (value.type) {
           case 'ObjectExpression': {
             // foo: {
@@ -77,6 +77,7 @@ module.exports = {
       }
 
       if (!hasType) {
+        const { node, propName } = prop
         const name =
           propName ||
           (node.type === 'Identifier' && node.name) ||
diff --git a/lib/rules/require-valid-default-prop.js b/lib/rules/require-valid-default-prop.js
index 50a3d191..9535f51d 100644
--- a/lib/rules/require-valid-default-prop.js
+++ b/lib/rules/require-valid-default-prop.js
@@ -7,9 +7,11 @@ const utils = require('../utils')
 const { capitalize } = require('../utils/casing')
 
 /**
+ * @typedef {import('../utils').ComponentProp} ComponentProp
  * @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp
  * @typedef {import('../utils').ComponentArrayProp} ComponentArrayProp
  * @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp
+ * @typedef {import('../utils').ComponentInferTypeProp} ComponentInferTypeProp
  * @typedef {import('../utils').ComponentUnknownProp} ComponentUnknownProp
  * @typedef {import('../utils').VueObjectData} VueObjectData
  */
@@ -108,7 +110,7 @@ module.exports = {
      */
     /**
      * @typedef {object} PropDefaultFunctionContext
-     * @property {ComponentObjectProp | ComponentTypeProp} prop
+     * @property {ComponentObjectProp | ComponentTypeProp | ComponentInferTypeProp} prop
      * @property {Set<string>} types
      * @property {FunctionValueType} default
      */
@@ -225,7 +227,7 @@ module.exports = {
 
     /**
      * @param {*} node
-     * @param {ComponentObjectProp | ComponentTypeProp} prop
+     * @param {ComponentObjectProp | ComponentTypeProp | ComponentInferTypeProp} prop
      * @param {Iterable<string>} expectedTypeNames
      */
     function report(node, prop, expectedTypeNames) {
@@ -245,7 +247,7 @@ module.exports = {
     }
 
     /**
-     * @param {(ComponentObjectDefineProp | ComponentTypeProp)[]} props
+     * @param {(ComponentObjectDefineProp | ComponentTypeProp | ComponentInferTypeProp)[]} props
      * @param { { [key: string]: Expression | undefined } } withDefaults
      */
     function processPropDefs(props, withDefaults) {
@@ -394,15 +396,15 @@ module.exports = {
       }),
       utils.defineScriptSetupVisitor(context, {
         onDefinePropsEnter(node, baseProps) {
-          /** @type {(ComponentObjectDefineProp | ComponentTypeProp)[]} */
           const props = baseProps.filter(
             /**
-             * @param {ComponentObjectProp | ComponentArrayProp | ComponentTypeProp | ComponentUnknownProp} prop
-             * @returns {prop is ComponentObjectDefineProp | ComponentTypeProp}
+             * @param {ComponentProp} prop
+             * @returns {prop is ComponentObjectDefineProp | ComponentInferTypeProp | ComponentTypeProp}
              */
             (prop) =>
               Boolean(
                 prop.type === 'type' ||
+                  prop.type === 'infer-type' ||
                   (prop.type === 'object' &&
                     prop.value.type === 'ObjectExpression')
               )
diff --git a/lib/rules/return-in-emits-validator.js b/lib/rules/return-in-emits-validator.js
index 4b8a3b1d..86db20ac 100644
--- a/lib/rules/return-in-emits-validator.js
+++ b/lib/rules/return-in-emits-validator.js
@@ -65,7 +65,7 @@ module.exports = {
      */
     function processEmits(emits) {
       for (const emit of emits) {
-        if (!emit.value) {
+        if (emit.type !== 'object' || !emit.value) {
           continue
         }
         if (
diff --git a/lib/utils/indent-ts.js b/lib/utils/indent-ts.js
index c7ad7ad6..5bee6688 100644
--- a/lib/utils/indent-ts.js
+++ b/lib/utils/indent-ts.js
@@ -12,7 +12,7 @@ const {
   isClosingBracketToken,
   isOpeningBracketToken
 } = require('@eslint-community/eslint-utils')
-const { isTypeNode } = require('./ts-ast-utils')
+const { isTypeNode } = require('./ts-utils')
 
 /**
  * @typedef {import('../../typings/eslint-plugin-vue/util-types/indent-helper').TSNodeListener} TSNodeListener
@@ -227,7 +227,7 @@ function defineVisitor({
       processSemicolons(node)
     },
     /**
-     * @param {TSESTreeNode} node
+     * @param {ASTNode} node
      */
     // eslint-disable-next-line complexity -- ignore
     '*[type=/^TS/]'(node) {
diff --git a/lib/utils/index.js b/lib/utils/index.js
index a63891bd..ef8cd0ee 100644
--- a/lib/utils/index.js
+++ b/lib/utils/index.js
@@ -15,11 +15,13 @@
  * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentArrayProp} ComponentArrayProp
  * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentObjectProp} ComponentObjectProp
  * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeProp} ComponentTypeProp
+ * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentInferTypeProp} ComponentInferTypeProp
  * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentUnknownProp} ComponentUnknownProp
  * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentProp} ComponentProp
  * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentArrayEmit} ComponentArrayEmit
  * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentObjectEmit} ComponentObjectEmit
  * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeEmit} ComponentTypeEmit
+ * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentInferTypeEmit} ComponentInferTypeEmit
  * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentUnknownEmit} ComponentUnknownEmit
  * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentEmit} ComponentEmit
  */
@@ -60,7 +62,7 @@ const {
   getComponentPropsFromTypeDefine,
   getComponentEmitsFromTypeDefine,
   isTypeNode
-} = require('./ts-ast-utils')
+} = require('./ts-utils')
 
 /**
  * @type { WeakMap<RuleContext, Token[]> }
@@ -2914,9 +2916,7 @@ function getComponentPropsFromOptions(componentObject) {
     return [
       {
         type: 'unknown',
-        key: null,
         propName: null,
-        value: null,
         node: propsNode.value
       }
     ]
@@ -2949,9 +2949,7 @@ function getComponentEmitsFromOptions(componentObject) {
     return [
       {
         type: 'unknown',
-        key: null,
         emitName: null,
-        value: null,
         node: emitsNode.value
       }
     ]
@@ -2964,7 +2962,7 @@ function getComponentEmitsFromOptions(componentObject) {
  * Get all props from `defineProps` call expression.
  * @param {RuleContext} context The rule context object.
  * @param {CallExpression} node `defineProps` call expression
- * @return {(ComponentArrayProp | ComponentObjectProp | ComponentTypeProp | ComponentUnknownProp)[]} Array of component props
+ * @return {ComponentProp[]} Array of component props
  */
 function getComponentPropsFromDefineProps(context, node) {
   if (node.arguments.length > 0) {
@@ -2975,9 +2973,7 @@ function getComponentPropsFromDefineProps(context, node) {
     return [
       {
         type: 'unknown',
-        key: null,
         propName: null,
-        value: null,
         node: node.arguments[0]
       }
     ]
@@ -2991,9 +2987,7 @@ function getComponentPropsFromDefineProps(context, node) {
   return [
     {
       type: 'unknown',
-      key: null,
       propName: null,
-      value: null,
       node: null
     }
   ]
@@ -3003,7 +2997,7 @@ function getComponentPropsFromDefineProps(context, node) {
  * Get all emits from `defineEmits` call expression.
  * @param {RuleContext} context The rule context object.
  * @param {CallExpression} node `defineEmits` call expression
- * @return {(ComponentArrayEmit | ComponentObjectEmit | ComponentTypeEmit | ComponentUnknownEmit)[]} Array of component emits
+ * @return {ComponentEmit[]} Array of component emits
  */
 function getComponentEmitsFromDefineEmits(context, node) {
   if (node.arguments.length > 0) {
@@ -3014,9 +3008,7 @@ function getComponentEmitsFromDefineEmits(context, node) {
     return [
       {
         type: 'unknown',
-        key: null,
         emitName: null,
-        value: null,
         node: node.arguments[0]
       }
     ]
@@ -3030,9 +3022,7 @@ function getComponentEmitsFromDefineEmits(context, node) {
   return [
     {
       type: 'unknown',
-      key: null,
       emitName: null,
-      value: null,
       node: null
     }
   ]
@@ -3044,34 +3034,35 @@ function getComponentEmitsFromDefineEmits(context, node) {
  */
 function getComponentPropsFromDefine(propsNode) {
   if (propsNode.type === 'ObjectExpression') {
-    return propsNode.properties.map((prop) => {
-      if (!isProperty(prop)) {
-        return {
-          type: 'unknown',
-          key: null,
-          propName: null,
-          value: null,
-          node: prop
+    return propsNode.properties.map(
+      /** @returns {ComponentArrayProp | ComponentObjectProp | ComponentUnknownProp} */
+      (prop) => {
+        if (!isProperty(prop)) {
+          return {
+            type: 'unknown',
+            propName: null,
+            node: prop
+          }
+        }
+        const propName = getStaticPropertyName(prop)
+        if (propName != null) {
+          return {
+            type: 'object',
+            key: prop.key,
+            propName,
+            value: skipTSAsExpression(prop.value),
+            node: prop
+          }
         }
-      }
-      const propName = getStaticPropertyName(prop)
-      if (propName != null) {
         return {
           type: 'object',
-          key: prop.key,
-          propName,
+          key: null,
+          propName: null,
           value: skipTSAsExpression(prop.value),
           node: prop
         }
       }
-      return {
-        type: 'object',
-        key: null,
-        propName: null,
-        value: skipTSAsExpression(prop.value),
-        node: prop
-      }
-    })
+    )
   }
 
   return propsNode.elements.filter(isDef).map((prop) => {
diff --git a/lib/utils/ts-ast-utils.js b/lib/utils/ts-ast-utils.js
deleted file mode 100644
index 330e6b98..00000000
--- a/lib/utils/ts-ast-utils.js
+++ /dev/null
@@ -1,354 +0,0 @@
-const { findVariable } = require('@eslint-community/eslint-utils')
-/**
- * @typedef {import('@typescript-eslint/types').TSESTree.TypeNode} TypeNode
- * @typedef {import('@typescript-eslint/types').TSESTree.TSInterfaceBody} TSInterfaceBody
- * @typedef {import('@typescript-eslint/types').TSESTree.TSTypeLiteral} TSTypeLiteral
- * @typedef {import('@typescript-eslint/types').TSESTree.Parameter} TSESTreeParameter
- * @typedef {import('@typescript-eslint/types').TSESTree.Node} Node
- *
- */
-/**
- * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeProp} ComponentTypeProp
- * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeEmit} ComponentTypeEmit
- * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentUnknownEmit} ComponentUnknownEmit
- */
-
-module.exports = {
-  isTypeNode,
-  getComponentPropsFromTypeDefine,
-  getComponentEmitsFromTypeDefine
-}
-
-/**
- * @param {Node | ASTNode} node
- * @returns {node is TypeNode}
- */
-function isTypeNode(node) {
-  return (
-    node.type === 'TSAnyKeyword' ||
-    node.type === 'TSArrayType' ||
-    node.type === 'TSBigIntKeyword' ||
-    node.type === 'TSBooleanKeyword' ||
-    node.type === 'TSConditionalType' ||
-    node.type === 'TSConstructorType' ||
-    node.type === 'TSFunctionType' ||
-    node.type === 'TSImportType' ||
-    node.type === 'TSIndexedAccessType' ||
-    node.type === 'TSInferType' ||
-    node.type === 'TSIntersectionType' ||
-    node.type === 'TSIntrinsicKeyword' ||
-    node.type === 'TSLiteralType' ||
-    node.type === 'TSMappedType' ||
-    node.type === 'TSNamedTupleMember' ||
-    node.type === 'TSNeverKeyword' ||
-    node.type === 'TSNullKeyword' ||
-    node.type === 'TSNumberKeyword' ||
-    node.type === 'TSObjectKeyword' ||
-    node.type === 'TSOptionalType' ||
-    node.type === 'TSRestType' ||
-    node.type === 'TSStringKeyword' ||
-    node.type === 'TSSymbolKeyword' ||
-    node.type === 'TSTemplateLiteralType' ||
-    node.type === 'TSThisType' ||
-    node.type === 'TSTupleType' ||
-    node.type === 'TSTypeLiteral' ||
-    node.type === 'TSTypeOperator' ||
-    node.type === 'TSTypePredicate' ||
-    node.type === 'TSTypeQuery' ||
-    node.type === 'TSTypeReference' ||
-    node.type === 'TSUndefinedKeyword' ||
-    node.type === 'TSUnionType' ||
-    node.type === 'TSUnknownKeyword' ||
-    node.type === 'TSVoidKeyword'
-  )
-}
-
-/**
- * @param {TypeNode} node
- * @returns {node is TSTypeLiteral}
- */
-function isTSTypeLiteral(node) {
-  return node.type === 'TSTypeLiteral'
-}
-/**
- * @param {TypeNode} node
- * @returns {node is TSFunctionType}
- */
-function isTSFunctionType(node) {
-  return node.type === 'TSFunctionType'
-}
-
-/**
- * Get all props by looking at all component's properties
- * @param {RuleContext} context The ESLint rule context object.
- * @param {TypeNode} propsNode Type with props definition
- * @return {ComponentTypeProp[]} Array of component props
- */
-function getComponentPropsFromTypeDefine(context, propsNode) {
-  /** @type {TSInterfaceBody | TSTypeLiteral|null} */
-  const defNode = resolveQualifiedType(context, propsNode, isTSTypeLiteral)
-  if (!defNode) {
-    return []
-  }
-  return [...extractRuntimeProps(context, defNode)]
-}
-
-/**
- * Get all emits by looking at all component's properties
- * @param {RuleContext} context The ESLint rule context object.
- * @param {TypeNode} emitsNode Type with emits definition
- * @return {ComponentTypeEmit[]} Array of component emits
- */
-function getComponentEmitsFromTypeDefine(context, emitsNode) {
-  /** @type {TSInterfaceBody | TSTypeLiteral | TSFunctionType | null} */
-  const defNode = resolveQualifiedType(
-    context,
-    emitsNode,
-    (n) => isTSTypeLiteral(n) || isTSFunctionType(n)
-  )
-  if (!defNode) {
-    return []
-  }
-  return [...extractRuntimeEmits(defNode)]
-}
-
-/**
- * @see https://github.com/vuejs/vue-next/blob/253ca2729d808fc051215876aa4af986e4caa43c/packages/compiler-sfc/src/compileScript.ts#L1512
- * @param {RuleContext} context The ESLint rule context object.
- * @param {TSTypeLiteral | TSInterfaceBody} node
- * @returns {IterableIterator<ComponentTypeProp>}
- */
-function* extractRuntimeProps(context, node) {
-  const members = node.type === 'TSTypeLiteral' ? node.members : node.body
-  for (const m of members) {
-    if (
-      (m.type === 'TSPropertySignature' || m.type === 'TSMethodSignature') &&
-      (m.key.type === 'Identifier' || m.key.type === 'Literal')
-    ) {
-      let type
-      if (m.type === 'TSMethodSignature') {
-        type = ['Function']
-      } else if (m.typeAnnotation) {
-        type = inferRuntimeType(context, m.typeAnnotation.typeAnnotation)
-      }
-      yield {
-        type: 'type',
-        key: /** @type {Identifier | Literal} */ (m.key),
-        propName: m.key.type === 'Identifier' ? m.key.name : `${m.key.value}`,
-        value: null,
-        node: /** @type {TSPropertySignature | TSMethodSignature} */ (m),
-
-        required: !m.optional,
-        types: type || [`null`]
-      }
-    }
-  }
-}
-
-/**
- * @see https://github.com/vuejs/vue-next/blob/348c3b01e56383ffa70b180d1376fdf4ac12e274/packages/compiler-sfc/src/compileScript.ts#L1632
- * @param {TSTypeLiteral | TSInterfaceBody | TSFunctionType} node
- * @returns {IterableIterator<ComponentTypeEmit | ComponentUnknownEmit>}
- */
-function* extractRuntimeEmits(node) {
-  if (node.type === 'TSFunctionType') {
-    yield* extractEventNames(node.params[0], node)
-    return
-  }
-  const members = node.type === 'TSTypeLiteral' ? node.members : node.body
-  for (const member of members) {
-    if (member.type === 'TSCallSignatureDeclaration') {
-      yield* extractEventNames(
-        member.params[0],
-        /** @type {TSCallSignatureDeclaration} */ (member)
-      )
-    } else if (
-      member.type === 'TSPropertySignature' ||
-      member.type === 'TSMethodSignature'
-    ) {
-      if (member.key.type !== 'Identifier' && member.key.type !== 'Literal') {
-        yield {
-          type: 'unknown',
-          node: member.key
-        }
-        continue
-      }
-      yield {
-        type: 'type',
-        key: /** @type {Identifier | Literal} */ (member.key),
-        emitName:
-          member.key.type === 'Identifier'
-            ? member.key.name
-            : `${member.key.value}`,
-        value: null,
-        node: /** @type {TSPropertySignature | TSMethodSignature} */ (member)
-      }
-    }
-  }
-}
-
-/**
- * @param {TSESTreeParameter} eventName
- * @param {TSCallSignatureDeclaration | TSFunctionType} member
- * @returns {IterableIterator<ComponentTypeEmit>}
- */
-function* extractEventNames(eventName, member) {
-  if (
-    eventName &&
-    eventName.type === 'Identifier' &&
-    eventName.typeAnnotation &&
-    eventName.typeAnnotation.type === 'TSTypeAnnotation'
-  ) {
-    const typeNode = eventName.typeAnnotation.typeAnnotation
-    if (
-      typeNode.type === 'TSLiteralType' &&
-      typeNode.literal.type === 'Literal'
-    ) {
-      const emitName = String(typeNode.literal.value)
-      yield {
-        type: 'type',
-        key: /** @type {TSLiteralType} */ (typeNode),
-        emitName,
-        value: null,
-        node: member
-      }
-    } else if (typeNode.type === 'TSUnionType') {
-      for (const t of typeNode.types) {
-        if (t.type === 'TSLiteralType' && t.literal.type === 'Literal') {
-          const emitName = String(t.literal.value)
-          yield {
-            type: 'type',
-            key: /** @type {TSLiteralType} */ (t),
-            emitName,
-            value: null,
-            node: member
-          }
-        }
-      }
-    }
-  }
-}
-
-/**
- * @see https://github.com/vuejs/vue-next/blob/253ca2729d808fc051215876aa4af986e4caa43c/packages/compiler-sfc/src/compileScript.ts#L425
- *
- * @param {RuleContext} context The ESLint rule context object.
- * @param {TypeNode} node
- * @param {(n: TypeNode)=> boolean } qualifier
- */
-function resolveQualifiedType(context, node, qualifier) {
-  if (qualifier(node)) {
-    return node
-  }
-  if (node.type === 'TSTypeReference' && node.typeName.type === 'Identifier') {
-    const refName = node.typeName.name
-    const variable = findVariable(context.getScope(), refName)
-    if (variable && variable.defs.length === 1) {
-      const def = variable.defs[0]
-      if (def.node.type === 'TSInterfaceDeclaration') {
-        return /** @type {any} */ (def.node).body
-      }
-      if (def.node.type === 'TSTypeAliasDeclaration') {
-        const typeAnnotation = /** @type {any} */ (def.node).typeAnnotation
-        return qualifier(typeAnnotation) ? typeAnnotation : null
-      }
-    }
-  }
-}
-
-/**
- * @param {RuleContext} context The ESLint rule context object.
- * @param {TypeNode} node
- * @param {Set<TypeNode>} [checked]
- * @returns {string[]}
- */
-function inferRuntimeType(context, node, checked = new Set()) {
-  switch (node.type) {
-    case 'TSStringKeyword':
-      return ['String']
-    case 'TSNumberKeyword':
-      return ['Number']
-    case 'TSBooleanKeyword':
-      return ['Boolean']
-    case 'TSObjectKeyword':
-      return ['Object']
-    case 'TSTypeLiteral':
-      return ['Object']
-    case 'TSFunctionType':
-      return ['Function']
-    case 'TSArrayType':
-    case 'TSTupleType':
-      return ['Array']
-
-    case 'TSLiteralType':
-      if (node.literal.type === 'Literal') {
-        switch (typeof node.literal.value) {
-          case 'boolean':
-            return ['Boolean']
-          case 'string':
-            return ['String']
-          case 'number':
-          case 'bigint':
-            return ['Number']
-        }
-        if (node.literal.value instanceof RegExp) {
-          return ['RegExp']
-        }
-      }
-      return [`null`]
-    case 'TSTypeReference':
-      if (node.typeName.type === 'Identifier') {
-        const variable = findVariable(context.getScope(), node.typeName.name)
-        if (variable && variable.defs.length === 1) {
-          const def = variable.defs[0]
-          if (def.node.type === 'TSInterfaceDeclaration') {
-            return [`Object`]
-          }
-          if (def.node.type === 'TSTypeAliasDeclaration') {
-            const typeAnnotation = /** @type {any} */ (def.node).typeAnnotation
-            if (!checked.has(typeAnnotation)) {
-              checked.add(typeAnnotation)
-              return inferRuntimeType(context, typeAnnotation, checked)
-            }
-          }
-        }
-        switch (node.typeName.name) {
-          case 'Array':
-          case 'Function':
-          case 'Object':
-          case 'Set':
-          case 'Map':
-          case 'WeakSet':
-          case 'WeakMap':
-          case 'Date':
-            return [node.typeName.name]
-          case 'Record':
-          case 'Partial':
-          case 'Readonly':
-          case 'Pick':
-          case 'Omit':
-          case 'Exclude':
-          case 'Extract':
-          case 'Required':
-          case 'InstanceType':
-            return ['Object']
-        }
-      }
-      return [`null`]
-
-    case 'TSUnionType':
-      const set = new Set()
-      for (const t of node.types) {
-        for (const tt of inferRuntimeType(context, t, checked)) {
-          set.add(tt)
-        }
-      }
-      return [...set]
-
-    case 'TSIntersectionType':
-      return ['Object']
-
-    default:
-      return [`null`] // no runtime check
-  }
-}
diff --git a/lib/utils/ts-utils/index.js b/lib/utils/ts-utils/index.js
new file mode 100644
index 00000000..8b6c53b2
--- /dev/null
+++ b/lib/utils/ts-utils/index.js
@@ -0,0 +1,88 @@
+const {
+  isTypeNode,
+  extractRuntimeProps,
+  isTSTypeLiteral,
+  isTSTypeLiteralOrTSFunctionType,
+  extractRuntimeEmits,
+  flattenTypeNodes,
+  isTSInterfaceBody
+} = require('./ts-ast')
+const {
+  getComponentPropsFromTypeDefineTypes,
+  getComponentEmitsFromTypeDefineTypes
+} = require('./ts-types')
+
+/**
+ * @typedef {import('@typescript-eslint/types').TSESTree.TypeNode} TSESTreeTypeNode
+ */
+/**
+ * @typedef {import('../index').ComponentTypeProp} ComponentTypeProp
+ * @typedef {import('../index').ComponentInferTypeProp} ComponentInferTypeProp
+ * @typedef {import('../index').ComponentUnknownProp} ComponentUnknownProp
+ * @typedef {import('../index').ComponentTypeEmit} ComponentTypeEmit
+ * @typedef {import('../index').ComponentInferTypeEmit} ComponentInferTypeEmit
+ * @typedef {import('../index').ComponentUnknownEmit} ComponentUnknownEmit
+ */
+
+module.exports = {
+  isTypeNode,
+  getComponentPropsFromTypeDefine,
+  getComponentEmitsFromTypeDefine
+}
+
+/**
+ * Get all props by looking at all component's properties
+ * @param {RuleContext} context The ESLint rule context object.
+ * @param {TypeNode} propsNode Type with props definition
+ * @return {(ComponentTypeProp|ComponentInferTypeProp|ComponentUnknownProp)[]} Array of component props
+ */
+function getComponentPropsFromTypeDefine(context, propsNode) {
+  /** @type {(ComponentTypeProp|ComponentInferTypeProp|ComponentUnknownProp)[]} */
+  const result = []
+  for (const defNode of flattenTypeNodes(
+    context,
+    /** @type {TSESTreeTypeNode} */ (propsNode)
+  )) {
+    if (isTSInterfaceBody(defNode) || isTSTypeLiteral(defNode)) {
+      result.push(...extractRuntimeProps(context, defNode))
+    } else {
+      result.push(
+        ...getComponentPropsFromTypeDefineTypes(
+          context,
+          /** @type {TypeNode} */ (defNode)
+        )
+      )
+    }
+  }
+  return result
+}
+
+/**
+ * Get all emits by looking at all component's properties
+ * @param {RuleContext} context The ESLint rule context object.
+ * @param {TypeNode} emitsNode Type with emits definition
+ * @return {(ComponentTypeEmit|ComponentInferTypeEmit|ComponentUnknownEmit)[]} Array of component emits
+ */
+function getComponentEmitsFromTypeDefine(context, emitsNode) {
+  /** @type {(ComponentTypeEmit|ComponentInferTypeEmit|ComponentUnknownEmit)[]} */
+  const result = []
+  for (const defNode of flattenTypeNodes(
+    context,
+    /** @type {TSESTreeTypeNode} */ (emitsNode)
+  )) {
+    if (
+      isTSInterfaceBody(defNode) ||
+      isTSTypeLiteralOrTSFunctionType(defNode)
+    ) {
+      result.push(...extractRuntimeEmits(defNode))
+    } else {
+      result.push(
+        ...getComponentEmitsFromTypeDefineTypes(
+          context,
+          /** @type {TypeNode} */ (defNode)
+        )
+      )
+    }
+  }
+  return result
+}
diff --git a/lib/utils/ts-utils/ts-ast.js b/lib/utils/ts-utils/ts-ast.js
new file mode 100644
index 00000000..8561bf56
--- /dev/null
+++ b/lib/utils/ts-utils/ts-ast.js
@@ -0,0 +1,528 @@
+const { findVariable } = require('@eslint-community/eslint-utils')
+const { inferRuntimeTypeFromTypeNode } = require('./ts-types')
+/**
+ * @typedef {import('@typescript-eslint/types').TSESTree.TypeNode} TSESTreeTypeNode
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSInterfaceBody} TSESTreeTSInterfaceBody
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSTypeLiteral} TSESTreeTSTypeLiteral
+ * @typedef {import('@typescript-eslint/types').TSESTree.TSFunctionType} TSESTreeTSFunctionType
+ * @typedef {import('@typescript-eslint/types').TSESTree.Parameter} TSESTreeParameter
+ * @typedef {import('@typescript-eslint/types').TSESTree.Node} TSESTreeNode
+ *
+ */
+/**
+ * @typedef {import('../index').ComponentTypeProp} ComponentTypeProp
+ * @typedef {import('../index').ComponentUnknownProp} ComponentUnknownProp
+ * @typedef {import('../index').ComponentTypeEmit} ComponentTypeEmit
+ * @typedef {import('../index').ComponentUnknownEmit} ComponentUnknownEmit
+ */
+
+const noop = Function.prototype
+
+module.exports = {
+  isTypeNode,
+  flattenTypeNodes,
+  isTSInterfaceBody,
+  isTSTypeLiteral,
+  isTSTypeLiteralOrTSFunctionType,
+  extractRuntimeProps,
+  extractRuntimeEmits
+}
+
+/**
+ * @param {ASTNode} node
+ * @returns {node is TypeNode}
+ */
+function isTypeNode(node) {
+  if (
+    node.type === 'TSAbstractKeyword' ||
+    node.type === 'TSAnyKeyword' ||
+    node.type === 'TSAsyncKeyword' ||
+    node.type === 'TSArrayType' ||
+    node.type === 'TSBigIntKeyword' ||
+    node.type === 'TSBooleanKeyword' ||
+    node.type === 'TSConditionalType' ||
+    node.type === 'TSConstructorType' ||
+    node.type === 'TSDeclareKeyword' ||
+    node.type === 'TSExportKeyword' ||
+    node.type === 'TSFunctionType' ||
+    node.type === 'TSImportType' ||
+    node.type === 'TSIndexedAccessType' ||
+    node.type === 'TSInferType' ||
+    node.type === 'TSIntersectionType' ||
+    node.type === 'TSIntrinsicKeyword' ||
+    node.type === 'TSLiteralType' ||
+    node.type === 'TSMappedType' ||
+    node.type === 'TSNamedTupleMember' ||
+    node.type === 'TSNeverKeyword' ||
+    node.type === 'TSNullKeyword' ||
+    node.type === 'TSNumberKeyword' ||
+    node.type === 'TSObjectKeyword' ||
+    node.type === 'TSOptionalType' ||
+    node.type === 'TSQualifiedName' ||
+    node.type === 'TSPrivateKeyword' ||
+    node.type === 'TSProtectedKeyword' ||
+    node.type === 'TSPublicKeyword' ||
+    node.type === 'TSReadonlyKeyword' ||
+    node.type === 'TSRestType' ||
+    node.type === 'TSStaticKeyword' ||
+    node.type === 'TSStringKeyword' ||
+    node.type === 'TSSymbolKeyword' ||
+    node.type === 'TSTemplateLiteralType' ||
+    node.type === 'TSThisType' ||
+    node.type === 'TSTupleType' ||
+    node.type === 'TSTypeLiteral' ||
+    node.type === 'TSTypeOperator' ||
+    node.type === 'TSTypePredicate' ||
+    node.type === 'TSTypeQuery' ||
+    node.type === 'TSTypeReference' ||
+    node.type === 'TSUndefinedKeyword' ||
+    node.type === 'TSUnionType' ||
+    node.type === 'TSUnknownKeyword' ||
+    node.type === 'TSVoidKeyword'
+  ) {
+    /** @type {TypeNode['type']} for type check */
+    const type = node.type
+    noop(type)
+    return true
+  }
+  /** @type {Exclude<ASTNode['type'], TypeNode['type']>} for type check */
+  const type = node.type
+  noop(type)
+  return false
+}
+
+/**
+ * @param {TSESTreeTypeNode|TSESTreeTSInterfaceBody} node
+ * @returns {node is TSESTreeTSInterfaceBody}
+ */
+function isTSInterfaceBody(node) {
+  return node.type === 'TSInterfaceBody'
+}
+/**
+ * @param {TSESTreeTypeNode} node
+ * @returns {node is TSESTreeTSTypeLiteral}
+ */
+function isTSTypeLiteral(node) {
+  return node.type === 'TSTypeLiteral'
+}
+/**
+ * @param {TSESTreeTypeNode} node
+ * @returns {node is TSESTreeTSFunctionType}
+ */
+function isTSFunctionType(node) {
+  return node.type === 'TSFunctionType'
+}
+/**
+ * @param {TSESTreeTypeNode} node
+ * @returns {node is TSESTreeTSTypeLiteral | TSESTreeTSFunctionType}
+ */
+function isTSTypeLiteralOrTSFunctionType(node) {
+  return isTSTypeLiteral(node) || isTSFunctionType(node)
+}
+
+/**
+ * @see https://github.com/vuejs/vue-next/blob/253ca2729d808fc051215876aa4af986e4caa43c/packages/compiler-sfc/src/compileScript.ts#L1512
+ * @param {RuleContext} context The ESLint rule context object.
+ * @param {TSESTreeTSTypeLiteral | TSESTreeTSInterfaceBody} node
+ * @returns {IterableIterator<ComponentTypeProp | ComponentUnknownProp>}
+ */
+function* extractRuntimeProps(context, node) {
+  const members = node.type === 'TSTypeLiteral' ? node.members : node.body
+  for (const member of members) {
+    if (
+      member.type === 'TSPropertySignature' ||
+      member.type === 'TSMethodSignature'
+    ) {
+      if (member.key.type !== 'Identifier' && member.key.type !== 'Literal') {
+        yield {
+          type: 'unknown',
+          propName: null,
+          node: /** @type {Expression} */ (member.key)
+        }
+        continue
+      }
+      /** @type {string[]|undefined} */
+      let types
+      if (member.type === 'TSMethodSignature') {
+        types = ['Function']
+      } else if (member.typeAnnotation) {
+        types = inferRuntimeType(context, member.typeAnnotation.typeAnnotation)
+      }
+      yield {
+        type: 'type',
+        key: /** @type {Identifier | Literal} */ (member.key),
+        propName:
+          member.key.type === 'Identifier'
+            ? member.key.name
+            : `${member.key.value}`,
+        node: /** @type {TSPropertySignature | TSMethodSignature} */ (member),
+
+        required: !member.optional,
+        types: types || [`null`]
+      }
+    }
+  }
+}
+
+/**
+ * @param {TSESTreeTSTypeLiteral | TSESTreeTSInterfaceBody | TSESTreeTSFunctionType} node
+ * @returns {IterableIterator<ComponentTypeEmit | ComponentUnknownEmit>}
+ */
+function* extractRuntimeEmits(node) {
+  if (node.type === 'TSFunctionType') {
+    yield* extractEventNames(
+      node.params[0],
+      /** @type {TSFunctionType} */ (node)
+    )
+    return
+  }
+  const members = node.type === 'TSTypeLiteral' ? node.members : node.body
+  for (const member of members) {
+    if (member.type === 'TSCallSignatureDeclaration') {
+      yield* extractEventNames(
+        member.params[0],
+        /** @type {TSCallSignatureDeclaration} */ (member)
+      )
+    } else if (
+      member.type === 'TSPropertySignature' ||
+      member.type === 'TSMethodSignature'
+    ) {
+      if (member.key.type !== 'Identifier' && member.key.type !== 'Literal') {
+        yield {
+          type: 'unknown',
+          emitName: null,
+          node: /** @type {Expression} */ (member.key)
+        }
+        continue
+      }
+      yield {
+        type: 'type',
+        key: /** @type {Identifier | Literal} */ (member.key),
+        emitName:
+          member.key.type === 'Identifier'
+            ? member.key.name
+            : `${member.key.value}`,
+        node: /** @type {TSPropertySignature | TSMethodSignature} */ (member)
+      }
+    }
+  }
+}
+
+/**
+ * @param {TSESTreeParameter} eventName
+ * @param {TSCallSignatureDeclaration | TSFunctionType} member
+ * @returns {IterableIterator<ComponentTypeEmit>}
+ */
+function* extractEventNames(eventName, member) {
+  if (
+    eventName &&
+    eventName.type === 'Identifier' &&
+    eventName.typeAnnotation &&
+    eventName.typeAnnotation.type === 'TSTypeAnnotation'
+  ) {
+    const typeNode = eventName.typeAnnotation.typeAnnotation
+    if (
+      typeNode.type === 'TSLiteralType' &&
+      typeNode.literal.type === 'Literal'
+    ) {
+      const emitName = String(typeNode.literal.value)
+      yield {
+        type: 'type',
+        key: /** @type {TSLiteralType} */ (typeNode),
+        emitName,
+        node: member
+      }
+    } else if (typeNode.type === 'TSUnionType') {
+      for (const t of typeNode.types) {
+        if (t.type === 'TSLiteralType' && t.literal.type === 'Literal') {
+          const emitName = String(t.literal.value)
+          yield {
+            type: 'type',
+            key: /** @type {TSLiteralType} */ (t),
+            emitName,
+            node: member
+          }
+        }
+      }
+    }
+  }
+}
+
+/**
+ * @param {RuleContext} context The ESLint rule context object.
+ * @param {TSESTreeTypeNode} node
+ * @returns {(TSESTreeTypeNode|TSESTreeTSInterfaceBody)[]}
+ */
+function flattenTypeNodes(context, node) {
+  /**
+   * @typedef {object} TraversedData
+   * @property {Set<TSESTreeTypeNode|TSESTreeTSInterfaceBody>} nodes
+   * @property {boolean} finished
+   */
+  /** @type {Map<TSESTreeTypeNode,TraversedData>} */
+  const traversed = new Map()
+
+  return [...flattenImpl(node)]
+  /**
+   * @param {TSESTreeTypeNode} node
+   * @returns {Iterable<TSESTreeTypeNode|TSESTreeTSInterfaceBody>}
+   */
+  function* flattenImpl(node) {
+    if (node.type === 'TSUnionType' || node.type === 'TSIntersectionType') {
+      for (const typeNode of node.types) {
+        yield* flattenImpl(typeNode)
+      }
+      return
+    }
+    if (
+      node.type === 'TSTypeReference' &&
+      node.typeName.type === 'Identifier'
+    ) {
+      const refName = node.typeName.name
+      const variable = findVariable(context.getScope(), refName)
+      if (variable && variable.defs.length === 1) {
+        const defNode = /** @type {TSESTreeNode} */ (variable.defs[0].node)
+        if (defNode.type === 'TSInterfaceDeclaration') {
+          yield defNode.body
+          return
+        } else if (defNode.type === 'TSTypeAliasDeclaration') {
+          const typeAnnotation = defNode.typeAnnotation
+          let traversedData = traversed.get(typeAnnotation)
+          if (traversedData) {
+            const copy = [...traversedData.nodes]
+            yield* copy
+            if (!traversedData.finished) {
+              // Include the node because it will probably be referenced recursively.
+              yield typeAnnotation
+            }
+            return
+          }
+          traversedData = { nodes: new Set(), finished: false }
+          traversed.set(typeAnnotation, traversedData)
+          for (const e of flattenImpl(typeAnnotation)) {
+            traversedData.nodes.add(e)
+          }
+          traversedData.finished = true
+          yield* traversedData.nodes
+          return
+        }
+      }
+    }
+    yield node
+  }
+}
+
+/**
+ * @param {RuleContext} context The ESLint rule context object.
+ * @param {TSESTreeTypeNode} node
+ * @param {Set<TSESTreeTypeNode>} [checked]
+ * @returns {string[]}
+ */
+function inferRuntimeType(context, node, checked = new Set()) {
+  switch (node.type) {
+    case 'TSStringKeyword':
+    case 'TSTemplateLiteralType':
+      return ['String']
+    case 'TSNumberKeyword':
+      return ['Number']
+    case 'TSBooleanKeyword':
+      return ['Boolean']
+    case 'TSObjectKeyword':
+      return ['Object']
+    case 'TSTypeLiteral':
+      return inferTypeLiteralType(node)
+    case 'TSFunctionType':
+      return ['Function']
+    case 'TSArrayType':
+    case 'TSTupleType':
+      return ['Array']
+    case 'TSSymbolKeyword':
+      return ['Symbol']
+
+    case 'TSLiteralType':
+      if (node.literal.type === 'Literal') {
+        switch (typeof node.literal.value) {
+          case 'boolean':
+            return ['Boolean']
+          case 'string':
+            return ['String']
+          case 'number':
+          case 'bigint':
+            return ['Number']
+        }
+        if (node.literal.value instanceof RegExp) {
+          return ['RegExp']
+        }
+      }
+      return inferRuntimeTypeFromTypeNode(
+        context,
+        /** @type {TypeNode} */ (node)
+      )
+    case 'TSTypeReference':
+      if (node.typeName.type === 'Identifier') {
+        const variable = findVariable(context.getScope(), node.typeName.name)
+        if (variable && variable.defs.length === 1) {
+          const defNode = /** @type {TSESTreeNode} */ (variable.defs[0].node)
+          if (defNode.type === 'TSInterfaceDeclaration') {
+            return [`Object`]
+          }
+          if (defNode.type === 'TSTypeAliasDeclaration') {
+            const typeAnnotation = defNode.typeAnnotation
+            if (!checked.has(typeAnnotation)) {
+              checked.add(typeAnnotation)
+              return inferRuntimeType(context, typeAnnotation, checked)
+            }
+          }
+          if (defNode.type === 'TSEnumDeclaration') {
+            return inferEnumType(context, defNode)
+          }
+        }
+        for (const name of [
+          node.typeName.name,
+          ...(node.typeName.name.startsWith('Readonly')
+            ? [node.typeName.name.slice(8)]
+            : [])
+        ]) {
+          switch (name) {
+            case 'Array':
+            case 'Function':
+            case 'Object':
+            case 'Set':
+            case 'Map':
+            case 'WeakSet':
+            case 'WeakMap':
+            case 'Date':
+              return [name]
+          }
+        }
+
+        switch (node.typeName.name) {
+          case 'Record':
+          case 'Partial':
+          case 'Readonly':
+          case 'Pick':
+          case 'Omit':
+          case 'Required':
+          case 'InstanceType':
+            return ['Object']
+          case 'Uppercase':
+          case 'Lowercase':
+          case 'Capitalize':
+          case 'Uncapitalize':
+            return ['String']
+          case 'Parameters':
+          case 'ConstructorParameters':
+            return ['Array']
+          case 'NonNullable':
+            if (node.typeParameters && node.typeParameters.params[0]) {
+              return inferRuntimeType(
+                context,
+                node.typeParameters.params[0],
+                checked
+              ).filter((t) => t !== 'null')
+            }
+            break
+          case 'Extract':
+            if (node.typeParameters && node.typeParameters.params[1]) {
+              return inferRuntimeType(
+                context,
+                node.typeParameters.params[1],
+                checked
+              )
+            }
+            break
+          case 'Exclude':
+          case 'OmitThisParameter':
+            if (node.typeParameters && node.typeParameters.params[0]) {
+              return inferRuntimeType(
+                context,
+                node.typeParameters.params[0],
+                checked
+              )
+            }
+            break
+        }
+      }
+      return inferRuntimeTypeFromTypeNode(
+        context,
+        /** @type {TypeNode} */ (node)
+      )
+
+    case 'TSUnionType':
+    case 'TSIntersectionType':
+      return inferUnionType(node)
+
+    default:
+      return inferRuntimeTypeFromTypeNode(
+        context,
+        /** @type {TypeNode} */ (node)
+      )
+  }
+
+  /**
+   * @param {import('@typescript-eslint/types').TSESTree.TSUnionType|import('@typescript-eslint/types').TSESTree.TSIntersectionType} node
+   * @returns {string[]}
+   */
+  function inferUnionType(node) {
+    const types = new Set()
+    for (const t of node.types) {
+      for (const tt of inferRuntimeType(context, t, checked)) {
+        types.add(tt)
+      }
+    }
+    return [...types]
+  }
+}
+
+/**
+ * @param {import('@typescript-eslint/types').TSESTree.TSTypeLiteral} node
+ * @returns {string[]}
+ */
+function inferTypeLiteralType(node) {
+  const types = new Set()
+  for (const m of node.members) {
+    switch (m.type) {
+      case 'TSCallSignatureDeclaration':
+      case 'TSConstructSignatureDeclaration':
+        types.add('Function')
+        break
+      default:
+        types.add('Object')
+    }
+  }
+  return types.size > 0 ? [...types] : ['Object']
+}
+/**
+ * @param {RuleContext} context The ESLint rule context object.
+ * @param {import('@typescript-eslint/types').TSESTree.TSEnumDeclaration} node
+ * @returns {string[]}
+ */
+function inferEnumType(context, node) {
+  const types = new Set()
+  for (const m of node.members) {
+    if (m.initializer) {
+      if (m.initializer.type === 'Literal') {
+        switch (typeof m.initializer.value) {
+          case 'string':
+            types.add('String')
+            break
+          case 'number':
+          case 'bigint': // Now it's a syntax error.
+            types.add('Number')
+            break
+          case 'boolean': // Now it's a syntax error.
+            types.add('Boolean')
+            break
+        }
+      } else {
+        for (const type of inferRuntimeTypeFromTypeNode(
+          context,
+          /** @type {Expression} */ (m.initializer)
+        )) {
+          types.add(type)
+        }
+      }
+    }
+  }
+  return types.size > 0 ? [...types] : ['Number']
+}
diff --git a/lib/utils/ts-utils/ts-types.js b/lib/utils/ts-utils/ts-types.js
new file mode 100644
index 00000000..e684831e
--- /dev/null
+++ b/lib/utils/ts-utils/ts-types.js
@@ -0,0 +1,275 @@
+const {
+  getTypeScript,
+  isAny,
+  isUnknown,
+  isNever,
+  isNull,
+  isObject,
+  isFunction,
+  isStringLike,
+  isNumberLike,
+  isBooleanLike,
+  isBigIntLike,
+  isArrayLikeObject,
+  isReferenceObject
+} = require('./typescript')
+/**
+ * @typedef {import('@typescript-eslint/types').TSESTree.Node} TSESTreeNode
+ * @typedef {import('typescript').Type} Type
+ * @typedef {import('typescript').TypeChecker} TypeChecker
+ * @typedef {import('typescript').Node} TypeScriptNode
+ */
+/**
+ * @typedef {import('../index').ComponentInferTypeProp} ComponentInferTypeProp
+ * @typedef {import('../index').ComponentUnknownProp} ComponentUnknownProp
+ * @typedef {import('../index').ComponentInferTypeEmit} ComponentInferTypeEmit
+ * @typedef {import('../index').ComponentUnknownEmit} ComponentUnknownEmit
+ */
+
+module.exports = {
+  getComponentPropsFromTypeDefineTypes,
+  getComponentEmitsFromTypeDefineTypes,
+  inferRuntimeTypeFromTypeNode
+}
+
+/**
+ * @typedef {object} Services
+ * @property {typeof import("typescript")} ts
+ * @property {Map<ESNode | TSNode | TSESTreeNode, TypeScriptNode>} tsNodeMap
+ * @property {import('typescript').TypeChecker} checker
+ */
+
+/**
+ * Get TypeScript parser services.
+ * @param {RuleContext} context The ESLint rule context object.
+ * @returns {Services|null}
+ */
+function getTSParserServices(context) {
+  const tsNodeMap = context.parserServices.esTreeNodeToTSNodeMap
+  if (!tsNodeMap) return null
+  const hasFullTypeInformation =
+    context.parserServices.hasFullTypeInformation !== false
+  const checker =
+    (hasFullTypeInformation &&
+      context.parserServices.program &&
+      context.parserServices.program.getTypeChecker()) ||
+    null
+  if (!checker) return null
+  const ts = getTypeScript()
+  if (!ts) return null
+
+  return {
+    ts,
+    tsNodeMap,
+    checker
+  }
+}
+
+/**
+ * Get all props by looking at all component's properties
+ * @param {RuleContext} context The ESLint rule context object.
+ * @param {TypeNode} propsNode Type with props definition
+ * @return {(ComponentInferTypeProp|ComponentUnknownProp)[]} Array of component props
+ */
+function getComponentPropsFromTypeDefineTypes(context, propsNode) {
+  const services = getTSParserServices(context)
+  const tsNode = services && services.tsNodeMap.get(propsNode)
+  const type = tsNode && services.checker.getTypeAtLocation(tsNode)
+  if (
+    !type ||
+    isAny(type) ||
+    isUnknown(type) ||
+    isNever(type) ||
+    isNull(type)
+  ) {
+    return [
+      {
+        type: 'unknown',
+        propName: null,
+        node: propsNode
+      }
+    ]
+  }
+  return [...extractRuntimeProps(type, tsNode, propsNode, services)]
+}
+
+/**
+ * Get all emits by looking at all component's properties
+ * @param {RuleContext} context The ESLint rule context object.
+ * @param {TypeNode} emitsNode Type with emits definition
+ * @return {(ComponentInferTypeEmit|ComponentUnknownEmit)[]} Array of component emits
+ */
+function getComponentEmitsFromTypeDefineTypes(context, emitsNode) {
+  const services = getTSParserServices(context)
+  const tsNode = services && services.tsNodeMap.get(emitsNode)
+  const type = tsNode && services.checker.getTypeAtLocation(tsNode)
+  if (
+    !type ||
+    isAny(type) ||
+    isUnknown(type) ||
+    isNever(type) ||
+    isNull(type)
+  ) {
+    return [
+      {
+        type: 'unknown',
+        emitName: null,
+        node: emitsNode
+      }
+    ]
+  }
+  return [...extractRuntimeEmits(type, tsNode, emitsNode, services)]
+}
+
+/**
+ * @param {RuleContext} context The ESLint rule context object.
+ * @param {TypeNode|Expression} node
+ * @returns {string[]}
+ */
+function inferRuntimeTypeFromTypeNode(context, node) {
+  const services = getTSParserServices(context)
+  const tsNode = services && services.tsNodeMap.get(node)
+  const type = tsNode && services.checker.getTypeAtLocation(tsNode)
+  if (!type) {
+    return ['null']
+  }
+  return inferRuntimeTypeInternal(type, services)
+}
+
+/**
+ * @param {Type} type
+ * @param {TypeScriptNode} tsNode
+ * @param {TypeNode} propsNode Type with props definition
+ * @param {Services} services
+ * @returns {IterableIterator<ComponentInferTypeProp>}
+ */
+function* extractRuntimeProps(type, tsNode, propsNode, services) {
+  const { ts, checker } = services
+  for (const property of type.getProperties()) {
+    const isOptional = (property.flags & ts.SymbolFlags.Optional) !== 0
+    const name = property.getName()
+
+    const type = checker.getTypeOfSymbolAtLocation(property, tsNode)
+
+    yield {
+      type: 'infer-type',
+      propName: name,
+      required: !isOptional,
+      node: propsNode,
+      types: inferRuntimeTypeInternal(type, services)
+    }
+  }
+}
+
+/**
+ * @param {Type} type
+ * @param {Services} services
+ * @returns {string[]}
+ */
+function inferRuntimeTypeInternal(type, services) {
+  const { checker } = services
+  /** @type {Set<string>} */
+  const types = new Set()
+  for (const targetType of iterateTypes(checker.getNonNullableType(type))) {
+    if (
+      isAny(targetType) ||
+      isUnknown(targetType) ||
+      isNever(targetType) ||
+      isNull(targetType)
+    ) {
+      types.add('null')
+    } else if (isStringLike(targetType)) {
+      types.add('String')
+    } else if (isNumberLike(targetType) || isBigIntLike(targetType)) {
+      types.add('Number')
+    } else if (isBooleanLike(targetType)) {
+      types.add('Boolean')
+    } else if (isFunction(targetType)) {
+      types.add('Function')
+    } else if (
+      isArrayLikeObject(targetType) ||
+      (targetType.isClassOrInterface() &&
+        ['Array', 'ReadonlyArray'].includes(
+          checker.getFullyQualifiedName(targetType.symbol)
+        ))
+    ) {
+      types.add('Array')
+    } else if (isObject(targetType)) {
+      types.add('Object')
+    }
+  }
+
+  if (types.size <= 0) types.add('null')
+
+  return [...types]
+}
+
+/**
+ * @param {Type} type
+ * @param {TypeScriptNode} tsNode
+ * @param {TypeNode} emitsNode Type with emits definition
+ * @param {Services} services
+ * @returns {IterableIterator<ComponentInferTypeEmit|ComponentUnknownEmit>}
+ */
+function* extractRuntimeEmits(type, tsNode, emitsNode, services) {
+  const { checker } = services
+  if (isFunction(type)) {
+    for (const signature of type.getCallSignatures()) {
+      const param = signature.getParameters()[0]
+      if (!param) {
+        yield {
+          type: 'unknown',
+          emitName: null,
+          node: emitsNode
+        }
+        continue
+      }
+      const type = checker.getTypeOfSymbolAtLocation(param, tsNode)
+
+      for (const targetType of iterateTypes(type)) {
+        yield targetType.isStringLiteral()
+          ? {
+              type: 'infer-type',
+              emitName: targetType.value,
+              node: emitsNode
+            }
+          : {
+              type: 'unknown',
+              emitName: null,
+              node: emitsNode
+            }
+      }
+    }
+  } else if (isObject(type)) {
+    for (const property of type.getProperties()) {
+      const name = property.getName()
+      yield {
+        type: 'infer-type',
+        emitName: name,
+        node: emitsNode
+      }
+    }
+  } else {
+    yield {
+      type: 'unknown',
+      emitName: null,
+      node: emitsNode
+    }
+  }
+}
+
+/**
+ * @param {Type} type
+ * @returns {Iterable<Type>}
+ */
+function* iterateTypes(type) {
+  if (isReferenceObject(type) && type.target !== type) {
+    yield* iterateTypes(type.target)
+  } else if (type.isUnion() && !isBooleanLike(type)) {
+    for (const t of type.types) {
+      yield* iterateTypes(t)
+    }
+  } else {
+    yield type
+  }
+}
diff --git a/lib/utils/ts-utils/typescript.js b/lib/utils/ts-utils/typescript.js
new file mode 100644
index 00000000..8d656401
--- /dev/null
+++ b/lib/utils/ts-utils/typescript.js
@@ -0,0 +1,214 @@
+/**
+ * @typedef {typeof import("typescript")} TypeScript
+ * @typedef {import("typescript").Type} Type
+ * @typedef {import("typescript").ObjectType} ObjectType
+ * @typedef {import("typescript").InterfaceType} InterfaceType
+ * @typedef {import("typescript").TypeReference} TypeReference
+ * @typedef {import("typescript").UnionOrIntersectionType} UnionOrIntersectionType
+ * @typedef {import("typescript").TypeParameter} TypeParameter
+ */
+
+/** @type {TypeScript | undefined} */
+let cacheTypeScript
+
+module.exports = {
+  getTypeScript,
+  isObject,
+  isAny,
+  isUnknown,
+  isNever,
+  isNull,
+  isFunction,
+  isArrayLikeObject,
+  isStringLike,
+  isNumberLike,
+  isBooleanLike,
+  isBigIntLike,
+  isReferenceObject,
+  extractTypeFlags,
+  extractObjectFlags
+}
+
+/**
+ * Get TypeScript instance
+ */
+function getTypeScript() {
+  if (cacheTypeScript) {
+    return cacheTypeScript
+  }
+  try {
+    return (cacheTypeScript = require('typescript'))
+  } catch (error) {
+    if (/** @type {any} */ (error).code === 'MODULE_NOT_FOUND') {
+      return undefined
+    }
+
+    throw error
+  }
+}
+/**
+ * For debug
+ * @param {Type} tsType
+ * @returns {string[]}
+ */
+function extractTypeFlags(tsType) {
+  const ts = /** @type {TypeScript} */ (getTypeScript())
+  /** @type {string[]} */
+  const result = []
+  const keys = /** @type {(keyof (typeof ts.TypeFlags))[]} */ (
+    Object.keys(ts.TypeFlags)
+  )
+  for (const k of keys) {
+    if ((tsType.flags & ts.TypeFlags[k]) !== 0) {
+      result.push(k)
+    }
+  }
+  return result
+}
+/**
+ * For debug
+ * @param {Type} tsType
+ * @returns {string[]}
+ */
+function extractObjectFlags(tsType) {
+  if (!isObject(tsType)) {
+    return []
+  }
+  const ts = /** @type {TypeScript} */ (getTypeScript())
+  /** @type {string[]} */
+  const result = []
+  const keys = /** @type {(keyof (typeof ts.ObjectFlags))[]} */ (
+    Object.keys(ts.ObjectFlags)
+  )
+  for (const k of keys) {
+    if ((tsType.objectFlags & ts.ObjectFlags[k]) !== 0) {
+      result.push(k)
+    }
+  }
+  return result
+}
+
+/**
+ * Check if a given type is an object type or not.
+ * @param {Type} tsType The type to check.
+ * @returns {tsType is ObjectType}
+ */
+function isObject(tsType) {
+  const ts = /** @type {TypeScript} */ (getTypeScript())
+  return (tsType.flags & ts.TypeFlags.Object) !== 0
+}
+
+/**
+ * Check if a given type is an array-like type or not.
+ * @param {Type} tsType The type to check.
+ */
+function isArrayLikeObject(tsType) {
+  const ts = /** @type {TypeScript} */ (getTypeScript())
+  return (
+    isObject(tsType) &&
+    (tsType.objectFlags &
+      (ts.ObjectFlags.ArrayLiteral |
+        ts.ObjectFlags.EvolvingArray |
+        ts.ObjectFlags.Tuple)) !==
+      0
+  )
+}
+/**
+ * Check if a given type is an any type or not.
+ * @param {Type} tsType The type to check.
+ */
+function isAny(tsType) {
+  const ts = /** @type {TypeScript} */ (getTypeScript())
+  return (tsType.flags & ts.TypeFlags.Any) !== 0
+}
+/**
+ * Check if a given type is an unknown type or not.
+ * @param {Type} tsType The type to check.
+ */
+function isUnknown(tsType) {
+  const ts = /** @type {TypeScript} */ (getTypeScript())
+  return (tsType.flags & ts.TypeFlags.Unknown) !== 0
+}
+/**
+ * Check if a given type is a never type or not.
+ * @param {Type} tsType The type to check.
+ */
+function isNever(tsType) {
+  const ts = /** @type {TypeScript} */ (getTypeScript())
+  return (tsType.flags & ts.TypeFlags.Never) !== 0
+}
+/**
+ * Check if a given type is an null type or not.
+ * @param {Type} tsType The type to check.
+ */
+function isNull(tsType) {
+  const ts = /** @type {TypeScript} */ (getTypeScript())
+  return (tsType.flags & ts.TypeFlags.Null) !== 0
+}
+
+/**
+ * Check if a given type is a string-like type or not.
+ * @param {Type} tsType The type to check.
+ * @returns {boolean} `true` if the type is a string-like type.
+ */
+function isStringLike(tsType) {
+  const ts = /** @type {TypeScript} */ (getTypeScript())
+  return (tsType.flags & ts.TypeFlags.StringLike) !== 0
+}
+/**
+ * Check if a given type is an number-like type or not.
+ * @param {Type} tsType The type to check.
+ * @returns {boolean} `true` if the type is a number-like type.
+ */
+function isNumberLike(tsType) {
+  const ts = /** @type {TypeScript} */ (getTypeScript())
+  return (tsType.flags & ts.TypeFlags.NumberLike) !== 0
+}
+/**
+ * Check if a given type is an boolean-like type or not.
+ * @param {Type} tsType The type to check.
+ * @returns {boolean} `true` if the type is a boolean-like type.
+ */
+function isBooleanLike(tsType) {
+  const ts = /** @type {TypeScript} */ (getTypeScript())
+  return (tsType.flags & ts.TypeFlags.BooleanLike) !== 0
+}
+/**
+ * Check if a given type is an bigint-like type or not.
+ * @param {Type} tsType The type to check.
+ * @returns {boolean} `true` if the type is a bigint-like type.
+ */
+function isBigIntLike(tsType) {
+  const ts = /** @type {TypeScript} */ (getTypeScript())
+  return (tsType.flags & ts.TypeFlags.BigIntLike) !== 0
+}
+
+/**
+ * Check if a given type is a reference type or not.
+ * @param {Type} tsType The type to check.
+ * @returns {tsType is TypeReference} `true` if the type is a reference type.
+ */
+function isReferenceObject(tsType) {
+  const ts = /** @type {TypeScript} */ (getTypeScript())
+  return (
+    isObject(tsType) && (tsType.objectFlags & ts.ObjectFlags.Reference) !== 0
+  )
+}
+/**
+ * Check if a given type is `function` or not.
+ * @param {Type} tsType The type to check.
+ */
+function isFunction(tsType) {
+  const ts = /** @type {TypeScript} */ (getTypeScript())
+  if (
+    tsType.symbol &&
+    (tsType.symbol.flags &
+      (ts.SymbolFlags.Function | ts.SymbolFlags.Method)) !==
+      0
+  ) {
+    return true
+  }
+
+  const signatures = tsType.getCallSignatures()
+  return signatures.length > 0
+}
diff --git a/typings/eslint-plugin-vue/global.d.ts b/typings/eslint-plugin-vue/global.d.ts
index c2ae5063..a581b3f6 100644
--- a/typings/eslint-plugin-vue/global.d.ts
+++ b/typings/eslint-plugin-vue/global.d.ts
@@ -159,6 +159,7 @@ declare global {
   type TSLiteralType = VAST.TSLiteralType
   type TSCallSignatureDeclaration = VAST.TSCallSignatureDeclaration
   type TSFunctionType = VAST.TSFunctionType
+  type TypeNode = VAST.TypeNode
 
   // ---- JSX Nodes ----
 
diff --git a/typings/eslint-plugin-vue/util-types/ast/ts-ast.ts b/typings/eslint-plugin-vue/util-types/ast/ts-ast.ts
index a79d457f..e3c5f0fa 100644
--- a/typings/eslint-plugin-vue/util-types/ast/ts-ast.ts
+++ b/typings/eslint-plugin-vue/util-types/ast/ts-ast.ts
@@ -12,6 +12,7 @@ export type TSNode =
   | TSLiteralType
   | TSCallSignatureDeclaration
   | TSFunctionType
+  | TypeNode
 
 export interface TSAsExpression extends HasParentNode {
   type: 'TSAsExpression'
@@ -21,7 +22,7 @@ export interface TSAsExpression extends HasParentNode {
 
 export interface TSTypeParameterInstantiation extends HasParentNode {
   type: 'TSTypeParameterInstantiation'
-  params: TSESTree.TypeNode[]
+  params: TypeNode[]
 }
 
 export type TSPropertySignature =
@@ -90,3 +91,12 @@ export interface TSCallSignatureDeclaration extends TSFunctionSignatureBase {
 export interface TSFunctionType extends TSFunctionSignatureBase {
   type: 'TSFunctionType'
 }
+
+type TypeNodeTypes = `${TSESTree.TypeNode['type']}`
+
+export type TypeNode =
+  | (HasParentNode & {
+      type: Exclude<TypeNodeTypes, 'TSFunctionType' | 'TSLiteralType'>
+    })
+  | TSFunctionType
+  | TSLiteralType
diff --git a/typings/eslint-plugin-vue/util-types/parser-services.ts b/typings/eslint-plugin-vue/util-types/parser-services.ts
index d59d76d3..6a9ccc77 100644
--- a/typings/eslint-plugin-vue/util-types/parser-services.ts
+++ b/typings/eslint-plugin-vue/util-types/parser-services.ts
@@ -1,6 +1,7 @@
 import * as VNODE from './node'
 import * as VAST from './ast'
 import * as eslint from 'eslint'
+import { ESNode, TSNode } from './ast'
 
 type TemplateListenerBase = {
   [T in keyof VAST.VNodeListenerMap]?: (node: VAST.VNodeListenerMap[T]) => void
@@ -27,6 +28,13 @@ export interface ParserServices {
     }
   ) => eslint.Rule.RuleListener
   getDocumentFragment?: () => VAST.VDocumentFragment | null
+  // for typescript-eslint/parser
+  esTreeNodeToTSNodeMap?: Map<
+    ESNode | TSNode | import('@typescript-eslint/types').TSESTree.Node,
+    import('typescript').Node
+  >
+  program?: import('typescript').Program
+  hasFullTypeInformation?: boolean
 }
 export namespace ParserServices {
   export interface TokenStore {
diff --git a/typings/eslint-plugin-vue/util-types/utils.ts b/typings/eslint-plugin-vue/util-types/utils.ts
index e16d8603..b2704769 100644
--- a/typings/eslint-plugin-vue/util-types/utils.ts
+++ b/typings/eslint-plugin-vue/util-types/utils.ts
@@ -55,14 +55,12 @@ type ComponentArrayPropDetectName = {
   type: 'array'
   key: Literal | TemplateLiteral
   propName: string
-  value: null
   node: Expression | SpreadElement
 }
 type ComponentArrayPropUnknownName = {
   type: 'array'
   key: null
   propName: null
-  value: null
   node: Expression | SpreadElement
 }
 export type ComponentArrayProp =
@@ -89,41 +87,46 @@ export type ComponentObjectProp =
 
 export type ComponentUnknownProp = {
   type: 'unknown'
-  key: null
   propName: null
-  value: null
-  node: Expression | SpreadElement | null
+  node: Expression | SpreadElement | TypeNode | null
 }
 
 export type ComponentTypeProp = {
   type: 'type'
   key: Identifier | Literal
   propName: string
-  value: null
   node: TSPropertySignature | TSMethodSignature
 
   required: boolean
   types: string[]
 }
 
+export type ComponentInferTypeProp = {
+  type: 'infer-type'
+  propName: string
+  node: TypeNode
+
+  required: boolean
+  types: string[]
+}
+
 export type ComponentProp =
   | ComponentArrayProp
   | ComponentObjectProp
   | ComponentTypeProp
+  | ComponentInferTypeProp
   | ComponentUnknownProp
 
 type ComponentArrayEmitDetectName = {
   type: 'array'
   key: Literal | TemplateLiteral
   emitName: string
-  value: null
   node: Expression | SpreadElement
 }
 type ComponentArrayEmitUnknownName = {
   type: 'array'
   key: null
   emitName: null
-  value: null
   node: Expression | SpreadElement
 }
 export type ComponentArrayEmit =
@@ -150,32 +153,35 @@ export type ComponentObjectEmit =
 
 export type ComponentUnknownEmit = {
   type: 'unknown'
-  key: null
   emitName: null
-  value: null
-  node: Expression | SpreadElement | null
+  node: Expression | SpreadElement | TypeNode | null
 }
 
 export type ComponentTypeEmitCallSignature = {
   type: 'type'
   key: TSLiteralType
   emitName: string
-  value: null
   node: TSCallSignatureDeclaration | TSFunctionType
 }
 export type ComponentTypeEmitPropertySignature = {
   type: 'type'
   key: Identifier | Literal
   emitName: string
-  value: null
   node: TSPropertySignature | TSMethodSignature
 }
 export type ComponentTypeEmit =
   | ComponentTypeEmitCallSignature
   | ComponentTypeEmitPropertySignature
 
+export type ComponentInferTypeEmit = {
+  type: 'infer-type'
+  emitName: string
+  node: TypeNode
+}
+
 export type ComponentEmit =
   | ComponentArrayEmit
   | ComponentObjectEmit
   | ComponentTypeEmit
+  | ComponentInferTypeEmit
   | ComponentUnknownEmit


Test patch:
diff --git a/tests/fixtures/typescript/src/test.vue b/tests/fixtures/typescript/src/test.vue
new file mode 100644
index 00000000..5bf03f4a
--- /dev/null
+++ b/tests/fixtures/typescript/src/test.vue
@@ -0,0 +1,1 @@
+<script lang="ts"></script>
diff --git a/tests/fixtures/typescript/src/test01.ts b/tests/fixtures/typescript/src/test01.ts
new file mode 100644
index 00000000..d1455084
--- /dev/null
+++ b/tests/fixtures/typescript/src/test01.ts
@@ -0,0 +1,20 @@
+export type Props1 = {
+  foo: string
+  bar?: number
+  baz?: boolean
+}
+export type Emits1 = {
+  (e: 'foo' | 'bar', payload: string): void
+  (e: 'baz', payload: number): void
+}
+export type Props2 = {
+  a: string
+  b?: number
+  c?: boolean
+  d?: boolean
+  e?: number | string
+  f?: () => number
+  g?: { foo?: string }
+  h?: string[]
+  i?: readonly string[]
+}
diff --git a/tests/fixtures/typescript/tsconfig.json b/tests/fixtures/typescript/tsconfig.json
new file mode 100644
index 00000000..c13ef64e
--- /dev/null
+++ b/tests/fixtures/typescript/tsconfig.json
@@ -0,0 +1,6 @@
+{
+  "compilerOptions": {
+    "strict": true,
+    "skipLibCheck": true
+  }
+}
diff --git a/tests/fixtures/utils/ts-utils/src/test.ts b/tests/fixtures/utils/ts-utils/src/test.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/fixtures/utils/ts-utils/src/test.vue b/tests/fixtures/utils/ts-utils/src/test.vue
new file mode 100644
index 00000000..5bf03f4a
--- /dev/null
+++ b/tests/fixtures/utils/ts-utils/src/test.vue
@@ -0,0 +1,1 @@
+<script lang="ts"></script>
diff --git a/tests/fixtures/utils/ts-utils/tsconfig.json b/tests/fixtures/utils/ts-utils/tsconfig.json
new file mode 100644
index 00000000..c13ef64e
--- /dev/null
+++ b/tests/fixtures/utils/ts-utils/tsconfig.json
@@ -0,0 +1,6 @@
+{
+  "compilerOptions": {
+    "strict": true,
+    "skipLibCheck": true
+  }
+}
diff --git a/tests/lib/rules/no-restricted-props.js b/tests/lib/rules/no-restricted-props.js
index a72d5b24..0f1947ed 100644
--- a/tests/lib/rules/no-restricted-props.js
+++ b/tests/lib/rules/no-restricted-props.js
@@ -6,6 +6,9 @@
 const semver = require('semver')
 const RuleTester = require('eslint').RuleTester
 const rule = require('../../../lib/rules/no-restricted-props')
+const {
+  getTypeScriptFixtureTestOptions
+} = require('../../test-utils/typescript')
 
 const tester = new RuleTester({
   parser: require.resolve('vue-eslint-parser'),
@@ -577,6 +580,23 @@ tester.run('no-restricted-props', rule, {
                 ]
               }
             ]
+          },
+          {
+            code: `
+            <script setup lang="ts">
+            import {Props1 as Props} from './test01'
+            defineProps<Props>()
+            </script>
+            `,
+            ...getTypeScriptFixtureTestOptions(),
+            options: [{ name: 'foo', suggest: 'Foo' }],
+            errors: [
+              {
+                message: 'Using `foo` props is not allowed.',
+                line: 4,
+                suggestions: null
+              }
+            ]
           }
         ])
   ]
diff --git a/tests/lib/rules/no-unused-properties.js b/tests/lib/rules/no-unused-properties.js
index 8d4b2b21..2db132c4 100644
--- a/tests/lib/rules/no-unused-properties.js
+++ b/tests/lib/rules/no-unused-properties.js
@@ -7,6 +7,9 @@
 const { RuleTester, Linter } = require('eslint')
 const assert = require('assert')
 const rule = require('../../../lib/rules/no-unused-properties')
+const {
+  getTypeScriptFixtureTestOptions
+} = require('../../test-utils/typescript')
 
 const tester = new RuleTester({
   parser: require.resolve('vue-eslint-parser'),
@@ -2961,6 +2964,24 @@ tester.run('no-unused-properties', rule, {
           line: 8
         }
       ]
+    },
+    // script setup with typescript
+    {
+      code: `
+      <script setup lang="ts">
+      import {Props1 as Props} from './test01'
+      defineProps<Props>()
+      </script>
+      <template>
+      {{ foo }}{{ bar }}
+      </template>`,
+      ...getTypeScriptFixtureTestOptions(),
+      errors: [
+        {
+          message: "'baz' of property found, but never used.",
+          line: 4
+        }
+      ]
     }
   ]
 })
diff --git a/tests/lib/rules/padding-lines-in-component-definition.js b/tests/lib/rules/padding-lines-in-component-definition.js
index 58647654..7b2eda6c 100644
--- a/tests/lib/rules/padding-lines-in-component-definition.js
+++ b/tests/lib/rules/padding-lines-in-component-definition.js
@@ -6,6 +6,9 @@
 
 const RuleTester = require('eslint').RuleTester
 const rule = require('../../../lib/rules/padding-lines-in-component-definition')
+const {
+  getTypeScriptFixtureTestOptions
+} = require('../../test-utils/typescript')
 
 const tester = new RuleTester({
   parser: require.resolve('vue-eslint-parser'),
@@ -398,6 +401,14 @@ tester.run('padding-lines-in-component-definition', rule, {
         </script>
       `,
       options: [{ betweenOptions: 'always', groupSingleLineProperties: false }]
+    },
+    {
+      code: `
+      <script setup lang="ts">
+      import {Props1 as Props} from './test01'
+      defineProps<Props>()
+      </script>`,
+      ...getTypeScriptFixtureTestOptions()
     }
   ],
   invalid: [
diff --git a/tests/lib/rules/prefer-prop-type-boolean-first.js b/tests/lib/rules/prefer-prop-type-boolean-first.js
index 9f67e336..1c66f841 100644
--- a/tests/lib/rules/prefer-prop-type-boolean-first.js
+++ b/tests/lib/rules/prefer-prop-type-boolean-first.js
@@ -6,6 +6,9 @@
 
 const RuleTester = require('eslint').RuleTester
 const rule = require('../../../lib/rules/prefer-prop-type-boolean-first')
+const {
+  getTypeScriptFixtureTestOptions
+} = require('../../test-utils/typescript')
 
 const tester = new RuleTester({
   parser: require.resolve('vue-eslint-parser'),
@@ -62,6 +65,14 @@ tester.run('prefer-prop-type-boolean-first', rule, {
           })
         </script>
       `
+    },
+    {
+      code: `
+      <script setup lang="ts">
+      import {Props1 as Props} from './test01'
+      defineProps<Props>()
+      </script>`,
+      ...getTypeScriptFixtureTestOptions()
     }
   ],
   invalid: [
diff --git a/tests/lib/rules/require-emit-validator.js b/tests/lib/rules/require-emit-validator.js
index 751f775e..2b60e891 100644
--- a/tests/lib/rules/require-emit-validator.js
+++ b/tests/lib/rules/require-emit-validator.js
@@ -5,6 +5,9 @@
 'use strict'
 
 const rule = require('../../../lib/rules/require-emit-validator')
+const {
+  getTypeScriptFixtureTestOptions
+} = require('../../test-utils/typescript')
 
 const RuleTester = require('eslint').RuleTester
 
@@ -169,6 +172,14 @@ ruleTester.run('require-emit-validator', rule, {
         sourceType: 'module',
         parser: require.resolve('@typescript-eslint/parser')
       }
+    },
+    {
+      code: `
+      <script setup lang="ts">
+      import {Emits1 as Emits} from './test01'
+      const emit = defineEmits<Emits>()
+      </script>`,
+      ...getTypeScriptFixtureTestOptions()
     }
   ],
 
diff --git a/tests/lib/rules/require-explicit-emits.js b/tests/lib/rules/require-explicit-emits.js
index a5bc5114..43a3c259 100644
--- a/tests/lib/rules/require-explicit-emits.js
+++ b/tests/lib/rules/require-explicit-emits.js
@@ -6,6 +6,9 @@
 
 const RuleTester = require('eslint').RuleTester
 const rule = require('../../../lib/rules/require-explicit-emits')
+const {
+  getTypeScriptFixtureTestOptions
+} = require('../../test-utils/typescript')
 
 const tester = new RuleTester({
   parser: require.resolve('vue-eslint-parser'),
@@ -618,6 +621,17 @@ tester.run('require-explicit-emits', rule, {
       </script>
       `,
       parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
+    },
+    {
+      code: `
+      <script setup lang="ts">
+      import {Emits1 as Emits} from './test01'
+      const emit = defineEmits<Emits>()
+      emit('foo')
+      emit('bar')
+      emit('baz')
+      </script>`,
+      ...getTypeScriptFixtureTestOptions()
     }
   ],
   invalid: [
@@ -1950,6 +1964,25 @@ emits: {'foo': null}
           line: 6
         }
       ]
+    },
+    {
+      code: `
+      <script setup lang="ts">
+      import {Emits1 as Emits} from './test01'
+      const emit = defineEmits<Emits>()
+      emit('foo')
+      emit('bar')
+      emit('baz')
+      emit('qux')
+      </script>`,
+      ...getTypeScriptFixtureTestOptions(),
+      errors: [
+        {
+          message:
+            'The "qux" event has been triggered but not declared on `defineEmits`.',
+          line: 8
+        }
+      ]
     }
   ]
 })
diff --git a/tests/lib/rules/require-prop-comment.js b/tests/lib/rules/require-prop-comment.js
index f72d6905..900f25ec 100644
--- a/tests/lib/rules/require-prop-comment.js
+++ b/tests/lib/rules/require-prop-comment.js
@@ -6,6 +6,9 @@
 
 const RuleTester = require('eslint').RuleTester
 const rule = require('../../../lib/rules/require-prop-comment')
+const {
+  getTypeScriptFixtureTestOptions
+} = require('../../test-utils/typescript')
 
 const tester = new RuleTester({
   parser: require.resolve('vue-eslint-parser'),
@@ -57,10 +60,10 @@ tester.run('require-prop-comment', rule, {
       const goodProps = defineProps({
         /** JSDoc comment */
         a: Number,
-      
+
         /* block comment */
         b: Number,
-      
+
         // line comment
         c: Number,
       })
@@ -93,6 +96,14 @@ tester.run('require-prop-comment', rule, {
       parserOptions: {
         parser: require.resolve('@typescript-eslint/parser')
       }
+    },
+    {
+      code: `
+      <script setup lang="ts">
+      import {Props1 as Props} from './test01'
+      defineProps<Props>()
+      </script>`,
+      ...getTypeScriptFixtureTestOptions()
     }
   ],
   invalid: [
@@ -102,10 +113,10 @@ tester.run('require-prop-comment', rule, {
         props: {
           // line comment
           b: Number,
-      
+
           /* block comment */
           c: Number,
-      
+
           d: Number,
         }
       })
@@ -134,10 +145,10 @@ tester.run('require-prop-comment', rule, {
       const badProps = defineProps({
         /** JSDoc comment */
         b: Number,
-      
+
         // line comment
         c: Number,
-      
+
         d: Number,
       })
       </script>
@@ -167,10 +178,10 @@ tester.run('require-prop-comment', rule, {
       const badProps = defineProps({
         /** JSDoc comment */
         b: Number,
-      
+
         /* block comment */
         c: Number,
-      
+
         d: Number,
       })
       </script>
diff --git a/tests/lib/rules/require-prop-type-constructor.js b/tests/lib/rules/require-prop-type-constructor.js
index a662e34d..9cd4eae6 100644
--- a/tests/lib/rules/require-prop-type-constructor.js
+++ b/tests/lib/rules/require-prop-type-constructor.js
@@ -5,6 +5,9 @@
 'use strict'
 
 const rule = require('../../../lib/rules/require-prop-type-constructor')
+const {
+  getTypeScriptFixtureTestOptions
+} = require('../../test-utils/typescript')
 const RuleTester = require('eslint').RuleTester
 
 const ruleTester = new RuleTester({
@@ -85,6 +88,14 @@ ruleTester.run('require-prop-type-constructor', rule, {
           props: ['name',,,]
         }
       `
+    },
+    {
+      code: `
+      <script setup lang="ts">
+      import {Props1 as Props} from './test01'
+      defineProps<Props>()
+      </script>`,
+      ...getTypeScriptFixtureTestOptions()
     }
   ],
 
diff --git a/tests/lib/rules/require-prop-types.js b/tests/lib/rules/require-prop-types.js
index bcbff901..9b7077fc 100644
--- a/tests/lib/rules/require-prop-types.js
+++ b/tests/lib/rules/require-prop-types.js
@@ -5,6 +5,9 @@
 'use strict'
 
 const rule = require('../../../lib/rules/require-prop-types')
+const {
+  getTypeScriptFixtureTestOptions
+} = require('../../test-utils/typescript')
 
 const RuleTester = require('eslint').RuleTester
 
@@ -176,6 +179,14 @@ ruleTester.run('require-prop-types', rule, {
         parser: require.resolve('@typescript-eslint/parser')
       },
       parser: require.resolve('vue-eslint-parser')
+    },
+    {
+      code: `
+      <script setup lang="ts">
+      import {Props1 as Props} from './test01'
+      defineProps<Props>()
+      </script>`,
+      ...getTypeScriptFixtureTestOptions()
     }
   ],
 
diff --git a/tests/lib/rules/require-valid-default-prop.js b/tests/lib/rules/require-valid-default-prop.js
index 2f776ea1..cec05ba0 100644
--- a/tests/lib/rules/require-valid-default-prop.js
+++ b/tests/lib/rules/require-valid-default-prop.js
@@ -5,6 +5,9 @@
 'use strict'
 
 const rule = require('../../../lib/rules/require-valid-default-prop')
+const {
+  getTypeScriptFixtureTestOptions
+} = require('../../test-utils/typescript')
 const RuleTester = require('eslint').RuleTester
 
 const parserOptions = {
@@ -267,6 +270,24 @@ ruleTester.run('require-valid-default-prop', rule, {
         parser: require.resolve('@typescript-eslint/parser')
       },
       parser: require.resolve('vue-eslint-parser')
+    },
+    {
+      code: `
+      <script setup lang="ts">
+      import {Props2 as Props} from './test01'
+      withDefaults(defineProps<Props>(), {
+        a: 's',
+        b: 42,
+        c: true,
+        d: false,
+        e: 's',
+        f: () => 42,
+        g: ()=>({ foo: 'foo' }),
+        h: ()=>(['foo', 'bar']),
+        i: ()=>(['foo', 'bar']),
+      })
+      </script>`,
+      ...getTypeScriptFixtureTestOptions()
     }
   ],
 
@@ -929,6 +950,63 @@ ruleTester.run('require-valid-default-prop', rule, {
           line: 4
         }
       ]
+    },
+    {
+      code: `
+      <script setup lang="ts">
+      import {Props2 as Props} from './test01'
+      withDefaults(defineProps<Props>(), {
+        a: 42,
+        b: 's',
+        c: {},
+        d: [],
+        e: [42],
+        f: {},
+        g: { foo: 'foo' },
+        h: ['foo', 'bar'],
+        i: ['foo', 'bar'],
+      })
+      </script>`,
+      ...getTypeScriptFixtureTestOptions(),
+      errors: [
+        {
+          message: "Type of the default value for 'a' prop must be a string.",
+          line: 5
+        },
+        {
+          message: "Type of the default value for 'b' prop must be a number.",
+          line: 6
+        },
+        {
+          message: "Type of the default value for 'c' prop must be a boolean.",
+          line: 7
+        },
+        {
+          message: "Type of the default value for 'd' prop must be a boolean.",
+          line: 8
+        },
+        {
+          message:
+            "Type of the default value for 'e' prop must be a string or number.",
+          line: 9
+        },
+        {
+          message: "Type of the default value for 'f' prop must be a function.",
+          line: 10
+        },
+        {
+          message: "Type of the default value for 'g' prop must be a function.",
+          line: 11
+        },
+        {
+          message: "Type of the default value for 'h' prop must be a function.",
+          line: 12
+        },
+        {
+          message: "Type of the default value for 'i' prop must be a function.",
+          line: 13
+        }
+      ]
     }
   ]
 })
diff --git a/tests/lib/rules/return-in-emits-validator.js b/tests/lib/rules/return-in-emits-validator.js
index d4b5272e..c72392bd 100644
--- a/tests/lib/rules/return-in-emits-validator.js
+++ b/tests/lib/rules/return-in-emits-validator.js
@@ -5,6 +5,9 @@
 'use strict'
 
 const rule = require('../../../lib/rules/return-in-emits-validator')
+const {
+  getTypeScriptFixtureTestOptions
+} = require('../../test-utils/typescript')
 
 const RuleTester = require('eslint').RuleTester
 
@@ -123,6 +126,14 @@ ruleTester.run('return-in-emits-validator', rule, {
         })
         </script>
       `
+    },
+    {
+      code: `
+      <script setup lang="ts">
+      import {Emits1 as Emits} from './test01'
+      const emit = defineEmits<Emits>()
+      </script>`,
+      ...getTypeScriptFixtureTestOptions()
     }
   ],
 
diff --git a/tests/lib/utils/index.js b/tests/lib/utils/index.js
index 9b02ba46..6afecaea 100644
--- a/tests/lib/utils/index.js
+++ b/tests/lib/utils/index.js
@@ -367,9 +367,9 @@ describe('getComponentProps', () => {
 
     assert.equal(props.length, 5, 'it detects all props')
 
-    assert.strictEqual(props[0].key, null)
+    assert.strictEqual(props[0].key, undefined)
     assert.strictEqual(props[0].node.type, 'SpreadElement')
-    assert.strictEqual(props[0].value, null)
+    assert.strictEqual(props[0].value, undefined)
 
     assert.strictEqual(props[1].key.type, 'Identifier')
     assert.strictEqual(props[1].node.type, 'Property')
diff --git a/tests/lib/utils/ts-utils/index/get-component-emits.js b/tests/lib/utils/ts-utils/index/get-component-emits.js
new file mode 100644
index 00000000..8bba2be7
--- /dev/null
+++ b/tests/lib/utils/ts-utils/index/get-component-emits.js
@@ -0,0 +1,122 @@
+/**
+ * Test for getComponentEmitsFromTypeDefineTypes
+ */
+'use strict'
+
+const path = require('path')
+const fs = require('fs')
+const Linter = require('eslint').Linter
+const parser = require('vue-eslint-parser')
+const tsParser = require('@typescript-eslint/parser')
+const utils = require('../../../../../lib/utils/index')
+const assert = require('assert')
+
+const FIXTURES_ROOT = path.resolve(
+  __dirname,
+  '../../../../fixtures/utils/ts-utils'
+)
+const TSCONFIG_PATH = path.resolve(FIXTURES_ROOT, './tsconfig.json')
+const SRC_TS_TEST_PATH = path.join(FIXTURES_ROOT, './src/test.ts')
+
+function extractComponentProps(code, tsFileCode) {
+  const linter = new Linter()
+  const config = {
+    parser: 'vue-eslint-parser',
+    parserOptions: {
+      ecmaVersion: 2020,
+      parser: tsParser,
+      project: [TSCONFIG_PATH],
+      extraFileExtensions: ['.vue']
+    },
+    rules: {
+      test: 'error'
+    }
+  }
+  linter.defineParser('vue-eslint-parser', parser)
+  const result = []
+  linter.defineRule('test', {
+    create(context) {
+      return utils.defineScriptSetupVisitor(context, {
+        onDefineEmitsEnter(_node, emits) {
+          result.push(
+            ...emits.map((emit) => ({
+              type: emit.type,
+              name: emit.emitName
+            }))
+          )
+        }
+      })
+    }
+  })
+  fs.writeFileSync(SRC_TS_TEST_PATH, tsFileCode || '', 'utf8')
+  // clean './src/test.ts' cache
+  tsParser.parseForESLint(tsFileCode || '', {
+    ...config.parserOptions,
+    filePath: SRC_TS_TEST_PATH
+  })
+  assert.deepStrictEqual(
+    linter.verify(code, config, path.join(FIXTURES_ROOT, './src/test.vue')),
+    []
+  )
+  // reset
+  fs.writeFileSync(SRC_TS_TEST_PATH, '', 'utf8')
+  return result
+}
+
+describe('getComponentEmitsFromTypeDefineTypes', () => {
+  for (const { scriptCode, tsFileCode, props: expected } of [
+    {
+      scriptCode: `defineEmits<{(e:'foo'):void,(e:'bar'):void}>()`,
+      props: [
+        { type: 'type', name: 'foo' },
+        { type: 'type', name: 'bar' }
+      ]
+    },
+    {
+      tsFileCode: `export type Emits = {(e:'foo'):void,(e:'bar'):void}`,
+      scriptCode: `import { Emits } from './test'
+      defineEmits<Emits>()`,
+      props: [
+        { type: 'infer-type', name: 'foo' },
+        { type: 'infer-type', name: 'bar' }
+      ]
+    },
+    {
+      tsFileCode: `export type Emits = any`,
+      scriptCode: `import { Emits } from './test'
+      defineEmits<Emits>()`,
+      props: [{ type: 'unknown', name: null }]
+    },
+    {
+      tsFileCode: `export type Emits = {(e:'foo' | 'bar'): void, (e:'baz',payload:number): void}`,
+      scriptCode: `import { Emits } from './test'
+      defineEmits<Emits>()`,
+      props: [
+        { type: 'infer-type', name: 'foo' },
+        { type: 'infer-type', name: 'bar' },
+        { type: 'infer-type', name: 'baz' }
+      ]
+    },
+    {
+      tsFileCode: `export type Emits = { a: [], b: [number], c: [string]}`,
+      scriptCode: `import { Emits } from './test'
+      defineEmits<Emits>()`,
+      props: [
+        { type: 'infer-type', name: 'a' },
+        { type: 'infer-type', name: 'b' },
+        { type: 'infer-type', name: 'c' }
+      ]
+    }
+  ]) {
+    const code = `<script setup lang="ts"> ${scriptCode} </script>`
+    it(`should return expected props with :${code}`, () => {
+      const props = extractComponentProps(code, tsFileCode)
+
+      assert.deepStrictEqual(
+        props,
+        expected,
+        `\n${JSON.stringify(props)}\n === \n${JSON.stringify(expected)}`
+      )
+    })
+  }
+})
diff --git a/tests/lib/utils/ts-utils/index/get-component-props.js b/tests/lib/utils/ts-utils/index/get-component-props.js
new file mode 100644
index 00000000..2ab0070c
--- /dev/null
+++ b/tests/lib/utils/ts-utils/index/get-component-props.js
@@ -0,0 +1,213 @@
+/**
+ * Test for getComponentPropsFromTypeDefineTypes
+ */
+'use strict'
+
+const path = require('path')
+const fs = require('fs')
+const Linter = require('eslint').Linter
+const parser = require('vue-eslint-parser')
+const tsParser = require('@typescript-eslint/parser')
+const utils = require('../../../../../lib/utils/index')
+const assert = require('assert')
+
+const FIXTURES_ROOT = path.resolve(
+  __dirname,
+  '../../../../fixtures/utils/ts-utils'
+)
+const TSCONFIG_PATH = path.resolve(FIXTURES_ROOT, './tsconfig.json')
+const SRC_TS_TEST_PATH = path.join(FIXTURES_ROOT, './src/test.ts')
+
+function extractComponentProps(code, tsFileCode) {
+  const linter = new Linter()
+  const config = {
+    parser: 'vue-eslint-parser',
+    parserOptions: {
+      ecmaVersion: 2020,
+      parser: tsParser,
+      project: [TSCONFIG_PATH],
+      extraFileExtensions: ['.vue']
+    },
+    rules: {
+      test: 'error'
+    }
+  }
+  linter.defineParser('vue-eslint-parser', parser)
+  const result = []
+  linter.defineRule('test', {
+    create(context) {
+      return utils.defineScriptSetupVisitor(context, {
+        onDefinePropsEnter(_node, props) {
+          result.push(
+            ...props.map((prop) => ({
+              type: prop.type,
+              name: prop.propName,
+              required: prop.required ?? null,
+              types: prop.types ?? null
+            }))
+          )
+        }
+      })
+    }
+  })
+  fs.writeFileSync(SRC_TS_TEST_PATH, tsFileCode || '', 'utf8')
+  // clean './src/test.ts' cache
+  tsParser.parseForESLint(tsFileCode || '', {
+    ...config.parserOptions,
+    filePath: SRC_TS_TEST_PATH
+  })
+  assert.deepStrictEqual(
+    linter.verify(code, config, path.join(FIXTURES_ROOT, './src/test.vue')),
+    []
+  )
+  // reset
+  fs.writeFileSync(SRC_TS_TEST_PATH, '', 'utf8')
+  return result
+}
+
+describe('getComponentPropsFromTypeDefineTypes', () => {
+  for (const { scriptCode, tsFileCode, props: expected } of [
+    {
+      scriptCode: `defineProps<{foo:string,bar?:number}>()`,
+      props: [
+        { type: 'type', name: 'foo', required: true, types: ['String'] },
+        { type: 'type', name: 'bar', required: false, types: ['Number'] }
+      ]
+    },
+    {
+      scriptCode: `defineProps<{foo:string,bar?:number} & {baz?:string|number}>()`,
+      props: [
+        { type: 'type', name: 'foo', required: true, types: ['String'] },
+        { type: 'type', name: 'bar', required: false, types: ['Number'] },
+        {
+          type: 'type',
+          name: 'baz',
+          required: false,
+          types: ['String', 'Number']
+        }
+      ]
+    },
+    {
+      tsFileCode: `export type Props = {foo:string,bar?:number}`,
+      scriptCode: `import { Props } from './test'
+      defineProps<Props>()`,
+      props: [
+        { type: 'infer-type', name: 'foo', required: true, types: ['String'] },
+        { type: 'infer-type', name: 'bar', required: false, types: ['Number'] }
+      ]
+    },
+    {
+      tsFileCode: `export type Props = any`,
+      scriptCode: `import { Props } from './test'
+      defineProps<Props>()`,
+      props: [{ type: 'unknown', name: null, required: null, types: null }]
+    },
+    {
+      tsFileCode: `
+      interface Props {
+        a?: number;
+        b?: string;
+      }
+      export interface Props2 extends Required<Props> {
+        c?: boolean;
+      }`,
+      scriptCode: `import { Props2 } from './test'
+      defineProps<Props2>()`,
+      props: [
+        { type: 'infer-type', name: 'c', required: false, types: ['Boolean'] },
+        { type: 'infer-type', name: 'a', required: true, types: ['Number'] },
+        { type: 'infer-type', name: 'b', required: true, types: ['String'] }
+      ]
+    },
+    {
+      tsFileCode: `
+      export type Props = {
+        a: string
+        b?: number
+        c?: boolean
+        d?: boolean
+        e?: number | string
+        f?: () => number
+        g?: { foo?: string }
+        h?: string[]
+        i?: readonly string[]
+      }`,
+      scriptCode: `import { Props } from './test'
+      defineProps<Props>()`,
+      props: [
+        { type: 'infer-type', name: 'a', required: true, types: ['String'] },
+        { type: 'infer-type', name: 'b', required: false, types: ['Number'] },
+        { type: 'infer-type', name: 'c', required: false, types: ['Boolean'] },
+        { type: 'infer-type', name: 'd', required: false, types: ['Boolean'] },
+        {
+          type: 'infer-type',
+          name: 'e',
+          required: false,
+          types: ['String', 'Number']
+        },
+        { type: 'infer-type', name: 'f', required: false, types: ['Function'] },
+        { type: 'infer-type', name: 'g', required: false, types: ['Object'] },
+        { type: 'infer-type', name: 'h', required: false, types: ['Array'] },
+        { type: 'infer-type', name: 'i', required: false, types: ['Array'] }
+      ]
+    },
+    {
+      tsFileCode: `
+      export interface Props {
+        a?: number;
+        b?: string;
+      }`,
+      scriptCode: `import { Props } from './test'
+defineProps<Props & {foo?:string}>()`,
+      props: [
+        { type: 'infer-type', name: 'a', required: false, types: ['Number'] },
+        { type: 'infer-type', name: 'b', required: false, types: ['String'] },
+        { type: 'type', name: 'foo', required: false, types: ['String'] }
+      ]
+    },
+    {
+      tsFileCode: `
+      export type A = string | number`,
+      scriptCode: `import { A } from './test'
+defineProps<{foo?:A}>()`,
+      props: [
+        {
+          type: 'type',
+          name: 'foo',
+          required: false,
+          types: ['String', 'Number']
+        }
+      ]
+    },
+    {
+      scriptCode: `enum A {a = 'a', b = 'b'}
+defineProps<{foo?:A}>()`,
+      props: [{ type: 'type', name: 'foo', required: false, types: ['String'] }]
+    },
+    {
+      scriptCode: `
+const foo = 42
+enum A {a = foo, b = 'b'}
+defineProps<{foo?:A}>()`,
+      props: [
+        {
+          type: 'type',
+          name: 'foo',
+          required: false,
+          types: ['Number', 'String']
+        }
+      ]
+    }
+  ]) {
+    const code = `<script setup lang="ts"> ${scriptCode} </script>`
+    it(`should return expected props with :${code}`, () => {
+      const props = extractComponentProps(code, tsFileCode)
+
+      assert.deepStrictEqual(
+        props,
+        expected,
+        `\n${JSON.stringify(props)}\n === \n${JSON.stringify(expected)}`
+      )
+    })
+  }
+})
diff --git a/tests/test-utils/typescript.js b/tests/test-utils/typescript.js
new file mode 100644
index 00000000..e06008a8
--- /dev/null
+++ b/tests/test-utils/typescript.js
@@ -0,0 +1,26 @@
+const path = require('path')
+const tsParser = require('@typescript-eslint/parser')
+
+const FIXTURES_ROOT = path.resolve(__dirname, '../fixtures/typescript')
+const TSCONFIG_PATH = path.resolve(FIXTURES_ROOT, './tsconfig.json')
+const SRC_VUE_TEST_PATH = path.join(FIXTURES_ROOT, './src/test.vue')
+
+module.exports = {
+  getTypeScriptFixtureTestOptions
+}
+
+function getTypeScriptFixtureTestOptions() {
+  const parser = require.resolve('vue-eslint-parser')
+  const parserOptions = {
+    parser: { ts: tsParser },
+    ecmaVersion: 2020,
+    sourceType: 'module',
+    project: [TSCONFIG_PATH],
+    extraFileExtensions: ['.vue']
+  }
+  return {
+    parser,
+    parserOptions,
+    filename: SRC_VUE_TEST_PATH
+  }
+}