valid-v-memo.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. /**
  2. * @author Yosuke Ota <https://github.com/ota-meshi>
  3. * See LICENSE file in root directory for full license.
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. module.exports = {
  8. meta: {
  9. type: 'problem',
  10. docs: {
  11. description: 'enforce valid `v-memo` directives',
  12. categories: ['vue3-essential'],
  13. url: 'https://eslint.vuejs.org/rules/valid-v-memo.html'
  14. },
  15. fixable: null,
  16. schema: [],
  17. messages: {
  18. unexpectedArgument: "'v-memo' directives require no argument.",
  19. unexpectedModifier: "'v-memo' directives require no modifier.",
  20. expectedValue: "'v-memo' directives require that attribute value.",
  21. expectedArray:
  22. "'v-memo' directives require the attribute value to be an array.",
  23. insideVFor: "'v-memo' directive does not work inside 'v-for'."
  24. }
  25. },
  26. /** @param {RuleContext} context */
  27. create(context) {
  28. /** @type {VElement | null} */
  29. let vForElement = null
  30. return utils.defineTemplateBodyVisitor(context, {
  31. VElement(node) {
  32. if (!vForElement && utils.hasDirective(node, 'for')) {
  33. vForElement = node
  34. }
  35. },
  36. 'VElement:exit'(node) {
  37. if (vForElement === node) {
  38. vForElement = null
  39. }
  40. },
  41. /** @param {VDirective} node */
  42. "VAttribute[directive=true][key.name.name='memo']"(node) {
  43. if (vForElement && vForElement !== node.parent.parent) {
  44. context.report({
  45. node: node.key,
  46. messageId: 'insideVFor'
  47. })
  48. }
  49. if (node.key.argument) {
  50. context.report({
  51. node: node.key.argument,
  52. messageId: 'unexpectedArgument'
  53. })
  54. }
  55. if (node.key.modifiers.length > 0) {
  56. context.report({
  57. node,
  58. loc: {
  59. start: node.key.modifiers[0].loc.start,
  60. end: node.key.modifiers[node.key.modifiers.length - 1].loc.end
  61. },
  62. messageId: 'unexpectedModifier'
  63. })
  64. }
  65. if (!node.value || utils.isEmptyValueDirective(node, context)) {
  66. context.report({
  67. node,
  68. messageId: 'expectedValue'
  69. })
  70. return
  71. }
  72. if (!node.value.expression) {
  73. return
  74. }
  75. const expressions = [node.value.expression]
  76. let expression
  77. while ((expression = expressions.pop())) {
  78. switch (expression.type) {
  79. case 'ObjectExpression':
  80. case 'ClassExpression':
  81. case 'ArrowFunctionExpression':
  82. case 'FunctionExpression':
  83. case 'Literal':
  84. case 'TemplateLiteral':
  85. case 'UnaryExpression':
  86. case 'BinaryExpression':
  87. case 'UpdateExpression': {
  88. context.report({
  89. node: expression,
  90. messageId: 'expectedArray'
  91. })
  92. break
  93. }
  94. case 'AssignmentExpression': {
  95. expressions.push(expression.right)
  96. break
  97. }
  98. case 'TSAsExpression': {
  99. expressions.push(expression.expression)
  100. break
  101. }
  102. case 'SequenceExpression': {
  103. expressions.push(
  104. expression.expressions[expression.expressions.length - 1]
  105. )
  106. break
  107. }
  108. case 'ConditionalExpression': {
  109. expressions.push(expression.consequent, expression.alternate)
  110. break
  111. }
  112. }
  113. }
  114. }
  115. })
  116. }
  117. }