jsx-curly-newline.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. /**
  2. * @fileoverview enforce consistent line breaks inside jsx curly
  3. */
  4. 'use strict';
  5. const docsUrl = require('../util/docsUrl');
  6. // ------------------------------------------------------------------------------
  7. // Rule Definition
  8. // ------------------------------------------------------------------------------
  9. function getNormalizedOption(context) {
  10. const rawOption = context.options[0] || 'consistent';
  11. if (rawOption === 'consistent') {
  12. return {
  13. multiline: 'consistent',
  14. singleline: 'consistent'
  15. };
  16. }
  17. if (rawOption === 'never') {
  18. return {
  19. multiline: 'forbid',
  20. singleline: 'forbid'
  21. };
  22. }
  23. return {
  24. multiline: rawOption.multiline || 'consistent',
  25. singleline: rawOption.singleline || 'consistent'
  26. };
  27. }
  28. module.exports = {
  29. meta: {
  30. type: 'layout',
  31. docs: {
  32. description: 'Enforce consistent line breaks inside jsx curly',
  33. category: 'Stylistic Issues',
  34. recommended: false,
  35. url: docsUrl('jsx-curly-newline')
  36. },
  37. fixable: 'whitespace',
  38. schema: [
  39. {
  40. oneOf: [
  41. {
  42. enum: ['consistent', 'never']
  43. },
  44. {
  45. type: 'object',
  46. properties: {
  47. singleline: {enum: ['consistent', 'require', 'forbid']},
  48. multiline: {enum: ['consistent', 'require', 'forbid']}
  49. },
  50. additionalProperties: false
  51. }
  52. ]
  53. }
  54. ],
  55. messages: {
  56. expectedBefore: 'Expected newline before \'}\'.',
  57. expectedAfter: 'Expected newline after \'{\'.',
  58. unexpectedBefore: 'Unexpected newline before \'}\'.',
  59. unexpectedAfter: 'Unexpected newline after \'{\'.'
  60. }
  61. },
  62. create(context) {
  63. const sourceCode = context.getSourceCode();
  64. const option = getNormalizedOption(context);
  65. // ----------------------------------------------------------------------
  66. // Helpers
  67. // ----------------------------------------------------------------------
  68. /**
  69. * Determines whether two adjacent tokens are on the same line.
  70. * @param {Object} left - The left token object.
  71. * @param {Object} right - The right token object.
  72. * @returns {boolean} Whether or not the tokens are on the same line.
  73. */
  74. function isTokenOnSameLine(left, right) {
  75. return left.loc.end.line === right.loc.start.line;
  76. }
  77. /**
  78. * Determines whether there should be newlines inside curlys
  79. * @param {ASTNode} expression The expression contained in the curlys
  80. * @param {boolean} hasLeftNewline `true` if the left curly has a newline in the current code.
  81. * @returns {boolean} `true` if there should be newlines inside the function curlys
  82. */
  83. function shouldHaveNewlines(expression, hasLeftNewline) {
  84. const isMultiline = expression.loc.start.line !== expression.loc.end.line;
  85. switch (isMultiline ? option.multiline : option.singleline) {
  86. case 'forbid': return false;
  87. case 'require': return true;
  88. case 'consistent':
  89. default: return hasLeftNewline;
  90. }
  91. }
  92. /**
  93. * Validates curlys
  94. * @param {Object} curlys An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token
  95. * @param {ASTNode} expression The expression inside the curly
  96. * @returns {void}
  97. */
  98. function validateCurlys(curlys, expression) {
  99. const leftCurly = curlys.leftCurly;
  100. const rightCurly = curlys.rightCurly;
  101. const tokenAfterLeftCurly = sourceCode.getTokenAfter(leftCurly);
  102. const tokenBeforeRightCurly = sourceCode.getTokenBefore(rightCurly);
  103. const hasLeftNewline = !isTokenOnSameLine(leftCurly, tokenAfterLeftCurly);
  104. const hasRightNewline = !isTokenOnSameLine(tokenBeforeRightCurly, rightCurly);
  105. const needsNewlines = shouldHaveNewlines(expression, hasLeftNewline);
  106. if (hasLeftNewline && !needsNewlines) {
  107. context.report({
  108. node: leftCurly,
  109. messageId: 'unexpectedAfter',
  110. fix(fixer) {
  111. return sourceCode
  112. .getText()
  113. .slice(leftCurly.range[1], tokenAfterLeftCurly.range[0])
  114. .trim()
  115. ? null // If there is a comment between the { and the first element, don't do a fix.
  116. : fixer.removeRange([leftCurly.range[1], tokenAfterLeftCurly.range[0]]);
  117. }
  118. });
  119. } else if (!hasLeftNewline && needsNewlines) {
  120. context.report({
  121. node: leftCurly,
  122. messageId: 'expectedAfter',
  123. fix: (fixer) => fixer.insertTextAfter(leftCurly, '\n')
  124. });
  125. }
  126. if (hasRightNewline && !needsNewlines) {
  127. context.report({
  128. node: rightCurly,
  129. messageId: 'unexpectedBefore',
  130. fix(fixer) {
  131. return sourceCode
  132. .getText()
  133. .slice(tokenBeforeRightCurly.range[1], rightCurly.range[0])
  134. .trim()
  135. ? null // If there is a comment between the last element and the }, don't do a fix.
  136. : fixer.removeRange([
  137. tokenBeforeRightCurly.range[1],
  138. rightCurly.range[0]
  139. ]);
  140. }
  141. });
  142. } else if (!hasRightNewline && needsNewlines) {
  143. context.report({
  144. node: rightCurly,
  145. messageId: 'expectedBefore',
  146. fix: (fixer) => fixer.insertTextBefore(rightCurly, '\n')
  147. });
  148. }
  149. }
  150. // ----------------------------------------------------------------------
  151. // Public
  152. // ----------------------------------------------------------------------
  153. return {
  154. JSXExpressionContainer(node) {
  155. const curlyTokens = {
  156. leftCurly: sourceCode.getFirstToken(node),
  157. rightCurly: sourceCode.getLastToken(node)
  158. };
  159. validateCurlys(curlyTokens, node.expression);
  160. }
  161. };
  162. }
  163. };