no-duplicate-attributes.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. /**
  2. * @author Toru Nagashima
  3. * @copyright 2017 Toru Nagashima. All rights reserved.
  4. * See LICENSE file in root directory for full license.
  5. */
  6. 'use strict'
  7. // ------------------------------------------------------------------------------
  8. // Requirements
  9. // ------------------------------------------------------------------------------
  10. const utils = require('../utils')
  11. // ------------------------------------------------------------------------------
  12. // Helpers
  13. // ------------------------------------------------------------------------------
  14. /**
  15. * Get the name of the given attribute node.
  16. * @param {VAttribute | VDirective} attribute The attribute node to get.
  17. * @returns {string | null} The name of the attribute.
  18. */
  19. function getName(attribute) {
  20. if (!attribute.directive) {
  21. return attribute.key.name
  22. }
  23. if (attribute.key.name.name === 'bind') {
  24. return (
  25. (attribute.key.argument &&
  26. attribute.key.argument.type === 'VIdentifier' &&
  27. attribute.key.argument.name) ||
  28. null
  29. )
  30. }
  31. return null
  32. }
  33. // ------------------------------------------------------------------------------
  34. // Rule Definition
  35. // ------------------------------------------------------------------------------
  36. module.exports = {
  37. meta: {
  38. type: 'problem',
  39. docs: {
  40. description: 'disallow duplication of attributes',
  41. categories: ['vue3-essential', 'essential'],
  42. url: 'https://eslint.vuejs.org/rules/no-duplicate-attributes.html'
  43. },
  44. fixable: null,
  45. schema: [
  46. {
  47. type: 'object',
  48. properties: {
  49. allowCoexistClass: {
  50. type: 'boolean'
  51. },
  52. allowCoexistStyle: {
  53. type: 'boolean'
  54. }
  55. },
  56. additionalProperties: false
  57. }
  58. ]
  59. },
  60. /** @param {RuleContext} context */
  61. create(context) {
  62. const options = context.options[0] || {}
  63. const allowCoexistStyle = options.allowCoexistStyle !== false
  64. const allowCoexistClass = options.allowCoexistClass !== false
  65. /** @type {Set<string>} */
  66. const directiveNames = new Set()
  67. /** @type {Set<string>} */
  68. const attributeNames = new Set()
  69. /**
  70. * @param {string} name
  71. * @param {boolean} isDirective
  72. */
  73. function isDuplicate(name, isDirective) {
  74. if (
  75. (allowCoexistStyle && name === 'style') ||
  76. (allowCoexistClass && name === 'class')
  77. ) {
  78. return isDirective ? directiveNames.has(name) : attributeNames.has(name)
  79. }
  80. return directiveNames.has(name) || attributeNames.has(name)
  81. }
  82. return utils.defineTemplateBodyVisitor(context, {
  83. VStartTag() {
  84. directiveNames.clear()
  85. attributeNames.clear()
  86. },
  87. VAttribute(node) {
  88. const name = getName(node)
  89. if (name == null) {
  90. return
  91. }
  92. if (isDuplicate(name, node.directive)) {
  93. context.report({
  94. node,
  95. loc: node.loc,
  96. message: "Duplicate attribute '{{name}}'.",
  97. data: { name }
  98. })
  99. }
  100. if (node.directive) {
  101. directiveNames.add(name)
  102. } else {
  103. attributeNames.add(name)
  104. }
  105. }
  106. })
  107. }
  108. }