@effected/yaml 0.15.0 → 0.15.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.
package/YamlFormat.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { YamlMap, YamlPair, YamlScalar, YamlSeq } from "./YamlNode.js";
2
2
  import { EMPTY_DOCUMENT, composeAllDocuments, composeFirstDocumentCounted } from "./internal/composer/document.js";
3
3
  import { isFatalCode } from "./internal/diagnostics.js";
4
- import { stringifyDocument, stripNodeComments } from "./internal/stringifier.js";
4
+ import { renderDoubleQuoted, renderSingleQuoted, stringifyDocument, stringifyValue, stripNodeComments } from "./internal/stringifier.js";
5
5
  import { YamlDiagnostic } from "./YamlDiagnostic.js";
6
6
  import { YamlStringifyOptions } from "./Yaml.js";
7
7
  import { YamlEdit, YamlRange } from "./YamlEdit.js";
@@ -192,6 +192,17 @@ function toOutputDocument(doc, preserveComments) {
192
192
  * A multi-document stream routes to {@link formatStream}, which formats
193
193
  * every document and shares the same directive refusal.
194
194
  */
195
+ /**
196
+ * Re-attach the source's leading byte-order mark to a whole-document
197
+ * re-emission. The stringifier never emits a BOM (it is framing, not
198
+ * content), so without this every `format` and whole-pipeline `modify` on a
199
+ * BOM-prefixed file would produce a delete-at-offset-0 edit. Preserving it
200
+ * keeps the file's encoding signature the author's business, in line with
201
+ * the fidelity contract that leaves untouched bytes alone.
202
+ */
203
+ function withSourceBom(text, formatted) {
204
+ return text.startsWith("") ? `\uFEFF${formatted}` : formatted;
205
+ }
195
206
  function formatDocument(text, options) {
196
207
  const { documents, streamErrors } = composeAllDocuments(text, {});
197
208
  if (documents.length > 1) return formatStream(text, documents, streamErrors, options);
@@ -402,6 +413,140 @@ function rebuildSeq(node, items) {
402
413
  });
403
414
  }
404
415
  /**
416
+ * Whether a value carries characters single-quoted style cannot express (it
417
+ * escapes only `'`): C0 controls — newline, carriage return, tab among them —
418
+ * plus DEL and the C1 range, the same conservative set the requote helper
419
+ * uses. A value carrying any of these keeps the whole-document pipeline
420
+ * rather than switching quote style. Char-code based, mirroring the
421
+ * stringifier's own control-character checks.
422
+ */
423
+ function isSingleQuoteUnsafe(s) {
424
+ for (let i = 0; i < s.length; i++) {
425
+ const code = s.charCodeAt(i);
426
+ if (code <= 31 || code >= 127 && code <= 159) return true;
427
+ }
428
+ return false;
429
+ }
430
+ /**
431
+ * Read-only navigation twin of `modifyNode`: returns the EXISTING node at
432
+ * `path` together with whether its parent collection is flow-styled, or
433
+ * `undefined` whenever the path does not resolve to a node (missing
434
+ * key/index, non-navigable node, empty path, null value mid-path).
435
+ * `undefined` sends `modify` down its normal pipeline, which owns the
436
+ * insert/append/remove semantics and the typed navigation errors — this
437
+ * helper never throws and never guesses.
438
+ */
439
+ function findExistingTarget(contents, path) {
440
+ if (contents === null || path.length === 0) return void 0;
441
+ let current = contents;
442
+ for (let depth = 0; depth < path.length; depth++) {
443
+ const segment = path[depth];
444
+ const isLast = depth === path.length - 1;
445
+ if (current instanceof YamlMap) {
446
+ const pair = current.items.find((p) => p.key instanceof YamlScalar && p.key.value === segment);
447
+ if (pair === void 0) return void 0;
448
+ if (isLast) return {
449
+ node: pair.value,
450
+ inFlow: current.style === "flow"
451
+ };
452
+ if (pair.value === null) return void 0;
453
+ current = pair.value;
454
+ } else if (current instanceof YamlSeq) {
455
+ const idx = typeof segment === "number" ? segment : Number(segment);
456
+ if (Number.isNaN(idx) || idx < 0 || idx >= current.items.length) return void 0;
457
+ const child = current.items[idx];
458
+ if (isLast) return {
459
+ node: child,
460
+ inFlow: current.style === "flow"
461
+ };
462
+ current = child;
463
+ } else return;
464
+ }
465
+ }
466
+ /**
467
+ * Render the replacement text for a region-confined splice, or `undefined`
468
+ * to fall back. A string keeps the target's quote style when that style can
469
+ * express it (single-quoted escapes only `'`, so newlines/tabs/controls bail
470
+ * out; double-quoted expresses everything); a non-string into a quoted
471
+ * target renders through `stringifyValue` — quoting it would change the
472
+ * resolved type — as does any plain target, so the stringifier's own
473
+ * plain-safety rules decide whether the text needs quotes — under the
474
+ * target's own context, so a flow indicator inside a value spliced into a
475
+ * flow collection is quoted rather than corrupting the collection (#695). A
476
+ * rendering that spans lines (folding, block styles) bails out: the fast
477
+ * path never introduces a line break inside the spliced region.
478
+ */
479
+ function renderRegionalScalarText(target, value, inFlow, options) {
480
+ let rendered;
481
+ if (typeof value === "string" && target.style === "single-quoted") {
482
+ if (isSingleQuoteUnsafe(value)) return void 0;
483
+ rendered = renderSingleQuoted(value);
484
+ } else if (typeof value === "string" && target.style === "double-quoted") rendered = renderDoubleQuoted(value);
485
+ else rendered = stringifyValue(value, {
486
+ ...toStringifyInput(options),
487
+ finalNewline: false,
488
+ inFlow
489
+ });
490
+ if (/[\n\r]/.test(rendered)) return void 0;
491
+ return rendered;
492
+ }
493
+ /**
494
+ * Prove the rendered text means exactly what the caller asked: re-compose a
495
+ * one-pair probe document and compare the resolved scalar with `Object.is`.
496
+ * The probe mirrors the target's context — `k: [rendered]` for a flow
497
+ * parent, so a rendering that would split or close the collection is caught
498
+ * as a structural surprise rather than a quiet corruption. Any parse error,
499
+ * structural surprise, or value drift (a plain rendering that re-resolves
500
+ * as a bool/timestamp instead of the caller's string, say) bails out to the
501
+ * whole-document pipeline.
502
+ */
503
+ function regionalRenderPreservesValue(rendered, value, inFlow) {
504
+ const probeText = inFlow ? `k: [${rendered}]\n` : `k: ${rendered}\n`;
505
+ const { document: probeDoc } = composeFirstDocumentCounted(probeText, {});
506
+ if (probeDoc.errors.some((e) => isFatalCode(e.code))) return false;
507
+ const contents = probeDoc.contents;
508
+ if (!(contents instanceof YamlMap)) return false;
509
+ let node = contents.items.find((p) => p.key instanceof YamlScalar && p.key.value === "k")?.value;
510
+ if (inFlow) {
511
+ if (!(node instanceof YamlSeq) || node.items.length !== 1) return false;
512
+ node = node.items[0];
513
+ }
514
+ if (!(node instanceof YamlScalar)) return false;
515
+ return Object.is(node.value, value);
516
+ }
517
+ /**
518
+ * Attempt the region-confined scalar splice for `modify` (#659): replace
519
+ * only the target scalar's byte range, re-emitting its original quote
520
+ * character and leaving line endings (and every other byte) elsewhere
521
+ * untouched. Returns the single edit, an empty array for a no-op
522
+ * replacement, or `undefined` when any precondition fails — the caller then
523
+ * runs the existing compose → replace → re-stringify → diff pipeline
524
+ * unchanged.
525
+ */
526
+ function tryRegionalScalarEdit(text, doc, path, value, options) {
527
+ if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") return;
528
+ if (options?.defaultScalarStyle !== void 0 || options?.forceDefaultStyles || options?.sortKeys === true || options?.indent !== void 0 || options?.indentSequences !== void 0 || options?.finalNewline !== void 0) return void 0;
529
+ const found = findExistingTarget(doc.contents, path);
530
+ if (found === void 0) return void 0;
531
+ const { node: target, inFlow } = found;
532
+ if (!(target instanceof YamlScalar)) return void 0;
533
+ if (target.length <= 0) return void 0;
534
+ if (target.tag !== void 0 || target.anchor !== void 0) return void 0;
535
+ if (target.style !== "plain" && target.style !== "single-quoted" && target.style !== "double-quoted") return;
536
+ if (target.sourceMultiline === true) return void 0;
537
+ const span = text.slice(target.offset, target.offset + target.length);
538
+ if (/[\n\r]/.test(span)) return void 0;
539
+ const rendered = renderRegionalScalarText(target, value, inFlow, options);
540
+ if (rendered === void 0) return void 0;
541
+ if (rendered === span) return [];
542
+ if (!regionalRenderPreservesValue(rendered, value, inFlow)) return void 0;
543
+ return [{
544
+ offset: target.offset,
545
+ length: target.length,
546
+ content: rendered
547
+ }];
548
+ }
549
+ /**
405
550
  * Formatting and modification statics. Not instantiable.
406
551
  *
407
552
  * @remarks
@@ -482,7 +627,7 @@ var YamlFormat = class YamlFormat {
482
627
  static format(text, range, options) {
483
628
  const formatted = formatDocument(text, options);
484
629
  if (formatted === void 0) return [];
485
- let edits = computeEdits(text, formatted);
630
+ let edits = computeEdits(text, withSourceBom(text, formatted));
486
631
  const effectiveRange = resolveRange(range, options?.range);
487
632
  if (effectiveRange !== void 0) {
488
633
  const rangeStart = effectiveRange.offset;
@@ -519,10 +664,29 @@ var YamlFormat = class YamlFormat {
519
664
  * `stringify` agree on what a given JavaScript value means. (Before
520
665
  * 0.14.0 only scalars were lowered and a mapping or sequence value was
521
666
  * coerced through `String(value)`, writing the literal text
522
- * `[object Object]` into the document — see #642.) Only the surrounding
523
- * document is preserved byte-for-byte; the replacement subtree is
524
- * synthesized, so it carries no comments and takes the stringifier's
525
- * styles.
667
+ * `[object Object]` into the document — see #642.) For a synthesized
668
+ * subtree (an object/array value) only the surrounding document is
669
+ * preserved byte-for-byte; the subtree carries no comments and takes the
670
+ * stringifier's styles.
671
+ *
672
+ * **Scalar replacement is region-confined and quote-preserving (#659).**
673
+ * When the path resolves to an existing single-line `plain`,
674
+ * `single-quoted`, or `double-quoted` scalar with no tag or anchor, and
675
+ * the replacement is a string, number, or boolean, `modify` splices
676
+ * ONLY the target scalar's source span and emits a single edit: a string
677
+ * into a quoted scalar keeps the original quote character, a non-string
678
+ * renders plain (quoting it would change the resolved type), and every
679
+ * byte outside the span — line endings included, so a CRLF document keeps
680
+ * its CRLFs and a same-line trailing comment survives — is untouched. The
681
+ * splice is taken only when the rendered text re-parses to exactly the
682
+ * caller's value and renders as a single line; a no-op replacement yields
683
+ * no edits. Anything else — removals (`value === undefined`), nulls,
684
+ * insertions, object/array values, block or multi-line scalars, tagged or anchored
685
+ * targets, or any document-shaping / explicit-style option
686
+ * (`defaultScalarStyle`, `forceDefaultStyles`, `sortKeys`, `indent`,
687
+ * `indentSequences`, `finalNewline`) — falls back to re-serialising the
688
+ * whole document, which normalises line endings to LF, applies those
689
+ * options, and renders the replacement in the stringifier's styles.
526
690
  *
527
691
  * Two replacement values have no finite rendering and fail typed rather
528
692
  * than hanging or overflowing the stack: one containing a circular
@@ -576,6 +740,8 @@ var YamlFormat = class YamlFormat {
576
740
  length: 0
577
741
  }, text)]
578
742
  });
743
+ const regional = tryRegionalScalarEdit(text, doc, path, value, options);
744
+ if (regional !== void 0) return regional.map((e) => YamlEdit.make(e));
579
745
  let newContents;
580
746
  try {
581
747
  newContents = modifyDocument(doc, path, value);
@@ -596,7 +762,7 @@ var YamlFormat = class YamlFormat {
596
762
  contents: newContents
597
763
  };
598
764
  const formatted = stringifyDocument(outputDoc, toStringifyInput(options));
599
- return computeEdits(text, formatted).map((e) => YamlEdit.make(e));
765
+ return computeEdits(text, withSourceBom(text, formatted)).map((e) => YamlEdit.make(e));
600
766
  });
601
767
  /**
602
768
  * Modify `text` and apply the resulting edits in one step
package/index.d.ts CHANGED
@@ -1230,10 +1230,29 @@ export declare class YamlFormat {
1230
1230
  * `stringify` agree on what a given JavaScript value means. (Before
1231
1231
  * 0.14.0 only scalars were lowered and a mapping or sequence value was
1232
1232
  * coerced through `String(value)`, writing the literal text
1233
- * `[object Object]` into the document — see #642.) Only the surrounding
1234
- * document is preserved byte-for-byte; the replacement subtree is
1235
- * synthesized, so it carries no comments and takes the stringifier's
1236
- * styles.
1233
+ * `[object Object]` into the document — see #642.) For a synthesized
1234
+ * subtree (an object/array value) only the surrounding document is
1235
+ * preserved byte-for-byte; the subtree carries no comments and takes the
1236
+ * stringifier's styles.
1237
+ *
1238
+ * **Scalar replacement is region-confined and quote-preserving (#659).**
1239
+ * When the path resolves to an existing single-line `plain`,
1240
+ * `single-quoted`, or `double-quoted` scalar with no tag or anchor, and
1241
+ * the replacement is a string, number, or boolean, `modify` splices
1242
+ * ONLY the target scalar's source span and emits a single edit: a string
1243
+ * into a quoted scalar keeps the original quote character, a non-string
1244
+ * renders plain (quoting it would change the resolved type), and every
1245
+ * byte outside the span — line endings included, so a CRLF document keeps
1246
+ * its CRLFs and a same-line trailing comment survives — is untouched. The
1247
+ * splice is taken only when the rendered text re-parses to exactly the
1248
+ * caller's value and renders as a single line; a no-op replacement yields
1249
+ * no edits. Anything else — removals (`value === undefined`), nulls,
1250
+ * insertions, object/array values, block or multi-line scalars, tagged or anchored
1251
+ * targets, or any document-shaping / explicit-style option
1252
+ * (`defaultScalarStyle`, `forceDefaultStyles`, `sortKeys`, `indent`,
1253
+ * `indentSequences`, `finalNewline`) — falls back to re-serialising the
1254
+ * whole document, which normalises line endings to LF, applies those
1255
+ * options, and renders the replacement in the stringifier's styles.
1237
1256
  *
1238
1257
  * Two replacement values have no finite rendering and fail typed rather
1239
1258
  * than hanging or overflowing the stack: one containing a circular
@@ -150,10 +150,17 @@ function rawCommentText(source) {
150
150
  function joinComments(a, b) {
151
151
  return a === void 0 ? b : `${a}\n${b}`;
152
152
  }
153
- /** Zero-based column of `offset` within its line. */
153
+ /**
154
+ * Zero-based column of `offset` within its line. A byte-order mark at the
155
+ * line start occupies no column — the lexer's convention (#694) — so a
156
+ * BOM-prefixed root mapping's content column is `0`, not `1`, and its
157
+ * terminal own-line comment is not mistaken for one escaping a nested map.
158
+ */
154
159
  function columnAt(text, offset) {
155
160
  if (offset <= 0) return 0;
156
- return offset - (text.lastIndexOf("\n", offset - 1) + 1);
161
+ const lineStart = text.lastIndexOf("\n", offset - 1) + 1;
162
+ const bom = text[lineStart] === "" && offset > lineStart ? 1 : 0;
163
+ return offset - lineStart - bom;
157
164
  }
158
165
  /**
159
166
  * Rebuild `node` with the given comment fields merged in (existing fields are
@@ -9,6 +9,11 @@ function getLineStarts(text) {
9
9
  lineStartsCache = starts;
10
10
  return starts;
11
11
  }
12
+ /**
13
+ * Zero-based line and column of `offset`. A byte-order mark at the line start
14
+ * occupies no column, matching the lexer and `columnAt` (#694), so a
15
+ * diagnostic's `character` behind a BOM equals the BOM-less document's.
16
+ */
12
17
  function lineCol(text, offset) {
13
18
  const starts = getLineStarts(text);
14
19
  const pos = Math.min(Math.max(offset, 0), text.length);
@@ -19,9 +24,11 @@ function lineCol(text, offset) {
19
24
  if (starts[mid] <= pos) lo = mid;
20
25
  else hi = mid - 1;
21
26
  }
27
+ const lineStart = starts[lo];
28
+ const bom = text[lineStart] === "" && pos > lineStart ? 1 : 0;
22
29
  return {
23
30
  line: lo,
24
- column: pos - starts[lo]
31
+ column: pos - lineStart - bom
25
32
  };
26
33
  }
27
34
  /**
package/internal/lexer.js CHANGED
@@ -665,10 +665,8 @@ function createScanner(text) {
665
665
  const ch = peek();
666
666
  if (ch === "") {
667
667
  const start = pos;
668
- const sLine = line;
669
- const sCol = col;
670
- advance();
671
- return makeToken("byte-order-mark", "", start, sLine, sCol);
668
+ pos++;
669
+ return makeToken("byte-order-mark", "", start, line, col);
672
670
  }
673
671
  if (isNewline(ch)) return scanNewline();
674
672
  if (isWhitespace(ch)) return scanWhitespace();
@@ -922,7 +920,7 @@ function createScanner(text) {
922
920
  if (text[pos] === "\n") {
923
921
  line++;
924
922
  col = 0;
925
- } else col++;
923
+ } else if (text[pos] !== "") col++;
926
924
  pos++;
927
925
  }
928
926
  lineIndent = 0;
@@ -133,10 +133,17 @@ function wouldBeResolved11(s) {
133
133
  * `quoteCompat` is strictly additive: `"yaml-1.1"` additionally quotes
134
134
  * strings a YAML 1.1 parser would coerce ({@link wouldBeResolved11}) and can
135
135
  * never un-quote anything the 1.2 rules require quoted.
136
+ *
137
+ * `inFlow` is additive too: inside a flow collection the flow indicators
138
+ * `,[]{}` are plain-unsafe anywhere in the scalar (YAML 1.2 §7.3.3,
139
+ * `ns-plain-safe(flow-in)` excludes `c-flow-indicator`), where block context
140
+ * lets them through. Without it a plain `p, q` rendered into `[x, y]` reads
141
+ * back as three items (#695).
136
142
  */
137
- function requiresQuoting(s, ignoreType = false, quoteCompat) {
143
+ function requiresQuoting(s, ignoreType = false, quoteCompat, inFlow = false) {
138
144
  if (s === "") return true;
139
145
  if (s.includes("\n")) return true;
146
+ if (inFlow && /[,[\]{}]/.test(s)) return true;
140
147
  if (!ignoreType && wouldBeResolved(s)) return true;
141
148
  if (!ignoreType && quoteCompat === "yaml-1.1" && wouldBeResolved11(s)) return true;
142
149
  const first = s[0];
@@ -219,7 +226,7 @@ function renderSingleQuoted(s) {
219
226
  * Block literal and block folded styles are always accepted for single-line
220
227
  * strings even though the output is unusual.
221
228
  */
222
- function renderString(s, style, indent, ignoreType = false, canonical = false, explicitChomp, parentPosition, quoteStyle = "single", explicitIndent, quoteCompat) {
229
+ function renderString(s, style, indent, ignoreType = false, canonical = false, explicitChomp, parentPosition, quoteStyle = "single", explicitIndent, quoteCompat, inFlow = false) {
223
230
  const fidelity = !canonical;
224
231
  const fidelityIndent = fidelity ? explicitIndent : void 0;
225
232
  if (s.includes("\n")) {
@@ -252,7 +259,7 @@ function renderString(s, style, indent, ignoreType = false, canonical = false, e
252
259
  if (s === "" && (style === "block-literal" || style === "block-folded")) return renderDoubleQuoted(s, canonical);
253
260
  switch (style) {
254
261
  case "plain":
255
- if (requiresQuoting(s, ignoreType, quoteCompat)) {
262
+ if (requiresQuoting(s, ignoreType, quoteCompat, inFlow)) {
256
263
  if (s.includes(" ") || s.includes("\r")) return renderDoubleQuoted(s, canonical);
257
264
  for (let i = 0; i < s.length; i++) if (isControlChar(s.charCodeAt(i))) return renderDoubleQuoted(s, canonical);
258
265
  return quoteStyle === "double" ? renderDoubleQuoted(s, canonical) : renderSingleQuoted(s);
@@ -313,7 +320,8 @@ function createContext(options) {
313
320
  quoteStyle: options?.quoteStyle ?? "single",
314
321
  quoteCompat: options?.quoteCompat,
315
322
  forceDefaultStyles: options?.forceDefaultStyles ?? false,
316
- seen: /* @__PURE__ */ new Set()
323
+ seen: /* @__PURE__ */ new Set(),
324
+ inFlow: options?.inFlow ?? false
317
325
  };
318
326
  }
319
327
  /**
@@ -331,7 +339,7 @@ function stringifyLines(value, ctx, depth, allowFold = true) {
331
339
  if (typeof value === "bigint") return [value.toString()];
332
340
  if (typeof value === "string") {
333
341
  const indentStr = " ".repeat(ctx.indent);
334
- const rendered = renderString(value, ctx.defaultScalarStyle, indentStr, false, ctx.forceDefaultStyles, void 0, void 0, ctx.quoteStyle, void 0, ctx.quoteCompat);
342
+ const rendered = renderString(value, ctx.defaultScalarStyle, indentStr, false, ctx.forceDefaultStyles, void 0, void 0, ctx.quoteStyle, void 0, ctx.quoteCompat, ctx.inFlow);
335
343
  return (allowFold && ctx.lineWidth > 0 ? foldRenderedScalar(rendered, indentStr, ctx.lineWidth) : rendered).split("\n");
336
344
  }
337
345
  if (Array.isArray(value)) {
@@ -359,7 +367,13 @@ function stringifyLines(value, ctx, depth, allowFold = true) {
359
367
  */
360
368
  function stringifyArrayLines(arr, ctx, depth) {
361
369
  if (arr.length === 0) return ["[]"];
362
- if (ctx.defaultCollectionStyle === "flow") return [`[${arr.map((item) => stringifyLines(item, ctx, depth + 1, false).join(" ")).join(", ")}]`];
370
+ if (ctx.defaultCollectionStyle === "flow") {
371
+ const flowCtx = {
372
+ ...ctx,
373
+ inFlow: true
374
+ };
375
+ return [`[${arr.map((item) => stringifyLines(item, flowCtx, depth + 1, false).join(" ")).join(", ")}]`];
376
+ }
363
377
  const pad = " ".repeat(ctx.indent);
364
378
  const lines = [];
365
379
  for (const item of arr) {
@@ -396,10 +410,16 @@ function stringifyObjectLines(obj, ctx, depth) {
396
410
  const keys = Object.keys(obj);
397
411
  if (keys.length === 0) return ["{}"];
398
412
  if (ctx.sortKeys) keys.sort();
399
- const renderPlainKey = (k) => renderString(k, "plain", "", false, false, void 0, void 0, ctx.quoteStyle, void 0, ctx.quoteCompat);
400
- if (ctx.defaultCollectionStyle === "flow") return [`{${keys.map((k) => {
401
- return `${renderPlainKey(k)}: ${stringifyLines(obj[k], ctx, depth + 1, false).join(" ")}`;
402
- }).join(", ")}}`];
413
+ const renderPlainKey = (k, inFlow = false) => renderString(k, "plain", "", false, false, void 0, void 0, ctx.quoteStyle, void 0, ctx.quoteCompat, inFlow);
414
+ if (ctx.defaultCollectionStyle === "flow") {
415
+ const flowCtx = {
416
+ ...ctx,
417
+ inFlow: true
418
+ };
419
+ return [`{${keys.map((k) => {
420
+ return `${renderPlainKey(k, true)}: ${stringifyLines(obj[k], flowCtx, depth + 1, false).join(" ")}`;
421
+ }).join(", ")}}`];
422
+ }
403
423
  const renderKey = (k) => k.includes("\n") ? renderDoubleQuoted(k, ctx.forceDefaultStyles) : renderPlainKey(k);
404
424
  const pad = " ".repeat(ctx.indent);
405
425
  const lines = [];
@@ -607,7 +627,7 @@ function stringifyScalarNodeLines(node, ctx) {
607
627
  else lines = ["null"];
608
628
  } else if (typeof val === "boolean") lines = [val ? "true" : "false"];
609
629
  else if (typeof val === "number") lines = [node.raw !== void 0 ? node.raw : renderNumber(val)];
610
- else if (typeof val === "string") lines = renderString(val, style, " ".repeat(ctx.indent), !!node.tag, ctx.forceDefaultStyles, node.chomp, ctx.parentPosition, ctx.quoteStyle, node.blockIndent, ctx.quoteCompat).split("\n");
630
+ else if (typeof val === "string") lines = renderString(val, style, " ".repeat(ctx.indent), !!node.tag, ctx.forceDefaultStyles, node.chomp, ctx.parentPosition, ctx.quoteStyle, node.blockIndent, ctx.quoteCompat, ctx.inFlow).split("\n");
611
631
  else lines = [renderDoubleQuoted(String(val))];
612
632
  if (node.tag) lines[0] = `${node.tag} ${lines[0]}`;
613
633
  if (node.anchor) lines[0] = `&${node.anchor} ${lines[0]}`;
@@ -797,6 +817,10 @@ function stringifyMapNodeLines(node, ctx, depth) {
797
817
  }
798
818
  if (style === "flow") {
799
819
  const flowPrefix = buildMetadataPrefix(node.tag, node.anchor);
820
+ const flowCtx = {
821
+ ...ctx,
822
+ inFlow: true
823
+ };
800
824
  if (!ctx.forceDefaultStyles && (items.some((p) => pairLeading(p) !== void 0 || pairTrailing(p) !== void 0 || pairSpaceBefore(p) === true) || node.comment !== void 0 && node.sourceMultiline === true)) {
801
825
  const flowPad = " ".repeat(ctx.indent);
802
826
  const flowLines = [flowPrefix ? `${flowPrefix} {` : "{"];
@@ -805,8 +829,8 @@ function stringifyMapNodeLines(node, ctx, depth) {
805
829
  if (pairSpaceBefore(pair) === true) flowLines.push("");
806
830
  const flowLeading = pairLeading(pair);
807
831
  if (flowLeading !== void 0) for (const cl of commentBlockLines(flowLeading)) flowLines.push(cl === "" ? "" : `${flowPad}${cl}`);
808
- const keyStr = pair.key ? stringifyMappingKeyLines(pair.key, ctx, depth + 1).join(" ") : "null";
809
- const valStr = pair.value ? stringifyNodeLines(pair.value, ctx, depth + 1).join(" ") : "null";
832
+ const keyStr = pair.key ? stringifyMappingKeyLines(pair.key, flowCtx, depth + 1).join(" ") : "null";
833
+ const valStr = pair.value ? stringifyNodeLines(pair.value, flowCtx, depth + 1).join(" ") : "null";
810
834
  const comma = fi < items.length - 1 ? "," : "";
811
835
  const flowTrailing = pairTrailing(pair);
812
836
  const trailing = flowTrailing !== void 0 ? renderTrailingComment(flowTrailing) : "";
@@ -817,7 +841,7 @@ function stringifyMapNodeLines(node, ctx, depth) {
817
841
  return flowLines;
818
842
  }
819
843
  let line = `{${items.map((pair) => {
820
- return `${pair.key ? stringifyMappingKeyLines(pair.key, ctx, depth + 1).join(" ") : "null"}: ${pair.value ? stringifyNodeLines(pair.value, ctx, depth + 1).join(" ") : "null"}`;
844
+ return `${pair.key ? stringifyMappingKeyLines(pair.key, flowCtx, depth + 1).join(" ") : "null"}: ${pair.value ? stringifyNodeLines(pair.value, flowCtx, depth + 1).join(" ") : "null"}`;
821
845
  }).join(", ")}}`;
822
846
  if (flowPrefix) line = `${flowPrefix} ${line}`;
823
847
  return [line];
@@ -1006,6 +1030,10 @@ function stringifySeqNodeLines(node, ctx, depth) {
1006
1030
  };
1007
1031
  if (style === "flow") {
1008
1032
  const flowPrefix = buildMetadataPrefix(node.tag, node.anchor);
1033
+ const flowCtx = {
1034
+ ...ctx,
1035
+ inFlow: true
1036
+ };
1009
1037
  if (emitComments && (items.some((it) => itemFields(it) !== void 0) || node.comment !== void 0 && node.sourceMultiline === true)) {
1010
1038
  const flowPad = " ".repeat(ctx.indent);
1011
1039
  const flowLines = [flowPrefix ? `${flowPrefix} [` : "["];
@@ -1014,7 +1042,7 @@ function stringifySeqNodeLines(node, ctx, depth) {
1014
1042
  const fields = itemFields(item);
1015
1043
  if (fields?.spaceBefore === true) flowLines.push("");
1016
1044
  if (fields?.commentBefore !== void 0) for (const cl of commentBlockLines(fields.commentBefore)) flowLines.push(cl === "" ? "" : `${flowPad}${cl}`);
1017
- const itemStr = stringifyNodeLines(item, ctx, depth + 1).join(" ");
1045
+ const itemStr = stringifyNodeLines(item, flowCtx, depth + 1).join(" ");
1018
1046
  const comma = fi < items.length - 1 ? "," : "";
1019
1047
  const trailing = fields?.comment !== void 0 ? renderTrailingComment(fields.comment) : "";
1020
1048
  flowLines.push(`${flowPad}${itemStr}${comma}${trailing}`);
@@ -1023,7 +1051,7 @@ function stringifySeqNodeLines(node, ctx, depth) {
1023
1051
  flowLines.push("]");
1024
1052
  return flowLines;
1025
1053
  }
1026
- let line = `[${items.map((item) => stringifyNodeLines(item, ctx, depth + 1).join(" ")).join(", ")}]`;
1054
+ let line = `[${items.map((item) => stringifyNodeLines(item, flowCtx, depth + 1).join(" ")).join(", ")}]`;
1027
1055
  if (flowPrefix) line = `${flowPrefix} ${line}`;
1028
1056
  return [line];
1029
1057
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/yaml",
3
- "version": "0.15.0",
3
+ "version": "0.15.1",
4
4
  "private": false,
5
5
  "description": "Zero-dependency YAML parsing, editing, formatting and linting as Effect schemas.",
6
6
  "keywords": [