@speclynx/apidom-parser-adapter-yaml-1-2 5.0.2 → 5.1.0

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,12 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [5.1.0](https://github.com/speclynx/apidom/compare/v5.0.2...v5.1.0) (2026-08-09)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **yaml:** make YAML style round-trips byte-perfect (comments, raw numbers) ([#441](https://github.com/speclynx/apidom/issues/441)) ([43dc5b6](https://github.com/speclynx/apidom/commit/43dc5b6a19bfe6268f0ecaedf8f099b1990a363d))
11
+
6
12
  ## [5.0.2](https://github.com/speclynx/apidom/compare/v5.0.1...v5.0.2) (2026-08-04)
7
13
 
8
14
  **Note:** Version bump only for package @speclynx/apidom-parser-adapter-yaml-1-2
@@ -274,8 +274,11 @@ const processChildren = (cursor, ctx, transformerMap) => {
274
274
  return results;
275
275
  };
276
276
 
277
- // strip leading '# ' (or bare '#') from comment text, keeping only the content
278
- const stripCommentHash = text => text.split('\n').map(line => line.replace(/^#\s?/, '')).join('\n');
277
+ // strip leading '#' from each line of comment text; the text after '#' is kept
278
+ // verbatim (including leading whitespace) so serializers can round-trip it exactly;
279
+ // a bare '#' becomes a single space — the yaml library's encoding for an empty
280
+ // comment, which its stringifier turns back into '#' (plain '' would be dropped)
281
+ const stripCommentHash = text => text.split('\n').map(line => line === '#' ? ' ' : line.replace(/^#/, '')).join('\n');
279
282
 
280
283
  // find the first YamlNode in a TransformResult (which may be an array from block_node)
281
284
  const findYamlNode = result => {
@@ -318,7 +321,10 @@ const processKeyValuePairChildren = (cursor, ctx, transformerMap) => {
318
321
  if (seenValue) {
319
322
  commentsAfterValue.push(commentText);
320
323
  } else if (seenKey) {
321
- commentsBetweenKeyValue.push(commentText);
324
+ commentsBetweenKeyValue.push({
325
+ text: commentText,
326
+ startLine: info.startPosition.row
327
+ });
322
328
  }
323
329
  continue;
324
330
  }
@@ -347,6 +353,35 @@ const processKeyValuePairChildren = (cursor, ctx, transformerMap) => {
347
353
  commentsAfterValue
348
354
  };
349
355
  };
356
+
357
+ // attach comments collected inside a pair: comments on the key's own line belong
358
+ // after the key (`key: # comment`), the rest before the value node; comments
359
+ // found after the value become the value's trailing comment
360
+ const attachPairComments = ({
361
+ key,
362
+ value,
363
+ commentsBetweenKeyValue,
364
+ commentsAfterValue
365
+ }) => {
366
+ if (commentsBetweenKeyValue.length > 0) {
367
+ const keyNode = findYamlNode(key);
368
+ const valueNode = findYamlNode(value);
369
+ const onKeyLine = commentsBetweenKeyValue.filter(comment => keyNode !== null && comment.startLine === keyNode.endLine);
370
+ const onOwnLine = commentsBetweenKeyValue.filter(comment => !onKeyLine.includes(comment));
371
+ if (onKeyLine.length > 0 && keyNode) {
372
+ keyNode.comment = onKeyLine.map(comment => comment.text).join('\n');
373
+ }
374
+ if (onOwnLine.length > 0 && valueNode) {
375
+ valueNode.commentBefore = onOwnLine.map(comment => comment.text).join('\n');
376
+ }
377
+ }
378
+ if (commentsAfterValue.length > 0) {
379
+ const valueNode = findYamlNode(value);
380
+ if (valueNode) {
381
+ valueNode.comment = commentsAfterValue.join('\n');
382
+ }
383
+ }
384
+ };
350
385
  const transform = (cursor, ctx, transformerMap, siblings = {}) => {
351
386
  const info = getCursorInfo(cursor);
352
387
 
@@ -518,13 +553,12 @@ const createTransformers = transformerMap => ({
518
553
  },
519
554
  block_mapping_pair(cursor, ctx) {
520
555
  const info = getCursorInfo(cursor);
556
+ const pairResult = processKeyValuePairChildren(cursor, ctx, transformerMap);
521
557
  const {
522
558
  key,
523
559
  value,
524
- errors,
525
- commentsBetweenKeyValue,
526
- commentsAfterValue
527
- } = processKeyValuePairChildren(cursor, ctx, transformerMap);
560
+ errors
561
+ } = pairResult;
528
562
  const children = [];
529
563
 
530
564
  // Handle empty key
@@ -570,21 +604,7 @@ const createTransformers = transformerMap => ({
570
604
  } else {
571
605
  children.push(value);
572
606
  }
573
-
574
- // attach comments found between key and value to the value node
575
- if (commentsBetweenKeyValue.length > 0) {
576
- const valueNode = findYamlNode(value);
577
- if (valueNode) {
578
- valueNode.commentBefore = commentsBetweenKeyValue.join('\n');
579
- }
580
- }
581
- // attach comments found after value to the value node
582
- if (commentsAfterValue.length > 0) {
583
- const valueNode = findYamlNode(value);
584
- if (valueNode) {
585
- valueNode.comment = commentsAfterValue.join('\n');
586
- }
587
- }
607
+ attachPairComments(pairResult);
588
608
  children.push(...errors);
589
609
  return new _ast_nodes_YamlKeyValuePair_ts__WEBPACK_IMPORTED_MODULE_9__["default"]({
590
610
  children: children.flat().filter(c => c !== null),
@@ -612,13 +632,12 @@ const createTransformers = transformerMap => ({
612
632
  },
613
633
  flow_pair(cursor, ctx) {
614
634
  const info = getCursorInfo(cursor);
635
+ const pairResult = processKeyValuePairChildren(cursor, ctx, transformerMap);
615
636
  const {
616
637
  key,
617
638
  value,
618
- errors,
619
- commentsBetweenKeyValue,
620
- commentsAfterValue
621
- } = processKeyValuePairChildren(cursor, ctx, transformerMap);
639
+ errors
640
+ } = pairResult;
622
641
  const children = [];
623
642
 
624
643
  // Handle empty key
@@ -664,21 +683,7 @@ const createTransformers = transformerMap => ({
664
683
  } else {
665
684
  children.push(value);
666
685
  }
667
-
668
- // attach comments found between key and value to the value node
669
- if (commentsBetweenKeyValue.length > 0) {
670
- const valueNode = findYamlNode(value);
671
- if (valueNode) {
672
- valueNode.commentBefore = commentsBetweenKeyValue.join('\n');
673
- }
674
- }
675
- // attach comments found after value to the value node
676
- if (commentsAfterValue.length > 0) {
677
- const valueNode = findYamlNode(value);
678
- if (valueNode) {
679
- valueNode.comment = commentsAfterValue.join('\n');
680
- }
681
- }
686
+ attachPairComments(pairResult);
682
687
  children.push(...errors);
683
688
  return new _ast_nodes_YamlKeyValuePair_ts__WEBPACK_IMPORTED_MODULE_9__["default"]({
684
689
  children: children.flat().filter(c => c !== null),
@@ -889,7 +894,9 @@ __webpack_require__.r(__webpack_exports__);
889
894
  /* harmony import */ var _speclynx_apidom_datamodel__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(94);
890
895
  /* harmony import */ var _speclynx_apidom_datamodel__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(4823);
891
896
  /* harmony import */ var _speclynx_apidom_datamodel__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(5810);
892
- /* harmony import */ var _ast_nodes_YamlStyle_ts__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(9276);
897
+ /* harmony import */ var _ast_nodes_predicates_ts__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(1228);
898
+ /* harmony import */ var _ast_nodes_YamlStyle_ts__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(9276);
899
+
893
900
 
894
901
 
895
902
 
@@ -920,14 +927,17 @@ const findFirst = (node, path) => {
920
927
  // detect indent from first nested block mapping (stream > document > mapping > keyValuePair > mapping)
921
928
  const detectIndent = node => {
922
929
  const nested = findFirst(node, ['document', 'mapping', 'keyValuePair', 'mapping']);
923
- if (nested?.styleGroup === _ast_nodes_YamlStyle_ts__WEBPACK_IMPORTED_MODULE_9__.YamlStyleGroup.Block && typeof nested.startCharacter === 'number' && nested.startCharacter > 0) {
930
+ if (nested?.styleGroup === _ast_nodes_YamlStyle_ts__WEBPACK_IMPORTED_MODULE_10__.YamlStyleGroup.Block && typeof nested.startCharacter === 'number' && nested.startCharacter > 0) {
924
931
  return nested.startCharacter;
925
932
  }
926
933
  return 2;
927
934
  };
928
935
 
929
- // strip leading '#' from each line of comment text; yaml library adds '#' during stringification
930
- const stripCommentHash = text => text.split('\n').map(line => line.replace(/^#/, '')).join('\n');
936
+ // strip leading '#' from each line of comment text; yaml library adds '#' during
937
+ // stringification; a bare '#' becomes a single space — the yaml library's encoding
938
+ // for an empty comment, which its stringifier turns back into '#' (plain '' would
939
+ // be dropped)
940
+ const stripCommentHash = text => text.split('\n').map(line => line === '#' ? ' ' : line.replace(/^#/, '')).join('\n');
931
941
 
932
942
  // collect comment texts from raw AST children, indexed by position relative to non-comment siblings
933
943
 
@@ -997,6 +1007,7 @@ const setYamlStyleProp = (element, key, value) => {
997
1007
  yaml[key] = value;
998
1008
  element.style.yaml = yaml;
999
1009
  };
1010
+ const getYamlStyleProp = (element, key) => element.style?.yaml?.[key];
1000
1011
 
1001
1012
  // apply collected comments to transformed ApiDOM elements
1002
1013
  const applyComments = (elements, comments) => {
@@ -1127,6 +1138,17 @@ const transformChildren = (children, ctx) => {
1127
1138
  return results;
1128
1139
  };
1129
1140
 
1141
+ // stream comments that precede the first document, joined; null when there are none
1142
+ // (stream children are in source order, so everything before the document node qualifies)
1143
+ const collectPreDocumentComments = node => {
1144
+ const texts = [];
1145
+ for (const child of node.children) {
1146
+ if ((0,_ast_nodes_predicates_ts__WEBPACK_IMPORTED_MODULE_9__.isDocument)(child)) break;
1147
+ if ((0,_ast_nodes_predicates_ts__WEBPACK_IMPORTED_MODULE_9__.isComment)(child)) texts.push(stripCommentHash(child.content));
1148
+ }
1149
+ return texts.length > 0 ? texts.join('\n') : null;
1150
+ };
1151
+
1130
1152
  // Stream: Wraps transformed children in ParseResultElement
1131
1153
  const transformStream = (node, ctx) => {
1132
1154
  // detect indent from the stream structure (only needed for style preservation)
@@ -1148,6 +1170,16 @@ const transformStream = (node, ctx) => {
1148
1170
  if (elements.length > 0) {
1149
1171
  const resultElement = elements[0];
1150
1172
  resultElement.classes.push('result');
1173
+
1174
+ // stream comments before the first document belong above the result element;
1175
+ // capture them as commentBefore so style round-trips can re-emit them
1176
+ if (ctx.style) {
1177
+ const preDocumentComments = collectPreDocumentComments(node);
1178
+ if (preDocumentComments !== null) {
1179
+ const existing = getYamlStyleProp(resultElement, 'commentBefore');
1180
+ setYamlStyleProp(resultElement, 'commentBefore', typeof existing === 'string' ? `${preDocumentComments}\n${existing}` : preDocumentComments);
1181
+ }
1182
+ }
1151
1183
  }
1152
1184
 
1153
1185
  // Add collected annotations
@@ -1183,7 +1215,7 @@ const transformCollection = (element, node, ctx) => {
1183
1215
  const typedNode = node;
1184
1216
  const children = node.children || [];
1185
1217
  if (ctx.style) {
1186
- if (ctx.flowCollectionPadding === null && node.styleGroup === _ast_nodes_YamlStyle_ts__WEBPACK_IMPORTED_MODULE_9__.YamlStyleGroup.Flow) {
1218
+ if (ctx.flowCollectionPadding === null && node.styleGroup === _ast_nodes_YamlStyle_ts__WEBPACK_IMPORTED_MODULE_10__.YamlStyleGroup.Flow) {
1187
1219
  ctx.flowCollectionPadding = detectFlowPadding(typedNode);
1188
1220
  }
1189
1221
  const comments = collectComments(children, typedNode.startCharacter);
@@ -1249,7 +1281,7 @@ const transformScalar = (node, ctx) => {
1249
1281
  const element = ctx.namespace.toElement(node.content);
1250
1282
 
1251
1283
  // Translate style information about empty nodes
1252
- if (node.content === '' && node.style === _ast_nodes_YamlStyle_ts__WEBPACK_IMPORTED_MODULE_9__.YamlStyle.Plain) {
1284
+ if (node.content === '' && node.style === _ast_nodes_YamlStyle_ts__WEBPACK_IMPORTED_MODULE_10__.YamlStyle.Plain) {
1253
1285
  element.classes.push('yaml-e-node');
1254
1286
  element.classes.push('yaml-e-scalar');
1255
1287
  }
@@ -21972,11 +22004,13 @@ __webpack_require__.r(__webpack_exports__);
21972
22004
  /* harmony import */ var ramda__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8138);
21973
22005
  /* harmony import */ var _ObjectSlice_mjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8504);
21974
22006
  /* harmony import */ var _KeyValuePair_mjs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6663);
21975
- /* harmony import */ var _predicates_index_mjs__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8252);
21976
- /* harmony import */ var _predicates_index_mjs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(5162);
21977
- /* harmony import */ var _elements_SourceMap_mjs__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(5810);
21978
- /* harmony import */ var _errors_DeepCloneError_mjs__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5018);
21979
- /* harmony import */ var _errors_ShallowCloneError_mjs__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(3686);
22007
+ /* harmony import */ var _Metadata_mjs__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(1844);
22008
+ /* harmony import */ var _predicates_index_mjs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(8252);
22009
+ /* harmony import */ var _predicates_index_mjs__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(5162);
22010
+ /* harmony import */ var _elements_SourceMap_mjs__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(5810);
22011
+ /* harmony import */ var _errors_DeepCloneError_mjs__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(5018);
22012
+ /* harmony import */ var _errors_ShallowCloneError_mjs__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(3686);
22013
+
21980
22014
 
21981
22015
 
21982
22016
 
@@ -22010,7 +22044,7 @@ const cloneDeepElement = (element, options) => {
22010
22044
  } = element;
22011
22045
  if (Array.isArray(content)) {
22012
22046
  copy.content = content.map(el => cloneDeepElement(el, passThroughOptions));
22013
- } else if ((0,_predicates_index_mjs__WEBPACK_IMPORTED_MODULE_4__.isElement)(content)) {
22047
+ } else if ((0,_predicates_index_mjs__WEBPACK_IMPORTED_MODULE_5__.isElement)(content)) {
22014
22048
  copy.content = cloneDeepElement(content, passThroughOptions);
22015
22049
  } else if (content instanceof _KeyValuePair_mjs__WEBPACK_IMPORTED_MODULE_2__["default"]) {
22016
22050
  copy.content = cloneDeepKeyValuePair(content, passThroughOptions);
@@ -22058,7 +22092,7 @@ const cloneDeepObjectSlice = (slice, options) => {
22058
22092
  };
22059
22093
 
22060
22094
  /**
22061
- * Creates a deep clone of an ApiDOM Element, KeyValuePair, or ObjectSlice.
22095
+ * Creates a deep clone of an ApiDOM Element, KeyValuePair, ObjectSlice, or Metadata.
22062
22096
  * Handles cycles by memoizing visited objects.
22063
22097
  * @public
22064
22098
  */
@@ -22069,10 +22103,13 @@ const cloneDeep = (value, options = {}) => {
22069
22103
  if (value instanceof _ObjectSlice_mjs__WEBPACK_IMPORTED_MODULE_1__["default"]) {
22070
22104
  return cloneDeepObjectSlice(value, options);
22071
22105
  }
22072
- if ((0,_predicates_index_mjs__WEBPACK_IMPORTED_MODULE_4__.isElement)(value)) {
22106
+ if (value instanceof _Metadata_mjs__WEBPACK_IMPORTED_MODULE_3__["default"]) {
22107
+ return value.cloneDeep();
22108
+ }
22109
+ if ((0,_predicates_index_mjs__WEBPACK_IMPORTED_MODULE_5__.isElement)(value)) {
22073
22110
  return cloneDeepElement(value, options);
22074
22111
  }
22075
- throw new _errors_DeepCloneError_mjs__WEBPACK_IMPORTED_MODULE_6__["default"]("Value provided to cloneDeep function couldn't be cloned", {
22112
+ throw new _errors_DeepCloneError_mjs__WEBPACK_IMPORTED_MODULE_7__["default"]("Value provided to cloneDeep function couldn't be cloned", {
22076
22113
  value
22077
22114
  });
22078
22115
  };
@@ -22099,21 +22136,21 @@ const cloneShallowElement = element => {
22099
22136
  const copy = new Ctor();
22100
22137
  copy.element = element.element;
22101
22138
  if (!element.isMetaEmpty) {
22102
- copy.meta = element.meta.cloneDeep();
22139
+ copy.meta = cloneDeep(element.meta);
22103
22140
  }
22104
22141
  if (!element.isAttributesEmpty) {
22105
22142
  copy.attributes = cloneDeep(element.attributes);
22106
22143
  }
22107
- if ((0,_predicates_index_mjs__WEBPACK_IMPORTED_MODULE_3__.hasElementSourceMap)(element)) {
22108
- _elements_SourceMap_mjs__WEBPACK_IMPORTED_MODULE_5__["default"].transfer(element, copy);
22144
+ if ((0,_predicates_index_mjs__WEBPACK_IMPORTED_MODULE_4__.hasElementSourceMap)(element)) {
22145
+ _elements_SourceMap_mjs__WEBPACK_IMPORTED_MODULE_6__["default"].transfer(element, copy);
22109
22146
  }
22110
- if ((0,_predicates_index_mjs__WEBPACK_IMPORTED_MODULE_3__.hasElementStyle)(element)) {
22147
+ if ((0,_predicates_index_mjs__WEBPACK_IMPORTED_MODULE_4__.hasElementStyle)(element)) {
22111
22148
  copy.style = (0,ramda__WEBPACK_IMPORTED_MODULE_0__["default"])(element.style);
22112
22149
  }
22113
22150
  const {
22114
22151
  content
22115
22152
  } = element;
22116
- if ((0,_predicates_index_mjs__WEBPACK_IMPORTED_MODULE_4__.isElement)(content)) {
22153
+ if ((0,_predicates_index_mjs__WEBPACK_IMPORTED_MODULE_5__.isElement)(content)) {
22117
22154
  copy.content = cloneShallowElement(content);
22118
22155
  } else if (Array.isArray(content)) {
22119
22156
  copy.content = [...content];
@@ -22126,7 +22163,7 @@ const cloneShallowElement = element => {
22126
22163
  };
22127
22164
 
22128
22165
  /**
22129
- * Creates a shallow clone of an ApiDOM Element, KeyValuePair, or ObjectSlice.
22166
+ * Creates a shallow clone of an ApiDOM Element, KeyValuePair, ObjectSlice, or Metadata.
22130
22167
  * The element itself is cloned, but content references are shared.
22131
22168
  * Meta and attributes are deep cloned to preserve semantic information.
22132
22169
  * @public
@@ -22138,10 +22175,13 @@ const cloneShallow = value => {
22138
22175
  if (value instanceof _ObjectSlice_mjs__WEBPACK_IMPORTED_MODULE_1__["default"]) {
22139
22176
  return cloneShallowObjectSlice(value);
22140
22177
  }
22141
- if ((0,_predicates_index_mjs__WEBPACK_IMPORTED_MODULE_4__.isElement)(value)) {
22178
+ if (value instanceof _Metadata_mjs__WEBPACK_IMPORTED_MODULE_3__["default"]) {
22179
+ return value.cloneShallow();
22180
+ }
22181
+ if ((0,_predicates_index_mjs__WEBPACK_IMPORTED_MODULE_5__.isElement)(value)) {
22142
22182
  return cloneShallowElement(value);
22143
22183
  }
22144
- throw new _errors_ShallowCloneError_mjs__WEBPACK_IMPORTED_MODULE_7__["default"]("Value provided to cloneShallow function couldn't be cloned", {
22184
+ throw new _errors_ShallowCloneError_mjs__WEBPACK_IMPORTED_MODULE_8__["default"]("Value provided to cloneShallow function couldn't be cloned", {
22145
22185
  value
22146
22186
  });
22147
22187
  };