From 48446121c47f8a4725bb4cbff70c40f5e7e58726 Mon Sep 17 00:00:00 2001 From: "marcin.piniarski" Date: Mon, 27 May 2024 14:58:13 +0200 Subject: [PATCH] feat: autofix in `define-props-declaration`: runtime syntax to type-based syntax (#2465) handle native types (String, Boolean etc.) --- docs/rules/define-props-declaration.md | 2 + lib/rules/define-props-declaration.js | 102 ++++++++++++- tests/lib/rules/define-props-declaration.js | 150 ++++++++++++++++++-- 3 files changed, 239 insertions(+), 15 deletions(-) diff --git a/docs/rules/define-props-declaration.md b/docs/rules/define-props-declaration.md index 5c72d538a..554777fbc 100644 --- a/docs/rules/define-props-declaration.md +++ b/docs/rules/define-props-declaration.md @@ -10,6 +10,8 @@ since: v9.5.0 > enforce declaration style of `defineProps` +- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule. + ## :book: Rule Details This rule enforces `defineProps` typing style which you should use `type-based` or `runtime` declaration. diff --git a/lib/rules/define-props-declaration.js b/lib/rules/define-props-declaration.js index 3862bb228..52c908ac2 100644 --- a/lib/rules/define-props-declaration.js +++ b/lib/rules/define-props-declaration.js @@ -6,6 +6,40 @@ const utils = require('../utils') +const mapNativeType = (/** @type {string} */ nativeType) => { + switch (nativeType) { + case 'String': { + return 'string' + } + case 'Number': { + return 'number' + } + case 'Boolean': + case 'BigInt': { + return 'boolean' + } + case 'Object': { + return 'Record' + } + case 'Array': { + return 'any[]' + } + case 'Function': { + return '() => void' + } + case 'Symbol': { + return 'symbol' + } + default: { + return nativeType + } + } +} + +/** + * @typedef {import('../utils').ComponentProp} ComponentProp + */ + module.exports = { meta: { type: 'suggestion', @@ -14,7 +48,7 @@ module.exports = { categories: undefined, url: 'https://eslint.vuejs.org/rules/define-props-declaration.html' }, - fixable: null, + fixable: 'code', schema: [ { enum: ['type-based', 'runtime'] @@ -27,6 +61,40 @@ module.exports = { }, /** @param {RuleContext} context */ create(context) { + /** + * @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 + } + 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 + } + const scriptSetup = utils.getScriptSetupElement(context) if (!scriptSetup || !utils.hasAttribute(scriptSetup, 'lang', 'ts')) { return {} @@ -34,13 +102,41 @@ module.exports = { const defineType = context.options[0] || 'type-based' return utils.defineScriptSetupVisitor(context, { - onDefinePropsEnter(node) { + onDefinePropsEnter(node, props) { switch (defineType) { case 'type-based': { if (node.arguments.length > 0) { context.report({ node, - messageId: 'hasArg' + messageId: 'hasArg', + *fix(fixer) { + const propTypes = props.map((prop) => { + const unknownType = { name: prop.propName, type: 'unknown' } + + if (prop.type !== 'object') { + return unknownType + } + const type = optionGetType(prop.value) + if (type === null) { + return unknownType + } + + return { + name: prop.propName, + type: mapNativeType(type) + } + }) + + const definePropsType = `{ ${propTypes + .map(({ name, type }) => `${name}: ${type}`) + .join(', ')} }` + + yield fixer.insertTextAfter( + node.callee, + `<${definePropsType}>` + ) + yield fixer.replaceText(node.arguments[0], '') + } }) } break diff --git a/tests/lib/rules/define-props-declaration.js b/tests/lib/rules/define-props-declaration.js index 3af4513ea..9190496f4 100644 --- a/tests/lib/rules/define-props-declaration.js +++ b/tests/lib/rules/define-props-declaration.js @@ -10,8 +10,11 @@ const rule = require('../../../lib/rules/define-props-declaration') const tester = new RuleTester({ languageOptions: { parser: require('vue-eslint-parser'), - ecmaVersion: 2020, - sourceType: 'module' + ecmaVersion: 'latest', + sourceType: 'module', + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + } } }) @@ -108,6 +111,7 @@ tester.run('define-props-declaration', rule, { } ], invalid: [ + // default { filename: 'test.vue', code: ` @@ -117,6 +121,34 @@ tester.run('define-props-declaration', rule, { }) `, + output: ` + + `, + errors: [ + { + message: 'Use type-based declaration instead of runtime declaration.', + line: 3 + } + ] + }, + /* TYPE-BASED */ + // shorthand syntax + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, errors: [ { message: 'Use type-based declaration instead of runtime declaration.', @@ -124,6 +156,7 @@ tester.run('define-props-declaration', rule, { } ] }, + // String { filename: 'test.vue', code: ` @@ -133,6 +166,11 @@ tester.run('define-props-declaration', rule, { }) `, + output: ` + + `, options: ['type-based'], errors: [ { @@ -141,27 +179,115 @@ tester.run('define-props-declaration', rule, { } ] }, + // Number { filename: 'test.vue', code: ` + `, + output: ` + `, - options: ['runtime'], errors: [ { - message: 'Use runtime declaration instead of type-based declaration.', + message: 'Use type-based declaration instead of runtime declaration.', line: 3 } - ], - languageOptions: { - parserOptions: { - parser: require.resolve('@typescript-eslint/parser') + ] + }, + // Boolean + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + message: 'Use type-based declaration instead of runtime declaration.', + line: 3 } - } + ] + }, + // Object + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + message: 'Use type-based declaration instead of runtime declaration.', + line: 3 + } + ] + }, + // Array + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + message: 'Use type-based declaration instead of runtime declaration.', + line: 3 + } + ] + }, + // Function + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + message: 'Use type-based declaration instead of runtime declaration.', + line: 3 + } + ] } ] })