@effected/yaml 0.14.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/Yaml.js +2 -2
- package/YamlDocument.js +1 -1
- package/YamlFormat.js +173 -7
- package/index.d.ts +23 -4
- package/internal/composer/comments.js +9 -2
- package/internal/composer/state.js +8 -1
- package/internal/lexer.js +3 -5
- package/internal/stringifier.js +44 -16
- package/package.json +2 -2
- package/tsdoc-metadata.json +1 -1
package/Yaml.js
CHANGED
|
@@ -592,7 +592,7 @@ var Yaml = class Yaml {
|
|
|
592
592
|
* paths; for the default-options case use {@link Yaml.YamlFromString}.
|
|
593
593
|
*/
|
|
594
594
|
static fromString(options) {
|
|
595
|
-
return Schema.String.pipe(Schema.decodeTo(Schema.Unknown, SchemaTransformation.
|
|
595
|
+
return Schema.String.pipe(Schema.decodeTo(Schema.Unknown, SchemaTransformation.transformEffect({
|
|
596
596
|
decode: (input) => Yaml.parse(input, options).pipe(Effect.mapError((error) => new SchemaIssue.InvalidValue({ message: error.message }, input))),
|
|
597
597
|
encode: (value) => stringifyOrFail(value).pipe(Effect.mapError((error) => new SchemaIssue.InvalidValue({ message: error.message }, value)))
|
|
598
598
|
})));
|
|
@@ -612,7 +612,7 @@ var Yaml = class Yaml {
|
|
|
612
612
|
* {@link Yaml.fromString}).
|
|
613
613
|
*/
|
|
614
614
|
static allFromString(options) {
|
|
615
|
-
return Schema.String.pipe(Schema.decodeTo(Schema.Array(Schema.Unknown), SchemaTransformation.
|
|
615
|
+
return Schema.String.pipe(Schema.decodeTo(Schema.Array(Schema.Unknown), SchemaTransformation.transformEffect({
|
|
616
616
|
decode: (input) => Yaml.parseAll(input, options).pipe(Effect.mapError((error) => new SchemaIssue.InvalidValue({ message: error.message }, input))),
|
|
617
617
|
encode: (values) => Effect.gen(function* () {
|
|
618
618
|
if (values.length === 0) return "";
|
package/YamlDocument.js
CHANGED
|
@@ -86,7 +86,7 @@ var YamlDocument = class YamlDocument extends Schema.Class("YamlDocument")({
|
|
|
86
86
|
* paths.
|
|
87
87
|
*/
|
|
88
88
|
static schema(options) {
|
|
89
|
-
return Schema.String.pipe(Schema.decodeTo(Schema.instanceOf(YamlDocument), SchemaTransformation.
|
|
89
|
+
return Schema.String.pipe(Schema.decodeTo(Schema.instanceOf(YamlDocument), SchemaTransformation.transformEffect({
|
|
90
90
|
decode: (input) => YamlDocument.parse(input, options).pipe(Effect.mapError((error) => new SchemaIssue.InvalidValue({ message: error.message }, input))),
|
|
91
91
|
encode: (doc) => doc.stringify().pipe(Effect.mapError((error) => new SchemaIssue.InvalidValue({ message: error.message }, doc)))
|
|
92
92
|
})));
|
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.)
|
|
523
|
-
*
|
|
524
|
-
*
|
|
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.)
|
|
1234
|
-
*
|
|
1235
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
|
|
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 -
|
|
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
|
-
|
|
669
|
-
|
|
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;
|
package/internal/stringifier.js
CHANGED
|
@@ -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")
|
|
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")
|
|
401
|
-
|
|
402
|
-
|
|
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,
|
|
809
|
-
const valStr = pair.value ? stringifyNodeLines(pair.value,
|
|
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,
|
|
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,
|
|
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,
|
|
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.
|
|
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": [
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"./package.json": "./package.json"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"effect": "4.0.0-rc.
|
|
44
|
+
"effect": "4.0.0-rc.115"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|
|
47
47
|
"node": ">=24.11.0"
|