stringifyCollection.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import { Collection } from '../nodes/Collection.js';
  2. import { isNode, isPair } from '../nodes/identity.js';
  3. import { stringify } from './stringify.js';
  4. import { lineComment, indentComment } from './stringifyComment.js';
  5. function stringifyCollection(collection, ctx, options) {
  6. const flow = ctx.inFlow ?? collection.flow;
  7. const stringify = flow ? stringifyFlowCollection : stringifyBlockCollection;
  8. return stringify(collection, ctx, options);
  9. }
  10. function stringifyBlockCollection({ comment, items }, ctx, { blockItemPrefix, flowChars, itemIndent, onChompKeep, onComment }) {
  11. const { indent, options: { commentString } } = ctx;
  12. const itemCtx = Object.assign({}, ctx, { indent: itemIndent, type: null });
  13. let chompKeep = false; // flag for the preceding node's status
  14. const lines = [];
  15. for (let i = 0; i < items.length; ++i) {
  16. const item = items[i];
  17. let comment = null;
  18. if (isNode(item)) {
  19. if (!chompKeep && item.spaceBefore)
  20. lines.push('');
  21. addCommentBefore(ctx, lines, item.commentBefore, chompKeep);
  22. if (item.comment)
  23. comment = item.comment;
  24. }
  25. else if (isPair(item)) {
  26. const ik = isNode(item.key) ? item.key : null;
  27. if (ik) {
  28. if (!chompKeep && ik.spaceBefore)
  29. lines.push('');
  30. addCommentBefore(ctx, lines, ik.commentBefore, chompKeep);
  31. }
  32. }
  33. chompKeep = false;
  34. let str = stringify(item, itemCtx, () => (comment = null), () => (chompKeep = true));
  35. if (comment)
  36. str += lineComment(str, itemIndent, commentString(comment));
  37. if (chompKeep && comment)
  38. chompKeep = false;
  39. lines.push(blockItemPrefix + str);
  40. }
  41. let str;
  42. if (lines.length === 0) {
  43. str = flowChars.start + flowChars.end;
  44. }
  45. else {
  46. str = lines[0];
  47. for (let i = 1; i < lines.length; ++i) {
  48. const line = lines[i];
  49. str += line ? `\n${indent}${line}` : '\n';
  50. }
  51. }
  52. if (comment) {
  53. str += '\n' + indentComment(commentString(comment), indent);
  54. if (onComment)
  55. onComment();
  56. }
  57. else if (chompKeep && onChompKeep)
  58. onChompKeep();
  59. return str;
  60. }
  61. function stringifyFlowCollection({ comment, items }, ctx, { flowChars, itemIndent, onComment }) {
  62. const { indent, indentStep, flowCollectionPadding: fcPadding, options: { commentString } } = ctx;
  63. itemIndent += indentStep;
  64. const itemCtx = Object.assign({}, ctx, {
  65. indent: itemIndent,
  66. inFlow: true,
  67. type: null
  68. });
  69. let reqNewline = false;
  70. let linesAtValue = 0;
  71. const lines = [];
  72. for (let i = 0; i < items.length; ++i) {
  73. const item = items[i];
  74. let comment = null;
  75. if (isNode(item)) {
  76. if (item.spaceBefore)
  77. lines.push('');
  78. addCommentBefore(ctx, lines, item.commentBefore, false);
  79. if (item.comment)
  80. comment = item.comment;
  81. }
  82. else if (isPair(item)) {
  83. const ik = isNode(item.key) ? item.key : null;
  84. if (ik) {
  85. if (ik.spaceBefore)
  86. lines.push('');
  87. addCommentBefore(ctx, lines, ik.commentBefore, false);
  88. if (ik.comment)
  89. reqNewline = true;
  90. }
  91. const iv = isNode(item.value) ? item.value : null;
  92. if (iv) {
  93. if (iv.comment)
  94. comment = iv.comment;
  95. if (iv.commentBefore)
  96. reqNewline = true;
  97. }
  98. else if (item.value == null && ik && ik.comment) {
  99. comment = ik.comment;
  100. }
  101. }
  102. if (comment)
  103. reqNewline = true;
  104. let str = stringify(item, itemCtx, () => (comment = null));
  105. if (i < items.length - 1)
  106. str += ',';
  107. if (comment)
  108. str += lineComment(str, itemIndent, commentString(comment));
  109. if (!reqNewline && (lines.length > linesAtValue || str.includes('\n')))
  110. reqNewline = true;
  111. lines.push(str);
  112. linesAtValue = lines.length;
  113. }
  114. let str;
  115. const { start, end } = flowChars;
  116. if (lines.length === 0) {
  117. str = start + end;
  118. }
  119. else {
  120. if (!reqNewline) {
  121. const len = lines.reduce((sum, line) => sum + line.length + 2, 2);
  122. reqNewline = len > Collection.maxFlowStringSingleLineLength;
  123. }
  124. if (reqNewline) {
  125. str = start;
  126. for (const line of lines)
  127. str += line ? `\n${indentStep}${indent}${line}` : '\n';
  128. str += `\n${indent}${end}`;
  129. }
  130. else {
  131. str = `${start}${fcPadding}${lines.join(' ')}${fcPadding}${end}`;
  132. }
  133. }
  134. if (comment) {
  135. str += lineComment(str, indent, commentString(comment));
  136. if (onComment)
  137. onComment();
  138. }
  139. return str;
  140. }
  141. function addCommentBefore({ indent, options: { commentString } }, lines, comment, chompKeep) {
  142. if (comment && chompKeep)
  143. comment = comment.replace(/^\n+/, '');
  144. if (comment) {
  145. const ic = indentComment(commentString(comment), indent);
  146. lines.push(ic.trimStart()); // Avoid double indent on first line
  147. }
  148. }
  149. export { stringifyCollection };