slot-attribute.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  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. let nameArgument = ''
  71. if (slotName) {
  72. nameArgument = vBind ? `:[${slotName}]` : `:${slotName}`
  73. }
  74. const scopeValue =
  75. scopeAttr && scopeAttr.value
  76. ? `=${sourceCode.getText(scopeAttr.value)}`
  77. : ''
  78. const replaceText = `v-slot${nameArgument}${scopeValue}`
  79. yield fixer.replaceText(slotAttr || scopeAttr, replaceText)
  80. if (slotAttr && scopeAttr) {
  81. yield fixer.remove(scopeAttr)
  82. }
  83. }
  84. /**
  85. * Reports `slot` node
  86. * @param {VAttribute} slotAttr node of `slot`
  87. * @returns {void}
  88. */
  89. function reportSlot(slotAttr) {
  90. context.report({
  91. node: slotAttr.key,
  92. messageId: 'forbiddenSlotAttribute',
  93. // fix to use `v-slot`
  94. *fix(fixer) {
  95. if (!canConvertFromSlotToVSlot(slotAttr)) {
  96. return
  97. }
  98. const slotName = slotAttr.value && slotAttr.value.value
  99. yield* fixSlotToVSlot(fixer, slotAttr, slotName, false)
  100. }
  101. })
  102. }
  103. /**
  104. * Reports `v-bind:slot` node
  105. * @param {VDirective} slotAttr node of `v-bind:slot`
  106. * @returns {void}
  107. */
  108. function reportVBindSlot(slotAttr) {
  109. context.report({
  110. node: slotAttr.key,
  111. messageId: 'forbiddenSlotAttribute',
  112. // fix to use `v-slot`
  113. *fix(fixer) {
  114. if (!canConvertFromVBindSlotToVSlot(slotAttr)) {
  115. return
  116. }
  117. const slotName =
  118. slotAttr.value &&
  119. slotAttr.value.expression &&
  120. sourceCode.getText(slotAttr.value.expression).trim()
  121. yield* fixSlotToVSlot(fixer, slotAttr, slotName, true)
  122. }
  123. })
  124. }
  125. return {
  126. "VAttribute[directive=false][key.name='slot']": reportSlot,
  127. "VAttribute[directive=true][key.name.name='bind'][key.argument.name='slot']":
  128. reportVBindSlot
  129. }
  130. }
  131. }