formatReactElementNode.js.flow 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. /* @flow */
  2. import spacer from './spacer';
  3. import formatTreeNode from './formatTreeNode';
  4. import formatProp from './formatProp';
  5. import mergeSiblingPlainStringChildrenReducer from './mergeSiblingPlainStringChildrenReducer';
  6. import sortPropsByNames from './sortPropsByNames';
  7. import createPropFilter from './createPropFilter';
  8. import type { Options } from './../options';
  9. import type { ReactElementTreeNode } from './../tree';
  10. const compensateMultilineStringElementIndentation = (
  11. element,
  12. formattedElement: string,
  13. inline: boolean,
  14. lvl: number,
  15. options: Options
  16. ) => {
  17. const { tabStop } = options;
  18. if (element.type === 'string') {
  19. return formattedElement
  20. .split('\n')
  21. .map((line, offset) => {
  22. if (offset === 0) {
  23. return line;
  24. }
  25. return `${spacer(lvl, tabStop)}${line}`;
  26. })
  27. .join('\n');
  28. }
  29. return formattedElement;
  30. };
  31. const formatOneChildren = (
  32. inline: boolean,
  33. lvl: number,
  34. options: Options
  35. ) => element =>
  36. compensateMultilineStringElementIndentation(
  37. element,
  38. formatTreeNode(element, inline, lvl, options),
  39. inline,
  40. lvl,
  41. options
  42. );
  43. const onlyPropsWithOriginalValue = (defaultProps, props) => propName => {
  44. const haveDefaultValue = Object.keys(defaultProps).includes(propName);
  45. return (
  46. !haveDefaultValue ||
  47. (haveDefaultValue && defaultProps[propName] !== props[propName])
  48. );
  49. };
  50. const isInlineAttributeTooLong = (
  51. attributes: string[],
  52. inlineAttributeString: string,
  53. lvl: number,
  54. tabStop: number,
  55. maxInlineAttributesLineLength: ?number
  56. ): boolean => {
  57. if (!maxInlineAttributesLineLength) {
  58. return attributes.length > 1;
  59. }
  60. return (
  61. spacer(lvl, tabStop).length + inlineAttributeString.length >
  62. maxInlineAttributesLineLength
  63. );
  64. };
  65. const shouldRenderMultilineAttr = (
  66. attributes: string[],
  67. inlineAttributeString: string,
  68. containsMultilineAttr: boolean,
  69. inline: boolean,
  70. lvl: number,
  71. tabStop: number,
  72. maxInlineAttributesLineLength: ?number
  73. ): boolean =>
  74. (isInlineAttributeTooLong(
  75. attributes,
  76. inlineAttributeString,
  77. lvl,
  78. tabStop,
  79. maxInlineAttributesLineLength
  80. ) ||
  81. containsMultilineAttr) &&
  82. !inline;
  83. export default (
  84. node: ReactElementTreeNode,
  85. inline: boolean,
  86. lvl: number,
  87. options: Options
  88. ): string => {
  89. const {
  90. type,
  91. displayName = '',
  92. childrens,
  93. props = {},
  94. defaultProps = {},
  95. } = node;
  96. if (type !== 'ReactElement') {
  97. throw new Error(
  98. `The "formatReactElementNode" function could only format node of type "ReactElement". Given: ${type}`
  99. );
  100. }
  101. const {
  102. filterProps,
  103. maxInlineAttributesLineLength,
  104. showDefaultProps,
  105. sortProps,
  106. tabStop,
  107. } = options;
  108. let out = `<${displayName}`;
  109. let outInlineAttr = out;
  110. let outMultilineAttr = out;
  111. let containsMultilineAttr = false;
  112. const visibleAttributeNames = [];
  113. const propFilter = createPropFilter(props, filterProps);
  114. Object.keys(props)
  115. .filter(propFilter)
  116. .filter(onlyPropsWithOriginalValue(defaultProps, props))
  117. .forEach(propName => visibleAttributeNames.push(propName));
  118. Object.keys(defaultProps)
  119. .filter(propFilter)
  120. .filter(() => showDefaultProps)
  121. .filter(defaultPropName => !visibleAttributeNames.includes(defaultPropName))
  122. .forEach(defaultPropName => visibleAttributeNames.push(defaultPropName));
  123. const attributes = sortPropsByNames(sortProps)(visibleAttributeNames);
  124. attributes.forEach(attributeName => {
  125. const {
  126. attributeFormattedInline,
  127. attributeFormattedMultiline,
  128. isMultilineAttribute,
  129. } = formatProp(
  130. attributeName,
  131. Object.keys(props).includes(attributeName),
  132. props[attributeName],
  133. Object.keys(defaultProps).includes(attributeName),
  134. defaultProps[attributeName],
  135. inline,
  136. lvl,
  137. options
  138. );
  139. if (isMultilineAttribute) {
  140. containsMultilineAttr = true;
  141. }
  142. outInlineAttr += attributeFormattedInline;
  143. outMultilineAttr += attributeFormattedMultiline;
  144. });
  145. outMultilineAttr += `\n${spacer(lvl, tabStop)}`;
  146. if (
  147. shouldRenderMultilineAttr(
  148. attributes,
  149. outInlineAttr,
  150. containsMultilineAttr,
  151. inline,
  152. lvl,
  153. tabStop,
  154. maxInlineAttributesLineLength
  155. )
  156. ) {
  157. out = outMultilineAttr;
  158. } else {
  159. out = outInlineAttr;
  160. }
  161. if (childrens && childrens.length > 0) {
  162. const newLvl = lvl + 1;
  163. out += '>';
  164. if (!inline) {
  165. out += '\n';
  166. out += spacer(newLvl, tabStop);
  167. }
  168. out += childrens
  169. .reduce(mergeSiblingPlainStringChildrenReducer, [])
  170. .map(formatOneChildren(inline, newLvl, options))
  171. .join(!inline ? `\n${spacer(newLvl, tabStop)}` : '');
  172. if (!inline) {
  173. out += '\n';
  174. out += spacer(newLvl - 1, tabStop);
  175. }
  176. out += `</${displayName}>`;
  177. } else {
  178. if (
  179. !isInlineAttributeTooLong(
  180. attributes,
  181. outInlineAttr,
  182. lvl,
  183. tabStop,
  184. maxInlineAttributesLineLength
  185. )
  186. ) {
  187. out += ' ';
  188. }
  189. out += '/>';
  190. }
  191. return out;
  192. };