Skip to content

Commit 461918d

Browse files
committed
Vuex and Pinia support for no-undef-properties
1 parent 9252468 commit 461918d

File tree

2 files changed

+307
-1
lines changed

2 files changed

+307
-1
lines changed

lib/rules/no-undef-properties.js

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ module.exports = {
111111
).map(toRegExp)
112112
const propertyReferenceExtractor = definePropertyReferenceExtractor(context)
113113
const programNode = context.getSourceCode().ast
114+
/**
115+
* Property names identified as defined via a Vuex or Pinia helpers
116+
* @type {string[]}
117+
*/
118+
const propertiesDefinedByVuexOrPiniaHelpers = []
114119

115120
/**
116121
* @param {ASTNode} node
@@ -185,7 +190,8 @@ module.exports = {
185190
report(node, name, messageId = 'undef') {
186191
if (
187192
reserved.includes(name) ||
188-
ignores.some((ignore) => ignore.test(name))
193+
ignores.some((ignore) => ignore.test(name)) ||
194+
propertiesDefinedByVuexOrPiniaHelpers.includes(name)
189195
) {
190196
return
191197
}
@@ -331,6 +337,55 @@ module.exports = {
331337
}
332338
}),
333339
utils.defineVueVisitor(context, {
340+
/**
341+
* @param {CallExpression} node
342+
*/
343+
CallExpression(node) {
344+
if (node.callee.type !== 'Identifier') return
345+
/** @type {'methods'|'computed'|null} */
346+
let groupName = null
347+
if (/^mapMutations|mapActions$/u.test(node.callee.name)) {
348+
groupName = GROUP_METHODS
349+
} else if (
350+
/^mapState|mapGetters|mapWritableState$/u.test(node.callee.name)
351+
) {
352+
groupName = GROUP_COMPUTED_PROPERTY
353+
}
354+
355+
if (!groupName || node.arguments.length === 0) return
356+
// On Pinia the store is always the first argument
357+
const arg =
358+
node.arguments.length === 2 ? node.arguments[1] : node.arguments[0]
359+
if (arg.type === 'ObjectExpression') {
360+
// e.g.
361+
// `mapMutations({ add: 'increment' })`
362+
// `mapState({ count: state => state.todosCount })`
363+
for (const prop of arg.properties) {
364+
const name =
365+
prop.type === 'SpreadElement'
366+
? null
367+
: utils.getStaticPropertyName(prop)
368+
if (name) {
369+
propertiesDefinedByVuexOrPiniaHelpers.push(name)
370+
}
371+
}
372+
} else if (arg.type === 'ArrayExpression') {
373+
// e.g. `mapMutations(['add'])`
374+
for (const element of arg.elements) {
375+
if (
376+
!element ||
377+
(element.type !== 'Literal' &&
378+
element.type !== 'TemplateLiteral')
379+
) {
380+
continue
381+
}
382+
const name = utils.getStringLiteralValue(element)
383+
if (name) {
384+
propertiesDefinedByVuexOrPiniaHelpers.push(name)
385+
}
386+
}
387+
}
388+
},
334389
onVueObjectEnter(node) {
335390
const ctx = getVueComponentContext(node)
336391

tests/lib/rules/no-undef-properties.js

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,258 @@ tester.run('no-undef-properties', rule, {
561561
}
562562
}
563563
},
564+
{
565+
// Vuex
566+
filename: 'test.vue',
567+
code: `
568+
<script>
569+
import { mapState } from 'Vuex';
570+
571+
export default {
572+
computed: {
573+
...mapState({
574+
a: (vm) => vm.a,
575+
b: (vm) => vm.b,
576+
})
577+
},
578+
methods: {
579+
c() {return this.a * this.b}
580+
}
581+
}
582+
</script>
583+
<template>
584+
{{ a }} {{ b }}
585+
</template>
586+
`
587+
},
588+
{
589+
filename: 'test.vue',
590+
code: `
591+
<script>
592+
import { mapActions } from 'Vuex';
564593
594+
export default {
595+
computed: {
596+
...mapActions({
597+
a: 'a',
598+
b: 'b',
599+
})
600+
},
601+
methods: {
602+
c() {return this.a()},
603+
d() {return this.b()},
604+
}
605+
}
606+
</script>
607+
<template>
608+
{{ a }} {{ b }}
609+
</template>
610+
`
611+
},
612+
{
613+
filename: 'test.vue',
614+
code: `
615+
<script>
616+
import { mapMutations } from 'Vuex';
617+
618+
export default {
619+
computed: {
620+
...mapMutations({
621+
a: 'a',
622+
b: 'b',
623+
})
624+
},
625+
methods: {
626+
c() {return this.a()},
627+
d() {return this.b()},
628+
}
629+
}
630+
</script>
631+
<template>
632+
{{ a }} {{ b }}
633+
</template>
634+
`
635+
},
636+
{
637+
filename: 'test.vue',
638+
code: `
639+
<script>
640+
import { mapActions } from 'Vuex';
641+
642+
export default {
643+
computed: {
644+
...mapActions(['a', 'b'])
645+
},
646+
methods: {
647+
c() {return this.a()},
648+
d() {return this.b()},
649+
}
650+
}
651+
</script>
652+
<template>
653+
{{ a }} {{ b }}
654+
</template>
655+
`
656+
},
657+
{
658+
filename: 'test.vue',
659+
code: `
660+
<script>
661+
import { mapMutations } from 'Vuex';
662+
663+
export default {
664+
computed: {
665+
...mapMutations(['a', 'b'])
666+
},
667+
methods: {
668+
c() {return this.a()},
669+
d() {return this.b()},
670+
}
671+
}
672+
</script>
673+
<template>
674+
{{ a }} {{ b }}
675+
</template>
676+
`
677+
},
678+
{
679+
filename: 'test.vue',
680+
code: `
681+
<script>
682+
import { mapGetters } from 'Vuex';
683+
684+
export default {
685+
computed: {
686+
...mapGetters(['a', 'b'])
687+
},
688+
methods: {
689+
c() {return this.a},
690+
d() {return this.b},
691+
}
692+
}
693+
</script>
694+
<template>
695+
{{ a }} {{ b }}
696+
</template>
697+
`
698+
},
699+
{
700+
// Pinia
701+
filename: 'test.vue',
702+
code: `
703+
<script>
704+
import { mapGetters } from 'pinia'
705+
import { useStore } from '../store'
706+
707+
export default {
708+
computed: {
709+
...mapGetters(useStore, ['a', 'b'])
710+
},
711+
methods: {
712+
c() {return this.a},
713+
d() {return this.b},
714+
}
715+
}
716+
</script>
717+
<template>
718+
{{ a }} {{ b }}
719+
</template>
720+
`
721+
},
722+
{
723+
filename: 'test.vue',
724+
code: `
725+
<script>
726+
import { mapState } from 'pinia'
727+
import { useStore } from '../store'
728+
729+
export default {
730+
computed: {
731+
...mapState(useStore, {
732+
a: 'a',
733+
b: store => store.b,
734+
})
735+
},
736+
methods: {
737+
c() {return this.a},
738+
d() {return this.b},
739+
}
740+
}
741+
</script>
742+
<template>
743+
{{ a }} {{ b }}
744+
</template>
745+
`
746+
},
747+
{
748+
filename: 'test.vue',
749+
code: `
750+
<script>
751+
import { mapWritableState } from 'pinia'
752+
import { useStore } from '../store'
753+
754+
export default {
755+
computed: {
756+
...mapWritableState(useStore, {
757+
a: 'a',
758+
b: 'b',
759+
})
760+
},
761+
methods: {
762+
c() {return this.a},
763+
d() {return this.b},
764+
}
765+
}
766+
</script>
767+
<template>
768+
{{ a }} {{ b }}
769+
</template>
770+
`
771+
},
772+
{
773+
filename: 'test.vue',
774+
code: `
775+
<script>
776+
import { mapWritableState } from 'pinia'
777+
import { useStore } from '../store'
778+
779+
export default {
780+
computed: {
781+
...mapWritableState(useStore, ['a', 'b'])
782+
},
783+
methods: {
784+
c() {return this.a},
785+
d() {return this.b},
786+
}
787+
}
788+
</script>
789+
<template>
790+
{{ a }} {{ b }}
791+
</template>
792+
`
793+
},
794+
{
795+
filename: 'test.vue',
796+
code: `
797+
<script>
798+
import { mapActions } from 'pinia'
799+
import { useStore } from '../store'
800+
801+
export default {
802+
computed: {
803+
...mapActions(useStore, ['a', 'b'])
804+
},
805+
methods: {
806+
c() {return this.a()},
807+
d() {return this.b()},
808+
}
809+
}
810+
</script>
811+
<template>
812+
{{ a() }} {{ b() }}
813+
</template>
814+
`
815+
},
565816
`
566817
<script setup>
567818
const model = defineModel();

0 commit comments

Comments
 (0)