require-toggle-inside-transition.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. /**
  2. * @author Yosuke Ota
  3. * See LICENSE file in root directory for full license.
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. /**
  8. * @param {VDirective} vBindAppear
  9. */
  10. function isValidBindAppear(vBindAppear) {
  11. if (
  12. vBindAppear.value?.expression &&
  13. vBindAppear.value.expression.type === 'Literal'
  14. ) {
  15. return vBindAppear.value.expression?.value !== false
  16. }
  17. return true
  18. }
  19. /**
  20. * @param {string[]} directives
  21. */
  22. function createDirectiveList(directives) {
  23. let str = ''
  24. for (const [index, directive] of directives.entries()) {
  25. if (index === 0) {
  26. str += `\`v-${directive}\``
  27. } else if (index < directives.length - 1) {
  28. str += `, \`v-${directive}\``
  29. } else {
  30. str += ` or \`v-${directive}\``
  31. }
  32. }
  33. return str
  34. }
  35. module.exports = {
  36. meta: {
  37. type: 'problem',
  38. docs: {
  39. description:
  40. 'require control the display of the content inside `<transition>`',
  41. categories: ['vue3-essential'],
  42. url: 'https://eslint.vuejs.org/rules/require-toggle-inside-transition.html'
  43. },
  44. fixable: null,
  45. schema: [
  46. {
  47. type: 'object',
  48. properties: {
  49. additionalDirectives: {
  50. type: 'array',
  51. items: {
  52. type: 'string'
  53. },
  54. uniqueItems: true
  55. }
  56. },
  57. additionalProperties: false
  58. }
  59. ],
  60. messages: {
  61. expected:
  62. 'The element inside `<transition>` is expected to have a {{allowedDirectives}} directive.'
  63. }
  64. },
  65. /** @param {RuleContext} context */
  66. create(context) {
  67. /** @type {Array<string>} */
  68. const additionalDirectives =
  69. (context.options[0] && context.options[0].additionalDirectives) || []
  70. const allowedDirectives = ['if', 'show', ...additionalDirectives]
  71. const allowedDirectivesString = createDirectiveList(allowedDirectives)
  72. /**
  73. * Check if the given element has display control.
  74. * @param {VElement} element The element node to check.
  75. */
  76. function verifyInsideElement(element) {
  77. if (utils.isCustomComponent(element)) {
  78. return
  79. }
  80. /** @type VElement */ // @ts-expect-error
  81. const parent = element.parent
  82. const vBindAppear = utils.getDirective(parent, 'bind', 'appear')
  83. if (
  84. utils.hasAttribute(parent, 'appear') ||
  85. (vBindAppear && isValidBindAppear(vBindAppear))
  86. ) {
  87. return
  88. }
  89. if (
  90. element.name !== 'slot' &&
  91. !allowedDirectives.some((directive) =>
  92. utils.hasDirective(element, directive)
  93. ) &&
  94. !utils.hasDirective(element, 'bind', 'key')
  95. ) {
  96. context.report({
  97. node: element.startTag,
  98. loc: element.startTag.loc,
  99. messageId: 'expected',
  100. data: {
  101. allowedDirectives: allowedDirectivesString
  102. }
  103. })
  104. }
  105. }
  106. return utils.defineTemplateBodyVisitor(context, {
  107. /** @param {VElement} node */
  108. "VElement[name='transition'] > VElement"(node) {
  109. const child = node.parent.children.find(utils.isVElement)
  110. if (child !== node) {
  111. return
  112. }
  113. verifyInsideElement(node)
  114. }
  115. })
  116. }
  117. }