Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 

221 lignes
7.3 KiB

  1. /**
  2. * @fileoverview Limit to one expression per line in JSX
  3. * @author Mark Ivan Allen <Vydia.com>
  4. */
  5. 'use strict';
  6. const docsUrl = require('../util/docsUrl');
  7. // ------------------------------------------------------------------------------
  8. // Rule Definition
  9. // ------------------------------------------------------------------------------
  10. const optionDefaults = {
  11. allow: 'none'
  12. };
  13. module.exports = {
  14. meta: {
  15. docs: {
  16. description: 'Limit to one expression per line in JSX',
  17. category: 'Stylistic Issues',
  18. recommended: false,
  19. url: docsUrl('jsx-one-expression-per-line')
  20. },
  21. fixable: 'whitespace',
  22. schema: [
  23. {
  24. type: 'object',
  25. properties: {
  26. allow: {
  27. enum: ['none', 'literal', 'single-child']
  28. }
  29. },
  30. default: optionDefaults,
  31. additionalProperties: false
  32. }
  33. ]
  34. },
  35. create: function (context) {
  36. const options = Object.assign({}, optionDefaults, context.options[0]);
  37. const sourceCode = context.getSourceCode();
  38. function nodeKey (node) {
  39. return `${node.loc.start.line},${node.loc.start.column}`;
  40. }
  41. function nodeDescriptor (n) {
  42. return n.openingElement ? n.openingElement.name.name : sourceCode.getText(n).replace(/\n/g, '');
  43. }
  44. return {
  45. JSXElement: function (node) {
  46. const children = node.children;
  47. if (!children || !children.length) {
  48. return;
  49. }
  50. const openingElement = node.openingElement;
  51. const closingElement = node.closingElement;
  52. const openingElementStartLine = openingElement.loc.start.line;
  53. const openingElementEndLine = openingElement.loc.end.line;
  54. const closingElementStartLine = closingElement.loc.start.line;
  55. const closingElementEndLine = closingElement.loc.end.line;
  56. if (children.length === 1) {
  57. const child = children[0];
  58. if (
  59. openingElementStartLine === openingElementEndLine &&
  60. openingElementEndLine === closingElementStartLine &&
  61. closingElementStartLine === closingElementEndLine &&
  62. closingElementEndLine === child.loc.start.line &&
  63. child.loc.start.line === child.loc.end.line
  64. ) {
  65. if (
  66. options.allow === 'single-child' ||
  67. options.allow === 'literal' && (child.type === 'Literal' || child.type === 'JSXText')
  68. ) {
  69. return;
  70. }
  71. }
  72. }
  73. const childrenGroupedByLine = {};
  74. const fixDetailsByNode = {};
  75. children.forEach(child => {
  76. let countNewLinesBeforeContent = 0;
  77. let countNewLinesAfterContent = 0;
  78. if (child.type === 'Literal' || child.type === 'JSXText') {
  79. if (/^\s*$/.test(child.raw)) {
  80. return;
  81. }
  82. countNewLinesBeforeContent = (child.raw.match(/^ *\n/g) || []).length;
  83. countNewLinesAfterContent = (child.raw.match(/\n *$/g) || []).length;
  84. }
  85. const startLine = child.loc.start.line + countNewLinesBeforeContent;
  86. const endLine = child.loc.end.line - countNewLinesAfterContent;
  87. if (startLine === endLine) {
  88. if (!childrenGroupedByLine[startLine]) {
  89. childrenGroupedByLine[startLine] = [];
  90. }
  91. childrenGroupedByLine[startLine].push(child);
  92. } else {
  93. if (!childrenGroupedByLine[startLine]) {
  94. childrenGroupedByLine[startLine] = [];
  95. }
  96. childrenGroupedByLine[startLine].push(child);
  97. if (!childrenGroupedByLine[endLine]) {
  98. childrenGroupedByLine[endLine] = [];
  99. }
  100. childrenGroupedByLine[endLine].push(child);
  101. }
  102. });
  103. Object.keys(childrenGroupedByLine).forEach(_line => {
  104. const line = parseInt(_line, 10);
  105. const firstIndex = 0;
  106. const lastIndex = childrenGroupedByLine[line].length - 1;
  107. childrenGroupedByLine[line].forEach((child, i) => {
  108. let prevChild;
  109. let nextChild;
  110. if (i === firstIndex) {
  111. if (line === openingElementEndLine) {
  112. prevChild = openingElement;
  113. }
  114. } else {
  115. prevChild = childrenGroupedByLine[line][i - 1];
  116. }
  117. if (i === lastIndex) {
  118. if (line === closingElementStartLine) {
  119. nextChild = closingElement;
  120. }
  121. } else {
  122. // We don't need to append a trailing because the next child will prepend a leading.
  123. // nextChild = childrenGroupedByLine[line][i + 1];
  124. }
  125. function spaceBetweenPrev () {
  126. return ((prevChild.type === 'Literal' || prevChild.type === 'JSXText') && / $/.test(prevChild.raw)) ||
  127. ((child.type === 'Literal' || child.type === 'JSXText') && /^ /.test(child.raw)) ||
  128. sourceCode.isSpaceBetweenTokens(prevChild, child);
  129. }
  130. function spaceBetweenNext () {
  131. return ((nextChild.type === 'Literal' || nextChild.type === 'JSXText') && /^ /.test(nextChild.raw)) ||
  132. ((child.type === 'Literal' || child.type === 'JSXText') && / $/.test(child.raw)) ||
  133. sourceCode.isSpaceBetweenTokens(child, nextChild);
  134. }
  135. if (!prevChild && !nextChild) {
  136. return;
  137. }
  138. const source = sourceCode.getText(child);
  139. const leadingSpace = !!(prevChild && spaceBetweenPrev());
  140. const trailingSpace = !!(nextChild && spaceBetweenNext());
  141. const leadingNewLine = !!prevChild;
  142. const trailingNewLine = !!nextChild;
  143. const key = nodeKey(child);
  144. if (!fixDetailsByNode[key]) {
  145. fixDetailsByNode[key] = {
  146. node: child,
  147. source: source,
  148. descriptor: nodeDescriptor(child)
  149. };
  150. }
  151. if (leadingSpace) {
  152. fixDetailsByNode[key].leadingSpace = true;
  153. }
  154. if (leadingNewLine) {
  155. fixDetailsByNode[key].leadingNewLine = true;
  156. }
  157. if (trailingNewLine) {
  158. fixDetailsByNode[key].trailingNewLine = true;
  159. }
  160. if (trailingSpace) {
  161. fixDetailsByNode[key].trailingSpace = true;
  162. }
  163. });
  164. });
  165. Object.keys(fixDetailsByNode).forEach(key => {
  166. const details = fixDetailsByNode[key];
  167. const nodeToReport = details.node;
  168. const descriptor = details.descriptor;
  169. const source = details.source.replace(/(^ +| +(?=\n)*$)/g, '');
  170. const leadingSpaceString = details.leadingSpace ? '\n{\' \'}' : '';
  171. const trailingSpaceString = details.trailingSpace ? '{\' \'}\n' : '';
  172. const leadingNewLineString = details.leadingNewLine ? '\n' : '';
  173. const trailingNewLineString = details.trailingNewLine ? '\n' : '';
  174. const replaceText = `${leadingSpaceString}${leadingNewLineString}${source}${trailingNewLineString}${trailingSpaceString}`;
  175. context.report({
  176. node: nodeToReport,
  177. message: `\`${descriptor}\` must be placed on a new line`,
  178. fix: function (fixer) {
  179. return fixer.replaceText(nodeToReport, replaceText);
  180. }
  181. });
  182. });
  183. }
  184. };
  185. }
  186. };