max-attributes-per-line.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. /**
  2. * @fileoverview Define the number of attributes allows per line
  3. * @author Filipa Lacerda
  4. */
  5. 'use strict'
  6. // ------------------------------------------------------------------------------
  7. // Rule Definition
  8. // ------------------------------------------------------------------------------
  9. const utils = require('../utils')
  10. module.exports = {
  11. meta: {
  12. docs: {
  13. description: 'enforce the maximum number of attributes per line',
  14. category: 'strongly-recommended',
  15. url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.7.1/docs/rules/max-attributes-per-line.md'
  16. },
  17. fixable: 'whitespace', // or "code" or "whitespace"
  18. schema: [
  19. {
  20. type: 'object',
  21. properties: {
  22. singleline: {
  23. anyOf: [
  24. {
  25. type: 'number',
  26. minimum: 1
  27. },
  28. {
  29. type: 'object',
  30. properties: {
  31. max: {
  32. type: 'number',
  33. minimum: 1
  34. }
  35. },
  36. additionalProperties: false
  37. }
  38. ]
  39. },
  40. multiline: {
  41. anyOf: [
  42. {
  43. type: 'number',
  44. minimum: 1
  45. },
  46. {
  47. type: 'object',
  48. properties: {
  49. max: {
  50. type: 'number',
  51. minimum: 1
  52. },
  53. allowFirstLine: {
  54. type: 'boolean'
  55. }
  56. },
  57. additionalProperties: false
  58. }
  59. ]
  60. }
  61. }
  62. }
  63. ]
  64. },
  65. create: function (context) {
  66. const configuration = parseOptions(context.options[0])
  67. const multilineMaximum = configuration.multiline
  68. const singlelinemMaximum = configuration.singleline
  69. const canHaveFirstLine = configuration.allowFirstLine
  70. return utils.defineTemplateBodyVisitor(context, {
  71. 'VStartTag' (node) {
  72. const numberOfAttributes = node.attributes.length
  73. if (!numberOfAttributes) return
  74. if (utils.isSingleLine(node) && numberOfAttributes > singlelinemMaximum) {
  75. showErrors(node.attributes.slice(singlelinemMaximum), node)
  76. }
  77. if (!utils.isSingleLine(node)) {
  78. if (!canHaveFirstLine && node.attributes[0].loc.start.line === node.loc.start.line) {
  79. showErrors([node.attributes[0]], node)
  80. }
  81. groupAttrsByLine(node.attributes)
  82. .filter(attrs => attrs.length > multilineMaximum)
  83. .forEach(attrs => showErrors(attrs.splice(multilineMaximum), node))
  84. }
  85. }
  86. })
  87. // ----------------------------------------------------------------------
  88. // Helpers
  89. // ----------------------------------------------------------------------
  90. function parseOptions (options) {
  91. const defaults = {
  92. singleline: 1,
  93. multiline: 1,
  94. allowFirstLine: false
  95. }
  96. if (options) {
  97. if (typeof options.singleline === 'number') {
  98. defaults.singleline = options.singleline
  99. } else if (options.singleline && options.singleline.max) {
  100. defaults.singleline = options.singleline.max
  101. }
  102. if (options.multiline) {
  103. if (typeof options.multiline === 'number') {
  104. defaults.multiline = options.multiline
  105. } else if (typeof options.multiline === 'object') {
  106. if (options.multiline.max) {
  107. defaults.multiline = options.multiline.max
  108. }
  109. if (options.multiline.allowFirstLine) {
  110. defaults.allowFirstLine = options.multiline.allowFirstLine
  111. }
  112. }
  113. }
  114. }
  115. return defaults
  116. }
  117. function showErrors (attributes, node) {
  118. attributes.forEach((prop, i) => {
  119. context.report({
  120. node: prop,
  121. loc: prop.loc,
  122. message: 'Attribute "{{propName}}" should be on a new line.',
  123. data: {
  124. propName: prop.key.name
  125. },
  126. fix: i === 0 ? (fixer) => fixer.insertTextBefore(prop, '\n') : undefined
  127. })
  128. })
  129. }
  130. function groupAttrsByLine (attributes) {
  131. const propsPerLine = [[attributes[0]]]
  132. attributes.reduce((previous, current) => {
  133. if (previous.loc.end.line === current.loc.start.line) {
  134. propsPerLine[propsPerLine.length - 1].push(current)
  135. } else {
  136. propsPerLine.push([current])
  137. }
  138. return current
  139. })
  140. return propsPerLine
  141. }
  142. }
  143. }