no-useless-template-attributes.js 2.5 KB

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