@speclynx/apidom-parser-adapter-yaml-1-2 6.0.6 → 6.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,6 +22,7 @@ var _YamlSequence = _interopRequireDefault(require("./ast/nodes/YamlSequence.cjs
22
22
  var _YamlStream = _interopRequireDefault(require("./ast/nodes/YamlStream.cjs"));
23
23
  var _YamlTag = _interopRequireWildcard(require("./ast/nodes/YamlTag.cjs"));
24
24
  var _YamlStyle = require("./ast/nodes/YamlStyle.cjs");
25
+ var _predicates = require("./ast/nodes/predicates.cjs");
25
26
  const getCursorInfo = cursor => ({
26
27
  type: cursor.nodeType,
27
28
  startPosition: cursor.startPosition,
@@ -35,6 +36,15 @@ const getCursorInfo = cursor => ({
35
36
  fieldName: cursor.currentFieldName
36
37
  });
37
38
 
39
+ // TreeCursor#copy() in web-tree-sitter 0.27.1 doesn't marshal the cursor it's called on, so it
40
+ // copies whichever cursor or node was marshalled last; resetTo() marshals both cursors
41
+ // https://github.com/tree-sitter/tree-sitter/issues/6045
42
+ const copyCursor = cursor => {
43
+ const copy = cursor.currentNode.tree.walk();
44
+ copy.resetTo(cursor);
45
+ return copy;
46
+ };
47
+
38
48
  // Flat position properties extracted from CursorInfo
39
49
 
40
50
  const toPositionProps = info => ({
@@ -242,6 +252,8 @@ const createTransformers = transformerMap => ({
242
252
  ...toPositionProps(info),
243
253
  isMissing: info.isMissing
244
254
  });
255
+ const lineBreak = info.text.match(/\r?\n/);
256
+ if (lineBreak) stream.lineBreak = lineBreak[0];
245
257
  return new _ParseResult.default({
246
258
  children: [stream]
247
259
  });
@@ -325,14 +337,18 @@ const createTransformers = transformerMap => ({
325
337
  return children;
326
338
  },
327
339
  flow_node(cursor, ctx, siblings) {
328
- // Check if there's a kind node (scalar, mapping, or sequence)
340
+ // Check if there's a kind node (scalar, mapping, sequence or alias)
329
341
  let hasKindNode = false;
330
342
  let lastChildInfo = null;
343
+ let tagInfo;
344
+ let anchorInfo;
331
345
  if (cursor.gotoFirstChild()) {
332
346
  do {
333
347
  const childInfo = getCursorInfo(cursor);
334
348
  lastChildInfo = childInfo;
335
- if (childInfo.type.endsWith('scalar') || childInfo.type.endsWith('mapping') || childInfo.type.endsWith('sequence')) {
349
+ if (childInfo.type === 'tag') tagInfo = childInfo;
350
+ if (childInfo.type === 'anchor') anchorInfo = childInfo;
351
+ if (childInfo.type.endsWith('scalar') || childInfo.type.endsWith('mapping') || childInfo.type.endsWith('sequence') || childInfo.type === 'alias') {
336
352
  hasKindNode = true;
337
353
  }
338
354
  } while (cursor.gotoNextSibling());
@@ -345,10 +361,15 @@ const createTransformers = transformerMap => ({
345
361
 
346
362
  // No kind node - create empty scalar
347
363
  if (lastChildInfo) {
364
+ const emptyAnchorInfo = anchorInfo ?? siblings.anchor;
348
365
  const emptyScalarNode = new _YamlScalar.default({
349
366
  content: '',
350
- anchor: siblings.anchor ? toYamlAnchor(siblings.anchor) : undefined,
351
- tag: toYamlTag(lastChildInfo, siblings.tag),
367
+ // the anchor and tag of an empty node are children of the flow node, not siblings
368
+ anchor: emptyAnchorInfo ? toYamlAnchor(emptyAnchorInfo) : undefined,
369
+ tag: toYamlTag({
370
+ ...lastChildInfo,
371
+ type: 'plain_scalar'
372
+ }, tagInfo ?? siblings.tag),
352
373
  startLine: lastChildInfo.endPosition.row,
353
374
  startCharacter: lastChildInfo.endPosition.column,
354
375
  startOffset: lastChildInfo.endIndex,
@@ -447,8 +468,39 @@ const createTransformers = transformerMap => ({
447
468
  const tag = toYamlTag(info, siblings.tag);
448
469
  const anchor = siblings.anchor ? toYamlAnchor(siblings.anchor) : undefined;
449
470
  const children = processChildren(cursor, ctx, transformerMap);
471
+
472
+ // an entry with only a key (e.g. { a }) has an empty value (YAML 1.2 §7.4.2); the CST has no
473
+ // flow_pair for it, so the pair is made here
474
+ const toPair = child => {
475
+ if (!(0, _predicates.isScalar)(child) && !(0, _predicates.isMapping)(child) && !(0, _predicates.isSequence)(child)) return child;
476
+ const emptyValue = new _YamlScalar.default({
477
+ content: '',
478
+ startLine: child.endLine,
479
+ startCharacter: child.endCharacter,
480
+ startOffset: child.endOffset,
481
+ endLine: child.endLine,
482
+ endCharacter: child.endCharacter,
483
+ endOffset: child.endOffset,
484
+ tag: new _YamlTag.default({
485
+ explicitName: '?',
486
+ kind: _YamlTag.YamlNodeKind.Scalar
487
+ }),
488
+ styleGroup: _YamlStyle.YamlStyleGroup.Flow,
489
+ style: _YamlStyle.YamlStyle.Plain
490
+ });
491
+ return new _YamlKeyValuePair.default({
492
+ children: [child, emptyValue],
493
+ startLine: child.startLine,
494
+ startCharacter: child.startCharacter,
495
+ startOffset: child.startOffset,
496
+ endLine: child.endLine,
497
+ endCharacter: child.endCharacter,
498
+ endOffset: child.endOffset,
499
+ styleGroup: _YamlStyle.YamlStyleGroup.Flow
500
+ });
501
+ };
450
502
  const mappingNode = new _YamlMapping.default({
451
- children: children.flat().filter(c => c !== null),
503
+ children: children.flat().filter(c => c !== null).map(toPair),
452
504
  ...toPositionProps(info),
453
505
  anchor,
454
506
  tag,
@@ -567,8 +619,30 @@ const createTransformers = transformerMap => ({
567
619
  const tag = toYamlTag(info, siblings.tag);
568
620
  const anchor = siblings.anchor ? toYamlAnchor(siblings.anchor) : undefined;
569
621
  const children = processChildren(cursor, ctx, transformerMap);
622
+
623
+ // a single pair as a flow sequence entry (e.g. [a: 1]) is a mapping with that one pair
624
+ // (YAML 1.2 §7.4.1); the CST has the flow_pair directly in the flow_sequence
625
+ const toSinglePairMapping = child => {
626
+ if (!(0, _predicates.isKeyValuePair)(child)) return child;
627
+ const mappingNode = new _YamlMapping.default({
628
+ children: [child],
629
+ startLine: child.startLine,
630
+ startCharacter: child.startCharacter,
631
+ startOffset: child.startOffset,
632
+ endLine: child.endLine,
633
+ endCharacter: child.endCharacter,
634
+ endOffset: child.endOffset,
635
+ tag: new _YamlTag.default({
636
+ explicitName: '!',
637
+ kind: _YamlTag.YamlNodeKind.Mapping
638
+ }),
639
+ styleGroup: _YamlStyle.YamlStyleGroup.Flow,
640
+ style: _YamlStyle.YamlStyle.SinglePair
641
+ });
642
+ return ctx.schema.resolve(mappingNode);
643
+ };
570
644
  const sequenceNode = new _YamlSequence.default({
571
- children: children.flat().filter(c => c !== null),
645
+ children: children.flat().filter(c => c !== null).map(toSinglePairMapping),
572
646
  ...toPositionProps(info),
573
647
  anchor,
574
648
  tag,
@@ -631,15 +705,44 @@ const createTransformers = transformerMap => ({
631
705
  const tag = toYamlTag(info, siblings.tag);
632
706
  const anchor = siblings.anchor ? toYamlAnchor(siblings.anchor) : undefined;
633
707
  const style = info.text.startsWith('|') ? _YamlStyle.YamlStyle.Literal : info.text.startsWith('>') ? _YamlStyle.YamlStyle.Folded : _YamlStyle.YamlStyle.Plain;
708
+
709
+ // trailing lines belong to the block scalar, but the CST node ends at its last content
710
+ // character when another node follows; the lines up to the line of the next node in
711
+ // source order are taken from the text of the node holding both
712
+ let trailingLines = '';
713
+ if (!info.text.endsWith('\n')) {
714
+ // a cursor copy walks up in constant time per step, unlike Node#parent
715
+ const following = copyCursor(cursor);
716
+ let hasNext = following.gotoNextSibling();
717
+ while (!hasNext && following.gotoParent()) {
718
+ hasNext = following.gotoNextSibling();
719
+ }
720
+ // without a next node the scalar ends the input, so there are no lines to add
721
+ if (hasNext) {
722
+ const nextIndex = following.startIndex;
723
+ following.gotoParent();
724
+ const gap = following.nodeText.slice(info.endIndex - following.startIndex, nextIndex - following.startIndex);
725
+ trailingLines = gap.slice(0, gap.lastIndexOf('\n') + 1);
726
+ }
727
+ following.delete();
728
+ }
729
+
730
+ // an explicit indentation indicator is relative to the parent node's indentation: the
731
+ // column of the mapping pair or sequence item holding the block node (0 at top level);
732
+ // content lines are rebased to it so that the indicator can be applied to them as is
733
+ const holder = cursor.currentNode.parent?.parent;
734
+ const parentIndentation = holder?.type === 'block_mapping_pair' || holder?.type === 'block_sequence_item' ? holder.startPosition.column : 0;
735
+ const rebased = (info.text + trailingLines).replace(new RegExp(`\n {0,${parentIndentation}}`, 'g'), '\n');
634
736
  const scalarNode = new _YamlScalar.default({
635
- content: info.text,
737
+ content: rebased,
636
738
  anchor,
637
739
  tag,
638
740
  ...toPositionProps(info),
639
741
  styleGroup: _YamlStyle.YamlStyleGroup.Block,
640
742
  style
641
743
  });
642
- scalarNode.rawContent = info.text;
744
+ // the block's source text, rebased to its parent's indentation and with its trailing lines
745
+ scalarNode.rawContent = rebased;
643
746
  registerAnchor(scalarNode, ctx);
644
747
  return ctx.schema.resolve(scalarNode);
645
748
  },
@@ -653,7 +756,8 @@ const createTransformers = transformerMap => ({
653
756
  alias(cursor, ctx) {
654
757
  const info = getCursorInfo(cursor);
655
758
  const alias = new _YamlAlias.default({
656
- content: info.text
759
+ content: info.text,
760
+ ...toPositionProps(info)
657
761
  });
658
762
  return ctx.referenceManager.resolveAlias(alias);
659
763
  },
@@ -14,6 +14,7 @@ import YamlSequence from "./ast/nodes/YamlSequence.mjs";
14
14
  import YamlStream from "./ast/nodes/YamlStream.mjs";
15
15
  import YamlTag, { YamlNodeKind } from "./ast/nodes/YamlTag.mjs";
16
16
  import { YamlStyle, YamlStyleGroup } from "./ast/nodes/YamlStyle.mjs";
17
+ import { isKeyValuePair, isMapping, isScalar, isSequence } from "./ast/nodes/predicates.mjs";
17
18
  const getCursorInfo = cursor => ({
18
19
  type: cursor.nodeType,
19
20
  startPosition: cursor.startPosition,
@@ -27,6 +28,15 @@ const getCursorInfo = cursor => ({
27
28
  fieldName: cursor.currentFieldName
28
29
  });
29
30
 
31
+ // TreeCursor#copy() in web-tree-sitter 0.27.1 doesn't marshal the cursor it's called on, so it
32
+ // copies whichever cursor or node was marshalled last; resetTo() marshals both cursors
33
+ // https://github.com/tree-sitter/tree-sitter/issues/6045
34
+ const copyCursor = cursor => {
35
+ const copy = cursor.currentNode.tree.walk();
36
+ copy.resetTo(cursor);
37
+ return copy;
38
+ };
39
+
30
40
  // Flat position properties extracted from CursorInfo
31
41
 
32
42
  const toPositionProps = info => ({
@@ -234,6 +244,8 @@ const createTransformers = transformerMap => ({
234
244
  ...toPositionProps(info),
235
245
  isMissing: info.isMissing
236
246
  });
247
+ const lineBreak = info.text.match(/\r?\n/);
248
+ if (lineBreak) stream.lineBreak = lineBreak[0];
237
249
  return new ParseResult({
238
250
  children: [stream]
239
251
  });
@@ -317,14 +329,18 @@ const createTransformers = transformerMap => ({
317
329
  return children;
318
330
  },
319
331
  flow_node(cursor, ctx, siblings) {
320
- // Check if there's a kind node (scalar, mapping, or sequence)
332
+ // Check if there's a kind node (scalar, mapping, sequence or alias)
321
333
  let hasKindNode = false;
322
334
  let lastChildInfo = null;
335
+ let tagInfo;
336
+ let anchorInfo;
323
337
  if (cursor.gotoFirstChild()) {
324
338
  do {
325
339
  const childInfo = getCursorInfo(cursor);
326
340
  lastChildInfo = childInfo;
327
- if (childInfo.type.endsWith('scalar') || childInfo.type.endsWith('mapping') || childInfo.type.endsWith('sequence')) {
341
+ if (childInfo.type === 'tag') tagInfo = childInfo;
342
+ if (childInfo.type === 'anchor') anchorInfo = childInfo;
343
+ if (childInfo.type.endsWith('scalar') || childInfo.type.endsWith('mapping') || childInfo.type.endsWith('sequence') || childInfo.type === 'alias') {
328
344
  hasKindNode = true;
329
345
  }
330
346
  } while (cursor.gotoNextSibling());
@@ -337,10 +353,15 @@ const createTransformers = transformerMap => ({
337
353
 
338
354
  // No kind node - create empty scalar
339
355
  if (lastChildInfo) {
356
+ const emptyAnchorInfo = anchorInfo ?? siblings.anchor;
340
357
  const emptyScalarNode = new YamlScalar({
341
358
  content: '',
342
- anchor: siblings.anchor ? toYamlAnchor(siblings.anchor) : undefined,
343
- tag: toYamlTag(lastChildInfo, siblings.tag),
359
+ // the anchor and tag of an empty node are children of the flow node, not siblings
360
+ anchor: emptyAnchorInfo ? toYamlAnchor(emptyAnchorInfo) : undefined,
361
+ tag: toYamlTag({
362
+ ...lastChildInfo,
363
+ type: 'plain_scalar'
364
+ }, tagInfo ?? siblings.tag),
344
365
  startLine: lastChildInfo.endPosition.row,
345
366
  startCharacter: lastChildInfo.endPosition.column,
346
367
  startOffset: lastChildInfo.endIndex,
@@ -439,8 +460,39 @@ const createTransformers = transformerMap => ({
439
460
  const tag = toYamlTag(info, siblings.tag);
440
461
  const anchor = siblings.anchor ? toYamlAnchor(siblings.anchor) : undefined;
441
462
  const children = processChildren(cursor, ctx, transformerMap);
463
+
464
+ // an entry with only a key (e.g. { a }) has an empty value (YAML 1.2 §7.4.2); the CST has no
465
+ // flow_pair for it, so the pair is made here
466
+ const toPair = child => {
467
+ if (!isScalar(child) && !isMapping(child) && !isSequence(child)) return child;
468
+ const emptyValue = new YamlScalar({
469
+ content: '',
470
+ startLine: child.endLine,
471
+ startCharacter: child.endCharacter,
472
+ startOffset: child.endOffset,
473
+ endLine: child.endLine,
474
+ endCharacter: child.endCharacter,
475
+ endOffset: child.endOffset,
476
+ tag: new YamlTag({
477
+ explicitName: '?',
478
+ kind: YamlNodeKind.Scalar
479
+ }),
480
+ styleGroup: YamlStyleGroup.Flow,
481
+ style: YamlStyle.Plain
482
+ });
483
+ return new YamlKeyValuePair({
484
+ children: [child, emptyValue],
485
+ startLine: child.startLine,
486
+ startCharacter: child.startCharacter,
487
+ startOffset: child.startOffset,
488
+ endLine: child.endLine,
489
+ endCharacter: child.endCharacter,
490
+ endOffset: child.endOffset,
491
+ styleGroup: YamlStyleGroup.Flow
492
+ });
493
+ };
442
494
  const mappingNode = new YamlMapping({
443
- children: children.flat().filter(c => c !== null),
495
+ children: children.flat().filter(c => c !== null).map(toPair),
444
496
  ...toPositionProps(info),
445
497
  anchor,
446
498
  tag,
@@ -559,8 +611,30 @@ const createTransformers = transformerMap => ({
559
611
  const tag = toYamlTag(info, siblings.tag);
560
612
  const anchor = siblings.anchor ? toYamlAnchor(siblings.anchor) : undefined;
561
613
  const children = processChildren(cursor, ctx, transformerMap);
614
+
615
+ // a single pair as a flow sequence entry (e.g. [a: 1]) is a mapping with that one pair
616
+ // (YAML 1.2 §7.4.1); the CST has the flow_pair directly in the flow_sequence
617
+ const toSinglePairMapping = child => {
618
+ if (!isKeyValuePair(child)) return child;
619
+ const mappingNode = new YamlMapping({
620
+ children: [child],
621
+ startLine: child.startLine,
622
+ startCharacter: child.startCharacter,
623
+ startOffset: child.startOffset,
624
+ endLine: child.endLine,
625
+ endCharacter: child.endCharacter,
626
+ endOffset: child.endOffset,
627
+ tag: new YamlTag({
628
+ explicitName: '!',
629
+ kind: YamlNodeKind.Mapping
630
+ }),
631
+ styleGroup: YamlStyleGroup.Flow,
632
+ style: YamlStyle.SinglePair
633
+ });
634
+ return ctx.schema.resolve(mappingNode);
635
+ };
562
636
  const sequenceNode = new YamlSequence({
563
- children: children.flat().filter(c => c !== null),
637
+ children: children.flat().filter(c => c !== null).map(toSinglePairMapping),
564
638
  ...toPositionProps(info),
565
639
  anchor,
566
640
  tag,
@@ -623,15 +697,44 @@ const createTransformers = transformerMap => ({
623
697
  const tag = toYamlTag(info, siblings.tag);
624
698
  const anchor = siblings.anchor ? toYamlAnchor(siblings.anchor) : undefined;
625
699
  const style = info.text.startsWith('|') ? YamlStyle.Literal : info.text.startsWith('>') ? YamlStyle.Folded : YamlStyle.Plain;
700
+
701
+ // trailing lines belong to the block scalar, but the CST node ends at its last content
702
+ // character when another node follows; the lines up to the line of the next node in
703
+ // source order are taken from the text of the node holding both
704
+ let trailingLines = '';
705
+ if (!info.text.endsWith('\n')) {
706
+ // a cursor copy walks up in constant time per step, unlike Node#parent
707
+ const following = copyCursor(cursor);
708
+ let hasNext = following.gotoNextSibling();
709
+ while (!hasNext && following.gotoParent()) {
710
+ hasNext = following.gotoNextSibling();
711
+ }
712
+ // without a next node the scalar ends the input, so there are no lines to add
713
+ if (hasNext) {
714
+ const nextIndex = following.startIndex;
715
+ following.gotoParent();
716
+ const gap = following.nodeText.slice(info.endIndex - following.startIndex, nextIndex - following.startIndex);
717
+ trailingLines = gap.slice(0, gap.lastIndexOf('\n') + 1);
718
+ }
719
+ following.delete();
720
+ }
721
+
722
+ // an explicit indentation indicator is relative to the parent node's indentation: the
723
+ // column of the mapping pair or sequence item holding the block node (0 at top level);
724
+ // content lines are rebased to it so that the indicator can be applied to them as is
725
+ const holder = cursor.currentNode.parent?.parent;
726
+ const parentIndentation = holder?.type === 'block_mapping_pair' || holder?.type === 'block_sequence_item' ? holder.startPosition.column : 0;
727
+ const rebased = (info.text + trailingLines).replace(new RegExp(`\n {0,${parentIndentation}}`, 'g'), '\n');
626
728
  const scalarNode = new YamlScalar({
627
- content: info.text,
729
+ content: rebased,
628
730
  anchor,
629
731
  tag,
630
732
  ...toPositionProps(info),
631
733
  styleGroup: YamlStyleGroup.Block,
632
734
  style
633
735
  });
634
- scalarNode.rawContent = info.text;
736
+ // the block's source text, rebased to its parent's indentation and with its trailing lines
737
+ scalarNode.rawContent = rebased;
635
738
  registerAnchor(scalarNode, ctx);
636
739
  return ctx.schema.resolve(scalarNode);
637
740
  },
@@ -645,7 +748,8 @@ const createTransformers = transformerMap => ({
645
748
  alias(cursor, ctx) {
646
749
  const info = getCursorInfo(cursor);
647
750
  const alias = new YamlAlias({
648
- content: info.text
751
+ content: info.text,
752
+ ...toPositionProps(info)
649
753
  });
650
754
  return ctx.referenceManager.resolveAlias(alias);
651
755
  },
@@ -134,7 +134,13 @@ const applyComments = (elements, comments) => {
134
134
  const detectFlowPadding = node => {
135
135
  const firstChild = (node.children || [])[0];
136
136
  if (firstChild && typeof node.startCharacter === 'number' && typeof firstChild.startCharacter === 'number') {
137
- return firstChild.startCharacter - node.startCharacter > 1;
137
+ // a sequence item starts with its tag or anchor, which aren't part of its node's position
138
+ const {
139
+ tag,
140
+ anchor
141
+ } = firstChild;
142
+ const firstStart = Math.min(firstChild.startCharacter, tag?.startCharacter ?? Infinity, anchor?.startCharacter ?? Infinity);
143
+ return firstStart - node.startCharacter > 1;
138
144
  }
139
145
  return true;
140
146
  };
@@ -148,12 +154,25 @@ const buildYamlStyle = (node, ctx, extras) => {
148
154
  if (ctx.flowCollectionPadding !== null) {
149
155
  yamlStyle.flowCollectionPadding = ctx.flowCollectionPadding;
150
156
  }
157
+ if (ctx.indentSeq !== null) {
158
+ yamlStyle.indentSeq = ctx.indentSeq;
159
+ }
151
160
  if (node.comment) {
152
161
  yamlStyle.comment = node.comment;
153
162
  }
154
163
  if (node.commentBefore) {
155
164
  yamlStyle.commentBefore = node.commentBefore;
156
165
  }
166
+ // an untagged node has the non-specific tag '?' when it's a plain scalar and '!' otherwise
167
+ // (YAML 1.2 §6.9.1); an explicit '!' on any other node means the same as no tag
168
+ const tag = node.tag?.explicitName;
169
+ if (tag !== undefined && tag !== (node.style === _YamlStyle.YamlStyle.Plain ? '?' : '!')) {
170
+ yamlStyle.tag = tag;
171
+ }
172
+ // the anchor's name, without the & indicator
173
+ if (node.anchor) {
174
+ yamlStyle.anchor = node.anchor.name.slice(1);
175
+ }
157
176
  if (extras) {
158
177
  for (const [key, value] of Object.entries(extras)) {
159
178
  if (value !== undefined) yamlStyle[key] = value;
@@ -247,13 +266,35 @@ const transformChildren = (children, ctx) => {
247
266
 
248
267
  // stream comments that precede the first document, joined; null when there are none
249
268
  // (stream children are in source order, so everything before the document node qualifies)
250
- const collectPreDocumentComments = node => {
251
- const texts = [];
269
+ // stream comments around the first document: before it, on its last line and on the lines after it,
270
+ // up to the next document
271
+ const collectStreamComments = node => {
272
+ const comments = {
273
+ before: [],
274
+ inline: [],
275
+ after: []
276
+ };
277
+ let root;
278
+ const collect = comment => {
279
+ const text = stripCommentHash(comment.content);
280
+ if (!root) comments.before.push(text);else if (comment.startLine === root.endLine) comments.inline.push(text);else comments.after.push(text);
281
+ };
252
282
  for (const child of node.children) {
253
- if ((0, _predicates.isDocument)(child)) break;
254
- if ((0, _predicates.isComment)(child)) texts.push(stripCommentHash(child.content));
283
+ if ((0, _predicates.isDocument)(child)) {
284
+ if (root) break;
285
+ // comments after an explicit document start (---) or before an explicit document end (...)
286
+ // are the document's own children, around its root node
287
+ const children = child.children || [];
288
+ const rootIndex = children.findIndex(c => !(0, _predicates.isComment)(c));
289
+ if (rootIndex === -1) break;
290
+ children.slice(0, rootIndex).filter(_predicates.isComment).forEach(collect);
291
+ root = children[rootIndex];
292
+ children.slice(rootIndex + 1).filter(_predicates.isComment).forEach(collect);
293
+ } else if ((0, _predicates.isComment)(child)) {
294
+ collect(child);
295
+ }
255
296
  }
256
- return texts.length > 0 ? texts.join('\n') : null;
297
+ return comments;
257
298
  };
258
299
 
259
300
  // Stream: Wraps transformed children in ParseResultElement
@@ -278,13 +319,32 @@ const transformStream = (node, ctx) => {
278
319
  const resultElement = elements[0];
279
320
  resultElement.classes.push('result');
280
321
 
281
- // stream comments before the first document belong above the result element;
282
- // capture them as commentBefore so style round-trips can re-emit them
322
+ // stream comments before the first document belong above the result element, ones on its last
323
+ // line to it and ones on later lines to the document; capture them so style round-trips can
324
+ // re-emit them
283
325
  if (ctx.style) {
284
- const preDocumentComments = collectPreDocumentComments(node);
285
- if (preDocumentComments !== null) {
326
+ const {
327
+ before,
328
+ inline,
329
+ after
330
+ } = collectStreamComments(node);
331
+ if (before.length > 0) {
286
332
  const existing = getYamlStyleProp(resultElement, 'commentBefore');
287
- setYamlStyleProp(resultElement, 'commentBefore', typeof existing === 'string' ? `${preDocumentComments}\n${existing}` : preDocumentComments);
333
+ const text = before.join('\n');
334
+ setYamlStyleProp(resultElement, 'commentBefore', typeof existing === 'string' ? `${text}\n${existing}` : text);
335
+ }
336
+ if (inline.length > 0 && typeof getYamlStyleProp(resultElement, 'comment') !== 'string') {
337
+ setYamlStyleProp(resultElement, 'comment', inline.join('\n'));
338
+ }
339
+ // comments promoted out of the root's last nested collection have no next sibling to precede
340
+ // and end the root, so they come before the stream comments after it
341
+ after.unshift(...ctx.promotedComments);
342
+ if (after.length > 0) {
343
+ setYamlStyleProp(resultElement, 'documentComment', after.join('\n'));
344
+ }
345
+ // line breaks other than LF, which the YAML library writes
346
+ if (node.lineBreak === '\r\n') {
347
+ setYamlStyleProp(resultElement, 'lineBreak', node.lineBreak);
288
348
  }
289
349
  }
290
350
  }
@@ -331,7 +391,9 @@ const transformCollection = (element, node, ctx) => {
331
391
  element._content = childElements; // eslint-disable-line @typescript-eslint/no-explicit-any
332
392
  applyComments(childElements, comments);
333
393
  element.style = buildYamlStyle(node, ctx, {
334
- comment: comments.trailing
394
+ comment: comments.trailing,
395
+ // a single pair written directly in a flow sequence (e.g. [a: 1])
396
+ singlePair: node.style === _YamlStyle.YamlStyle.SinglePair || undefined
335
397
  });
336
398
  if (comments.outOfScope.length > 0) {
337
399
  ctx.promotedComments.push(...comments.outOfScope);
@@ -352,12 +414,34 @@ const transformMapping = (node, ctx) => {
352
414
  // KeyValuePair: Transforms to MemberElement
353
415
  const transformKeyValuePair = (node, ctx) => {
354
416
  const element = new _apidomDatamodel.MemberElement();
417
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
418
+ const {
419
+ key,
420
+ value
421
+ } = node;
422
+ if (ctx.style && ctx.indentSeq === null && value?.type === 'sequence') {
423
+ const {
424
+ styleGroup,
425
+ startCharacter
426
+ } = value;
427
+ if (styleGroup === _YamlStyle.YamlStyleGroup.Block) {
428
+ ctx.indentSeq = startCharacter > node.startCharacter;
429
+ }
430
+ }
355
431
 
356
432
  // Transform key and value (these are dynamically defined properties)
357
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
358
- const keyResult = transform(node.key, ctx);
359
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
360
- const valueResult = transform(node.value, ctx);
433
+ let keyResult = transform(key, ctx);
434
+ const valueResult = transform(value, ctx);
435
+
436
+ // an empty key is a null in YAML, but a JavaScript property name can't be null, so it's the empty
437
+ // string it's named by, marked as an empty key
438
+ if ((0, _apidomDatamodel.isNullElement)(keyResult) && (0, _apidomDatamodel.includesClasses)(keyResult, ['yaml-e-node'])) {
439
+ const emptyKey = new _apidomDatamodel.StringElement('', keyResult.meta, keyResult.attributes);
440
+ emptyKey.classes.push('yaml-e-key');
441
+ _apidomDatamodel.SourceMapElement.transfer(keyResult, emptyKey);
442
+ _apidomDatamodel.StyleElement.transfer(keyResult, emptyKey);
443
+ keyResult = emptyKey;
444
+ }
361
445
 
362
446
  // @ts-ignore
363
447
  element.content.key = keyResult;
@@ -385,10 +469,12 @@ const transformSequence = (node, ctx) => {
385
469
 
386
470
  // Scalar: Converts to Element via namespace
387
471
  const transformScalar = (node, ctx) => {
388
- const element = ctx.namespace.toElement(node.content);
389
-
390
- // Translate style information about empty nodes
391
- if (node.content === '' && node.style === _YamlStyle.YamlStyle.Plain) {
472
+ // an empty node (YAML 1.2 §7.2) is a null unless a tag other than the null tag makes it
473
+ // something else (e.g. !!str makes it an empty string); its classes tell it from an explicit null
474
+ const tag = node.tag?.explicitName;
475
+ const isEmptyNode = node.content === '' && node.style === _YamlStyle.YamlStyle.Plain && [undefined, '?', '!!null', '!<tag:yaml.org,2002:null>'].includes(tag);
476
+ const element = isEmptyNode ? new _apidomDatamodel.NullElement() : ctx.namespace.toElement(node.content);
477
+ if (isEmptyNode) {
392
478
  element.classes.push('yaml-e-node');
393
479
  element.classes.push('yaml-e-scalar');
394
480
  }
@@ -399,6 +485,9 @@ const transformScalar = (node, ctx) => {
399
485
  if (node.rawContent !== undefined) {
400
486
  extras.rawContent = node.rawContent;
401
487
  }
488
+ if (node.alias !== undefined) {
489
+ extras.alias = node.alias;
490
+ }
402
491
  element.style = buildYamlStyle(node, ctx, extras);
403
492
  }
404
493
  maybeAddSourceMap(node, element, ctx);
@@ -476,6 +565,7 @@ const transformYamlAstToApiDOM = (yamlAst, {
476
565
  processedDocumentCount: 0,
477
566
  indent: 2,
478
567
  flowCollectionPadding: null,
568
+ indentSeq: null,
479
569
  promotedComments: []
480
570
  };
481
571
  const rootNode = yamlAst.rootNode;