no-lone-template.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  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. // https://github.com/vuejs/vue-next/blob/64e2f4643602c5980361e66674141e61ba60ef70/packages/compiler-core/src/parse.ts#L405
  8. const SPECIAL_TEMPLATE_DIRECTIVES = new Set([
  9. 'if',
  10. 'else',
  11. 'else-if',
  12. 'for',
  13. 'slot'
  14. ])
  15. /**
  16. * @param {VAttribute | VDirective} attr
  17. */
  18. function getKeyName(attr) {
  19. if (attr.directive) {
  20. if (attr.key.name.name !== 'bind') {
  21. // no v-bind
  22. return null
  23. }
  24. if (
  25. !attr.key.argument ||
  26. attr.key.argument.type === 'VExpressionContainer'
  27. ) {
  28. // unknown
  29. return null
  30. }
  31. return attr.key.argument.name
  32. }
  33. return attr.key.name
  34. }
  35. module.exports = {
  36. meta: {
  37. type: 'problem',
  38. docs: {
  39. description: 'disallow unnecessary `<template>`',
  40. categories: ['vue3-recommended', 'vue2-recommended'],
  41. url: 'https://eslint.vuejs.org/rules/no-lone-template.html'
  42. },
  43. fixable: null,
  44. schema: [
  45. {
  46. type: 'object',
  47. properties: {
  48. ignoreAccessible: {
  49. type: 'boolean'
  50. }
  51. },
  52. additionalProperties: false
  53. }
  54. ],
  55. messages: {
  56. requireDirective: '`<template>` require directive.'
  57. }
  58. },
  59. /** @param {RuleContext} context */
  60. create(context) {
  61. const options = context.options[0] || {}
  62. const ignoreAccessible = options.ignoreAccessible === true
  63. return utils.defineTemplateBodyVisitor(context, {
  64. /** @param {VStartTag} node */
  65. "VElement[name='template'][parent.type='VElement'] > VStartTag"(node) {
  66. if (
  67. node.attributes.some((attr) => {
  68. if (attr.directive) {
  69. const directiveName = attr.key.name.name
  70. if (SPECIAL_TEMPLATE_DIRECTIVES.has(directiveName)) {
  71. return true
  72. }
  73. if (directiveName === 'slot-scope') {
  74. // `slot-scope` is deprecated in Vue.js 2.6
  75. return true
  76. }
  77. if (directiveName === 'scope') {
  78. // `scope` is deprecated in Vue.js 2.5
  79. return true
  80. }
  81. }
  82. const keyName = getKeyName(attr)
  83. if (keyName === 'slot') {
  84. // `slot` is deprecated in Vue.js 2.6
  85. return true
  86. }
  87. return false
  88. })
  89. ) {
  90. return
  91. }
  92. if (
  93. ignoreAccessible &&
  94. node.attributes.some((attr) => {
  95. const keyName = getKeyName(attr)
  96. return keyName === 'id' || keyName === 'ref'
  97. })
  98. ) {
  99. return
  100. }
  101. context.report({
  102. node,
  103. messageId: 'requireDirective'
  104. })
  105. }
  106. })
  107. }
  108. }