You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

148 lines
4.3 KiB

/**
* @author @neferqiqi
* See LICENSE file in root directory for full license.
*/
'use strict'
const utils = require('../utils')
/**
* @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp
* @typedef {import('../utils').ComponentArrayProp} ComponentArrayProp
* @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp
* @typedef {import('../utils').ComponentUnknownProp} ComponentUnknownProp
* @typedef {import('../utils').ComponentProp} ComponentProp
*/
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce props with default values to be optional',
categories: undefined,
url: 'https://eslint.vuejs.org/rules/no-required-prop-with-default.html'
},
fixable: 'code',
hasSuggestions: true,
schema: [
{
type: 'object',
properties: {
autofix: {
type: 'boolean'
}
},
additionalProperties: false
}
],
messages: {
requireOptional: `Prop "{{ key }}" should be optional.`,
fixRequiredProp: `Change this prop to be optional.`
}
},
/** @param {RuleContext} context */
create(context) {
let canAutoFix = false
const option = context.options[0]
if (option) {
canAutoFix = option.autofix
}
/**
* @param {ComponentArrayProp | ComponentObjectProp | ComponentUnknownProp | ComponentProp} prop
* */
const handleObjectProp = (prop) => {
if (
prop.type === 'object' &&
prop.propName &&
prop.value.type === 'ObjectExpression' &&
utils.findProperty(prop.value, 'default')
) {
const requiredProperty = utils.findProperty(prop.value, 'required')
if (!requiredProperty) return
const requiredNode = requiredProperty.value
if (
requiredNode &&
requiredNode.type === 'Literal' &&
!!requiredNode.value
) {
context.report({
node: prop.node,
loc: prop.node.loc,
data: {
key: prop.propName
},
messageId: 'requireOptional',
fix: canAutoFix
? (fixer) => fixer.replaceText(requiredNode, 'false')
: null,
suggest: canAutoFix
? null
: [
{
messageId: 'fixRequiredProp',
fix: (fixer) => fixer.replaceText(requiredNode, 'false')
}
]
})
}
}
}
return utils.compositingVisitors(
utils.defineVueVisitor(context, {
onVueObjectEnter(node) {
utils.getComponentPropsFromOptions(node).map(handleObjectProp)
}
}),
utils.defineScriptSetupVisitor(context, {
onDefinePropsEnter(node, props) {
if (!utils.hasWithDefaults(node)) {
props.map(handleObjectProp)
return
}
const withDefaultsProps = Object.keys(
utils.getWithDefaultsPropExpressions(node)
)
const requiredProps = props.flatMap((item) =>
item.type === 'type' && item.required ? [item] : []
)
for (const prop of requiredProps) {
if (withDefaultsProps.includes(prop.propName)) {
// skip setter & getter case
if (
prop.node.type === 'TSMethodSignature' &&
(prop.node.kind === 'get' || prop.node.kind === 'set')
) {
return
}
// skip computed
if (prop.node.computed) {
return
}
context.report({
node: prop.node,
loc: prop.node.loc,
data: {
key: prop.propName
},
messageId: 'requireOptional',
fix: canAutoFix
? (fixer) => fixer.insertTextAfter(prop.key, '?')
: null,
suggest: canAutoFix
? null
: [
{
messageId: 'fixRequiredProp',
fix: (fixer) => fixer.insertTextAfter(prop.key, '?')
}
]
})
}
}
}
})
)
}
}