Skip to content

Commit

Permalink
feat: autofix in define-props-declaration: runtime syntax to type-b…
Browse files Browse the repository at this point in the history
…ased syntax (vuejs#2465)

handle native types (String, Boolean etc.)
  • Loading branch information
mpiniarski committed May 27, 2024
1 parent 516253d commit 4844612
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 15 deletions.
2 changes: 2 additions & 0 deletions docs/rules/define-props-declaration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
102 changes: 99 additions & 3 deletions lib/rules/define-props-declaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, any>'
}
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',
Expand All @@ -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']
Expand All @@ -27,20 +61,82 @@ 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 {}
}

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
Expand Down
150 changes: 138 additions & 12 deletions tests/lib/rules/define-props-declaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
}
}
})

Expand Down Expand Up @@ -108,6 +111,7 @@ tester.run('define-props-declaration', rule, {
}
],
invalid: [
// default
{
filename: 'test.vue',
code: `
Expand All @@ -117,13 +121,42 @@ tester.run('define-props-declaration', rule, {
})
</script>
`,
output: `
<script setup lang="ts">
const props = defineProps<{ kind: string }>()
</script>
`,
errors: [
{
message: 'Use type-based declaration instead of runtime declaration.',
line: 3
}
]
},
/* TYPE-BASED */
// shorthand syntax
{
filename: 'test.vue',
code: `
<script setup lang="ts">
const props = defineProps({
kind: String
})
</script>
`,
output: `
<script setup lang="ts">
const props = defineProps<{ kind: string }>()
</script>
`,
errors: [
{
message: 'Use type-based declaration instead of runtime declaration.',
line: 3
}
]
},
// String
{
filename: 'test.vue',
code: `
Expand All @@ -133,6 +166,11 @@ tester.run('define-props-declaration', rule, {
})
</script>
`,
output: `
<script setup lang="ts">
const props = defineProps<{ kind: string }>()
</script>
`,
options: ['type-based'],
errors: [
{
Expand All @@ -141,27 +179,115 @@ tester.run('define-props-declaration', rule, {
}
]
},
// Number
{
filename: 'test.vue',
code: `
<script setup lang="ts">
const props = defineProps<{
kind: string;
}>()
const props = defineProps({
kind: { type: Number}
})
</script>
`,
output: `
<script setup lang="ts">
const props = defineProps<{ kind: number }>()
</script>
`,
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: `
<script setup lang="ts">
const props = defineProps({
kind: { type:Boolean}
})
</script>
`,
output: `
<script setup lang="ts">
const props = defineProps<{ kind: boolean }>()
</script>
`,
errors: [
{
message: 'Use type-based declaration instead of runtime declaration.',
line: 3
}
}
]
},
// Object
{
filename: 'test.vue',
code: `
<script setup lang="ts">
const props = defineProps({
kind: { type:Object}
})
</script>
`,
output: `
<script setup lang="ts">
const props = defineProps<{ kind: Record<string, any> }>()
</script>
`,
errors: [
{
message: 'Use type-based declaration instead of runtime declaration.',
line: 3
}
]
},
// Array
{
filename: 'test.vue',
code: `
<script setup lang="ts">
const props = defineProps({
kind: { type:Array}
})
</script>
`,
output: `
<script setup lang="ts">
const props = defineProps<{ kind: any[] }>()
</script>
`,
errors: [
{
message: 'Use type-based declaration instead of runtime declaration.',
line: 3
}
]
},
// Function
{
filename: 'test.vue',
code: `
<script setup lang="ts">
const props = defineProps({
kind: { type: Function}
})
</script>
`,
output: `
<script setup lang="ts">
const props = defineProps<{ kind: () => void }>()
</script>
`,
errors: [
{
message: 'Use type-based declaration instead of runtime declaration.',
line: 3
}
]
}
]
})

0 comments on commit 4844612

Please sign in to comment.