Skip to content

Commit

Permalink
Update vue/no-boolean-default rule to support <script setup> (#1550)
Browse files Browse the repository at this point in the history
* Update `vue/no-boolean-default` rule to support `<script setup>`

* Fix test
  • Loading branch information
ota-meshi committed Jul 6, 2021
1 parent 9b43c9f commit cd2ad1c
Show file tree
Hide file tree
Showing 2 changed files with 263 additions and 53 deletions.
120 changes: 68 additions & 52 deletions lib/rules/no-boolean-default.js
Expand Up @@ -9,6 +9,7 @@ const utils = require('../utils')
/**
* @typedef {import('../utils').ComponentArrayProp} ComponentArrayProp
* @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp
* @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp
*/

// ------------------------------------------------------------------------------
Expand All @@ -29,31 +30,10 @@ function isBooleanProp(prop) {
}

/**
* @typedef {ComponentObjectProp & { value : ObjectExpression }} ObjectExpressionProp
* @param {ObjectExpression} propDefValue
*/

/**
* @param {(ComponentArrayProp | ComponentObjectProp)[]} props
* @returns {ObjectExpressionProp[]}
*/
function getBooleanProps(props) {
return props.filter(
/**
* @param {ComponentArrayProp | ComponentObjectProp} prop
* @returns {prop is ObjectExpressionProp}
*/
(prop) =>
prop.value != null &&
prop.value.type === 'ObjectExpression' &&
prop.value.properties.some(isBooleanProp)
)
}

/**
* @param {ObjectExpressionProp} propDef
*/
function getDefaultNode(propDef) {
return utils.findProperty(propDef.value, 'default')
function getDefaultNode(propDefValue) {
return utils.findProperty(propDefValue, 'default')
}

module.exports = {
Expand All @@ -73,42 +53,78 @@ module.exports = {
},
/** @param {RuleContext} context */
create(context) {
return utils.executeOnVueComponent(context, (obj) => {
const props = utils.getComponentProps(obj)
const booleanProps = getBooleanProps(props)

if (!booleanProps.length) return

const booleanType = context.options[0] || 'no-default'

booleanProps.forEach((propDef) => {
const defaultNode = getDefaultNode(propDef)
const booleanType = context.options[0] || 'no-default'
/**
* @param {ComponentArrayProp | ComponentObjectProp | ComponentTypeProp} prop
* @param { { [key: string]: Expression | undefined } } [withDefaultsExpressions]
*/
function processProp(prop, withDefaultsExpressions) {
if (prop.type === 'object') {
if (prop.value.type !== 'ObjectExpression') {
return
}
if (!prop.value.properties.some(isBooleanProp)) {
return
}
const defaultNode = getDefaultNode(prop.value)
if (!defaultNode) {
return
}
verifyDefaultExpression(defaultNode.value)
} else if (prop.type === 'type') {
if (prop.types.length !== 1 || prop.types[0] !== 'Boolean') {
return
}
const defaultNode =
withDefaultsExpressions && withDefaultsExpressions[prop.propName]
if (!defaultNode) {
return
}
verifyDefaultExpression(defaultNode)
}
}
/**
* @param {(ComponentArrayProp | ComponentObjectProp | ComponentTypeProp)[]} props
* @param { { [key: string]: Expression | undefined } } [withDefaultsExpressions]
*/
function processProps(props, withDefaultsExpressions) {
for (const prop of props) {
processProp(prop, withDefaultsExpressions)
}
}

/**
* @param {Expression} defaultNode
*/
function verifyDefaultExpression(defaultNode) {
switch (booleanType) {
case 'no-default':
context.report({
node: defaultNode,
message:
'Boolean prop should not set a default (Vue defaults it to false).'
})
break

switch (booleanType) {
case 'no-default':
case 'default-false':
if (defaultNode.type !== 'Literal' || defaultNode.value !== false) {
context.report({
node: defaultNode,
message:
'Boolean prop should not set a default (Vue defaults it to false).'
message: 'Boolean prop should only be defaulted to false.'
})
break

case 'default-false':
if (
defaultNode.value.type !== 'Literal' ||
defaultNode.value.value !== false
) {
context.report({
node: defaultNode,
message: 'Boolean prop should only be defaulted to false.'
})
}
break
}
break
}
}
return utils.compositingVisitors(
utils.executeOnVueComponent(context, (obj) => {
processProps(utils.getComponentProps(obj))
}),
utils.defineScriptSetupVisitor(context, {
onDefinePropsEnter(node, props) {
processProps(props, utils.getWithDefaultsPropExpressions(node))
}
})
})
)
}
}
196 changes: 195 additions & 1 deletion tests/lib/rules/no-boolean-default.js
Expand Up @@ -8,6 +8,7 @@
// Requirements
// ------------------------------------------------------------------------------

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

const RuleTester = require('eslint').RuleTester
Expand Down Expand Up @@ -229,6 +230,102 @@ ruleTester.run('no-boolean-default', rule, {
}
`,
options: ['default-false']
},
{
filename: 'test.vue',
code: `
<script setup>
defineProps({
foo: {
type:Boolean
}
})
</script>
`,
parser: require.resolve('vue-eslint-parser')
},
{
filename: 'test.vue',
code: `
<script setup>
defineProps({
foo: {
type:Boolean,
default: false
}
})
</script>
`,
parser: require.resolve('vue-eslint-parser'),
options: ['default-false']
},
{
filename: 'test.vue',
code: `
<script setup lang="ts">
interface Props {
foo: boolean
}
withDefaults(defineProps<Props>(), {
})
</script>
`,
parser: require.resolve('vue-eslint-parser'),
parserOptions: {
parser: require.resolve('@typescript-eslint/parser')
}
},
{
filename: 'test.vue',
code: `
<script setup lang="ts">
interface Props {
foo: boolean
}
withDefaults(defineProps<Props>(), {
foo: false
})
</script>
`,
parser: require.resolve('vue-eslint-parser'),
parserOptions: {
parser: require.resolve('@typescript-eslint/parser')
},
options: ['default-false']
},
{
filename: 'test.vue',
code: `
<script setup lang="ts">
interface Props {
foo: boolean | string
}
withDefaults(defineProps<Props>(), {
foo: false
})
</script>
`,
parser: require.resolve('vue-eslint-parser'),
parserOptions: {
parser: require.resolve('@typescript-eslint/parser')
}
},
{
filename: 'test.vue',
code: `
<script setup lang="ts">
interface Props {
foo: string
}
withDefaults(defineProps<Props>(), {
foo: false
})
</script>
`,
parser: require.resolve('vue-eslint-parser'),
parserOptions: {
parser: require.resolve('@typescript-eslint/parser')
}
}
],

Expand Down Expand Up @@ -314,6 +411,103 @@ ruleTester.run('no-boolean-default', rule, {
line: 6
}
]
}
},
{
filename: 'test.vue',
code: `
<script setup>
defineProps({
foo: {
type:Boolean,
default: false
}
})
</script>
`,
parser: require.resolve('vue-eslint-parser'),
errors: [
{
message:
'Boolean prop should not set a default (Vue defaults it to false).',
line: 6
}
]
},
{
filename: 'test.vue',
code: `
<script setup>
defineProps({
foo: {
type:Boolean,
default: true
}
})
</script>
`,
parser: require.resolve('vue-eslint-parser'),
options: ['default-false'],
errors: [
{
message: 'Boolean prop should only be defaulted to false.',
line: 6
}
]
},
...(semver.lt(
require('@typescript-eslint/parser/package.json').version,
'4.0.0'
)
? []
: [
{
filename: 'test.vue',
code: `
<script setup lang="ts">
interface Props {
foo: boolean
}
withDefaults(defineProps<Props>(), {
foo: false
})
</script>
`,
parser: require.resolve('vue-eslint-parser'),
parserOptions: {
parser: require.resolve('@typescript-eslint/parser')
},
errors: [
{
message:
'Boolean prop should not set a default (Vue defaults it to false).',
line: 7
}
]
},
{
filename: 'test.vue',
code: `
<script setup lang="ts">
interface Props {
foo: boolean
}
withDefaults(defineProps<Props>(), {
foo: true
})
</script>
`,
parser: require.resolve('vue-eslint-parser'),
parserOptions: {
parser: require.resolve('@typescript-eslint/parser')
},
options: ['default-false'],
errors: [
{
message: 'Boolean prop should only be defaulted to false.',
line: 7
}
]
}
])
]
})

0 comments on commit cd2ad1c

Please sign in to comment.