no-useless-template-attributes.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  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. categories: ['vue3-essential', 'essential'],
  30. url: 'https://eslint.vuejs.org/rules/no-useless-template-attributes.html'
  31. },
  32. fixable: null,
  33. schema: [],
  34. messages: {
  35. unexpectedAttr: 'Unexpected useless attribute on `<template>`.',
  36. unexpectedDir: 'Unexpected useless directive on `<template>`.'
  37. }
  38. },
  39. /** @param {RuleContext} context */
  40. create(context) {
  41. /**
  42. * @param {VAttribute | VDirective} attr
  43. */
  44. function getKeyName(attr) {
  45. if (attr.directive) {
  46. if (attr.key.name.name !== 'bind') {
  47. // no v-bind
  48. return null
  49. }
  50. if (
  51. !attr.key.argument ||
  52. attr.key.argument.type === 'VExpressionContainer'
  53. ) {
  54. // unknown
  55. return null
  56. }
  57. return attr.key.argument.name
  58. }
  59. return attr.key.name
  60. }
  61. /**
  62. * @param {VAttribute | VDirective} attr
  63. */
  64. function isFragmentTemplateAttribute(attr) {
  65. if (attr.directive) {
  66. const directiveName = attr.key.name.name
  67. if (SPECIAL_TEMPLATE_DIRECTIVES.has(directiveName)) {
  68. return true
  69. }
  70. if (directiveName === 'slot-scope') {
  71. // `slot-scope` is deprecated in Vue.js 2.6
  72. return true
  73. }
  74. if (directiveName === 'scope') {
  75. // `scope` is deprecated in Vue.js 2.5
  76. return true
  77. }
  78. }
  79. const keyName = getKeyName(attr)
  80. if (keyName === 'slot') {
  81. // `slot` is deprecated in Vue.js 2.6
  82. return true
  83. }
  84. return false
  85. }
  86. return utils.defineTemplateBodyVisitor(context, {
  87. /** @param {VStartTag} node */
  88. "VElement[name='template'][parent.type='VElement'] > VStartTag"(node) {
  89. if (!node.attributes.some(isFragmentTemplateAttribute)) {
  90. return
  91. }
  92. for (const attr of node.attributes) {
  93. if (isFragmentTemplateAttribute(attr)) {
  94. continue
  95. }
  96. const keyName = getKeyName(attr)
  97. if (keyName === 'key') {
  98. continue
  99. }
  100. context.report({
  101. node: attr,
  102. messageId: attr.directive ? 'unexpectedDir' : 'unexpectedAttr'
  103. })
  104. }
  105. }
  106. })
  107. }
  108. }