From 207477eb4953d929fbb9db15a320feda2577abe6 Mon Sep 17 00:00:00 2001 From: "marcin.piniarski" Date: Tue, 28 May 2024 11:56:54 +0200 Subject: [PATCH] feat: autofix in `define-props-declaration`: runtime syntax to type-based syntax (#2465) copy type for unknown expressions, ignore fixing cases when error is thrown --- lib/rules/define-props-declaration.js | 115 ++++++++++---------- tests/lib/rules/define-props-declaration.js | 24 ++++ 2 files changed, 83 insertions(+), 56 deletions(-) diff --git a/lib/rules/define-props-declaration.js b/lib/rules/define-props-declaration.js index 233560500..c59477a22 100644 --- a/lib/rules/define-props-declaration.js +++ b/lib/rules/define-props-declaration.js @@ -47,9 +47,6 @@ function getComponentPropData(prop, sourceCode) { throw new Error(`Unexpected prop type: ${prop.type}.`) } const type = optionGetType(prop.value, sourceCode) - if (type === null) { - throw new Error(`Unable to read prop type`) - } const required = optionGetRequired(prop.value) const defaultValue = optionGetDefault(prop.value) @@ -64,7 +61,7 @@ function getComponentPropData(prop, sourceCode) { /** * @param {Expression} node * @param {SourceCode} sourceCode - * @returns {string | null} + * @returns {string} */ function optionGetType(node, sourceCode) { switch (node.type) { @@ -74,7 +71,7 @@ function optionGetType(node, sourceCode) { case 'ObjectExpression': { const typeProperty = utils.findProperty(node, 'type') if (typeProperty == null) { - return null + return sourceCode.getText(node) } return optionGetType(typeProperty.value, sourceCode) } @@ -83,7 +80,7 @@ function optionGetType(node, sourceCode) { .map((element) => { // TODO handle SpreadElement if (element === null || element.type === 'SpreadElement') { - return null + return sourceCode.getText(node) } return optionGetType(element, sourceCode) @@ -94,7 +91,7 @@ function optionGetType(node, sourceCode) { case 'TSAsExpression': { const typeAnnotation = node.typeAnnotation if (typeAnnotation.typeName.name !== 'PropType') { - return null + return sourceCode.getText(node) } // in some project configuration parser populates deprecated field `typeParameters` instead of `typeArguments` @@ -108,20 +105,15 @@ function optionGetType(node, sourceCode) { : typeArguments.params[0] if (typeArgument === undefined) { - return null + return sourceCode.getText(node) } return sourceCode.getText(typeArgument) } - - case 'FunctionExpression': - case 'ArrowFunctionExpression': { - return null + default: { + return sourceCode.getText(node) } } - - // Unknown - return null } /** @@ -215,55 +207,66 @@ module.exports = { node, messageId: 'hasArg', *fix(fixer) { - const propTypes = props.map((prop) => - getComponentPropData(prop, sourceCode) - ) - - const definePropsType = `{ ${propTypes - .map( - ({ name, type, required, defaultValue }) => - `${name}${ - required === false || defaultValue ? '?' : '' - }: ${type}` + try { + const propTypes = props.map((prop) => + getComponentPropData(prop, sourceCode) ) - .join(', ')} }` - // remove defineProps function parameters - yield fixer.replaceText(node.arguments[0], '') + const definePropsType = `{ ${propTypes + .map( + ({ name, type, required, defaultValue }) => + `${name}${ + required === false || defaultValue ? '?' : '' + }: ${type}` + ) + .join(', ')} }` + + // remove defineProps function parameters + yield fixer.replaceText(node.arguments[0], '') - // add type annotation - if (separateInterface) { - const variableDeclarationNode = node.parent.parent - if (!variableDeclarationNode) { - return + // add type annotation + if (separateInterface) { + const variableDeclarationNode = node.parent.parent + if (!variableDeclarationNode) { + return + } + + yield fixer.insertTextBefore( + variableDeclarationNode, + `interface Props ${definePropsType.replace( + /;/g, + ',' + )}; ` + ) + yield fixer.insertTextAfter(node.callee, ``) + } else { + yield fixer.insertTextAfter( + node.callee, + `<${definePropsType}>` + ) } - yield fixer.insertTextBefore( - variableDeclarationNode, - `interface Props ${definePropsType.replace(/;/g, ',')}; ` - ) - yield fixer.insertTextAfter(node.callee, ``) - } else { - yield fixer.insertTextAfter( - node.callee, - `<${definePropsType}>` + // add defaults if needed + const defaults = propTypes.filter( + ({ defaultValue }) => defaultValue ) - } + if (defaults.length > 0) { + const defaultsCode = defaults + .map( + ({ name, defaultValue }) => + `${name}: ${sourceCode.getText(defaultValue)}` + ) + .join(', ') - // add defaults if needed - const defaults = propTypes.filter( - ({ defaultValue }) => defaultValue - ) - if (defaults.length > 0) { - const defaultsCode = defaults - .map( - ({ name, defaultValue }) => - `${name}: ${sourceCode.getText(defaultValue)}` + yield fixer.insertTextBefore(node, `withDefaults(`) + yield fixer.insertTextAfter( + node, + `, { ${defaultsCode} })` ) - .join(', ') - - yield fixer.insertTextBefore(node, `withDefaults(`) - yield fixer.insertTextAfter(node, `, { ${defaultsCode} })`) + } + return null + } catch (error) { + return null } } }) diff --git a/tests/lib/rules/define-props-declaration.js b/tests/lib/rules/define-props-declaration.js index 4b0318cd2..4c58861dd 100644 --- a/tests/lib/rules/define-props-declaration.js +++ b/tests/lib/rules/define-props-declaration.js @@ -588,6 +588,30 @@ tester.run('define-props-declaration', rule, { } ] }, + // Some unhandled expression type + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + message: 'Use type-based declaration instead of runtime declaration.', + line: 3 + } + ] + }, // runtime { filename: 'test.vue',