diff --git a/lib/rules/define-props-declaration.js b/lib/rules/define-props-declaration.js
index f05e6da3c..17dfb8830 100644
--- a/lib/rules/define-props-declaration.js
+++ b/lib/rules/define-props-declaration.js
@@ -36,6 +36,127 @@ const mapNativeType = (/** @type {string} */ nativeType) => {
}
}
+/**
+ * @param {ComponentProp} prop
+ * @param {SourceCode} sourceCode
+ */
+function getComponentPropData(prop, sourceCode) {
+ const unknownType = {
+ name: prop.propName,
+ type: 'unknown',
+ required: undefined,
+ defaultValue: undefined
+ }
+
+ if (prop.type !== 'object') {
+ return unknownType
+ }
+ const type = optionGetType(prop.value, sourceCode)
+ if (type === null) {
+ return unknownType
+ }
+ const required = optionGetRequired(prop.value)
+ const defaultValue = optionGetDefault(prop.value)
+
+ return {
+ name: prop.propName,
+ type: mapNativeType(type),
+ required,
+ defaultValue
+ }
+}
+
+/**
+ * @param {Expression} node
+ * @param {SourceCode} sourceCode
+ * @returns {string | null}
+ */
+function optionGetType(node, sourceCode) {
+ switch (node.type) {
+ case 'Identifier': {
+ return node.name
+ }
+ case 'ObjectExpression': {
+ // foo: {
+ const typeProperty = utils.findProperty(node, 'type')
+ if (typeProperty == null) {
+ return null
+ }
+ if (typeProperty.value.type === 'TSAsExpression') {
+ const typeAnnotation = typeProperty.value.typeAnnotation
+ if (typeAnnotation.typeName.name !== 'PropType') {
+ return null
+ }
+
+ // in some project configuration parser populates deprecated field `typeParameters` instead of `typeArguments`
+ const typeArguments =
+ 'typeArguments' in typeProperty.value
+ ? typeAnnotation.typeArguments
+ : typeAnnotation.typeParameters
+
+ const typeArgument = Array.isArray(typeArguments)
+ ? typeArguments[0].params[0]
+ : typeArguments.params[0]
+
+ if (typeArgument === undefined) {
+ return null
+ }
+
+ return sourceCode.getText(typeArgument)
+ }
+ return optionGetType(typeProperty.value, sourceCode)
+ }
+ case 'ArrayExpression': {
+ return null
+ }
+ case 'FunctionExpression':
+ case 'ArrowFunctionExpression': {
+ return null
+ }
+ }
+
+ // Unknown
+ return null
+}
+
+/**
+ * @param {Expression} node
+ * @returns {boolean | undefined }
+ */
+function optionGetRequired(node) {
+ if (node.type === 'ObjectExpression') {
+ const requiredProperty = utils.findProperty(node, 'required')
+ if (requiredProperty == null) {
+ return undefined
+ }
+
+ if (requiredProperty.value.type === 'Literal') {
+ return Boolean(requiredProperty.value.value)
+ }
+ }
+
+ // Unknown
+ return undefined
+}
+
+/**
+ * @param {Expression} node
+ * @returns {Expression | undefined }
+ */
+function optionGetDefault(node) {
+ if (node.type === 'ObjectExpression') {
+ const defaultProperty = utils.findProperty(node, 'default')
+ if (defaultProperty == null) {
+ return undefined
+ }
+
+ return defaultProperty.value
+ }
+
+ // Unknown
+ return undefined
+}
+
/**
* @typedef {import('../utils').ComponentProp} ComponentProp
*/
@@ -72,93 +193,6 @@ module.exports = {
create(context) {
const sourceCode = context.getSourceCode()
- /**
- * @param {Expression} node
- * @returns {string | null}
- */
- function optionGetType(node) {
- switch (node.type) {
- case 'Identifier': {
- return node.name
- }
- case 'ObjectExpression': {
- // foo: {
- const typeProperty = utils.findProperty(node, 'type')
- if (typeProperty == null) {
- return null
- }
- if (typeProperty.value.type === 'TSAsExpression') {
- if (
- typeProperty.value.typeAnnotation.typeName.name !== 'PropType'
- ) {
- return null
- }
-
- const typeArgument =
- typeProperty.value.typeAnnotation.typeArguments.params[0]
- if (typeArgument === undefined) {
- return null
- }
-
- return sourceCode.getText(typeArgument)
- }
- return optionGetType(typeProperty.value)
- }
- case 'ArrayExpression': {
- // foo: [
- return null
- // return node.elements.map((arrayElement) =>
- // optionGetType(arrayElement)
- // )
- }
- case 'FunctionExpression':
- case 'ArrowFunctionExpression': {
- return null
- }
- }
-
- // Unknown
- return null
- }
-
- /**
- * @param {Expression} node
- * @returns {boolean | undefined }
- */
- function optionGetRequired(node) {
- if (node.type === 'ObjectExpression') {
- const requiredProperty = utils.findProperty(node, 'required')
- if (requiredProperty == null) {
- return undefined
- }
-
- if (requiredProperty.value.type === 'Literal') {
- return Boolean(requiredProperty.value.value)
- }
- }
-
- // Unknown
- return undefined
- }
-
- /**
- * @param {Expression} node
- * @returns {Expression | undefined }
- */
- function optionGetDefault(node) {
- if (node.type === 'ObjectExpression') {
- const defaultProperty = utils.findProperty(node, 'default')
- if (defaultProperty == null) {
- return undefined
- }
-
- return defaultProperty.value
- }
-
- // Unknown
- return undefined
- }
-
const scriptSetup = utils.getScriptSetupElement(context)
if (!scriptSetup || !utils.hasAttribute(scriptSetup, 'lang', 'ts')) {
return {}
@@ -176,31 +210,9 @@ module.exports = {
node,
messageId: 'hasArg',
*fix(fixer) {
- const propTypes = props.map((prop) => {
- const unknownType = {
- name: prop.propName,
- type: 'unknown',
- required: undefined,
- defaultValue: undefined
- }
-
- if (prop.type !== 'object') {
- return unknownType
- }
- const type = optionGetType(prop.value)
- if (type === null) {
- return unknownType
- }
- const required = optionGetRequired(prop.value)
- const defaultValue = optionGetDefault(prop.value)
-
- return {
- name: prop.propName,
- type: mapNativeType(type),
- required,
- defaultValue
- }
- })
+ const propTypes = props.map((prop) =>
+ getComponentPropData(prop, sourceCode)
+ )
const definePropsType = `{ ${propTypes
.map(
@@ -209,8 +221,10 @@ module.exports = {
)
.join(', ')} }`
+ // remove defineProps function parameters
yield fixer.replaceText(node.arguments[0], '')
+ // add type annotation
if (separateInterface) {
const variableDeclarationNode = node.parent.parent
if (!variableDeclarationNode) return
@@ -227,6 +241,7 @@ module.exports = {
)
}
+ // add defaults if needed
const defaults = propTypes.filter(
({ defaultValue }) => defaultValue
)
diff --git a/tests/lib/rules/define-props-declaration.js b/tests/lib/rules/define-props-declaration.js
index 87c6633ca..dd98c3a2f 100644
--- a/tests/lib/rules/define-props-declaration.js
+++ b/tests/lib/rules/define-props-declaration.js
@@ -10,7 +10,7 @@ const rule = require('../../../lib/rules/define-props-declaration')
const tester = new RuleTester({
languageOptions: {
parser: require('vue-eslint-parser'),
- ecmaVersion: 'latest',
+ ecmaVersion: '2020',
sourceType: 'module',
parserOptions: {
parser: require.resolve('@typescript-eslint/parser')
@@ -289,6 +289,28 @@ tester.run('define-props-declaration', rule, {
}
]
},
+ // Custom type
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ output: `
+
+ `,
+ errors: [
+ {
+ message: 'Use type-based declaration instead of runtime declaration.',
+ line: 3
+ }
+ ]
+ },
// Native Type with PropType
{
filename: 'test.vue',
@@ -337,6 +359,62 @@ tester.run('define-props-declaration', rule, {
}
]
},
+ // Object with PropType with separate type
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ output: `
+
+ `,
+ errors: [
+ {
+ message: 'Use type-based declaration instead of runtime declaration.',
+ line: 5
+ }
+ ]
+ },
+ // Object with PropType with separate imported type
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ output: `
+
+ `,
+ errors: [
+ {
+ message: 'Use type-based declaration instead of runtime declaration.',
+ line: 5
+ }
+ ]
+ },
// Array with PropType
{
filename: 'test.vue',