diff --git a/lib/rules/no-undef-properties.js b/lib/rules/no-undef-properties.js index 29fbb714b..711c2ed22 100644 --- a/lib/rules/no-undef-properties.js +++ b/lib/rules/no-undef-properties.js @@ -111,6 +111,11 @@ module.exports = { ).map(toRegExp) const propertyReferenceExtractor = definePropertyReferenceExtractor(context) const programNode = context.getSourceCode().ast + /** + * Property names identified as defined via a Vuex or Pinia helpers + * @type {Set} + */ + const propertiesDefinedByStoreHelpers = new Set() /** * @param {ASTNode} node @@ -185,7 +190,8 @@ module.exports = { report(node, name, messageId = 'undef') { if ( reserved.includes(name) || - ignores.some((ignore) => ignore.test(name)) + ignores.some((ignore) => ignore.test(name)) || + propertiesDefinedByStoreHelpers.has(name) ) { return } @@ -331,6 +337,51 @@ module.exports = { } }), 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) { const ctx = getVueComponentContext(node) diff --git a/tests/lib/rules/no-undef-properties.js b/tests/lib/rules/no-undef-properties.js index 83812a248..63313b4b0 100644 --- a/tests/lib/rules/no-undef-properties.js +++ b/tests/lib/rules/no-undef-properties.js @@ -561,7 +561,248 @@ tester.run('no-undef-properties', rule, { } } }, + { + // Vuex + filename: 'test.vue', + code: ` + + + ` + }, + { + filename: 'test.vue', + code: ` + + + ` + }, + { + filename: 'test.vue', + code: ` + + + ` + }, + { + filename: 'test.vue', + code: ` + + + ` + }, + { + filename: 'test.vue', + code: ` + + + ` + }, + { + filename: 'test.vue', + code: ` + + + ` + }, + { + // Pinia + filename: 'test.vue', + code: ` + + + ` + }, + { + filename: 'test.vue', + code: ` + + + ` + }, + { + filename: 'test.vue', + code: ` + + + ` + }, + { + filename: 'test.vue', + code: ` + + + ` + }, + { + filename: 'test.vue', + code: ` + + + ` + }, ` + + `, + errors: [ + { + message: "'g' is not defined.", + line: 13 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'f' is not defined.", + line: 12 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'x' is not defined.", + line: 13 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'f' is not defined.", + line: 10 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'q' is not defined.", + line: 14 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'z' is not defined.", + line: 12 + } + ] + }, + { + // Pinia + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'z' is not defined.", + line: 13 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'q' is not defined.", + line: 20 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'z' is not defined.", + line: 16 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'z' is not defined.", + line: 13 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'x' is not defined.", + line: 11 + } + ] } ] })