stringify.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  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. const match = tags.filter(t => t.identify?.(obj));
  57. tagObj =
  58. match.find(t => t.format === item.format) ?? match.find(t => !t.format);
  59. }
  60. else {
  61. obj = item;
  62. tagObj = tags.find(t => t.nodeClass && obj instanceof t.nodeClass);
  63. }
  64. if (!tagObj) {
  65. const name = obj?.constructor?.name ?? typeof obj;
  66. throw new Error(`Tag not resolved for ${name} value`);
  67. }
  68. return tagObj;
  69. }
  70. // needs to be called before value stringifier to allow for circular anchor refs
  71. function stringifyProps(node, tagObj, { anchors, doc }) {
  72. if (!doc.directives)
  73. return '';
  74. const props = [];
  75. const anchor = (isScalar(node) || isCollection(node)) && node.anchor;
  76. if (anchor && anchorIsValid(anchor)) {
  77. anchors.add(anchor);
  78. props.push(`&${anchor}`);
  79. }
  80. const tag = node.tag ? node.tag : tagObj.default ? null : tagObj.tag;
  81. if (tag)
  82. props.push(doc.directives.tagString(tag));
  83. return props.join(' ');
  84. }
  85. function stringify(item, ctx, onComment, onChompKeep) {
  86. if (isPair(item))
  87. return item.toString(ctx, onComment, onChompKeep);
  88. if (isAlias(item)) {
  89. if (ctx.doc.directives)
  90. return item.toString(ctx);
  91. if (ctx.resolvedAliases?.has(item)) {
  92. throw new TypeError(`Cannot stringify circular structure without alias nodes`);
  93. }
  94. else {
  95. if (ctx.resolvedAliases)
  96. ctx.resolvedAliases.add(item);
  97. else
  98. ctx.resolvedAliases = new Set([item]);
  99. item = item.resolve(ctx.doc);
  100. }
  101. }
  102. let tagObj = undefined;
  103. const node = isNode(item)
  104. ? item
  105. : ctx.doc.createNode(item, { onTagObj: o => (tagObj = o) });
  106. if (!tagObj)
  107. tagObj = getTagObject(ctx.doc.schema.tags, node);
  108. const props = stringifyProps(node, tagObj, ctx);
  109. if (props.length > 0)
  110. ctx.indentAtStart = (ctx.indentAtStart ?? 0) + props.length + 1;
  111. const str = typeof tagObj.stringify === 'function'
  112. ? tagObj.stringify(node, ctx, onComment, onChompKeep)
  113. : isScalar(node)
  114. ? stringifyString(node, ctx, onComment, onChompKeep)
  115. : node.toString(ctx, onComment, onChompKeep);
  116. if (!props)
  117. return str;
  118. return isScalar(node) || str[0] === '{' || str[0] === '['
  119. ? `${props} ${str}`
  120. : `${props}\n${ctx.indent}${str}`;
  121. }
  122. export { createStringifyContext, stringify };