Skip to content

Commit

Permalink
Add support for props destructure to vue/no-restricted-props rule
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi committed Sep 17, 2024
1 parent 8b877f7 commit 7921d20
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 15 deletions.
55 changes: 41 additions & 14 deletions lib/rules/no-restricted-props.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ module.exports = {

/**
* @param {ComponentProp[]} props
* @param { { [key: string]: Property | undefined } } [withDefaultsProps]
* @param {(fixer: RuleFixer, propName: string, replaceKeyText: string) => Iterable<Fix>} [fixPropInOtherPlaces]
*/
function processProps(props, withDefaultsProps) {
function processProps(props, fixPropInOtherPlaces) {
for (const prop of props) {
if (!prop.propName) {
continue
Expand All @@ -118,7 +118,14 @@ module.exports = {
: createSuggest(
prop.key,
option,
withDefaultsProps && withDefaultsProps[prop.propName]
fixPropInOtherPlaces
? (fixer, replaceKeyText) =>
fixPropInOtherPlaces(
fixer,
prop.propName,
replaceKeyText
)
: undefined
)
})
break
Expand All @@ -129,7 +136,33 @@ module.exports = {
return utils.compositingVisitors(
utils.defineScriptSetupVisitor(context, {
onDefinePropsEnter(node, props) {
processProps(props, utils.getWithDefaultsProps(node))
processProps(props, fixPropInOtherPlaces)

/**
* @param {RuleFixer} fixer
* @param {string} propName
* @param {string} replaceKeyText
*/
function fixPropInOtherPlaces(fixer, propName, replaceKeyText) {
/** @type {(Property|AssignmentProperty)[]} */
const propertyNodes = []
const withDefault = utils.getWithDefaultsProps(node)[propName]
if (withDefault) {
propertyNodes.push(withDefault)
}
const propDestructure = utils.getPropsDestructure(node)[propName]
if (propDestructure) {
propertyNodes.push(propDestructure)
}
return propertyNodes.map((propertyNode) =>
propertyNode.shorthand
? fixer.insertTextBefore(
propertyNode.value,
`${replaceKeyText}:`
)
: fixer.replaceText(propertyNode.key, replaceKeyText)
)
}
}
}),
utils.defineVueVisitor(context, {
Expand All @@ -144,10 +177,10 @@ module.exports = {
/**
* @param {Expression} node
* @param {ParsedOption} option
* @param {Property} [withDefault]
* @param {(fixer: RuleFixer, replaceKeyText: string) => Iterable<Fix>} [fixPropInOtherPlaces]
* @returns {Rule.SuggestionReportDescriptor[]}
*/
function createSuggest(node, option, withDefault) {
function createSuggest(node, option, fixPropInOtherPlaces) {
if (!option.suggest) {
return []
}
Expand All @@ -168,14 +201,8 @@ function createSuggest(node, option, withDefault) {
{
fix(fixer) {
const fixes = [fixer.replaceText(node, replaceText)]
if (withDefault) {
if (withDefault.shorthand) {
fixes.push(
fixer.insertTextBefore(withDefault.value, `${replaceText}:`)
)
} else {
fixes.push(fixer.replaceText(withDefault.key, replaceText))
}
if (fixPropInOtherPlaces) {
fixes.push(...fixPropInOtherPlaces(fixer, replaceText))
}
return fixes.sort((a, b) => a.range[0] - b.range[0])
},
Expand Down
84 changes: 84 additions & 0 deletions lib/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1537,6 +1537,28 @@ module.exports = {
* @returns { { [key: string]: Property | undefined } }
*/
getWithDefaultsProps,
/**
* Gets the default definition nodes for defineProp
* using the props destructure with assignment pattern.
* @param {CallExpression} node The node of defineProps
* @returns { Record<string, {prop: AssignmentProperty , expression: Expression} | undefined> }
*/
getDefaultPropExpressionsForPropsDestructure,
/**
* Checks whether the given defineProps node is using Props Destructure.
* @param {CallExpression} node The node of defineProps
* @returns {boolean}
*/
isUsingPropsDestructure(node) {
const left = getLeftOfDefineProps(node)
return left?.type === 'ObjectPattern'
},
/**
* Gets the props destructure property nodes for defineProp.
* @param {CallExpression} node The node of defineProps
* @returns { Record<string, AssignmentProperty | undefined> }
*/
getPropsDestructure,

getVueObjectType,
/**
Expand Down Expand Up @@ -3144,6 +3166,68 @@ function getWithDefaultsProps(node) {
return result
}

/**
* Gets the props destructure property nodes for defineProp.
* @param {CallExpression} node The node of defineProps
* @returns { Record<string, AssignmentProperty | undefined> }
*/
function getPropsDestructure(node) {
/** @type {ReturnType<typeof getPropsDestructure>} */
const result = Object.create(null)
const left = getLeftOfDefineProps(node)
if (!left || left.type !== 'ObjectPattern') {
return result
}
for (const prop of left.properties) {
if (prop.type !== 'Property') continue
const name = getStaticPropertyName(prop)
if (name != null) {
result[name] = prop
}
}
return result
}

/**
* Gets the default definition nodes for defineProp
* using the props destructure with assignment pattern.
* @param {CallExpression} node The node of defineProps
* @returns { Record<string, {prop: AssignmentProperty , expression: Expression} | undefined> }
*/
function getDefaultPropExpressionsForPropsDestructure(node) {
/** @type {ReturnType<typeof getDefaultPropExpressionsForPropsDestructure>} */
const result = Object.create(null)
for (const [name, prop] of Object.entries(getPropsDestructure(node))) {
if (!prop) continue
const value = prop.value
if (value.type !== 'AssignmentPattern') continue
result[name] = { prop, expression: value.right }
}
return result
}

/**
* Gets the pattern of the left operand of defineProps.
* @param {CallExpression} node The node of defineProps
* @returns {Pattern | null} The pattern of the left operand of defineProps
*/
function getLeftOfDefineProps(node) {
let target = node
if (hasWithDefaults(target)) {
target = target.parent
}
if (!target.parent) {
return null
}
if (
target.parent.type === 'VariableDeclarator' &&
target.parent.init === target
) {
return target.parent.id
}
return null
}

/**
* Get all props from component options object.
* @param {ObjectExpression} componentObject Object with component definition
Expand Down
52 changes: 51 additions & 1 deletion tests/lib/rules/no-restricted-props.js
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,56 @@ tester.run('no-restricted-props', rule, {
}
]
}
])
]),
{
filename: 'test.vue',
code: `
<script setup>
const {foo=false} = defineProps({foo:Boolean})
</script>
`,
options: [{ name: 'foo', suggest: 'Foo' }],
errors: [
{
message: 'Using `foo` props is not allowed.',
line: 3,
suggestions: [
{
desc: 'Instead, change to `Foo`.',
output: `
<script setup>
const {Foo:foo=false} = defineProps({Foo:Boolean})
</script>
`
}
]
}
]
},
{
filename: 'test.vue',
code: `
<script setup>
const {foo:bar=false} = defineProps({foo:Boolean})
</script>
`,
options: [{ name: 'foo', suggest: 'Foo' }],
errors: [
{
message: 'Using `foo` props is not allowed.',
line: 3,
suggestions: [
{
desc: 'Instead, change to `Foo`.',
output: `
<script setup>
const {Foo:bar=false} = defineProps({Foo:Boolean})
</script>
`
}
]
}
]
}
]
})

0 comments on commit 7921d20

Please sign in to comment.