createNode.js 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import { Alias } from '../nodes/Alias.js';
  2. import { isNode, isPair, MAP, SEQ, isDocument } from '../nodes/identity.js';
  3. import { Scalar } from '../nodes/Scalar.js';
  4. const defaultTagPrefix = 'tag:yaml.org,2002:';
  5. function findTagObject(value, tagName, tags) {
  6. if (tagName) {
  7. const match = tags.filter(t => t.tag === tagName);
  8. const tagObj = match.find(t => !t.format) ?? match[0];
  9. if (!tagObj)
  10. throw new Error(`Tag ${tagName} not found`);
  11. return tagObj;
  12. }
  13. return tags.find(t => t.identify?.(value) && !t.format);
  14. }
  15. function createNode(value, tagName, ctx) {
  16. if (isDocument(value))
  17. value = value.contents;
  18. if (isNode(value))
  19. return value;
  20. if (isPair(value)) {
  21. const map = ctx.schema[MAP].createNode?.(ctx.schema, null, ctx);
  22. map.items.push(value);
  23. return map;
  24. }
  25. if (value instanceof String ||
  26. value instanceof Number ||
  27. value instanceof Boolean ||
  28. (typeof BigInt !== 'undefined' && value instanceof BigInt) // not supported everywhere
  29. ) {
  30. // https://tc39.es/ecma262/#sec-serializejsonproperty
  31. value = value.valueOf();
  32. }
  33. const { aliasDuplicateObjects, onAnchor, onTagObj, schema, sourceObjects } = ctx;
  34. // Detect duplicate references to the same object & use Alias nodes for all
  35. // after first. The `ref` wrapper allows for circular references to resolve.
  36. let ref = undefined;
  37. if (aliasDuplicateObjects && value && typeof value === 'object') {
  38. ref = sourceObjects.get(value);
  39. if (ref) {
  40. if (!ref.anchor)
  41. ref.anchor = onAnchor(value);
  42. return new Alias(ref.anchor);
  43. }
  44. else {
  45. ref = { anchor: null, node: null };
  46. sourceObjects.set(value, ref);
  47. }
  48. }
  49. if (tagName?.startsWith('!!'))
  50. tagName = defaultTagPrefix + tagName.slice(2);
  51. let tagObj = findTagObject(value, tagName, schema.tags);
  52. if (!tagObj) {
  53. if (value && typeof value.toJSON === 'function') {
  54. // eslint-disable-next-line @typescript-eslint/no-unsafe-call
  55. value = value.toJSON();
  56. }
  57. if (!value || typeof value !== 'object') {
  58. const node = new Scalar(value);
  59. if (ref)
  60. ref.node = node;
  61. return node;
  62. }
  63. tagObj =
  64. value instanceof Map
  65. ? schema[MAP]
  66. : Symbol.iterator in Object(value)
  67. ? schema[SEQ]
  68. : schema[MAP];
  69. }
  70. if (onTagObj) {
  71. onTagObj(tagObj);
  72. delete ctx.onTagObj;
  73. }
  74. const node = tagObj?.createNode
  75. ? tagObj.createNode(ctx.schema, value, ctx)
  76. : typeof tagObj?.nodeClass?.from === 'function'
  77. ? tagObj.nodeClass.from(ctx.schema, value, ctx)
  78. : new Scalar(value);
  79. if (tagName)
  80. node.tag = tagName;
  81. else if (!tagObj.default)
  82. node.tag = tagObj.tag;
  83. if (ref)
  84. ref.node = node;
  85. return node;
  86. }
  87. export { createNode };