stringify.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import { anchorIsValid } from '../doc/anchors.js';
  2. import { isPair, isAlias, isNode, isScalar, isCollection } from '../nodes/identity.js';
  3. import { stringifyComment } from './stringifyComment.js';
  4. import { stringifyString } from './stringifyString.js';
  5. function createStringifyContext(doc, options) {
  6. const opt = Object.assign({
  7. blockQuote: true,
  8. commentString: stringifyComment,
  9. defaultKeyType: null,
  10. defaultStringType: 'PLAIN',
  11. directives: null,
  12. doubleQuotedAsJSON: false,
  13. doubleQuotedMinMultiLineLength: 40,
  14. falseStr: 'false',
  15. flowCollectionPadding: true,
  16. indentSeq: true,
  17. lineWidth: 80,
  18. minContentWidth: 20,
  19. nullStr: 'null',
  20. simpleKeys: false,
  21. singleQuote: null,
  22. trueStr: 'true',
  23. verifyAliasOrder: true
  24. }, doc.schema.toStringOptions, options);
  25. let inFlow;
  26. switch (opt.collectionStyle) {
  27. case 'block':
  28. inFlow = false;
  29. break;
  30. case 'flow':
  31. inFlow = true;
  32. break;
  33. default:
  34. inFlow = null;
  35. }
  36. return {
  37. anchors: new Set(),
  38. doc,
  39. flowCollectionPadding: opt.flowCollectionPadding ? ' ' : '',
  40. indent: '',
  41. indentStep: typeof opt.indent === 'number' ? ' '.repeat(opt.indent) : ' ',
  42. inFlow,
  43. options: opt
  44. };
  45. }
  46. function getTagObject(tags, item) {
  47. if (item.tag) {
  48. const match = tags.filter(t => t.tag === item.tag);
  49. if (match.length > 0)
  50. return match.find(t => t.format === item.format) ?? match[0];
  51. }
  52. let tagObj = undefined;
  53. let obj;
  54. if (isScalar(item)) {
  55. obj = item.value;
  56. let match = tags.filter(t => t.identify?.(obj));
  57. if (match.length > 1) {
  58. const testMatch = match.filter(t => t.test);
  59. if (testMatch.length > 0)
  60. match = testMatch;
  61. }
  62. tagObj =
  63. match.find(t => t.format === item.format) ?? match.find(t => !t.format);
  64. }
  65. else {
  66. obj = item;
  67. tagObj = tags.find(t => t.nodeClass && obj instanceof t.nodeClass);
  68. }
  69. if (!tagObj) {
  70. const name = obj?.constructor?.name ?? typeof obj;
  71. throw new Error(`Tag not resolved for ${name} value`);
  72. }
  73. return tagObj;
  74. }
  75. // needs to be called before value stringifier to allow for circular anchor refs
  76. function stringifyProps(node, tagObj, { anchors, doc }) {
  77. if (!doc.directives)
  78. return '';
  79. const props = [];
  80. const anchor = (isScalar(node) || isCollection(node)) && node.anchor;
  81. if (anchor && anchorIsValid(anchor)) {
  82. anchors.add(anchor);
  83. props.push(`&${anchor}`);
  84. }
  85. const tag = node.tag ? node.tag : tagObj.default ? null : tagObj.tag;
  86. if (tag)
  87. props.push(doc.directives.tagString(tag));
  88. return props.join(' ');
  89. }
  90. function stringify(item, ctx, onComment, onChompKeep) {
  91. if (isPair(item))
  92. return item.toString(ctx, onComment, onChompKeep);
  93. if (isAlias(item)) {
  94. if (ctx.doc.directives)
  95. return item.toString(ctx);
  96. if (ctx.resolvedAliases?.has(item)) {
  97. throw new TypeError(`Cannot stringify circular structure without alias nodes`);
  98. }
  99. else {
  100. if (ctx.resolvedAliases)
  101. ctx.resolvedAliases.add(item);
  102. else
  103. ctx.resolvedAliases = new Set([item]);
  104. item = item.resolve(ctx.doc);
  105. }
  106. }
  107. let tagObj = undefined;
  108. const node = isNode(item)
  109. ? item
  110. : ctx.doc.createNode(item, { onTagObj: o => (tagObj = o) });
  111. if (!tagObj)
  112. tagObj = getTagObject(ctx.doc.schema.tags, node);
  113. const props = stringifyProps(node, tagObj, ctx);
  114. if (props.length > 0)
  115. ctx.indentAtStart = (ctx.indentAtStart ?? 0) + props.length + 1;
  116. const str = typeof tagObj.stringify === 'function'
  117. ? tagObj.stringify(node, ctx, onComment, onChompKeep)
  118. : isScalar(node)
  119. ? stringifyString(node, ctx, onComment, onChompKeep)
  120. : node.toString(ctx, onComment, onChompKeep);
  121. if (!props)
  122. return str;
  123. return isScalar(node) || str[0] === '{' || str[0] === '['
  124. ? `${props} ${str}`
  125. : `${props}\n${ctx.indent}${str}`;
  126. }
  127. export { createStringifyContext, stringify };