Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 4499597

Browse files
feat: autofix in define-props-declaration: runtime syntax to type-based syntax (#2465)
additional tests and refactoring
1 parent 583c0db commit 4499597

File tree

2 files changed

+206
-113
lines changed

2 files changed

+206
-113
lines changed

‎lib/rules/define-props-declaration.js‎

Lines changed: 127 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,127 @@ const mapNativeType = (/** @type {string} */ nativeType) => {
3636
}
3737
}
3838

39+
/**
40+
* @param {ComponentProp} prop
41+
* @param {SourceCode} sourceCode
42+
*/
43+
function getComponentPropData(prop, sourceCode) {
44+
const unknownType = {
45+
name: prop.propName,
46+
type: 'unknown',
47+
required: undefined,
48+
defaultValue: undefined
49+
}
50+
51+
if (prop.type !== 'object') {
52+
return unknownType
53+
}
54+
const type = optionGetType(prop.value, sourceCode)
55+
if (type === null) {
56+
return unknownType
57+
}
58+
const required = optionGetRequired(prop.value)
59+
const defaultValue = optionGetDefault(prop.value)
60+
61+
return {
62+
name: prop.propName,
63+
type: mapNativeType(type),
64+
required,
65+
defaultValue
66+
}
67+
}
68+
69+
/**
70+
* @param {Expression} node
71+
* @param {SourceCode} sourceCode
72+
* @returns {string | null}
73+
*/
74+
function optionGetType(node, sourceCode) {
75+
switch (node.type) {
76+
case 'Identifier': {
77+
return node.name
78+
}
79+
case 'ObjectExpression': {
80+
// foo: {
81+
const typeProperty = utils.findProperty(node, 'type')
82+
if (typeProperty == null) {
83+
return null
84+
}
85+
if (typeProperty.value.type === 'TSAsExpression') {
86+
const typeAnnotation = typeProperty.value.typeAnnotation
87+
if (typeAnnotation.typeName.name !== 'PropType') {
88+
return null
89+
}
90+
91+
// in some project configuration parser populates deprecated field `typeParameters` instead of `typeArguments`
92+
const typeArguments =
93+
'typeArguments' in typeProperty.value
94+
? typeAnnotation.typeArguments
95+
: typeAnnotation.typeParameters
96+
97+
const typeArgument = Array.isArray(typeArguments)
98+
? typeArguments[0].params[0]
99+
: typeArguments.params[0]
100+
101+
if (typeArgument === undefined) {
102+
return null
103+
}
104+
105+
return sourceCode.getText(typeArgument)
106+
}
107+
return optionGetType(typeProperty.value, sourceCode)
108+
}
109+
case 'ArrayExpression': {
110+
return null
111+
}
112+
case 'FunctionExpression':
113+
case 'ArrowFunctionExpression': {
114+
return null
115+
}
116+
}
117+
118+
// Unknown
119+
return null
120+
}
121+
122+
/**
123+
* @param {Expression} node
124+
* @returns {boolean | undefined }
125+
*/
126+
function optionGetRequired(node) {
127+
if (node.type === 'ObjectExpression') {
128+
const requiredProperty = utils.findProperty(node, 'required')
129+
if (requiredProperty == null) {
130+
return undefined
131+
}
132+
133+
if (requiredProperty.value.type === 'Literal') {
134+
return Boolean(requiredProperty.value.value)
135+
}
136+
}
137+
138+
// Unknown
139+
return undefined
140+
}
141+
142+
/**
143+
* @param {Expression} node
144+
* @returns {Expression | undefined }
145+
*/
146+
function optionGetDefault(node) {
147+
if (node.type === 'ObjectExpression') {
148+
const defaultProperty = utils.findProperty(node, 'default')
149+
if (defaultProperty == null) {
150+
return undefined
151+
}
152+
153+
return defaultProperty.value
154+
}
155+
156+
// Unknown
157+
return undefined
158+
}
159+
39160
/**
40161
* @typedef {import('../utils').ComponentProp} ComponentProp
41162
*/
@@ -72,93 +193,6 @@ module.exports = {
72193
create(context) {
73194
const sourceCode = context.getSourceCode()
74195

75-
/**
76-
* @param {Expression} node
77-
* @returns {string | null}
78-
*/
79-
function optionGetType(node) {
80-
switch (node.type) {
81-
case 'Identifier': {
82-
return node.name
83-
}
84-
case 'ObjectExpression': {
85-
// foo: {
86-
const typeProperty = utils.findProperty(node, 'type')
87-
if (typeProperty == null) {
88-
return null
89-
}
90-
if (typeProperty.value.type === 'TSAsExpression') {
91-
if (
92-
typeProperty.value.typeAnnotation.typeName.name !== 'PropType'
93-
) {
94-
return null
95-
}
96-
97-
const typeArgument =
98-
typeProperty.value.typeAnnotation.typeArguments.params[0]
99-
if (typeArgument === undefined) {
100-
return null
101-
}
102-
103-
return sourceCode.getText(typeArgument)
104-
}
105-
return optionGetType(typeProperty.value)
106-
}
107-
case 'ArrayExpression': {
108-
// foo: [
109-
return null
110-
// return node.elements.map((arrayElement) =>
111-
// optionGetType(arrayElement)
112-
// )
113-
}
114-
case 'FunctionExpression':
115-
case 'ArrowFunctionExpression': {
116-
return null
117-
}
118-
}
119-
120-
// Unknown
121-
return null
122-
}
123-
124-
/**
125-
* @param {Expression} node
126-
* @returns {boolean | undefined }
127-
*/
128-
function optionGetRequired(node) {
129-
if (node.type === 'ObjectExpression') {
130-
const requiredProperty = utils.findProperty(node, 'required')
131-
if (requiredProperty == null) {
132-
return undefined
133-
}
134-
135-
if (requiredProperty.value.type === 'Literal') {
136-
return Boolean(requiredProperty.value.value)
137-
}
138-
}
139-
140-
// Unknown
141-
return undefined
142-
}
143-
144-
/**
145-
* @param {Expression} node
146-
* @returns {Expression | undefined }
147-
*/
148-
function optionGetDefault(node) {
149-
if (node.type === 'ObjectExpression') {
150-
const defaultProperty = utils.findProperty(node, 'default')
151-
if (defaultProperty == null) {
152-
return undefined
153-
}
154-
155-
return defaultProperty.value
156-
}
157-
158-
// Unknown
159-
return undefined
160-
}
161-
162196
const scriptSetup = utils.getScriptSetupElement(context)
163197
if (!scriptSetup || !utils.hasAttribute(scriptSetup, 'lang', 'ts')) {
164198
return {}
@@ -176,31 +210,9 @@ module.exports = {
176210
node,
177211
messageId: 'hasArg',
178212
*fix(fixer) {
179-
const propTypes = props.map((prop) => {
180-
const unknownType = {
181-
name: prop.propName,
182-
type: 'unknown',
183-
required: undefined,
184-
defaultValue: undefined
185-
}
186-
187-
if (prop.type !== 'object') {
188-
return unknownType
189-
}
190-
const type = optionGetType(prop.value)
191-
if (type === null) {
192-
return unknownType
193-
}
194-
const required = optionGetRequired(prop.value)
195-
const defaultValue = optionGetDefault(prop.value)
196-
197-
return {
198-
name: prop.propName,
199-
type: mapNativeType(type),
200-
required,
201-
defaultValue
202-
}
203-
})
213+
const propTypes = props.map((prop) =>
214+
getComponentPropData(prop, sourceCode)
215+
)
204216

205217
const definePropsType = `{ ${propTypes
206218
.map(
@@ -209,8 +221,10 @@ module.exports = {
209221
)
210222
.join(', ')} }`
211223

224+
// remove defineProps function parameters
212225
yield fixer.replaceText(node.arguments[0], '')
213226

227+
// add type annotation
214228
if (separateInterface) {
215229
const variableDeclarationNode = node.parent.parent
216230
if (!variableDeclarationNode) return
@@ -227,6 +241,7 @@ module.exports = {
227241
)
228242
}
229243

244+
// add defaults if needed
230245
const defaults = propTypes.filter(
231246
({ defaultValue }) => defaultValue
232247
)

‎tests/lib/rules/define-props-declaration.js‎

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const rule = require('../../../lib/rules/define-props-declaration')
1010
const tester = new RuleTester({
1111
languageOptions: {
1212
parser: require('vue-eslint-parser'),
13-
ecmaVersion: 'latest',
13+
ecmaVersion: '2020',
1414
sourceType: 'module',
1515
parserOptions: {
1616
parser: require.resolve('@typescript-eslint/parser')
@@ -289,6 +289,28 @@ tester.run('define-props-declaration', rule, {
289289
}
290290
]
291291
},
292+
// Custom type
293+
{
294+
filename: 'test.vue',
295+
code: `
296+
<script setup lang="ts">
297+
const props = defineProps({
298+
kind: User
299+
})
300+
</script>
301+
`,
302+
output: `
303+
<script setup lang="ts">
304+
const props = defineProps<{ kind: User }>()
305+
</script>
306+
`,
307+
errors: [
308+
{
309+
message: 'Use type-based declaration instead of runtime declaration.',
310+
line: 3
311+
}
312+
]
313+
},
292314
// Native Type with PropType
293315
{
294316
filename: 'test.vue',
@@ -337,6 +359,62 @@ tester.run('define-props-declaration', rule, {
337359
}
338360
]
339361
},
362+
// Object with PropType with separate type
363+
{
364+
filename: 'test.vue',
365+
code: `
366+
<script setup lang="ts">
367+
interface Kind { id: number; name: string }
368+
369+
const props = defineProps({
370+
kind: {
371+
type: Object as PropType<Kind>,
372+
}
373+
})
374+
</script>
375+
`,
376+
output: `
377+
<script setup lang="ts">
378+
interface Kind { id: number; name: string }
379+
380+
const props = defineProps<{ kind: Kind }>()
381+
</script>
382+
`,
383+
errors: [
384+
{
385+
message: 'Use type-based declaration instead of runtime declaration.',
386+
line: 5
387+
}
388+
]
389+
},
390+
// Object with PropType with separate imported type
391+
{
392+
filename: 'test.vue',
393+
code: `
394+
<script setup lang="ts">
395+
import Kind from 'test'
396+
397+
const props = defineProps({
398+
kind: {
399+
type: Object as PropType<Kind>,
400+
}
401+
})
402+
</script>
403+
`,
404+
output: `
405+
<script setup lang="ts">
406+
import Kind from 'test'
407+
408+
const props = defineProps<{ kind: Kind }>()
409+
</script>
410+
`,
411+
errors: [
412+
{
413+
message: 'Use type-based declaration instead of runtime declaration.',
414+
line: 5
415+
}
416+
]
417+
},
340418
// Array with PropType
341419
{
342420
filename: 'test.vue',

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /