stringifyCollection.js 5.1 KB

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