jsx-newline.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. /**
  2. * @fileoverview Require or prevent a new line after jsx elements and expressions.
  3. * @author Johnny Zabala
  4. * @author Joseph Stiles
  5. */
  6. 'use strict';
  7. const docsUrl = require('../util/docsUrl');
  8. const report = require('../util/report');
  9. // ------------------------------------------------------------------------------
  10. // Rule Definition
  11. // ------------------------------------------------------------------------------
  12. const messages = {
  13. require: 'JSX element should start in a new line',
  14. prevent: 'JSX element should not start in a new line',
  15. allowMultilines: 'Multiline JSX elements should start in a new line',
  16. };
  17. function isMultilined(node) {
  18. return node.loc.start.line !== node.loc.end.line;
  19. }
  20. module.exports = {
  21. meta: {
  22. docs: {
  23. description: 'Require or prevent a new line after jsx elements and expressions.',
  24. category: 'Stylistic Issues',
  25. recommended: false,
  26. url: docsUrl('jsx-newline'),
  27. },
  28. fixable: 'code',
  29. messages,
  30. schema: [
  31. {
  32. type: 'object',
  33. properties: {
  34. prevent: {
  35. default: false,
  36. type: 'boolean',
  37. },
  38. allowMultilines: {
  39. default: false,
  40. type: 'boolean',
  41. },
  42. },
  43. additionalProperties: false,
  44. if: {
  45. properties: {
  46. allowMultilines: {
  47. const: true,
  48. },
  49. },
  50. },
  51. then: {
  52. properties: {
  53. prevent: {
  54. const: true,
  55. },
  56. },
  57. required: [
  58. 'prevent',
  59. ],
  60. },
  61. },
  62. ],
  63. },
  64. create(context) {
  65. const jsxElementParents = new Set();
  66. const sourceCode = context.getSourceCode();
  67. return {
  68. 'Program:exit'() {
  69. jsxElementParents.forEach((parent) => {
  70. parent.children.forEach((element, index, elements) => {
  71. if (element.type === 'JSXElement' || element.type === 'JSXExpressionContainer') {
  72. const configuration = context.options[0] || {};
  73. const prevent = configuration.prevent || false;
  74. const allowMultilines = configuration.allowMultilines || false;
  75. const firstAdjacentSibling = elements[index + 1];
  76. const secondAdjacentSibling = elements[index + 2];
  77. const hasSibling = firstAdjacentSibling
  78. && secondAdjacentSibling
  79. && (firstAdjacentSibling.type === 'Literal' || firstAdjacentSibling.type === 'JSXText');
  80. if (!hasSibling) return;
  81. // Check adjacent sibling has the proper amount of newlines
  82. const isWithoutNewLine = !/\n\s*\n/.test(firstAdjacentSibling.value);
  83. if (allowMultilines && (isMultilined(element) || isMultilined(secondAdjacentSibling))) {
  84. if (!isWithoutNewLine) return;
  85. const regex = /(\n)(?!.*\1)/g;
  86. const replacement = '\n\n';
  87. const messageId = 'allowMultilines';
  88. report(context, messages[messageId], messageId, {
  89. node: secondAdjacentSibling,
  90. fix(fixer) {
  91. return fixer.replaceText(
  92. firstAdjacentSibling,
  93. sourceCode.getText(firstAdjacentSibling)
  94. .replace(regex, replacement)
  95. );
  96. },
  97. });
  98. return;
  99. }
  100. if (isWithoutNewLine === prevent) return;
  101. const messageId = prevent
  102. ? 'prevent'
  103. : 'require';
  104. const regex = prevent
  105. ? /(\n\n)(?!.*\1)/g
  106. : /(\n)(?!.*\1)/g;
  107. const replacement = prevent
  108. ? '\n'
  109. : '\n\n';
  110. report(context, messages[messageId], messageId, {
  111. node: secondAdjacentSibling,
  112. fix(fixer) {
  113. return fixer.replaceText(
  114. firstAdjacentSibling,
  115. // double or remove the last newline
  116. sourceCode.getText(firstAdjacentSibling)
  117. .replace(regex, replacement)
  118. );
  119. },
  120. });
  121. }
  122. });
  123. });
  124. },
  125. ':matches(JSXElement, JSXFragment) > :matches(JSXElement, JSXExpressionContainer)': (node) => {
  126. jsxElementParents.add(node.parent);
  127. },
  128. };
  129. },
  130. };