compose-node.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import { Alias } from '../nodes/Alias.js';
  2. import { isScalar } from '../nodes/identity.js';
  3. import { composeCollection } from './compose-collection.js';
  4. import { composeScalar } from './compose-scalar.js';
  5. import { resolveEnd } from './resolve-end.js';
  6. import { emptyScalarPosition } from './util-empty-scalar-position.js';
  7. const CN = { composeNode, composeEmptyNode };
  8. function composeNode(ctx, token, props, onError) {
  9. const atKey = ctx.atKey;
  10. const { spaceBefore, comment, anchor, tag } = props;
  11. let node;
  12. let isSrcToken = true;
  13. switch (token.type) {
  14. case 'alias':
  15. node = composeAlias(ctx, token, onError);
  16. if (anchor || tag)
  17. onError(token, 'ALIAS_PROPS', 'An alias node must not specify any properties');
  18. break;
  19. case 'scalar':
  20. case 'single-quoted-scalar':
  21. case 'double-quoted-scalar':
  22. case 'block-scalar':
  23. node = composeScalar(ctx, token, tag, onError);
  24. if (anchor)
  25. node.anchor = anchor.source.substring(1);
  26. break;
  27. case 'block-map':
  28. case 'block-seq':
  29. case 'flow-collection':
  30. node = composeCollection(CN, ctx, token, props, onError);
  31. if (anchor)
  32. node.anchor = anchor.source.substring(1);
  33. break;
  34. default: {
  35. const message = token.type === 'error'
  36. ? token.message
  37. : `Unsupported token (type: ${token.type})`;
  38. onError(token, 'UNEXPECTED_TOKEN', message);
  39. node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError);
  40. isSrcToken = false;
  41. }
  42. }
  43. if (anchor && node.anchor === '')
  44. onError(anchor, 'BAD_ALIAS', 'Anchor cannot be an empty string');
  45. if (atKey &&
  46. ctx.options.stringKeys &&
  47. (!isScalar(node) ||
  48. typeof node.value !== 'string' ||
  49. (node.tag && node.tag !== 'tag:yaml.org,2002:str'))) {
  50. const msg = 'With stringKeys, all keys must be strings';
  51. onError(tag ?? token, 'NON_STRING_KEY', msg);
  52. }
  53. if (spaceBefore)
  54. node.spaceBefore = true;
  55. if (comment) {
  56. if (token.type === 'scalar' && token.source === '')
  57. node.comment = comment;
  58. else
  59. node.commentBefore = comment;
  60. }
  61. // @ts-expect-error Type checking misses meaning of isSrcToken
  62. if (ctx.options.keepSourceTokens && isSrcToken)
  63. node.srcToken = token;
  64. return node;
  65. }
  66. function composeEmptyNode(ctx, offset, before, pos, { spaceBefore, comment, anchor, tag, end }, onError) {
  67. const token = {
  68. type: 'scalar',
  69. offset: emptyScalarPosition(offset, before, pos),
  70. indent: -1,
  71. source: ''
  72. };
  73. const node = composeScalar(ctx, token, tag, onError);
  74. if (anchor) {
  75. node.anchor = anchor.source.substring(1);
  76. if (node.anchor === '')
  77. onError(anchor, 'BAD_ALIAS', 'Anchor cannot be an empty string');
  78. }
  79. if (spaceBefore)
  80. node.spaceBefore = true;
  81. if (comment) {
  82. node.comment = comment;
  83. node.range[2] = end;
  84. }
  85. return node;
  86. }
  87. function composeAlias({ options }, { offset, source, end }, onError) {
  88. const alias = new Alias(source.substring(1));
  89. if (alias.source === '')
  90. onError(offset, 'BAD_ALIAS', 'Alias cannot be an empty string');
  91. if (alias.source.endsWith(':'))
  92. onError(offset + source.length - 1, 'BAD_ALIAS', 'Alias ending in : is ambiguous', true);
  93. const valueEnd = offset + source.length;
  94. const re = resolveEnd(end, valueEnd, options.strict, onError);
  95. alias.range = [offset, valueEnd, re.offset];
  96. if (re.comment)
  97. alias.comment = re.comment;
  98. return alias;
  99. }
  100. export { composeEmptyNode, composeNode };