diff --git a/lib/rules/no-unused-properties.js b/lib/rules/no-unused-properties.js index d09aca5c2..b15cf5a85 100644 --- a/lib/rules/no-unused-properties.js +++ b/lib/rules/no-unused-properties.js @@ -233,6 +233,8 @@ module.exports = { const deepData = Boolean(options.deepData) const ignorePublicMembers = Boolean(options.ignorePublicMembers) const unreferencedOptions = new Set(options.unreferencedOptions || []) + /** @type {null | Pattern} */ + let propsReferencePattern = null const propertyReferenceExtractor = definePropertyReferenceExtractor( context, @@ -332,8 +334,9 @@ module.exports = { for (const property of container.properties) { if ( - property.groupName === 'props' && - propertyReferencesForProps.hasProperty(property.name) + (property.groupName === 'props' && + propertyReferencesForProps.hasProperty(property.name)) || + propertyReferences.hasProperty('$props') ) { // used props continue @@ -369,6 +372,7 @@ module.exports = { } continue } + context.report({ node: property.node, messageId: 'unused', @@ -450,9 +454,9 @@ module.exports = { return } - const pattern = target.parent.id + propsReferencePattern = target.parent.id const propertyReferences = - propertyReferenceExtractor.extractFromPattern(pattern) + propertyReferenceExtractor.extractFromPattern(propsReferencePattern) container.propertyReferencesForProps.push(propertyReferences) }, onDefineModelEnter(node, model) { @@ -496,12 +500,16 @@ module.exports = { let groupName = null if (/^mapMutations|mapActions$/u.test(node.callee.name)) { groupName = 'methods' - } else if (/^mapState|mapGetters$/u.test(node.callee.name)) { + } else if ( + /^mapState|mapGetters|mapWritableState$/u.test(node.callee.name) + ) { groupName = 'computed' } if (!groupName || node.arguments.length === 0) return - const arg = node.arguments[0] + // 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' })` @@ -705,9 +713,26 @@ module.exports = { * @param {VExpressionContainer} node */ VExpressionContainer(node) { - templatePropertiesContainer.propertyReferences.push( + const property = propertyReferenceExtractor.extractFromVExpressionContainer(node) - ) + + templatePropertiesContainer.propertyReferences.push(property) + + if (!propsReferencePattern) { + return + } + + // props.prop in template + for (const key of property.allProperties().keys()) { + if ( + propsReferencePattern.type === 'Identifier' && + propsReferencePattern.name === key + ) { + templatePropertiesContainer.propertyReferences.push( + property.getNest(key) + ) + } + } }, /** * @param {VAttribute} node diff --git a/lib/rules/no-use-computed-property-like-method.js b/lib/rules/no-use-computed-property-like-method.js index 5d5bd8b4a..6e89ad263 100644 --- a/lib/rules/no-use-computed-property-like-method.js +++ b/lib/rules/no-use-computed-property-like-method.js @@ -68,26 +68,39 @@ function resolvedExpressions(context, node) { * @returns {Iterable} */ function* extractResolvedExpressions(node) { - if (node.type === 'Identifier') { - const variable = utils.findVariableByIdentifier(context, node) - if (variable) { - for (const ref of variable.references) { - const id = ref.identifier - if (id.parent.type === 'VariableDeclarator') { - if (id.parent.id === id && id.parent.init) { - yield* resolvedExpressionsInternal(id.parent.init) + switch (node.type) { + case 'Identifier': { + const variable = utils.findVariableByIdentifier(context, node) + if (variable) { + for (const ref of variable.references) { + const id = ref.identifier + if (id.parent.type === 'VariableDeclarator') { + if (id.parent.id === id && id.parent.init) { + yield* resolvedExpressionsInternal(id.parent.init) + } + } else if ( + id.parent.type === 'AssignmentExpression' && + id.parent.left === id + ) { + yield* resolvedExpressionsInternal(id.parent.right) } - } else if ( - id.parent.type === 'AssignmentExpression' && - id.parent.left === id - ) { - yield* resolvedExpressionsInternal(id.parent.right) } } + + break + } + case 'ConditionalExpression': { + yield* resolvedExpressionsInternal(node.consequent) + yield* resolvedExpressionsInternal(node.alternate) + + break + } + case 'LogicalExpression': { + yield* resolvedExpressionsInternal(node.left) + yield* resolvedExpressionsInternal(node.right) + + break } - } else if (node.type === 'ConditionalExpression') { - yield* resolvedExpressionsInternal(node.consequent) - yield* resolvedExpressionsInternal(node.alternate) } } } @@ -103,7 +116,7 @@ function resolvedExpressions(context, node) { * props: { * propA: String, // => String * propB: { - * type: Number // => String + * type: Number // => Number * }, * } */ @@ -189,7 +202,6 @@ function maybeFunction(context, node) { expr.type === 'Literal' || expr.type === 'TemplateLiteral' || expr.type === 'BinaryExpression' || - expr.type === 'LogicalExpression' || expr.type === 'UnaryExpression' || expr.type === 'UpdateExpression' ) { diff --git a/package.json b/package.json index efa2d8ab3..ee2e37c71 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-vue", - "version": "9.24.1", + "version": "9.25.0", "description": "Official ESLint plugin for Vue.js", "main": "lib/index.js", "scripts": { diff --git a/tests/lib/rules/no-unused-properties.js b/tests/lib/rules/no-unused-properties.js index 65b185298..b89ea01f0 100644 --- a/tests/lib/rules/no-unused-properties.js +++ b/tests/lib/rules/no-unused-properties.js @@ -53,6 +53,7 @@ const unreferencedOptions = { tester.run('no-unused-properties', rule, { valid: [ + // vuex getters { filename: 'test.vue', code: ` @@ -74,6 +75,68 @@ tester.run('no-unused-properties', rule, { `, options: allOptions }, + { + filename: 'test.vue', + code: ` + + + ` + }, + { + filename: 'test.vue', + code: ` + + + ` + }, + { + filename: 'test.vue', + code: ` + + + ` + }, + { + filename: 'test.vue', + code: ` + + + ` + }, + + // vuex mutations { filename: 'test.vue', code: ` @@ -136,6 +199,8 @@ tester.run('no-unused-properties', rule, { ` }, + + // vuex actions { filename: 'test.vue', code: ` @@ -196,6 +261,8 @@ tester.run('no-unused-properties', rule, { ` }, + + // vuex state { filename: 'test.vue', code: ` @@ -251,7 +318,7 @@ tester.run('no-unused-properties', rule, { @@ -260,27 +327,37 @@ tester.run('no-unused-properties', rule, { ` }, + + // pinia getters { filename: 'test.vue', code: ` - ` + `, + options: allOptions }, { filename: 'test.vue', code: ` ` - } - ], - invalid: [ + }, + + // props.prop in template + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + code: ` + + `, + ...getTypeScriptFixtureTestOptions() + } + ], + invalid: [ + // vuex unused mutations + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'add2' of method found, but never used.", + line: 5 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'add2' of method found, but never used.", + line: 6 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'add2' of method found, but never used.", + line: 4 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'add2' of method found, but never used.", + line: 6 + } + ] + }, + + // vuex unused actions + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'add2' of method found, but never used.", + line: 6 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'add2' of method found, but never used.", + line: 5 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'add2' of method found, but never used.", + line: 4 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'add2' of method found, but never used.", + line: 5 + } + ] + }, + + // vuex unused state + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'count2' of computed property found, but never used.", + line: 5 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'count2' of computed property found, but never used.", + line: 6 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'count2' of computed property found, but never used.", + line: 5 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'count' of computed property found, but never used.", + line: 5 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'count2' of computed property found, but never used.", + line: 6 + } + ] + }, + + // vuex unused getters + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'count2' of computed property found, but never used.", + line: 5 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'count' of computed property found, but never used.", + line: 4 + } + ] + }, { filename: 'test.vue', code: ` `, errors: [ { - message: "'add2' of method found, but never used.", + message: "'count2' of computed property found, but never used.", line: 5 } ] @@ -2197,22 +2889,22 @@ tester.run('no-unused-properties', rule, { code: ` `, errors: [ { - message: "'add2' of method found, but never used.", - line: 6 + message: "'count3' of computed property found, but never used.", + line: 7 } ] }, @@ -2221,17 +2913,21 @@ tester.run('no-unused-properties', rule, { code: ` `, errors: [ { - message: "'add2' of method found, but never used.", - line: 4 + message: "'a' of computed property found, but never used.", + line: 6 } ] }, @@ -2240,31 +2936,34 @@ tester.run('no-unused-properties', rule, { code: ` `, errors: [ { - message: "'add2' of method found, but never used.", + message: "'a' of computed property found, but never used.", line: 6 } ] }, + + // pinia unused actions { filename: 'test.vue', code: `