eslint-plugin-vue
Advanced tools
Comparing version 9.28.0 to 9.29.0
@@ -100,2 +100,3 @@ /* | ||
'no-deprecated-data-object-declaration': require('./rules/no-deprecated-data-object-declaration'), | ||
'no-deprecated-delete-set': require('./rules/no-deprecated-delete-set'), | ||
'no-deprecated-destroyed-lifecycle': require('./rules/no-deprecated-destroyed-lifecycle'), | ||
@@ -102,0 +103,0 @@ 'no-deprecated-dollar-listeners-api': require('./rules/no-deprecated-dollar-listeners-api'), |
@@ -11,14 +11,23 @@ /** | ||
* @typedef {import('../utils').ComponentProp} ComponentProp | ||
* @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp | ||
*/ | ||
/** | ||
* @param {Property | SpreadElement} prop | ||
* @param {Expression|undefined} node | ||
*/ | ||
function isBooleanIdentifier(node) { | ||
return Boolean(node && node.type === 'Identifier' && node.name === 'Boolean') | ||
} | ||
/** | ||
* Detects whether given prop node is a Boolean | ||
* @param {ComponentObjectProp} prop | ||
* @return {Boolean} | ||
*/ | ||
function isBooleanProp(prop) { | ||
const value = utils.skipTSAsExpression(prop.value) | ||
return ( | ||
prop.type === 'Property' && | ||
prop.key.type === 'Identifier' && | ||
prop.key.name === 'type' && | ||
prop.value.type === 'Identifier' && | ||
prop.value.name === 'Boolean' | ||
isBooleanIdentifier(value) || | ||
(value.type === 'ObjectExpression' && | ||
isBooleanIdentifier(utils.findProperty(value, 'type')?.value)) | ||
) | ||
@@ -59,17 +68,20 @@ } | ||
* @param {ComponentProp} prop | ||
* @param { { [key: string]: Expression | undefined } } [withDefaultsExpressions] | ||
* @param {(propName: string) => Expression[]} otherDefaultProvider | ||
*/ | ||
function processProp(prop, withDefaultsExpressions) { | ||
function processProp(prop, otherDefaultProvider) { | ||
if (prop.type === 'object') { | ||
if (prop.value.type !== 'ObjectExpression') { | ||
if (!isBooleanProp(prop)) { | ||
return | ||
} | ||
if (!prop.value.properties.some(isBooleanProp)) { | ||
return | ||
if (prop.value.type === 'ObjectExpression') { | ||
const defaultNode = getDefaultNode(prop.value) | ||
if (defaultNode) { | ||
verifyDefaultExpression(defaultNode.value) | ||
} | ||
} | ||
const defaultNode = getDefaultNode(prop.value) | ||
if (!defaultNode) { | ||
return | ||
if (prop.propName != null) { | ||
for (const defaultNode of otherDefaultProvider(prop.propName)) { | ||
verifyDefaultExpression(defaultNode) | ||
} | ||
} | ||
verifyDefaultExpression(defaultNode.value) | ||
} else if (prop.type === 'type') { | ||
@@ -79,8 +91,5 @@ if (prop.types.length !== 1 || prop.types[0] !== 'Boolean') { | ||
} | ||
const defaultNode = | ||
withDefaultsExpressions && withDefaultsExpressions[prop.propName] | ||
if (!defaultNode) { | ||
return | ||
for (const defaultNode of otherDefaultProvider(prop.propName)) { | ||
verifyDefaultExpression(defaultNode) | ||
} | ||
verifyDefaultExpression(defaultNode) | ||
} | ||
@@ -90,7 +99,7 @@ } | ||
* @param {ComponentProp[]} props | ||
* @param { { [key: string]: Expression | undefined } } [withDefaultsExpressions] | ||
* @param {(propName: string) => Expression[]} otherDefaultProvider | ||
*/ | ||
function processProps(props, withDefaultsExpressions) { | ||
function processProps(props, otherDefaultProvider) { | ||
for (const prop of props) { | ||
processProp(prop, withDefaultsExpressions) | ||
processProp(prop, otherDefaultProvider) | ||
} | ||
@@ -125,7 +134,16 @@ } | ||
utils.executeOnVueComponent(context, (obj) => { | ||
processProps(utils.getComponentPropsFromOptions(obj)) | ||
processProps(utils.getComponentPropsFromOptions(obj), () => []) | ||
}), | ||
utils.defineScriptSetupVisitor(context, { | ||
onDefinePropsEnter(node, props) { | ||
processProps(props, utils.getWithDefaultsPropExpressions(node)) | ||
const defaultsByWithDefaults = | ||
utils.getWithDefaultsPropExpressions(node) | ||
const defaultsByAssignmentPatterns = | ||
utils.getDefaultPropExpressionsForPropsDestructure(node) | ||
processProps(props, (propName) => | ||
[ | ||
defaultsByWithDefaults[propName], | ||
defaultsByAssignmentPatterns[propName]?.expression | ||
].filter(utils.isDef) | ||
) | ||
} | ||
@@ -132,0 +150,0 @@ }) |
@@ -50,5 +50,6 @@ /** | ||
/** | ||
* @param {ComponentArrayProp | ComponentObjectProp | ComponentUnknownProp | ComponentProp} prop | ||
* */ | ||
const handleObjectProp = (prop) => { | ||
* @param {ComponentProp} prop | ||
* @param {Set<string>} [defaultProps] | ||
**/ | ||
const handleObjectProp = (prop, defaultProps) => { | ||
if ( | ||
@@ -58,3 +59,4 @@ prop.type === 'object' && | ||
prop.value.type === 'ObjectExpression' && | ||
utils.findProperty(prop.value, 'default') | ||
(utils.findProperty(prop.value, 'default') || | ||
defaultProps?.has(prop.propName)) | ||
) { | ||
@@ -89,2 +91,37 @@ const requiredProperty = utils.findProperty(prop.value, 'required') | ||
} | ||
} else if ( | ||
prop.type === 'type' && | ||
defaultProps?.has(prop.propName) && | ||
prop.required | ||
) { | ||
// 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, '?') | ||
} | ||
] | ||
}) | ||
} | ||
@@ -96,3 +133,5 @@ } | ||
onVueObjectEnter(node) { | ||
utils.getComponentPropsFromOptions(node).map(handleObjectProp) | ||
utils | ||
.getComponentPropsFromOptions(node) | ||
.map((prop) => handleObjectProp(prop)) | ||
} | ||
@@ -102,47 +141,9 @@ }), | ||
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, '?') | ||
} | ||
] | ||
}) | ||
} | ||
} | ||
const defaultProps = new Set([ | ||
...Object.keys(utils.getWithDefaultsPropExpressions(node)), | ||
...Object.keys( | ||
utils.getDefaultPropExpressionsForPropsDestructure(node) | ||
) | ||
]) | ||
props.map((prop) => handleObjectProp(prop, defaultProps)) | ||
} | ||
@@ -149,0 +150,0 @@ }) |
@@ -98,5 +98,5 @@ /** | ||
* @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) { | ||
@@ -122,3 +122,10 @@ if (!prop.propName) { | ||
option, | ||
withDefaultsProps && withDefaultsProps[prop.propName] | ||
fixPropInOtherPlaces | ||
? (fixer, replaceKeyText) => | ||
fixPropInOtherPlaces( | ||
fixer, | ||
prop.propName, | ||
replaceKeyText | ||
) | ||
: undefined | ||
) | ||
@@ -134,3 +141,29 @@ }) | ||
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) | ||
) | ||
} | ||
} | ||
@@ -150,6 +183,6 @@ }), | ||
* @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) { | ||
@@ -175,10 +208,4 @@ return [] | ||
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)) | ||
} | ||
@@ -185,0 +212,0 @@ return fixes.sort((a, b) => a.range[0] - b.range[0]) |
@@ -9,2 +9,56 @@ /** | ||
/** | ||
* @typedef {'props'|'prop'} PropIdKind | ||
* - `'props'`: A node is a container object that has props. | ||
* - `'prop'`: A node is a variable with one prop. | ||
*/ | ||
/** | ||
* @typedef {object} PropId | ||
* @property {Pattern} node | ||
* @property {PropIdKind} kind | ||
*/ | ||
/** | ||
* Iterates over Prop identifiers by parsing the given pattern | ||
* in the left operand of defineProps(). | ||
* @param {Pattern} node | ||
* @returns {IterableIterator<PropId>} | ||
*/ | ||
function* iteratePropIds(node) { | ||
switch (node.type) { | ||
case 'ObjectPattern': { | ||
for (const prop of node.properties) { | ||
yield prop.type === 'Property' | ||
? { | ||
// e.g. `const { prop } = defineProps()` | ||
node: unwrapAssignment(prop.value), | ||
kind: 'prop' | ||
} | ||
: { | ||
// RestElement | ||
// e.g. `const { x, ...prop } = defineProps()` | ||
node: unwrapAssignment(prop.argument), | ||
kind: 'props' | ||
} | ||
} | ||
break | ||
} | ||
default: { | ||
// e.g. `const props = defineProps()` | ||
yield { node: unwrapAssignment(node), kind: 'props' } | ||
} | ||
} | ||
} | ||
/** | ||
* @template {Pattern} T | ||
* @param {T} node | ||
* @returns {Pattern} | ||
*/ | ||
function unwrapAssignment(node) { | ||
if (node.type === 'AssignmentPattern') { | ||
return node.left | ||
} | ||
return node | ||
} | ||
module.exports = { | ||
@@ -35,3 +89,5 @@ meta: { | ||
* @typedef {object} ScopePropsReferences | ||
* @property {Set<Identifier>} refs | ||
* @property {object} refs | ||
* @property {Set<Identifier>} refs.props A set of references to container objects with multiple props. | ||
* @property {Set<Identifier>} refs.prop A set of references a variable with one property. | ||
* @property {string} scopeName | ||
@@ -77,21 +133,40 @@ */ | ||
) { | ||
return report(rightNode, 'getProperty', propsReferences.scopeName) | ||
// e.g. `const foo = { x: props.x }` | ||
report(rightNode, 'getProperty', propsReferences.scopeName) | ||
return | ||
} | ||
// Get the expression that provides the value. | ||
/** @type {Expression | Super} */ | ||
let expression = rightNode | ||
while (expression.type === 'MemberExpression') { | ||
expression = utils.skipChainExpression(expression.object) | ||
} | ||
/** A list of expression nodes to verify */ | ||
const expressions = | ||
expression.type === 'TemplateLiteral' | ||
? expression.expressions | ||
: expression.type === 'ConditionalExpression' | ||
? [expression.test, expression.consequent, expression.alternate] | ||
: expression.type === 'Identifier' | ||
? [expression] | ||
: [] | ||
if ( | ||
left.type !== 'ArrayPattern' && | ||
left.type !== 'ObjectPattern' && | ||
rightNode.type !== 'MemberExpression' && | ||
rightNode.type !== 'ConditionalExpression' && | ||
rightNode.type !== 'TemplateLiteral' | ||
(left.type === 'ArrayPattern' || left.type === 'ObjectPattern') && | ||
expressions.some( | ||
(expr) => | ||
expr.type === 'Identifier' && propsReferences.refs.props.has(expr) | ||
) | ||
) { | ||
// e.g. `const {foo} = props` | ||
report(left, 'getProperty', propsReferences.scopeName) | ||
return | ||
} | ||
if (rightNode.type === 'TemplateLiteral') { | ||
rightNode.expressions.some((expression) => | ||
checkMemberAccess(expression, propsReferences, left, right) | ||
) | ||
} else { | ||
checkMemberAccess(rightNode, propsReferences, left, right) | ||
const reportNode = expressions.find((expr) => | ||
isPropsMemberAccessed(expr, propsReferences) | ||
) | ||
if (reportNode) { | ||
report(reportNode, 'getProperty', propsReferences.scopeName) | ||
} | ||
@@ -101,36 +176,7 @@ } | ||
/** | ||
* @param {Expression | Super} rightId | ||
* @param {Expression | Super} node | ||
* @param {ScopePropsReferences} propsReferences | ||
* @param {Pattern} left | ||
* @param {Expression} right | ||
* @return {boolean} | ||
*/ | ||
function checkMemberAccess(rightId, propsReferences, left, right) { | ||
while (rightId.type === 'MemberExpression') { | ||
rightId = utils.skipChainExpression(rightId.object) | ||
} | ||
if (rightId.type === 'Identifier' && propsReferences.refs.has(rightId)) { | ||
report(left, 'getProperty', propsReferences.scopeName) | ||
return true | ||
} | ||
if ( | ||
rightId.type === 'ConditionalExpression' && | ||
(isPropsMemberAccessed(rightId.test, propsReferences) || | ||
isPropsMemberAccessed(rightId.consequent, propsReferences) || | ||
isPropsMemberAccessed(rightId.alternate, propsReferences)) | ||
) { | ||
report(right, 'getProperty', propsReferences.scopeName) | ||
return true | ||
} | ||
return false | ||
} | ||
/** | ||
* @param {Expression} node | ||
* @param {ScopePropsReferences} propsReferences | ||
*/ | ||
function isPropsMemberAccessed(node, propsReferences) { | ||
const propRefs = [...propsReferences.refs.values()] | ||
return propRefs.some((props) => { | ||
for (const props of propsReferences.refs.props) { | ||
const isPropsInExpressionRange = utils.inRange(node.range, props) | ||
@@ -141,4 +187,16 @@ const isPropsMemberExpression = | ||
return isPropsInExpressionRange && isPropsMemberExpression | ||
}) | ||
if (isPropsInExpressionRange && isPropsMemberExpression) { | ||
return true | ||
} | ||
} | ||
// Checks for actual member access using prop destructuring. | ||
for (const prop of propsReferences.refs.prop) { | ||
const isPropsInExpressionRange = utils.inRange(node.range, prop) | ||
if (isPropsInExpressionRange) { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
@@ -157,3 +215,3 @@ | ||
/** | ||
* @param {Pattern | null} node | ||
* @param {PropId} propId | ||
* @param {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression | Program} scopeNode | ||
@@ -163,7 +221,3 @@ * @param {import('eslint').Scope.Scope} currentScope | ||
*/ | ||
function processPattern(node, scopeNode, currentScope, scopeName) { | ||
if (!node) { | ||
// no arguments | ||
return | ||
} | ||
function processPropId({ node, kind }, scopeNode, currentScope, scopeName) { | ||
if ( | ||
@@ -186,3 +240,15 @@ node.type === 'RestElement' || | ||
} | ||
const propsReferenceIds = new Set() | ||
let scopePropsReferences = setupScopePropsReferenceIds.get(scopeNode) | ||
if (!scopePropsReferences) { | ||
scopePropsReferences = { | ||
refs: { | ||
props: new Set(), | ||
prop: new Set() | ||
}, | ||
scopeName | ||
} | ||
setupScopePropsReferenceIds.set(scopeNode, scopePropsReferences) | ||
} | ||
const propsReferenceIds = scopePropsReferences.refs[kind] | ||
for (const reference of variable.references) { | ||
@@ -200,7 +266,4 @@ // If reference is in another scope, we can't check it. | ||
} | ||
setupScopePropsReferenceIds.set(scopeNode, { | ||
refs: propsReferenceIds, | ||
scopeName | ||
}) | ||
} | ||
return utils.compositingVisitors( | ||
@@ -299,9 +362,12 @@ { | ||
} | ||
if (!id) return | ||
const currentScope = utils.getScope(context, node) | ||
processPattern( | ||
id, | ||
context.getSourceCode().ast, | ||
currentScope, | ||
'<script setup>' | ||
) | ||
for (const propId of iteratePropIds(id)) { | ||
processPropId( | ||
propId, | ||
context.getSourceCode().ast, | ||
currentScope, | ||
'<script setup>' | ||
) | ||
} | ||
} | ||
@@ -313,3 +379,9 @@ }), | ||
const propsParam = utils.skipDefaultParamValue(node.params[0]) | ||
processPattern(propsParam, node, currentScope, 'setup()') | ||
if (!propsParam) return | ||
processPropId( | ||
{ node: propsParam, kind: 'props' }, | ||
node, | ||
currentScope, | ||
'setup()' | ||
) | ||
} | ||
@@ -316,0 +388,0 @@ }) |
@@ -114,2 +114,7 @@ /** | ||
const programNode = context.getSourceCode().ast | ||
/** | ||
* Property names identified as defined via a Vuex or Pinia helpers | ||
* @type {Set<string>} | ||
*/ | ||
const propertiesDefinedByStoreHelpers = new Set() | ||
@@ -189,3 +194,4 @@ /** | ||
reserved.includes(name) || | ||
ignores.some((ignore) => ignore.test(name)) | ||
ignores.some((ignore) => ignore.test(name)) || | ||
propertiesDefinedByStoreHelpers.has(name) | ||
) { | ||
@@ -336,2 +342,47 @@ return | ||
utils.defineVueVisitor(context, { | ||
/** | ||
* @param {CallExpression} node | ||
*/ | ||
CallExpression(node) { | ||
if (node.callee.type !== 'Identifier') return | ||
/** @type {'methods'|'computed'|null} */ | ||
let groupName = null | ||
if (/^mapMutations|mapActions$/u.test(node.callee.name)) { | ||
groupName = GROUP_METHODS | ||
} else if ( | ||
/^mapState|mapGetters|mapWritableState$/u.test(node.callee.name) | ||
) { | ||
groupName = GROUP_COMPUTED_PROPERTY | ||
} | ||
if (!groupName || node.arguments.length === 0) return | ||
// On Pinia the store is always the first argument | ||
const arg = | ||
node.arguments.length === 2 ? node.arguments[1] : node.arguments[0] | ||
if (arg.type === 'ObjectExpression') { | ||
// e.g. | ||
// `mapMutations({ add: 'increment' })` | ||
// `mapState({ count: state => state.todosCount })` | ||
for (const prop of arg.properties) { | ||
const name = | ||
prop.type === 'SpreadElement' | ||
? null | ||
: utils.getStaticPropertyName(prop) | ||
if (name) { | ||
propertiesDefinedByStoreHelpers.add(name) | ||
} | ||
} | ||
} else if (arg.type === 'ArrayExpression') { | ||
// e.g. `mapMutations(['add'])` | ||
for (const element of arg.elements) { | ||
if (!element || !utils.isStringLiteral(element)) { | ||
continue | ||
} | ||
const name = utils.getStringLiteralValue(element) | ||
if (name) { | ||
propertiesDefinedByStoreHelpers.add(name) | ||
} | ||
} | ||
} | ||
}, | ||
onVueObjectEnter(node) { | ||
@@ -338,0 +389,0 @@ const ctx = getVueComponentContext(node) |
@@ -236,2 +236,15 @@ /** | ||
extractUsedForPattern(refsNode) | ||
}, | ||
CallExpression(callExpression) { | ||
const firstArgument = callExpression.arguments[0] | ||
if ( | ||
callExpression.callee.name !== 'useTemplateRef' || | ||
!firstArgument | ||
) { | ||
return | ||
} | ||
const name = utils.getStringLiteralValue(firstArgument) | ||
if (name !== null) { | ||
usedRefs.add(name) | ||
} | ||
} | ||
@@ -238,0 +251,0 @@ } |
@@ -10,2 +10,3 @@ /** | ||
* @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp | ||
* @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp | ||
* @typedef {ComponentObjectProp & { value: ObjectExpression} } ComponentObjectPropObject | ||
@@ -141,8 +142,10 @@ */ | ||
* @param {ComponentProp[]} props | ||
* @param {boolean} [withDefaults] | ||
* @param { { [key: string]: Expression | undefined } } [withDefaultsExpressions] | ||
* @param {(prop: ComponentObjectProp|ComponentTypeProp)=>boolean} [ignore] | ||
*/ | ||
function processProps(props, withDefaults, withDefaultsExpressions) { | ||
function processProps(props, ignore) { | ||
for (const prop of props) { | ||
if (prop.type === 'object' && !prop.node.shorthand) { | ||
if (prop.type === 'object') { | ||
if (prop.node.shorthand) { | ||
continue | ||
} | ||
if (!isWithoutDefaultValue(prop)) { | ||
@@ -154,2 +157,5 @@ continue | ||
} | ||
if (ignore?.(prop)) { | ||
continue | ||
} | ||
const propName = | ||
@@ -167,7 +173,3 @@ prop.propName == null | ||
}) | ||
} else if ( | ||
prop.type === 'type' && | ||
withDefaults && | ||
withDefaultsExpressions | ||
) { | ||
} else if (prop.type === 'type') { | ||
if (prop.required) { | ||
@@ -179,11 +181,12 @@ continue | ||
} | ||
if (!withDefaultsExpressions[prop.propName]) { | ||
context.report({ | ||
node: prop.node, | ||
messageId: `missingDefault`, | ||
data: { | ||
propName: prop.propName | ||
} | ||
}) | ||
if (ignore?.(prop)) { | ||
continue | ||
} | ||
context.report({ | ||
node: prop.node, | ||
messageId: `missingDefault`, | ||
data: { | ||
propName: prop.propName | ||
} | ||
}) | ||
} | ||
@@ -196,7 +199,29 @@ } | ||
onDefinePropsEnter(node, props) { | ||
processProps( | ||
props, | ||
utils.hasWithDefaults(node), | ||
const hasWithDefaults = utils.hasWithDefaults(node) | ||
const defaultsByWithDefaults = | ||
utils.getWithDefaultsPropExpressions(node) | ||
) | ||
const isUsingPropsDestructure = utils.isUsingPropsDestructure(node) | ||
const defaultsByAssignmentPatterns = | ||
utils.getDefaultPropExpressionsForPropsDestructure(node) | ||
processProps(props, (prop) => { | ||
if (prop.type === 'type') { | ||
if (!hasWithDefaults) { | ||
// If don't use withDefaults(), exclude it from the report. | ||
return true | ||
} | ||
if (defaultsByWithDefaults[prop.propName]) { | ||
return true | ||
} | ||
} | ||
if (!isUsingPropsDestructure) { | ||
return false | ||
} | ||
if (prop.propName == null) { | ||
// If using Props Destructure but the property name cannot be determined, | ||
// it will be ignored. | ||
return true | ||
} | ||
return Boolean(defaultsByAssignmentPatterns[prop.propName]) | ||
}) | ||
} | ||
@@ -203,0 +228,0 @@ }), |
@@ -253,6 +253,6 @@ /** | ||
/** | ||
* @param {(ComponentObjectDefineProp | ComponentTypeProp | ComponentInferTypeProp)[]} props | ||
* @param { { [key: string]: Expression | undefined } } withDefaults | ||
* @param {(ComponentObjectProp | ComponentTypeProp | ComponentInferTypeProp)[]} props | ||
* @param {(propName: string) => Expression[]} otherDefaultProvider | ||
*/ | ||
function processPropDefs(props, withDefaults) { | ||
function processPropDefs(props, otherDefaultProvider) { | ||
/** @type {PropDefaultFunctionContext[]} */ | ||
@@ -262,19 +262,27 @@ const propContexts = [] | ||
let typeList | ||
let defExpr | ||
/** @type {Expression[]} */ | ||
const defExprList = [] | ||
if (prop.type === 'object') { | ||
const type = getPropertyNode(prop.value, 'type') | ||
if (!type) continue | ||
if (prop.value.type === 'ObjectExpression') { | ||
const type = getPropertyNode(prop.value, 'type') | ||
if (!type) continue | ||
typeList = getTypes(type.value) | ||
typeList = getTypes(type.value) | ||
const def = getPropertyNode(prop.value, 'default') | ||
if (!def) continue | ||
const def = getPropertyNode(prop.value, 'default') | ||
if (!def) continue | ||
defExpr = def.value | ||
defExprList.push(def.value) | ||
} else { | ||
typeList = getTypes(prop.value) | ||
} | ||
} else { | ||
typeList = prop.types | ||
defExpr = withDefaults[prop.propName] | ||
} | ||
if (!defExpr) continue | ||
if (prop.propName != null) { | ||
defExprList.push(...otherDefaultProvider(prop.propName)) | ||
} | ||
if (defExprList.length === 0) continue | ||
const typeNames = new Set( | ||
@@ -286,36 +294,38 @@ typeList.filter((item) => NATIVE_TYPES.has(item)) | ||
const defType = getValueType(defExpr) | ||
for (const defExpr of defExprList) { | ||
const defType = getValueType(defExpr) | ||
if (!defType) continue | ||
if (!defType) continue | ||
if (defType.function) { | ||
if (typeNames.has('Function')) { | ||
continue | ||
} | ||
if (defType.expression) { | ||
if (!defType.returnType || typeNames.has(defType.returnType)) { | ||
if (defType.function) { | ||
if (typeNames.has('Function')) { | ||
continue | ||
} | ||
report(defType.functionBody, prop, typeNames) | ||
if (defType.expression) { | ||
if (!defType.returnType || typeNames.has(defType.returnType)) { | ||
continue | ||
} | ||
report(defType.functionBody, prop, typeNames) | ||
} else { | ||
propContexts.push({ | ||
prop, | ||
types: typeNames, | ||
default: defType | ||
}) | ||
} | ||
} else { | ||
propContexts.push({ | ||
if ( | ||
typeNames.has(defType.type) && | ||
!FUNCTION_VALUE_TYPES.has(defType.type) | ||
) { | ||
continue | ||
} | ||
report( | ||
defExpr, | ||
prop, | ||
types: typeNames, | ||
default: defType | ||
}) | ||
[...typeNames].map((type) => | ||
FUNCTION_VALUE_TYPES.has(type) ? 'Function' : type | ||
) | ||
) | ||
} | ||
} else { | ||
if ( | ||
typeNames.has(defType.type) && | ||
!FUNCTION_VALUE_TYPES.has(defType.type) | ||
) { | ||
continue | ||
} | ||
report( | ||
defExpr, | ||
prop, | ||
[...typeNames].map((type) => | ||
FUNCTION_VALUE_TYPES.has(type) ? 'Function' : type | ||
) | ||
) | ||
} | ||
@@ -370,3 +380,3 @@ } | ||
) | ||
const propContexts = processPropDefs(props, {}) | ||
const propContexts = processPropDefs(props, () => []) | ||
vueObjectPropsContexts.set(obj, propContexts) | ||
@@ -409,3 +419,3 @@ }, | ||
* @param {ComponentProp} prop | ||
* @returns {prop is ComponentObjectDefineProp | ComponentInferTypeProp | ComponentTypeProp} | ||
* @returns {prop is ComponentObjectProp | ComponentInferTypeProp | ComponentTypeProp} | ||
*/ | ||
@@ -416,8 +426,15 @@ (prop) => | ||
prop.type === 'infer-type' || | ||
(prop.type === 'object' && | ||
prop.value.type === 'ObjectExpression') | ||
prop.type === 'object' | ||
) | ||
) | ||
const defaults = utils.getWithDefaultsPropExpressions(node) | ||
const propContexts = processPropDefs(props, defaults) | ||
const defaultsByWithDefaults = | ||
utils.getWithDefaultsPropExpressions(node) | ||
const defaultsByAssignmentPatterns = | ||
utils.getDefaultPropExpressionsForPropsDestructure(node) | ||
const propContexts = processPropDefs(props, (propName) => | ||
[ | ||
defaultsByWithDefaults[propName], | ||
defaultsByAssignmentPatterns[propName]?.expression | ||
].filter(utils.isDef) | ||
) | ||
scriptSetupPropsContexts.push({ node, props: propContexts }) | ||
@@ -424,0 +441,0 @@ }, |
@@ -30,2 +30,7 @@ [ | ||
"UnwrapRef", | ||
"WatchCallback", | ||
"WatchEffect", | ||
"WatchHandle", | ||
"WatchSource", | ||
"WatchStopHandle", | ||
"WritableComputedOptions", | ||
@@ -37,2 +42,3 @@ "WritableComputedRef", | ||
"getCurrentScope", | ||
"getCurrentWatcher", | ||
"isProxy", | ||
@@ -45,2 +51,3 @@ "isReactive", | ||
"onScopeDispose", | ||
"onWatcherCleanup", | ||
"proxyRefs", | ||
@@ -73,6 +80,36 @@ "reactive", | ||
"queuePostFlushCb", | ||
"ComponentPropsOptions", | ||
"ComponentObjectPropsOptions", | ||
"Prop", | ||
"PropType", | ||
"ExtractPropTypes", | ||
"ExtractPublicPropTypes", | ||
"ExtractDefaultPropTypes", | ||
"defineProps", | ||
"DefineProps", | ||
"defineEmits", | ||
"ComponentTypeEmits", | ||
"defineExpose", | ||
"defineOptions", | ||
"defineSlots", | ||
"ModelRef", | ||
"defineModel", | ||
"withDefaults", | ||
"useSlots", | ||
"useAttrs", | ||
"ObjectEmitsOptions", | ||
"EmitsOptions", | ||
"EmitsToProps", | ||
"ShortEmitsToObject", | ||
"EmitFn", | ||
"DirectiveBinding", | ||
"DirectiveHook", | ||
"ObjectDirective", | ||
"FunctionDirective", | ||
"Directive", | ||
"DirectiveArguments", | ||
"withDirectives", | ||
"ComponentCustomProperties", | ||
"CreateComponentPublicInstance", | ||
"CreateComponentPublicInstanceWithMixins", | ||
"ComponentPublicInstance", | ||
@@ -115,16 +152,2 @@ "SuspenseProps", | ||
"onErrorCaptured", | ||
"ComponentPropsOptions", | ||
"ComponentObjectPropsOptions", | ||
"Prop", | ||
"PropType", | ||
"ExtractPropTypes", | ||
"ExtractPublicPropTypes", | ||
"ExtractDefaultPropTypes", | ||
"DirectiveBinding", | ||
"DirectiveHook", | ||
"ObjectDirective", | ||
"FunctionDirective", | ||
"Directive", | ||
"DirectiveArguments", | ||
"withDirectives", | ||
"ComponentCustomOptions", | ||
@@ -134,5 +157,2 @@ "RenderFunction", | ||
"RuntimeCompilerOptions", | ||
"ComponentOptionsWithoutProps", | ||
"ComponentOptionsWithArrayProps", | ||
"ComponentOptionsWithObjectProps", | ||
"ComponentOptions", | ||
@@ -144,2 +164,5 @@ "ComponentOptionsMixin", | ||
"ComponentInjectOptions", | ||
"ComponentOptionsWithoutProps", | ||
"ComponentOptionsWithArrayProps", | ||
"ComponentOptionsWithObjectProps", | ||
"InjectionKey", | ||
@@ -193,2 +216,4 @@ "provide", | ||
"ComponentCustomProps", | ||
"GlobalDirectives", | ||
"GlobalComponents", | ||
"AllowedComponentProps", | ||
@@ -203,27 +228,22 @@ "FunctionalComponent", | ||
"isRuntimeOnly", | ||
"WatchEffect", | ||
"WatchSource", | ||
"WatchCallback", | ||
"WatchOptionsBase", | ||
"ComponentCustomElementInterface", | ||
"WatchEffectOptions", | ||
"WatchOptions", | ||
"WatchStopHandle", | ||
"watchEffect", | ||
"watchPostEffect", | ||
"watchSyncEffect", | ||
"MultiWatchSources", | ||
"watch", | ||
"HydrationStrategy", | ||
"HydrationStrategyFactory", | ||
"hydrateOnIdle", | ||
"hydrateOnVisible", | ||
"hydrateOnMediaQuery", | ||
"hydrateOnInteraction", | ||
"AsyncComponentLoader", | ||
"AsyncComponentOptions", | ||
"defineAsyncComponent", | ||
"defineProps", | ||
"DefineProps", | ||
"defineEmits", | ||
"defineExpose", | ||
"defineOptions", | ||
"defineSlots", | ||
"ModelRef", | ||
"defineModel", | ||
"withDefaults", | ||
"useSlots", | ||
"useAttrs", | ||
"useModel", | ||
"useTemplateRef", | ||
"useId", | ||
"h", | ||
@@ -255,9 +275,4 @@ "ssrContextKey", | ||
"DeprecationTypes", | ||
"WatchOptionsBase", | ||
"createElementVNode", | ||
"VueElementConstructor", | ||
"defineCustomElement", | ||
"defineSSRCustomElement", | ||
"VueElement", | ||
"useCssModule", | ||
"useCssVars", | ||
"TransitionProps", | ||
@@ -267,2 +282,5 @@ "Transition", | ||
"TransitionGroup", | ||
"vShow", | ||
"withModifiers", | ||
"withKeys", | ||
"vModelText", | ||
@@ -273,5 +291,11 @@ "vModelCheckbox", | ||
"vModelDynamic", | ||
"withModifiers", | ||
"withKeys", | ||
"vShow", | ||
"VueElementConstructor", | ||
"CustomElementOptions", | ||
"defineCustomElement", | ||
"defineSSRCustomElement", | ||
"VueElement", | ||
"useHost", | ||
"useShadowRoot", | ||
"useCssModule", | ||
"useCssVars", | ||
"CSSProperties", | ||
@@ -278,0 +302,0 @@ "AriaAttributes", |
{ | ||
"name": "eslint-plugin-vue", | ||
"version": "9.28.0", | ||
"version": "9.29.0", | ||
"description": "Official ESLint plugin for Vue.js", | ||
"main": "lib/index.js", | ||
"types": "lib/index.d.ts", | ||
"scripts": { | ||
@@ -7,0 +8,0 @@ "new": "node tools/new-rule.js", |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1364751
320
44624