Skip to content

Commit cd2ad1c

Browse files
authored
Update vue/no-boolean-default rule to support <script setup> (#1550)
* Update `vue/no-boolean-default` rule to support `<script setup>` * Fix test
1 parent 9b43c9f commit cd2ad1c

File tree

2 files changed

+263
-53
lines changed

2 files changed

+263
-53
lines changed

Diff for: lib/rules/no-boolean-default.js

+68-52
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const utils = require('../utils')
99
/**
1010
* @typedef {import('../utils').ComponentArrayProp} ComponentArrayProp
1111
* @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp
12+
* @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp
1213
*/
1314

1415
// ------------------------------------------------------------------------------
@@ -29,31 +30,10 @@ function isBooleanProp(prop) {
2930
}
3031

3132
/**
32-
* @typedef {ComponentObjectProp & { value : ObjectExpression }} ObjectExpressionProp
33+
* @param {ObjectExpression} propDefValue
3334
*/
34-
35-
/**
36-
* @param {(ComponentArrayProp | ComponentObjectProp)[]} props
37-
* @returns {ObjectExpressionProp[]}
38-
*/
39-
function getBooleanProps(props) {
40-
return props.filter(
41-
/**
42-
* @param {ComponentArrayProp | ComponentObjectProp} prop
43-
* @returns {prop is ObjectExpressionProp}
44-
*/
45-
(prop) =>
46-
prop.value != null &&
47-
prop.value.type === 'ObjectExpression' &&
48-
prop.value.properties.some(isBooleanProp)
49-
)
50-
}
51-
52-
/**
53-
* @param {ObjectExpressionProp} propDef
54-
*/
55-
function getDefaultNode(propDef) {
56-
return utils.findProperty(propDef.value, 'default')
35+
function getDefaultNode(propDefValue) {
36+
return utils.findProperty(propDefValue, 'default')
5737
}
5838

5939
module.exports = {
@@ -73,42 +53,78 @@ module.exports = {
7353
},
7454
/** @param {RuleContext} context */
7555
create(context) {
76-
return utils.executeOnVueComponent(context, (obj) => {
77-
const props = utils.getComponentProps(obj)
78-
const booleanProps = getBooleanProps(props)
79-
80-
if (!booleanProps.length) return
81-
82-
const booleanType = context.options[0] || 'no-default'
83-
84-
booleanProps.forEach((propDef) => {
85-
const defaultNode = getDefaultNode(propDef)
56+
const booleanType = context.options[0] || 'no-default'
57+
/**
58+
* @param {ComponentArrayProp | ComponentObjectProp | ComponentTypeProp} prop
59+
* @param { { [key: string]: Expression | undefined } } [withDefaultsExpressions]
60+
*/
61+
function processProp(prop, withDefaultsExpressions) {
62+
if (prop.type === 'object') {
63+
if (prop.value.type !== 'ObjectExpression') {
64+
return
65+
}
66+
if (!prop.value.properties.some(isBooleanProp)) {
67+
return
68+
}
69+
const defaultNode = getDefaultNode(prop.value)
70+
if (!defaultNode) {
71+
return
72+
}
73+
verifyDefaultExpression(defaultNode.value)
74+
} else if (prop.type === 'type') {
75+
if (prop.types.length !== 1 || prop.types[0] !== 'Boolean') {
76+
return
77+
}
78+
const defaultNode =
79+
withDefaultsExpressions && withDefaultsExpressions[prop.propName]
8680
if (!defaultNode) {
8781
return
8882
}
83+
verifyDefaultExpression(defaultNode)
84+
}
85+
}
86+
/**
87+
* @param {(ComponentArrayProp | ComponentObjectProp | ComponentTypeProp)[]} props
88+
* @param { { [key: string]: Expression | undefined } } [withDefaultsExpressions]
89+
*/
90+
function processProps(props, withDefaultsExpressions) {
91+
for (const prop of props) {
92+
processProp(prop, withDefaultsExpressions)
93+
}
94+
}
95+
96+
/**
97+
* @param {Expression} defaultNode
98+
*/
99+
function verifyDefaultExpression(defaultNode) {
100+
switch (booleanType) {
101+
case 'no-default':
102+
context.report({
103+
node: defaultNode,
104+
message:
105+
'Boolean prop should not set a default (Vue defaults it to false).'
106+
})
107+
break
89108

90-
switch (booleanType) {
91-
case 'no-default':
109+
case 'default-false':
110+
if (defaultNode.type !== 'Literal' || defaultNode.value !== false) {
92111
context.report({
93112
node: defaultNode,
94-
message:
95-
'Boolean prop should not set a default (Vue defaults it to false).'
113+
message: 'Boolean prop should only be defaulted to false.'
96114
})
97-
break
98-
99-
case 'default-false':
100-
if (
101-
defaultNode.value.type !== 'Literal' ||
102-
defaultNode.value.value !== false
103-
) {
104-
context.report({
105-
node: defaultNode,
106-
message: 'Boolean prop should only be defaulted to false.'
107-
})
108-
}
109-
break
115+
}
116+
break
117+
}
118+
}
119+
return utils.compositingVisitors(
120+
utils.executeOnVueComponent(context, (obj) => {
121+
processProps(utils.getComponentProps(obj))
122+
}),
123+
utils.defineScriptSetupVisitor(context, {
124+
onDefinePropsEnter(node, props) {
125+
processProps(props, utils.getWithDefaultsPropExpressions(node))
110126
}
111127
})
112-
})
128+
)
113129
}
114130
}

Diff for: tests/lib/rules/no-boolean-default.js

+195-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
// Requirements
99
// ------------------------------------------------------------------------------
1010

11+
const semver = require('semver')
1112
const rule = require('../../../lib/rules/no-boolean-default')
1213

1314
const RuleTester = require('eslint').RuleTester
@@ -229,6 +230,102 @@ ruleTester.run('no-boolean-default', rule, {
229230
}
230231
`,
231232
options: ['default-false']
233+
},
234+
{
235+
filename: 'test.vue',
236+
code: `
237+
<script setup>
238+
defineProps({
239+
foo: {
240+
type:Boolean
241+
}
242+
})
243+
</script>
244+
`,
245+
parser: require.resolve('vue-eslint-parser')
246+
},
247+
{
248+
filename: 'test.vue',
249+
code: `
250+
<script setup>
251+
defineProps({
252+
foo: {
253+
type:Boolean,
254+
default: false
255+
}
256+
})
257+
</script>
258+
`,
259+
parser: require.resolve('vue-eslint-parser'),
260+
options: ['default-false']
261+
},
262+
{
263+
filename: 'test.vue',
264+
code: `
265+
<script setup lang="ts">
266+
interface Props {
267+
foo: boolean
268+
}
269+
withDefaults(defineProps<Props>(), {
270+
})
271+
</script>
272+
`,
273+
parser: require.resolve('vue-eslint-parser'),
274+
parserOptions: {
275+
parser: require.resolve('@typescript-eslint/parser')
276+
}
277+
},
278+
{
279+
filename: 'test.vue',
280+
code: `
281+
<script setup lang="ts">
282+
interface Props {
283+
foo: boolean
284+
}
285+
withDefaults(defineProps<Props>(), {
286+
foo: false
287+
})
288+
</script>
289+
`,
290+
parser: require.resolve('vue-eslint-parser'),
291+
parserOptions: {
292+
parser: require.resolve('@typescript-eslint/parser')
293+
},
294+
options: ['default-false']
295+
},
296+
{
297+
filename: 'test.vue',
298+
code: `
299+
<script setup lang="ts">
300+
interface Props {
301+
foo: boolean | string
302+
}
303+
withDefaults(defineProps<Props>(), {
304+
foo: false
305+
})
306+
</script>
307+
`,
308+
parser: require.resolve('vue-eslint-parser'),
309+
parserOptions: {
310+
parser: require.resolve('@typescript-eslint/parser')
311+
}
312+
},
313+
{
314+
filename: 'test.vue',
315+
code: `
316+
<script setup lang="ts">
317+
interface Props {
318+
foo: string
319+
}
320+
withDefaults(defineProps<Props>(), {
321+
foo: false
322+
})
323+
</script>
324+
`,
325+
parser: require.resolve('vue-eslint-parser'),
326+
parserOptions: {
327+
parser: require.resolve('@typescript-eslint/parser')
328+
}
232329
}
233330
],
234331

@@ -314,6 +411,103 @@ ruleTester.run('no-boolean-default', rule, {
314411
line: 6
315412
}
316413
]
317-
}
414+
},
415+
{
416+
filename: 'test.vue',
417+
code: `
418+
<script setup>
419+
defineProps({
420+
foo: {
421+
type:Boolean,
422+
default: false
423+
}
424+
})
425+
</script>
426+
`,
427+
parser: require.resolve('vue-eslint-parser'),
428+
errors: [
429+
{
430+
message:
431+
'Boolean prop should not set a default (Vue defaults it to false).',
432+
line: 6
433+
}
434+
]
435+
},
436+
{
437+
filename: 'test.vue',
438+
code: `
439+
<script setup>
440+
defineProps({
441+
foo: {
442+
type:Boolean,
443+
default: true
444+
}
445+
})
446+
</script>
447+
`,
448+
parser: require.resolve('vue-eslint-parser'),
449+
options: ['default-false'],
450+
errors: [
451+
{
452+
message: 'Boolean prop should only be defaulted to false.',
453+
line: 6
454+
}
455+
]
456+
},
457+
...(semver.lt(
458+
require('@typescript-eslint/parser/package.json').version,
459+
'4.0.0'
460+
)
461+
? []
462+
: [
463+
{
464+
filename: 'test.vue',
465+
code: `
466+
<script setup lang="ts">
467+
interface Props {
468+
foo: boolean
469+
}
470+
withDefaults(defineProps<Props>(), {
471+
foo: false
472+
})
473+
</script>
474+
`,
475+
parser: require.resolve('vue-eslint-parser'),
476+
parserOptions: {
477+
parser: require.resolve('@typescript-eslint/parser')
478+
},
479+
errors: [
480+
{
481+
message:
482+
'Boolean prop should not set a default (Vue defaults it to false).',
483+
line: 7
484+
}
485+
]
486+
},
487+
{
488+
filename: 'test.vue',
489+
code: `
490+
<script setup lang="ts">
491+
interface Props {
492+
foo: boolean
493+
}
494+
withDefaults(defineProps<Props>(), {
495+
foo: true
496+
})
497+
</script>
498+
`,
499+
parser: require.resolve('vue-eslint-parser'),
500+
parserOptions: {
501+
parser: require.resolve('@typescript-eslint/parser')
502+
},
503+
options: ['default-false'],
504+
errors: [
505+
{
506+
message: 'Boolean prop should only be defaulted to false.',
507+
line: 7
508+
}
509+
]
510+
}
511+
])
318512
]
319513
})

0 commit comments

Comments
 (0)