no-duplicate-attr-inheritance.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. /**
  2. * @fileoverview Disable inheritAttrs when using v-bind="$attrs"
  3. * @author Hiroki Osame
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. /** @param {VElement[]} elements */
  8. function isConditionalGroup(elements) {
  9. if (elements.length < 2) {
  10. return false
  11. }
  12. const firstElement = elements[0]
  13. const lastElement = elements[elements.length - 1]
  14. const inBetweenElements = elements.slice(1, -1)
  15. return (
  16. utils.hasDirective(firstElement, 'if') &&
  17. (utils.hasDirective(lastElement, 'else-if') ||
  18. utils.hasDirective(lastElement, 'else')) &&
  19. inBetweenElements.every((element) => utils.hasDirective(element, 'else-if'))
  20. )
  21. }
  22. /** @param {VElement[]} elements */
  23. function isMultiRootNodes(elements) {
  24. if (elements.length > 1 && !isConditionalGroup(elements)) {
  25. return true
  26. }
  27. return false
  28. }
  29. module.exports = {
  30. meta: {
  31. type: 'suggestion',
  32. docs: {
  33. description:
  34. 'enforce `inheritAttrs` to be set to `false` when using `v-bind="$attrs"`',
  35. categories: undefined,
  36. url: 'https://eslint.vuejs.org/rules/no-duplicate-attr-inheritance.html'
  37. },
  38. fixable: null,
  39. schema: [
  40. {
  41. type: 'object',
  42. properties: {
  43. checkMultiRootNodes: {
  44. type: 'boolean'
  45. }
  46. },
  47. additionalProperties: false
  48. }
  49. ],
  50. messages: {
  51. noDuplicateAttrInheritance: 'Set "inheritAttrs" to false.'
  52. }
  53. },
  54. /** @param {RuleContext} context */
  55. create(context) {
  56. const options = context.options[0] || {}
  57. const checkMultiRootNodes = options.checkMultiRootNodes === true
  58. /** @type {string | number | boolean | RegExp | BigInt | null} */
  59. let inheritsAttrs = true
  60. /** @type {VReference[]} */
  61. const attrsRefs = []
  62. /** @param {ObjectExpression} node */
  63. function processOptions(node) {
  64. const inheritAttrsProp = utils.findProperty(node, 'inheritAttrs')
  65. if (inheritAttrsProp && inheritAttrsProp.value.type === 'Literal') {
  66. inheritsAttrs = inheritAttrsProp.value.value
  67. }
  68. }
  69. return utils.compositingVisitors(
  70. utils.executeOnVue(context, processOptions),
  71. utils.defineScriptSetupVisitor(context, {
  72. onDefineOptionsEnter(node) {
  73. if (node.arguments.length === 0) return
  74. const define = node.arguments[0]
  75. if (define.type !== 'ObjectExpression') return
  76. processOptions(define)
  77. }
  78. }),
  79. utils.defineTemplateBodyVisitor(context, {
  80. /** @param {VExpressionContainer} node */
  81. "VAttribute[directive=true][key.name.name='bind'][key.argument=null] > VExpressionContainer"(
  82. node
  83. ) {
  84. if (!inheritsAttrs) {
  85. return
  86. }
  87. const reference = node.references.find((reference) => {
  88. if (reference.variable != null) {
  89. // Not vm reference
  90. return false
  91. }
  92. return reference.id.name === '$attrs'
  93. })
  94. if (reference) {
  95. attrsRefs.push(reference)
  96. }
  97. }
  98. }),
  99. {
  100. 'Program:exit'(program) {
  101. const element = program.templateBody
  102. if (element == null) {
  103. return
  104. }
  105. const rootElements = element.children.filter(utils.isVElement)
  106. if (!checkMultiRootNodes && isMultiRootNodes(rootElements)) return
  107. if (attrsRefs.length > 0) {
  108. for (const attrsRef of attrsRefs) {
  109. context.report({
  110. node: attrsRef.id,
  111. messageId: 'noDuplicateAttrInheritance'
  112. })
  113. }
  114. }
  115. }
  116. }
  117. )
  118. }
  119. }