jsx-max-props-per-line.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. /**
  2. * @fileoverview Limit maximum of props on a single line in JSX
  3. * @author Yannick Croissant
  4. */
  5. 'use strict';
  6. const docsUrl = require('../util/docsUrl');
  7. const getText = require('../util/eslint').getText;
  8. const report = require('../util/report');
  9. function getPropName(context, propNode) {
  10. if (propNode.type === 'JSXSpreadAttribute') {
  11. return getText(context, propNode.argument);
  12. }
  13. return propNode.name.name;
  14. }
  15. // ------------------------------------------------------------------------------
  16. // Rule Definition
  17. // ------------------------------------------------------------------------------
  18. const messages = {
  19. newLine: 'Prop `{{prop}}` must be placed on a new line',
  20. };
  21. /** @type {import('eslint').Rule.RuleModule} */
  22. module.exports = {
  23. meta: {
  24. docs: {
  25. description: 'Enforce maximum of props on a single line in JSX',
  26. category: 'Stylistic Issues',
  27. recommended: false,
  28. url: docsUrl('jsx-max-props-per-line'),
  29. },
  30. fixable: 'code',
  31. messages,
  32. schema: [{
  33. anyOf: [{
  34. type: 'object',
  35. properties: {
  36. maximum: {
  37. type: 'object',
  38. properties: {
  39. single: {
  40. type: 'integer',
  41. minimum: 1,
  42. },
  43. multi: {
  44. type: 'integer',
  45. minimum: 1,
  46. },
  47. },
  48. },
  49. },
  50. additionalProperties: false,
  51. }, {
  52. type: 'object',
  53. properties: {
  54. maximum: {
  55. type: 'number',
  56. minimum: 1,
  57. },
  58. when: {
  59. type: 'string',
  60. enum: ['always', 'multiline'],
  61. },
  62. },
  63. additionalProperties: false,
  64. }],
  65. }],
  66. },
  67. create(context) {
  68. const configuration = context.options[0] || {};
  69. const maximum = configuration.maximum || 1;
  70. const maxConfig = typeof maximum === 'number'
  71. ? {
  72. single: configuration.when === 'multiline' ? Infinity : maximum,
  73. multi: maximum,
  74. }
  75. : {
  76. single: maximum.single || Infinity,
  77. multi: maximum.multi || Infinity,
  78. };
  79. function generateFixFunction(line, max) {
  80. const output = [];
  81. const front = line[0].range[0];
  82. const back = line[line.length - 1].range[1];
  83. for (let i = 0; i < line.length; i += max) {
  84. const nodes = line.slice(i, i + max);
  85. output.push(nodes.reduce((prev, curr) => {
  86. if (prev === '') {
  87. return getText(context, curr);
  88. }
  89. return `${prev} ${getText(context, curr)}`;
  90. }, ''));
  91. }
  92. const code = output.join('\n');
  93. return function fix(fixer) {
  94. return fixer.replaceTextRange([front, back], code);
  95. };
  96. }
  97. return {
  98. JSXOpeningElement(node) {
  99. if (!node.attributes.length) {
  100. return;
  101. }
  102. const isSingleLineTag = node.loc.start.line === node.loc.end.line;
  103. if ((isSingleLineTag ? maxConfig.single : maxConfig.multi) === Infinity) {
  104. return;
  105. }
  106. const firstProp = node.attributes[0];
  107. const linePartitionedProps = [[firstProp]];
  108. node.attributes.reduce((last, decl) => {
  109. if (last.loc.end.line === decl.loc.start.line) {
  110. linePartitionedProps[linePartitionedProps.length - 1].push(decl);
  111. } else {
  112. linePartitionedProps.push([decl]);
  113. }
  114. return decl;
  115. });
  116. linePartitionedProps.forEach((propsInLine) => {
  117. const maxPropsCountPerLine = isSingleLineTag && propsInLine[0].loc.start.line === node.loc.start.line
  118. ? maxConfig.single
  119. : maxConfig.multi;
  120. if (propsInLine.length > maxPropsCountPerLine) {
  121. const name = getPropName(context, propsInLine[maxPropsCountPerLine]);
  122. report(context, messages.newLine, 'newLine', {
  123. node: propsInLine[maxPropsCountPerLine],
  124. data: {
  125. prop: name,
  126. },
  127. fix: generateFixFunction(propsInLine, maxPropsCountPerLine),
  128. });
  129. }
  130. });
  131. },
  132. };
  133. },
  134. };