slot-attribute.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. /**
  2. * @author Yosuke Ota
  3. * See LICENSE file in root directory for full license.
  4. */
  5. 'use strict'
  6. const canConvertToVSlot = require('./utils/can-convert-to-v-slot')
  7. module.exports = {
  8. deprecated: '2.6.0',
  9. supported: '<3.0.0',
  10. /** @param {RuleContext} context @returns {TemplateListener} */
  11. createTemplateBodyVisitor(context) {
  12. const sourceCode = context.getSourceCode()
  13. const tokenStore =
  14. context.parserServices.getTemplateBodyTokenStore &&
  15. context.parserServices.getTemplateBodyTokenStore()
  16. /**
  17. * Checks whether the given node can convert to the `v-slot`.
  18. * @param {VAttribute} slotAttr node of `slot`
  19. * @returns {boolean} `true` if the given node can convert to the `v-slot`
  20. */
  21. function canConvertFromSlotToVSlot(slotAttr) {
  22. if (!canConvertToVSlot(slotAttr.parent.parent, sourceCode, tokenStore)) {
  23. return false
  24. }
  25. if (!slotAttr.value) {
  26. return true
  27. }
  28. const slotName = slotAttr.value.value
  29. // If other than alphanumeric, underscore and hyphen characters are included it can not be converted.
  30. return !/[^\w\-]/u.test(slotName)
  31. }
  32. /**
  33. * Checks whether the given node can convert to the `v-slot`.
  34. * @param {VDirective} slotAttr node of `v-bind:slot`
  35. * @returns {boolean} `true` if the given node can convert to the `v-slot`
  36. */
  37. function canConvertFromVBindSlotToVSlot(slotAttr) {
  38. if (!canConvertToVSlot(slotAttr.parent.parent, sourceCode, tokenStore)) {
  39. return false
  40. }
  41. if (!slotAttr.value) {
  42. return true
  43. }
  44. if (!slotAttr.value.expression) {
  45. // parse error or empty expression
  46. return false
  47. }
  48. const slotName = sourceCode.getText(slotAttr.value.expression).trim()
  49. // If non-Latin characters are included it can not be converted.
  50. // It does not check the space only because `a>b?c:d` should be rejected.
  51. return !/[^a-z]/i.test(slotName)
  52. }
  53. /**
  54. * Convert to `v-slot`.
  55. * @param {RuleFixer} fixer fixer
  56. * @param {VAttribute|VDirective} slotAttr node of `slot`
  57. * @param {string | null} slotName name of `slot`
  58. * @param {boolean} vBind `true` if `slotAttr` is `v-bind:slot`
  59. * @returns {IterableIterator<Fix>} fix data
  60. */
  61. function* fixSlotToVSlot(fixer, slotAttr, slotName, vBind) {
  62. const element = slotAttr.parent
  63. const scopeAttr = element.attributes.find(
  64. (attr) =>
  65. attr.directive === true &&
  66. attr.key.name &&
  67. (attr.key.name.name === 'slot-scope' ||
  68. attr.key.name.name === 'scope')
  69. )
  70. const nameArgument = slotName
  71. ? vBind
  72. ? `:[${slotName}]`
  73. : `:${slotName}`
  74. : ''
  75. const scopeValue =
  76. scopeAttr && scopeAttr.value
  77. ? `=${sourceCode.getText(scopeAttr.value)}`
  78. : ''
  79. const replaceText = `v-slot${nameArgument}${scopeValue}`
  80. yield fixer.replaceText(slotAttr || scopeAttr, replaceText)
  81. if (slotAttr && scopeAttr) {
  82. yield fixer.remove(scopeAttr)
  83. }
  84. }
  85. /**
  86. * Reports `slot` node
  87. * @param {VAttribute} slotAttr node of `slot`
  88. * @returns {void}
  89. */
  90. function reportSlot(slotAttr) {
  91. context.report({
  92. node: slotAttr.key,
  93. messageId: 'forbiddenSlotAttribute',
  94. // fix to use `v-slot`
  95. *fix(fixer) {
  96. if (!canConvertFromSlotToVSlot(slotAttr)) {
  97. return
  98. }
  99. const slotName = slotAttr.value && slotAttr.value.value
  100. yield* fixSlotToVSlot(fixer, slotAttr, slotName, false)
  101. }
  102. })
  103. }
  104. /**
  105. * Reports `v-bind:slot` node
  106. * @param {VDirective} slotAttr node of `v-bind:slot`
  107. * @returns {void}
  108. */
  109. function reportVBindSlot(slotAttr) {
  110. context.report({
  111. node: slotAttr.key,
  112. messageId: 'forbiddenSlotAttribute',
  113. // fix to use `v-slot`
  114. *fix(fixer) {
  115. if (!canConvertFromVBindSlotToVSlot(slotAttr)) {
  116. return
  117. }
  118. const slotName =
  119. slotAttr.value &&
  120. slotAttr.value.expression &&
  121. sourceCode.getText(slotAttr.value.expression).trim()
  122. yield* fixSlotToVSlot(fixer, slotAttr, slotName, true)
  123. }
  124. })
  125. }
  126. return {
  127. "VAttribute[directive=false][key.name='slot']": reportSlot,
  128. "VAttribute[directive=true][key.name.name='bind'][key.argument.name='slot']":
  129. reportVBindSlot
  130. }
  131. }
  132. }