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)

copy type for unknown expressions, ignore fixing cases when error is thrown
  • Loading branch information
mpiniarski committed May 28, 2024
1 parent f301546 commit 207477e
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 56 deletions.
115 changes: 59 additions & 56 deletions lib/rules/define-props-declaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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) {
Expand All @@ -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)
}
Expand All @@ -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)
Expand All @@ -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`
Expand All @@ -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
}

/**
Expand Down Expand Up @@ -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, `<Props>`)
} else {
yield fixer.insertTextAfter(
node.callee,
`<${definePropsType}>`
)
}

yield fixer.insertTextBefore(
variableDeclarationNode,
`interface Props ${definePropsType.replace(/;/g, ',')}; `
)
yield fixer.insertTextAfter(node.callee, `<Props>`)
} 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
}
}
})
Expand Down
24 changes: 24 additions & 0 deletions tests/lib/rules/define-props-declaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,30 @@ tester.run('define-props-declaration', rule, {
}
]
},
// Some unhandled expression type
{
filename: 'test.vue',
code: `
<script setup lang="ts">
const props = defineProps({
kind: {
type: typeof Test
}
})
</script>
`,
output: `
<script setup lang="ts">
const props = defineProps<{ kind: typeof Test }>()
</script>
`,
errors: [
{
message: 'Use type-based declaration instead of runtime declaration.',
line: 3
}
]
},
// runtime
{
filename: 'test.vue',
Expand Down

0 comments on commit 207477e

Please sign in to comment.