no-useless-template-attributes.js 3.3 KB

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