valid-v-memo.js 3.9 KB

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