jsx-newline.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  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. function isBlockCommentInCurlyBraces(element) {
  68. const elementRawValue = sourceCode.getText(element);
  69. return /^\s*{\/\*/.test(elementRawValue);
  70. }
  71. function isNonBlockComment(element) {
  72. return !isBlockCommentInCurlyBraces(element) && (element.type === 'JSXElement' || element.type === 'JSXExpressionContainer');
  73. }
  74. return {
  75. 'Program:exit'() {
  76. jsxElementParents.forEach((parent) => {
  77. parent.children.forEach((element, index, elements) => {
  78. if (element.type === 'JSXElement' || element.type === 'JSXExpressionContainer') {
  79. const configuration = context.options[0] || {};
  80. const prevent = configuration.prevent || false;
  81. const allowMultilines = configuration.allowMultilines || false;
  82. const firstAdjacentSibling = elements[index + 1];
  83. const secondAdjacentSibling = elements[index + 2];
  84. const hasSibling = firstAdjacentSibling
  85. && secondAdjacentSibling
  86. && (firstAdjacentSibling.type === 'Literal' || firstAdjacentSibling.type === 'JSXText');
  87. if (!hasSibling) return;
  88. // Check adjacent sibling has the proper amount of newlines
  89. const isWithoutNewLine = !/\n\s*\n/.test(firstAdjacentSibling.value);
  90. if (isBlockCommentInCurlyBraces(element)) return;
  91. if (
  92. allowMultilines
  93. && (
  94. isMultilined(element)
  95. || isMultilined(elements.slice(index + 2).find(isNonBlockComment))
  96. )
  97. ) {
  98. if (!isWithoutNewLine) return;
  99. const regex = /(\n)(?!.*\1)/g;
  100. const replacement = '\n\n';
  101. const messageId = 'allowMultilines';
  102. report(context, messages[messageId], messageId, {
  103. node: secondAdjacentSibling,
  104. fix(fixer) {
  105. return fixer.replaceText(
  106. firstAdjacentSibling,
  107. sourceCode.getText(firstAdjacentSibling)
  108. .replace(regex, replacement)
  109. );
  110. },
  111. });
  112. return;
  113. }
  114. if (isWithoutNewLine === prevent) return;
  115. const messageId = prevent
  116. ? 'prevent'
  117. : 'require';
  118. const regex = prevent
  119. ? /(\n\n)(?!.*\1)/g
  120. : /(\n)(?!.*\1)/g;
  121. const replacement = prevent
  122. ? '\n'
  123. : '\n\n';
  124. report(context, messages[messageId], messageId, {
  125. node: secondAdjacentSibling,
  126. fix(fixer) {
  127. return fixer.replaceText(
  128. firstAdjacentSibling,
  129. // double or remove the last newline
  130. sourceCode.getText(firstAdjacentSibling)
  131. .replace(regex, replacement)
  132. );
  133. },
  134. });
  135. }
  136. });
  137. });
  138. },
  139. ':matches(JSXElement, JSXFragment) > :matches(JSXElement, JSXExpressionContainer)': (node) => {
  140. jsxElementParents.add(node.parent);
  141. },
  142. };
  143. },
  144. };