no-use-v-if-with-v-for.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. /**
  2. * @author Yosuke Ota
  3. *
  4. * Style guide: https://vuejs.org/style-guide/rules-essential.html#avoid-v-if-with-v-for
  5. */
  6. 'use strict'
  7. const utils = require('../utils')
  8. /**
  9. * Check whether the given `v-if` node is using the variable which is defined by the `v-for` directive.
  10. * @param {VDirective} vIf The `v-if` attribute node to check.
  11. * @returns {boolean} `true` if the `v-if` is using the variable which is defined by the `v-for` directive.
  12. */
  13. function isUsingIterationVar(vIf) {
  14. return !!getVForUsingIterationVar(vIf)
  15. }
  16. /** @param {VDirective} vIf */
  17. function getVForUsingIterationVar(vIf) {
  18. if (!vIf.value) {
  19. return null
  20. }
  21. const element = vIf.parent.parent
  22. for (const reference of vIf.value.references) {
  23. const targetVFor = element.variables.find(
  24. (variable) =>
  25. variable.id.name === reference.id.name && variable.kind === 'v-for'
  26. )
  27. if (targetVFor) {
  28. return targetVFor
  29. }
  30. }
  31. return null
  32. }
  33. module.exports = {
  34. meta: {
  35. type: 'suggestion',
  36. docs: {
  37. description: 'disallow using `v-if` on the same element as `v-for`',
  38. categories: ['vue3-essential', 'vue2-essential'],
  39. url: 'https://eslint.vuejs.org/rules/no-use-v-if-with-v-for.html'
  40. },
  41. fixable: null,
  42. schema: [
  43. {
  44. type: 'object',
  45. properties: {
  46. allowUsingIterationVar: {
  47. type: 'boolean'
  48. }
  49. },
  50. additionalProperties: false
  51. }
  52. ],
  53. messages: {
  54. movedToWrapper: "This 'v-if' should be moved to the wrapper element.",
  55. shouldUseComputed:
  56. "The '{{iteratorName}}' {{kind}} inside 'v-for' directive should be replaced with a computed property that returns filtered array instead. You should not mix 'v-for' with 'v-if'."
  57. }
  58. },
  59. /** @param {RuleContext} context */
  60. create(context) {
  61. const options = context.options[0] || {}
  62. const allowUsingIterationVar = options.allowUsingIterationVar === true // default false
  63. return utils.defineTemplateBodyVisitor(context, {
  64. /** @param {VDirective} node */
  65. "VAttribute[directive=true][key.name.name='if']"(node) {
  66. const element = node.parent.parent
  67. if (utils.hasDirective(element, 'for')) {
  68. if (isUsingIterationVar(node)) {
  69. if (!allowUsingIterationVar) {
  70. const vForVar = getVForUsingIterationVar(node)
  71. if (!vForVar) {
  72. return
  73. }
  74. let targetVForExpr = vForVar.id.parent
  75. while (targetVForExpr.type !== 'VForExpression') {
  76. targetVForExpr = /** @type {ASTNode} */ (targetVForExpr.parent)
  77. }
  78. const iteratorNode = targetVForExpr.right
  79. context.report({
  80. node,
  81. loc: node.loc,
  82. messageId: 'shouldUseComputed',
  83. data: {
  84. iteratorName:
  85. iteratorNode.type === 'Identifier'
  86. ? iteratorNode.name
  87. : context.getSourceCode().getText(iteratorNode),
  88. kind:
  89. iteratorNode.type === 'Identifier'
  90. ? 'variable'
  91. : 'expression'
  92. }
  93. })
  94. }
  95. } else {
  96. context.report({
  97. node,
  98. loc: node.loc,
  99. messageId: 'movedToWrapper'
  100. })
  101. }
  102. }
  103. }
  104. })
  105. }
  106. }