Skip to content

Commit 207477e

Browse files
committed
feat: autofix in define-props-declaration: runtime syntax to type-based syntax (vuejs#2465)
copy type for unknown expressions, ignore fixing cases when error is thrown
1 parent f301546 commit 207477e

File tree

2 files changed

+83
-56
lines changed

2 files changed

+83
-56
lines changed

Diff for: lib/rules/define-props-declaration.js

+59-56
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,6 @@ function getComponentPropData(prop, sourceCode) {
4747
throw new Error(`Unexpected prop type: ${prop.type}.`)
4848
}
4949
const type = optionGetType(prop.value, sourceCode)
50-
if (type === null) {
51-
throw new Error(`Unable to read prop type`)
52-
}
5350
const required = optionGetRequired(prop.value)
5451
const defaultValue = optionGetDefault(prop.value)
5552

@@ -64,7 +61,7 @@ function getComponentPropData(prop, sourceCode) {
6461
/**
6562
* @param {Expression} node
6663
* @param {SourceCode} sourceCode
67-
* @returns {string | null}
64+
* @returns {string}
6865
*/
6966
function optionGetType(node, sourceCode) {
7067
switch (node.type) {
@@ -74,7 +71,7 @@ function optionGetType(node, sourceCode) {
7471
case 'ObjectExpression': {
7572
const typeProperty = utils.findProperty(node, 'type')
7673
if (typeProperty == null) {
77-
return null
74+
return sourceCode.getText(node)
7875
}
7976
return optionGetType(typeProperty.value, sourceCode)
8077
}
@@ -83,7 +80,7 @@ function optionGetType(node, sourceCode) {
8380
.map((element) => {
8481
// TODO handle SpreadElement
8582
if (element === null || element.type === 'SpreadElement') {
86-
return null
83+
return sourceCode.getText(node)
8784
}
8885

8986
return optionGetType(element, sourceCode)
@@ -94,7 +91,7 @@ function optionGetType(node, sourceCode) {
9491
case 'TSAsExpression': {
9592
const typeAnnotation = node.typeAnnotation
9693
if (typeAnnotation.typeName.name !== 'PropType') {
97-
return null
94+
return sourceCode.getText(node)
9895
}
9996

10097
// in some project configuration parser populates deprecated field `typeParameters` instead of `typeArguments`
@@ -108,20 +105,15 @@ function optionGetType(node, sourceCode) {
108105
: typeArguments.params[0]
109106

110107
if (typeArgument === undefined) {
111-
return null
108+
return sourceCode.getText(node)
112109
}
113110

114111
return sourceCode.getText(typeArgument)
115112
}
116-
117-
case 'FunctionExpression':
118-
case 'ArrowFunctionExpression': {
119-
return null
113+
default: {
114+
return sourceCode.getText(node)
120115
}
121116
}
122-
123-
// Unknown
124-
return null
125117
}
126118

127119
/**
@@ -215,55 +207,66 @@ module.exports = {
215207
node,
216208
messageId: 'hasArg',
217209
*fix(fixer) {
218-
const propTypes = props.map((prop) =>
219-
getComponentPropData(prop, sourceCode)
220-
)
221-
222-
const definePropsType = `{ ${propTypes
223-
.map(
224-
({ name, type, required, defaultValue }) =>
225-
`${name}${
226-
required === false || defaultValue ? '?' : ''
227-
}: ${type}`
210+
try {
211+
const propTypes = props.map((prop) =>
212+
getComponentPropData(prop, sourceCode)
228213
)
229-
.join(', ')} }`
230214

231-
// remove defineProps function parameters
232-
yield fixer.replaceText(node.arguments[0], '')
215+
const definePropsType = `{ ${propTypes
216+
.map(
217+
({ name, type, required, defaultValue }) =>
218+
`${name}${
219+
required === false || defaultValue ? '?' : ''
220+
}: ${type}`
221+
)
222+
.join(', ')} }`
223+
224+
// remove defineProps function parameters
225+
yield fixer.replaceText(node.arguments[0], '')
233226

234-
// add type annotation
235-
if (separateInterface) {
236-
const variableDeclarationNode = node.parent.parent
237-
if (!variableDeclarationNode) {
238-
return
227+
// add type annotation
228+
if (separateInterface) {
229+
const variableDeclarationNode = node.parent.parent
230+
if (!variableDeclarationNode) {
231+
return
232+
}
233+
234+
yield fixer.insertTextBefore(
235+
variableDeclarationNode,
236+
`interface Props ${definePropsType.replace(
237+
/;/g,
238+
','
239+
)}; `
240+
)
241+
yield fixer.insertTextAfter(node.callee, `<Props>`)
242+
} else {
243+
yield fixer.insertTextAfter(
244+
node.callee,
245+
`<${definePropsType}>`
246+
)
239247
}
240248

241-
yield fixer.insertTextBefore(
242-
variableDeclarationNode,
243-
`interface Props ${definePropsType.replace(/;/g, ',')}; `
244-
)
245-
yield fixer.insertTextAfter(node.callee, `<Props>`)
246-
} else {
247-
yield fixer.insertTextAfter(
248-
node.callee,
249-
`<${definePropsType}>`
249+
// add defaults if needed
250+
const defaults = propTypes.filter(
251+
({ defaultValue }) => defaultValue
250252
)
251-
}
253+
if (defaults.length > 0) {
254+
const defaultsCode = defaults
255+
.map(
256+
({ name, defaultValue }) =>
257+
`${name}: ${sourceCode.getText(defaultValue)}`
258+
)
259+
.join(', ')
252260

253-
// add defaults if needed
254-
const defaults = propTypes.filter(
255-
({ defaultValue }) => defaultValue
256-
)
257-
if (defaults.length > 0) {
258-
const defaultsCode = defaults
259-
.map(
260-
({ name, defaultValue }) =>
261-
`${name}: ${sourceCode.getText(defaultValue)}`
261+
yield fixer.insertTextBefore(node, `withDefaults(`)
262+
yield fixer.insertTextAfter(
263+
node,
264+
`, { ${defaultsCode} })`
262265
)
263-
.join(', ')
264-
265-
yield fixer.insertTextBefore(node, `withDefaults(`)
266-
yield fixer.insertTextAfter(node, `, { ${defaultsCode} })`)
266+
}
267+
return null
268+
} catch (error) {
269+
return null
267270
}
268271
}
269272
})

Diff for: tests/lib/rules/define-props-declaration.js

+24
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,30 @@ tester.run('define-props-declaration', rule, {
588588
}
589589
]
590590
},
591+
// Some unhandled expression type
592+
{
593+
filename: 'test.vue',
594+
code: `
595+
<script setup lang="ts">
596+
const props = defineProps({
597+
kind: {
598+
type: typeof Test
599+
}
600+
})
601+
</script>
602+
`,
603+
output: `
604+
<script setup lang="ts">
605+
const props = defineProps<{ kind: typeof Test }>()
606+
</script>
607+
`,
608+
errors: [
609+
{
610+
message: 'Use type-based declaration instead of runtime declaration.',
611+
line: 3
612+
}
613+
]
614+
},
591615
// runtime
592616
{
593617
filename: 'test.vue',

0 commit comments

Comments
 (0)