@effected/yaml 0.7.0 → 0.9.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/README.md +80 -7
- package/Yaml.js +80 -32
- package/YamlDocument.js +23 -2
- package/YamlFormat.js +162 -21
- package/YamlLint.js +239 -0
- package/YamlLintRule.js +36 -0
- package/YamlNode.js +43 -4
- package/YamlToken.js +139 -0
- package/YamlVisitor.js +52 -16
- package/index.d.ts +446 -12
- package/index.js +4 -1
- package/internal/composer/anchors.js +2 -27
- package/internal/composer/block.js +291 -67
- package/internal/composer/comments.js +217 -0
- package/internal/composer/document.js +141 -14
- package/internal/composer/flow.js +103 -10
- package/internal/composer/scalars.js +48 -11
- package/internal/composer/state.js +16 -2
- package/internal/cst-parser.js +2 -1
- package/internal/diagnostics.js +12 -3
- package/internal/fold.js +22 -10
- package/internal/rules/catalog.js +39 -0
- package/internal/rules/colon-spacing.js +77 -0
- package/internal/rules/comments-spacing.js +68 -0
- package/internal/rules/document-end.js +65 -0
- package/internal/rules/document-start.js +64 -0
- package/internal/rules/empty-lines.js +70 -0
- package/internal/rules/eof-newline.js +33 -0
- package/internal/rules/hyphen-spacing.js +52 -0
- package/internal/rules/indentation.js +103 -0
- package/internal/rules/key-duplicates.js +49 -0
- package/internal/rules/line-length.js +36 -0
- package/internal/rules/parse-validity.js +33 -0
- package/internal/rules/quoted-strings.js +81 -0
- package/internal/rules/trailing-spaces.js +39 -0
- package/internal/rules/truthy.js +84 -0
- package/internal/rules/util.js +92 -0
- package/internal/stringifier.js +342 -54
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://nodejs.org/)
|
|
6
6
|
[](https://www.typescriptlang.org/)
|
|
7
7
|
|
|
8
|
-
Zero-dependency YAML 1.2 parsing, editing and
|
|
8
|
+
Zero-dependency YAML 1.2 parsing, editing, formatting and linting expressed as Effect schemas and pure functions. Parse a single document or a multi-document stream into plain values or an offset-preserving AST, resolve anchors and aliases, read and preserve per-node comments, strip comments, compute byte-minimal edits, format, modify by path, walk a document as a `Stream` of AST events or positioned tokens, lint it against a rule catalog with surgical autofix, and decode straight into a validated domain schema.
|
|
9
9
|
|
|
10
10
|
> **Pre-release.** This package is part of the `@effected/*` kit, in pre-`1.0.0`
|
|
11
11
|
> development against a single pinned Effect v4 beta. Packages graduate to
|
|
@@ -106,18 +106,91 @@ Effect.runPromise(Effect.result(Yaml.parse(bomb))).then(console.log);
|
|
|
106
106
|
|
|
107
107
|
Collection nesting past the depth cap behaves the same way, yielding a `NestingDepthExceeded` diagnostic instead of a stack overflow, and `Yaml.stringify` caps the mirror-image recursion when encoding a value back to text. The guarantee is the same everywhere: every fallible entry point carries a real error channel, and nothing reaches your process as a defect.
|
|
108
108
|
|
|
109
|
+
## Comments survive an edit
|
|
110
|
+
|
|
111
|
+
A YAML file a human maintains is mostly explanation, and the explanation is the part a round-trip usually loses. `YamlFormat` computes its edits against the original bytes, so changing a value rewrites the value and leaves the header block, the trailing `# LTS`, the blank line and the indentation where they were. Setting `preserveComments: false` is how you ask for the other behavior:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { YamlFormat, YamlFormattingOptions } from "@effected/yaml";
|
|
115
|
+
|
|
116
|
+
const source = 'node: "20.11.0" # LTS\n\n# why the port\nport: 3000\n';
|
|
117
|
+
|
|
118
|
+
console.log(YamlFormat.formatToString(source));
|
|
119
|
+
// node: "20.11.0" # LTS
|
|
120
|
+
//
|
|
121
|
+
// # why the port
|
|
122
|
+
// port: 3000
|
|
123
|
+
|
|
124
|
+
console.log(YamlFormat.formatToString(source, undefined, YamlFormattingOptions.make({ preserveComments: false })));
|
|
125
|
+
// node: "20.11.0"
|
|
126
|
+
// port: 3000
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Comments are readable as data too. Every node class — `YamlScalar`, `YamlMap`, `YamlSeq` and `YamlAlias` — carries `commentBefore` (the own-line block above the node), `comment` (strictly the trailing comment on the node's own line) and `spaceBefore` (a blank line came first). `YamlPair` carries none of them: an entry is its `key` and its `value`, and the comments live on those two nodes. `YamlDocument` uses the same two names as the nodes do, `commentBefore` for a header block sitting ahead of a `---` marker and `comment` for the trailing block, and `YamlVisitor` tags every `Comment` event with `placement: "leading" | "trailing"`.
|
|
130
|
+
|
|
131
|
+
Which node a comment lands on follows one rule. An own-line comment above an entry leads that entry's key node, setting `commentBefore` and — when a blank line came first — `spaceBefore`. A trailing comment belongs to the last node on its line: in `a: 1 # t` the value ends the line, so `# t` is the value's `comment`; in `push: # only main` the value renders below, so the comment is the key's. Two slots per entry rather than one is what lets `push: # only main` and an own-line comment above the value both round-trip byte-intact, instead of one spelling being rewritten into the other. A header block with no `---` marker to separate it from the item stream leads the first entry's key by the same rule; one after the marker leads the root node.
|
|
132
|
+
|
|
133
|
+
## Lint and autofix
|
|
134
|
+
|
|
135
|
+
`YamlLint.run` checks a string against a rule catalog and returns positioned diagnostics; `YamlLint.fix` applies the fixes that can be expressed as surgical edits and returns the fixed text. Both are pure and synchronous. This is the lint engine, not a runner — no file discovery, no config-file loading, nothing written to disk:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import { YamlLint, YamlLintConfig } from "@effected/yaml";
|
|
139
|
+
import { Result } from "effect";
|
|
140
|
+
|
|
141
|
+
const source = "# pinned\nname: demo \nport: yes\n";
|
|
142
|
+
|
|
143
|
+
for (const d of YamlLint.run(source, YamlLint.builtins, YamlLintConfig.default)) {
|
|
144
|
+
console.log(`${d.line}:${d.character} ${d.severity} ${d.rule} ${d.message}`);
|
|
145
|
+
}
|
|
146
|
+
// 1:5 error colon-spacing Too many spaces after ":" (2 > 1)
|
|
147
|
+
// 1:11 error trailing-spaces Trailing whitespace
|
|
148
|
+
// 2:6 error truthy Truthy value "yes" is not in the allowed spellings
|
|
149
|
+
|
|
150
|
+
const fixed = YamlLint.fix(source, YamlLint.builtins, YamlLintConfig.default);
|
|
151
|
+
console.log(Result.isSuccess(fixed) ? fixed.success : "fatal parse error — not safely fixable");
|
|
152
|
+
// # pinned
|
|
153
|
+
// name: demo
|
|
154
|
+
// port: "yes"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Positions are zero-based throughout, so the first finding above sits on the second line of the source. Fixes route through `YamlEdit.applyAll` rather than a reformat, so an autofix cannot move a comment it was not asked to touch, and a rule whose repair would need reflowing the document ships without a fix at all. `YamlLintConfig.default` and `YamlLintConfig.relaxed` are the presets; compose your own by spreading a preset's `rules` into `YamlLintConfig.make`. Config validation is rule-aware for the built-in catalog — a misspelled option key on a built-in rule or an attempt to switch off the always-on `parse-validity` rule fails schema validation instead of being quietly ignored — while an unknown (custom) rule id passes through with its options treated as opaque, left for the custom rule itself to validate. Custom rules are plain array concatenation — `YamlLint.run(text, [...YamlLint.builtins, myRule], config)` — and configuring `myRule` in that config works precisely because of the pass-through.
|
|
158
|
+
|
|
159
|
+
## Migrating from Prettier
|
|
160
|
+
|
|
161
|
+
`YamlFormat.formatToString(text, undefined, options)` replaces a `prettier --write` over YAML files. The option mapping:
|
|
162
|
+
|
|
163
|
+
| Prettier | `YamlFormattingOptions` |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| `tabWidth` | `indent` |
|
|
166
|
+
| `singleQuote` | `quoteStyle: "single" \| "double"` |
|
|
167
|
+
| indented block sequences (always on) | `indentSequences: true` |
|
|
168
|
+
| `printWidth` / `proseWrap` | no analog — see below |
|
|
169
|
+
| comments (best-effort) | `preserveComments: true` (the default) |
|
|
170
|
+
|
|
171
|
+
Two defaults differ from Prettier's output and are worth setting explicitly. The stringify default is `quoteStyle: "single"` for byte-compatibility with the kit's legacy form, so if you ran Prettier with its own default (`singleQuote: false`), set `quoteStyle: "double"`. Likewise `indentSequences` defaults to `false` — the flat legacy form with the `-` markers at the key's column — while Prettier always indented block sequences one level, so an ex-Prettier consumer almost certainly wants `indentSequences: true`.
|
|
172
|
+
|
|
173
|
+
One expectation to reset when carrying over `singleQuote`: on the format path, `quoteStyle` governs quotes the stringifier *introduces* — it never re-quotes scalars that already carry quotes in your source. Formatting `a: 'x'` with `quoteStyle: "double"` leaves `'x'` untouched; Prettier would have rewritten it to `"x"`. Existing quote styles are source, and the formatter preserves source. If it looks like the option "does nothing" on an already-quoted corpus, this is why — the same class of format-path inertness as `lineWidth` below.
|
|
174
|
+
|
|
175
|
+
`printWidth` and `proseWrap` have no analog here: `lineWidth` folding is a value-path feature only (`Yaml.stringify` / `Yaml.stringifyResult`), and the option is deliberately inert on the format path by contract — `YamlFormat` never reflows scalar content (the `lineWidth` TSDoc on `YamlStringifyOptions` pins this). Comments, which Prettier handled best-effort, are preserved exactly by default.
|
|
176
|
+
|
|
177
|
+
Multi-document streams — a Kubernetes manifest, or the two-document `pnpm-lock.yaml` pnpm 11 writes under `configDependencies` — format whole: every document is re-emitted in order with its own `---`/`...` framing and comments, so no document is ever dropped. Document detection is CST-level, so a `---` inside a block scalar or quoted string is content and formats normally. The formatter leaves input byte-identical rather than corrupting it in exactly two cases: a fatal parse error in any document, and a stream carrying `%YAML`/`%TAG` directives (which the emitter cannot re-emit faithfully). Validate a whole stream with `Yaml.parseAllResult`, which fails typed when any document carries a fatal diagnostic; `YamlFormat.modify` stays single-document, since a path names no particular document of a stream.
|
|
178
|
+
|
|
109
179
|
## Features
|
|
110
180
|
|
|
111
181
|
- `Yaml.parse` / `Yaml.parseAll` — error-recovery parsing of a single document or a `---`-separated stream into plain values, resolving anchors and aliases and aggregating every diagnostic into one `YamlParseError`.
|
|
112
|
-
- `Yaml.stringify` — serialize a plain value back to YAML, failing typed with `YamlStringifyError` on circular references or on excessively deep nesting.
|
|
113
|
-
- `Yaml.parseResult` / `Yaml.stringifyResult` — the pure synchronous counterparts, returning a `Result` instead of an `Effect` for config-time callers that cannot await; each runs the same engine call as its `Effect` variant, so the two forms cannot diverge, and a fatal diagnostic, a duplicate key, an alias bomb or a circular reference still fails typed rather than throwing.
|
|
182
|
+
- `Yaml.stringify` — serialize a plain value back to YAML, failing typed with `YamlStringifyError` on circular references or on excessively deep nesting; a block-mapping key too long to render on one line spills into explicit-key form (`? key` / `: value`) so strict parsers still accept the output.
|
|
183
|
+
- `Yaml.parseResult` / `Yaml.parseAllResult` / `Yaml.stringifyResult` — the pure synchronous counterparts, returning a `Result` instead of an `Effect` for config-time callers that cannot await; each runs the same engine call as its `Effect` variant, so the two forms cannot diverge, and a fatal diagnostic, a duplicate key, an alias bomb or a circular reference still fails typed rather than throwing. `parseAllResult` doubles as a whole-stream validity check: it fails when any document in the stream is invalid.
|
|
114
184
|
- `Yaml.stripComments` — quote-aware comment removal that keeps line numbers stable, or every byte offset stable when given a replacement character.
|
|
115
185
|
- `Yaml.equals` / `Yaml.equalsValue` — semantic equality that ignores comments, whitespace, formatting and mapping key order while keeping sequence order significant.
|
|
116
186
|
- `Yaml.schema` / `Yaml.fromString` / `Yaml.YamlFromString` / `Yaml.allFromString` — string→domain schema factories that decode a single document or a whole stream into a validated Effect `Schema` value.
|
|
117
|
-
- `YamlNode` — an offset-preserving AST (`YamlScalar`, `YamlMap`, `YamlSeq`, `YamlPair`, `YamlAlias`) for locating and reading nodes by position or path
|
|
118
|
-
- `YamlDocument` — the full parsed AST plus the recovered `errors` and `warnings` arrays, so a partially valid document is still inspectable.
|
|
119
|
-
- `YamlEdit` / `YamlFormat` — compute byte-minimal edit arrays for formatting and path-based modification, so a change preserves every comment and byte you did not touch.
|
|
120
|
-
- `YamlVisitor` — walk a parsed document as a `Stream` of a tagged-enum event union, with `Stream.take` early termination on large inputs.
|
|
187
|
+
- `YamlNode` — an offset-preserving AST (`YamlScalar`, `YamlMap`, `YamlSeq`, `YamlPair`, `YamlAlias`) for locating and reading nodes by position or path; every node class except `YamlPair` carries its own `commentBefore`, `comment` and `spaceBefore`, and a pair is just its `key` and `value`.
|
|
188
|
+
- `YamlDocument` — the full parsed AST plus the recovered `errors` and `warnings` arrays, so a partially valid document is still inspectable, and the header and trailing comment blocks as `commentBefore` / `comment`, the same two names the node classes use.
|
|
189
|
+
- `YamlEdit` / `YamlFormat` — compute byte-minimal edit arrays for formatting and path-based modification, so a change preserves every comment and byte you did not touch; `preserveComments: false` opts out. `format` handles multi-document streams whole, re-emitting every document and its framing; `modify` is single-document by contract (a path names no particular document of a stream) and fails typed with `MultiDocumentStream` rather than guessing.
|
|
190
|
+
- `YamlVisitor` — walk a parsed document as a `Stream` of a tagged-enum event union, with `Stream.take` early termination on large inputs and a `placement` on every comment event.
|
|
191
|
+
- `YamlToken` / `YamlTokens` — the positioned token stream the lint rules read: `tokenize` is a synchronous `Result` that always succeeds, surfacing lexical problems as `error`-kind tokens, and `stream` derives an Effect `Stream` from the same walk.
|
|
192
|
+
- `YamlLint` — `run`, `fix` and the `builtins` catalog: 14 rules covering parse validity, line length, trailing whitespace, blank lines, end-of-file newline, document markers, duplicate keys, quoting, truthy spellings, comment and colon and hyphen spacing, and indentation.
|
|
193
|
+
- `YamlLintConfig` / `YamlLintRule` / `YamlLintDiagnostic` — the `default` and `relaxed` presets, a config schema validated against the rule catalog, and the model a custom rule implements.
|
|
121
194
|
- `YamlParseError` / `YamlStringifyError` / `YamlModificationError` — tagged errors carrying structured, positional `YamlDiagnostic` arrays rather than opaque messages.
|
|
122
195
|
|
|
123
196
|
## License
|
package/Yaml.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { AliasExpansionBudgetExceeded, CollectionStyle, QuoteStyle, ScalarStyle, nodeToJsValue } from "./YamlNode.js";
|
|
2
|
-
import { buildAnchorMap } from "./internal/composer/anchors.js";
|
|
3
2
|
import { composeAllDocuments, composeFirstDocument } from "./internal/composer/document.js";
|
|
4
3
|
import { isFatalCode } from "./internal/diagnostics.js";
|
|
5
4
|
import { StringifyDepthExceeded, StringifyFailure, stringifyValue } from "./internal/stringifier.js";
|
|
@@ -122,7 +121,9 @@ var YamlStringifyOptions = class extends Schema.Class("YamlStringifyOptions")({
|
|
|
122
121
|
* Error-recovery parse failure: aggregates every fatal {@link YamlDiagnostic}
|
|
123
122
|
* encountered, so a single failure reports the whole batch. Raised by
|
|
124
123
|
* {@link Yaml.parse}, {@link Yaml.parseAll}, `YamlDocument.parse`/`parseAll`
|
|
125
|
-
* and the decode direction of the schema factories.
|
|
124
|
+
* and the decode direction of the schema factories. The error itself has no
|
|
125
|
+
* `code` field: read the code from the diagnostics —
|
|
126
|
+
* `error.diagnostics[0].code` is the primary failure.
|
|
126
127
|
*
|
|
127
128
|
* @public
|
|
128
129
|
*/
|
|
@@ -140,7 +141,8 @@ var YamlParseError = class extends Schema.TaggedError()("YamlParseError", {
|
|
|
140
141
|
* Stringification failure (the circular-reference guard), carrying structured
|
|
141
142
|
* {@link YamlDiagnostic} entries and the offending value. Raised by
|
|
142
143
|
* {@link Yaml.stringify}, `YamlDocument#stringify` and the encode direction of
|
|
143
|
-
* the schema factories.
|
|
144
|
+
* the schema factories. The error itself has no `code` field: read the code
|
|
145
|
+
* from the diagnostics — `error.diagnostics[0].code` is the primary failure.
|
|
144
146
|
*
|
|
145
147
|
* @public
|
|
146
148
|
*/
|
|
@@ -238,6 +240,35 @@ const parseResultImpl = (text, options) => {
|
|
|
238
240
|
}
|
|
239
241
|
};
|
|
240
242
|
/**
|
|
243
|
+
* Synchronous multi-document parse returning a `Result`. Mirrors
|
|
244
|
+
* {@link parseResultImpl} over the whole stream: stream-level
|
|
245
|
+
* directive-placement errors and every document's failure records aggregate
|
|
246
|
+
* into one {@link YamlParseError}; each document's value is extracted against
|
|
247
|
+
* its own independent anchor map (anchors are document-scoped in a stream),
|
|
248
|
+
* with the alias-expansion budget materialized as a typed failure.
|
|
249
|
+
*/
|
|
250
|
+
const parseAllResultImpl = (text, options) => {
|
|
251
|
+
const { documents, streamErrors } = composeAllDocuments(text, toParseInput(options));
|
|
252
|
+
const uniqueKeys = options?.uniqueKeys ?? true;
|
|
253
|
+
const failures = [...streamErrors.filter((e) => isFatalCode(e.code)), ...documents.flatMap((d) => failureRecords(d, uniqueKeys))];
|
|
254
|
+
if (failures.length > 0) return Result.fail(new YamlParseError({
|
|
255
|
+
diagnostics: toDiagnostics(text, failures),
|
|
256
|
+
input: text
|
|
257
|
+
}));
|
|
258
|
+
const maxAliasCount = options?.maxAliasCount ?? 100;
|
|
259
|
+
const values = [];
|
|
260
|
+
for (const d of documents) {
|
|
261
|
+
const anchors = /* @__PURE__ */ new Map();
|
|
262
|
+
try {
|
|
263
|
+
values.push(nodeToJsValue(d.contents, anchors, maxAliasCount));
|
|
264
|
+
} catch (defect) {
|
|
265
|
+
if (defect instanceof AliasExpansionBudgetExceeded) return Result.fail(aliasCountExceededError(defect.message, text));
|
|
266
|
+
throw defect;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return Result.succeed(values);
|
|
270
|
+
};
|
|
271
|
+
/**
|
|
241
272
|
* Synchronous stringify returning a `Result`. Mirrors {@link Yaml.stringify}
|
|
242
273
|
* without the Effect wrapper: a circular reference or a value nested past the
|
|
243
274
|
* recursion budget yields a `Failure` carrying a typed {@link YamlStringifyError},
|
|
@@ -263,19 +294,6 @@ const failureRecords = (doc, uniqueKeys) => {
|
|
|
263
294
|
if (fatal.length > 0) return fatal;
|
|
264
295
|
return uniqueKeys ? doc.warnings.filter((w) => w.code === "DuplicateKey") : [];
|
|
265
296
|
};
|
|
266
|
-
/**
|
|
267
|
-
* Extract a document's plain-JS value with an alias-expansion budget derived
|
|
268
|
-
* from `maxAliasCount`, materializing a "billion laughs" blow-up as a typed
|
|
269
|
-
* fatal {@link YamlParseError} (`AliasCountExceeded`) instead of letting the
|
|
270
|
-
* heap-exhausting expansion escape as an unhandled defect.
|
|
271
|
-
*/
|
|
272
|
-
const extractDocumentValue = (contents, anchors, maxAliasCount, text) => Effect.try({
|
|
273
|
-
try: () => nodeToJsValue(contents, anchors, maxAliasCount),
|
|
274
|
-
catch: (defect) => {
|
|
275
|
-
if (defect instanceof AliasExpansionBudgetExceeded) return aliasCountExceededError(defect.message, text);
|
|
276
|
-
throw defect;
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
297
|
const stringifyOrFail = (value, options) => Effect.try({
|
|
280
298
|
try: () => stringifyValue(value, toStringifyInput(options)),
|
|
281
299
|
catch: (defect) => {
|
|
@@ -334,23 +352,11 @@ var Yaml = class Yaml {
|
|
|
334
352
|
* A "billion laughs" alias-expansion blow-up in any document also fails
|
|
335
353
|
* through {@link YamlParseError} with an `AliasCountExceeded` diagnostic,
|
|
336
354
|
* never as an unhandled defect.
|
|
355
|
+
*
|
|
356
|
+
* Defined in terms of {@link Yaml.parseAllResult} — synchronous callers
|
|
357
|
+
* can use that variant directly.
|
|
337
358
|
*/
|
|
338
|
-
static parseAll = Effect.fn("Yaml.parseAll")(
|
|
339
|
-
const { documents, streamErrors } = composeAllDocuments(text, toParseInput(options));
|
|
340
|
-
const uniqueKeys = options?.uniqueKeys ?? true;
|
|
341
|
-
const failures = [...streamErrors.filter((e) => e.code === "InvalidDirective"), ...documents.flatMap((d) => failureRecords(d, uniqueKeys))];
|
|
342
|
-
if (failures.length > 0) return yield* new YamlParseError({
|
|
343
|
-
diagnostics: toDiagnostics(text, failures),
|
|
344
|
-
input: text
|
|
345
|
-
});
|
|
346
|
-
const maxAliasCount = options?.maxAliasCount ?? 100;
|
|
347
|
-
const values = [];
|
|
348
|
-
for (const d of documents) {
|
|
349
|
-
const anchors = buildAnchorMap(d.contents);
|
|
350
|
-
values.push(yield* extractDocumentValue(d.contents, anchors, maxAliasCount, text));
|
|
351
|
-
}
|
|
352
|
-
return values;
|
|
353
|
-
});
|
|
359
|
+
static parseAll = Effect.fn("Yaml.parseAll")((text, options) => Effect.fromResult(Yaml.parseAllResult(text, options)));
|
|
354
360
|
/**
|
|
355
361
|
* Stringify a plain JavaScript value as YAML. Fails with
|
|
356
362
|
* {@link YamlStringifyError} on circular references (`CircularReference`)
|
|
@@ -406,6 +412,48 @@ var Yaml = class Yaml {
|
|
|
406
412
|
return parseResultImpl(text, options);
|
|
407
413
|
}
|
|
408
414
|
/**
|
|
415
|
+
* Synchronous multi-document parse, returning a `Result` instead of an
|
|
416
|
+
* `Effect` — the {@link Yaml.parseResult} counterpart to
|
|
417
|
+
* {@link Yaml.parseAll}. Empty input succeeds with `[null]` (the engine
|
|
418
|
+
* reads `""` as one empty document, exactly as {@link Yaml.parse} yields
|
|
419
|
+
* `null` for it); a single-document stream succeeds with a one-element
|
|
420
|
+
* array whose value is exactly what {@link Yaml.parseResult} yields.
|
|
421
|
+
*
|
|
422
|
+
* @remarks
|
|
423
|
+
* This is the package's single multi-document parse path.
|
|
424
|
+
* {@link Yaml.parseAll} is defined in terms of it (`Effect.fromResult`
|
|
425
|
+
* behind the named span), so the two variants cannot diverge. Anchors are
|
|
426
|
+
* document-scoped: each document's aliases resolve against its own anchor
|
|
427
|
+
* map, never a neighbor's.
|
|
428
|
+
*
|
|
429
|
+
* Fails with the aggregate {@link YamlParseError} when **any** document in
|
|
430
|
+
* the stream carries a fatal diagnostic (or a stream-level
|
|
431
|
+
* directive-placement error), which makes it a whole-stream validity
|
|
432
|
+
* check: a `Success` means every document parsed clean. Preserves the
|
|
433
|
+
* package contract — malformed and adversarial input (including a
|
|
434
|
+
* "billion laughs" alias bomb in any document) fails typed, never as a
|
|
435
|
+
* defect; the method never throws.
|
|
436
|
+
*
|
|
437
|
+
* @example
|
|
438
|
+
* ```ts
|
|
439
|
+
* import { Yaml } from "@effected/yaml";
|
|
440
|
+
* import { Result } from "effect";
|
|
441
|
+
*
|
|
442
|
+
* // Whole-stream validity check: fails if ANY document is invalid.
|
|
443
|
+
* const result = Yaml.parseAllResult("a: 1\n---\nb: 2\n");
|
|
444
|
+
* if (Result.isSuccess(result)) {
|
|
445
|
+
* result.success; // [{ a: 1 }, { b: 2 }]
|
|
446
|
+
* } else {
|
|
447
|
+
* result.failure; // YamlParseError aggregating every fatal diagnostic
|
|
448
|
+
* }
|
|
449
|
+
* ```
|
|
450
|
+
*
|
|
451
|
+
* @public
|
|
452
|
+
*/
|
|
453
|
+
static parseAllResult(text, options) {
|
|
454
|
+
return parseAllResultImpl(text, options);
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
409
457
|
* Synchronous stringify, returning a `Result` instead of an `Effect`.
|
|
410
458
|
* The pure counterpart to {@link Yaml.stringify} for config-time callers
|
|
411
459
|
* that cannot `await`.
|
package/YamlDocument.js
CHANGED
|
@@ -22,9 +22,15 @@ var YamlDirective = class extends Schema.Class("YamlDirective")({
|
|
|
22
22
|
/**
|
|
23
23
|
* A parsed YAML document: the root {@link (YamlNode:type)} (or `null` when
|
|
24
24
|
* empty), recovered `errors` and `warnings` as {@link YamlDiagnostic} data,
|
|
25
|
-
* the {@link YamlDirective} list,
|
|
25
|
+
* the {@link YamlDirective} list, the optional document-level comments and the
|
|
26
26
|
* `---`/`...` framing flags (absent flags read as `false`).
|
|
27
27
|
*
|
|
28
|
+
* `commentBefore` is a header block sitting AHEAD of a `---` marker; `comment`
|
|
29
|
+
* is the trailing block after the content or the `...` marker. A header with
|
|
30
|
+
* no marker, or one after the marker, belongs to the content rather than the
|
|
31
|
+
* document — it leads the root node, or the first entry when there is no
|
|
32
|
+
* marker to separate it from the item stream.
|
|
33
|
+
*
|
|
28
34
|
* Construct via `YamlDocument.parse` / `parseAll`; `YamlDocument.make` is for
|
|
29
35
|
* synthetic documents.
|
|
30
36
|
*
|
|
@@ -35,6 +41,7 @@ var YamlDocument = class YamlDocument extends Schema.Class("YamlDocument")({
|
|
|
35
41
|
errors: Schema.Array(YamlDiagnostic),
|
|
36
42
|
warnings: Schema.Array(YamlDiagnostic),
|
|
37
43
|
directives: Schema.Array(YamlDirective),
|
|
44
|
+
commentBefore: Schema.optionalKey(Schema.String),
|
|
38
45
|
comment: Schema.optionalKey(Schema.String),
|
|
39
46
|
hasDocumentStart: Schema.optionalKey(Schema.Boolean),
|
|
40
47
|
hasDocumentEnd: Schema.optionalKey(Schema.Boolean),
|
|
@@ -159,6 +166,18 @@ const toStringifyInput = (options) => options === void 0 ? {} : {
|
|
|
159
166
|
finalNewline: options.finalNewline,
|
|
160
167
|
forceDefaultStyles: options.forceDefaultStyles
|
|
161
168
|
};
|
|
169
|
+
/**
|
|
170
|
+
* Materialize a raw engine document into the public class — including
|
|
171
|
+
* documents whose diagnostics are fatal, which `YamlDocument.parse` refuses.
|
|
172
|
+
* The lint layer builds its context through this so linting runs on
|
|
173
|
+
* malformed input; it is NOT a second public parse entry point (not
|
|
174
|
+
* re-exported from the package index).
|
|
175
|
+
*
|
|
176
|
+
* @internal
|
|
177
|
+
*/
|
|
178
|
+
function documentFromRaw(raw, text) {
|
|
179
|
+
return fromRawDocument(raw, text);
|
|
180
|
+
}
|
|
162
181
|
/** Materialize a raw engine document into the public class. */
|
|
163
182
|
function fromRawDocument(raw, text) {
|
|
164
183
|
return new YamlDocument({
|
|
@@ -169,6 +188,7 @@ function fromRawDocument(raw, text) {
|
|
|
169
188
|
name: d.name,
|
|
170
189
|
parameters: d.parameters
|
|
171
190
|
})),
|
|
191
|
+
...raw.commentBefore !== void 0 ? { commentBefore: raw.commentBefore } : {},
|
|
172
192
|
...raw.comment !== void 0 ? { comment: raw.comment } : {},
|
|
173
193
|
hasDocumentStart: raw.hasDocumentStart,
|
|
174
194
|
hasDocumentEnd: raw.hasDocumentEnd,
|
|
@@ -182,6 +202,7 @@ function toRawDocument(doc) {
|
|
|
182
202
|
errors: [],
|
|
183
203
|
warnings: [],
|
|
184
204
|
directives: doc.directives,
|
|
205
|
+
...doc.commentBefore !== void 0 ? { commentBefore: doc.commentBefore } : {},
|
|
185
206
|
...doc.comment !== void 0 ? { comment: doc.comment } : {},
|
|
186
207
|
hasDocumentStart: doc.hasDocumentStart ?? false,
|
|
187
208
|
hasDocumentEnd: doc.hasDocumentEnd ?? false,
|
|
@@ -190,4 +211,4 @@ function toRawDocument(doc) {
|
|
|
190
211
|
}
|
|
191
212
|
|
|
192
213
|
//#endregion
|
|
193
|
-
export { YamlDirective, YamlDocument };
|
|
214
|
+
export { YamlDirective, YamlDocument, documentFromRaw };
|
package/YamlFormat.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { YamlMap, YamlPair, YamlScalar, YamlSeq } from "./YamlNode.js";
|
|
2
|
-
import {
|
|
2
|
+
import { EMPTY_DOCUMENT, composeAllDocuments, composeFirstDocumentCounted } from "./internal/composer/document.js";
|
|
3
3
|
import { isFatalCode } from "./internal/diagnostics.js";
|
|
4
4
|
import { stringifyDocument, stripNodeComments } from "./internal/stringifier.js";
|
|
5
5
|
import { YamlDiagnostic } from "./YamlDiagnostic.js";
|
|
@@ -41,9 +41,16 @@ var YamlFormattingOptions = class extends Schema.Class("YamlFormattingOptions")(
|
|
|
41
41
|
}) {};
|
|
42
42
|
/**
|
|
43
43
|
* Raised when `YamlFormat.modify` cannot navigate the requested path against
|
|
44
|
-
* the composed AST (a structural mismatch)
|
|
44
|
+
* the composed AST (a structural mismatch), the source fails to parse, the
|
|
45
|
+
* source is a multi-document stream (`MultiDocumentStream` — a path names no
|
|
46
|
+
* particular document of a stream, so modify refuses rather than guessing),
|
|
47
|
+
* or the document carries `%YAML`/`%TAG` directives
|
|
48
|
+
* (`DirectiveCarryingDocument` — modify does not re-emit directive lines, and
|
|
49
|
+
* dropping a `%TAG` would orphan the shorthand tags that depend on it).
|
|
45
50
|
* Carries structured {@link YamlDiagnostic} entries — never a collapsed
|
|
46
|
-
* `reason` string (the structure-preserving-errors house rule).
|
|
51
|
+
* `reason` string (the structure-preserving-errors house rule). The error
|
|
52
|
+
* itself has no `code` field: read the code from the diagnostics —
|
|
53
|
+
* `error.diagnostics[0].code` is the primary failure.
|
|
47
54
|
*
|
|
48
55
|
* @public
|
|
49
56
|
*/
|
|
@@ -97,26 +104,77 @@ function definedFields(fields) {
|
|
|
97
104
|
for (const key of Object.keys(fields)) if (fields[key] !== void 0) out[key] = fields[key];
|
|
98
105
|
return out;
|
|
99
106
|
}
|
|
100
|
-
/**
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
* fatal parse error (never corrupt malformed input).
|
|
104
|
-
*/
|
|
105
|
-
function formatDocument(text, options) {
|
|
106
|
-
const doc = composeFirstDocument(text, {});
|
|
107
|
-
if (doc.errors.some((e) => isFatalCode(e.code))) return void 0;
|
|
108
|
-
const preserveComments = options?.preserveComments ?? true;
|
|
109
|
-
const outputDoc = {
|
|
107
|
+
/** Rebuild a composed document for re-emission, stripping comments when asked. */
|
|
108
|
+
function toOutputDocument(doc, preserveComments) {
|
|
109
|
+
return {
|
|
110
110
|
contents: !preserveComments && doc.contents !== null ? stripNodeComments(doc.contents) : doc.contents,
|
|
111
111
|
errors: doc.errors,
|
|
112
112
|
warnings: doc.warnings,
|
|
113
113
|
directives: doc.directives,
|
|
114
|
-
...preserveComments ? definedFields({
|
|
114
|
+
...preserveComments ? definedFields({
|
|
115
|
+
commentBefore: doc.commentBefore,
|
|
116
|
+
comment: doc.comment
|
|
117
|
+
}) : {},
|
|
115
118
|
hasDocumentStart: doc.hasDocumentStart,
|
|
116
119
|
hasDocumentEnd: doc.hasDocumentEnd,
|
|
117
120
|
hasDocumentStartTab: doc.hasDocumentStartTab
|
|
118
121
|
};
|
|
119
|
-
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Format a document via the parse → stringify round-trip and diff the
|
|
125
|
+
* output against the source, returning `undefined` when the input has a
|
|
126
|
+
* fatal parse error (never corrupt malformed input) or carries
|
|
127
|
+
* `%YAML`/`%TAG` directives — `stringifyDocument` does not re-emit
|
|
128
|
+
* directive lines, and dropping a `%TAG` while keeping the shorthand tags
|
|
129
|
+
* that depend on it would turn a valid document into an unparseable one.
|
|
130
|
+
* A multi-document stream routes to {@link formatStream}, which formats
|
|
131
|
+
* every document and shares the same directive refusal.
|
|
132
|
+
*/
|
|
133
|
+
function formatDocument(text, options) {
|
|
134
|
+
const { documents, streamErrors } = composeAllDocuments(text, {});
|
|
135
|
+
if (documents.length > 1) return formatStream(documents, streamErrors, options);
|
|
136
|
+
if (streamErrors.some((e) => isFatalCode(e.code))) return void 0;
|
|
137
|
+
const doc = documents[0] ?? EMPTY_DOCUMENT;
|
|
138
|
+
if (doc.errors.some((e) => isFatalCode(e.code))) return void 0;
|
|
139
|
+
if (doc.directives.length > 0) return void 0;
|
|
140
|
+
return stringifyDocument(toOutputDocument(doc, options?.preserveComments ?? true), toStringifyInput(options));
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Format a multi-document stream: every document is re-emitted in order with
|
|
144
|
+
* its own framing (`---`, `...`, leading/trailing comment blocks — all of
|
|
145
|
+
* which `stringifyDocument` renders per document), so no document is ever
|
|
146
|
+
* dropped. Receives the documents and stream errors from the ONE
|
|
147
|
+
* composition {@link formatDocument} already performed. Returns `undefined`
|
|
148
|
+
* — no edits — when the stream cannot be re-emitted faithfully:
|
|
149
|
+
*
|
|
150
|
+
* - any document (or the stream itself) carries a fatal diagnostic, the same
|
|
151
|
+
* posture as the single-document path;
|
|
152
|
+
* - any document carries `%YAML`/`%TAG` directives — `stringifyDocument`
|
|
153
|
+
* does not re-emit directive lines, and dropping a `%TAG` would change
|
|
154
|
+
* what the re-emitted document means.
|
|
155
|
+
*
|
|
156
|
+
* The single-document path ({@link formatDocument}) shares the directive
|
|
157
|
+
* refusal: re-emitting a directive-carrying document without its directive
|
|
158
|
+
* line while keeping the dependent shorthand tags produced unparseable
|
|
159
|
+
* output (probe-verified downstream against an independent oracle).
|
|
160
|
+
*/
|
|
161
|
+
function formatStream(documents, streamErrors, options) {
|
|
162
|
+
if (streamErrors.some((e) => isFatalCode(e.code))) return void 0;
|
|
163
|
+
if (documents.some((d) => d.errors.some((e) => isFatalCode(e.code)))) return void 0;
|
|
164
|
+
if (documents.some((d) => d.directives.length > 0)) return void 0;
|
|
165
|
+
const preserveComments = options?.preserveComments ?? true;
|
|
166
|
+
const base = toStringifyInput(options);
|
|
167
|
+
const parts = [];
|
|
168
|
+
for (let i = 0; i < documents.length; i++) {
|
|
169
|
+
const doc = documents[i];
|
|
170
|
+
if (i > 0 && !doc.hasDocumentStart && !documents[i - 1].hasDocumentEnd) return void 0;
|
|
171
|
+
const docOptions = i < documents.length - 1 ? {
|
|
172
|
+
...base,
|
|
173
|
+
finalNewline: true
|
|
174
|
+
} : base;
|
|
175
|
+
parts.push(stringifyDocument(toOutputDocument(doc, preserveComments), docOptions));
|
|
176
|
+
}
|
|
177
|
+
return parts.join("");
|
|
120
178
|
}
|
|
121
179
|
/** Convert a plain JS scalar value into a synthetic `YamlScalar` (offset/length are irrelevant — immediately re-stringified). */
|
|
122
180
|
function jsValueToNode(value) {
|
|
@@ -150,8 +208,7 @@ function modifyNode(node, path, depth, value) {
|
|
|
150
208
|
const oldPair = newItems[pairIndex];
|
|
151
209
|
newItems[pairIndex] = YamlPair.make({
|
|
152
210
|
key: oldPair.key,
|
|
153
|
-
value: newValueNode
|
|
154
|
-
...definedFields({ comment: oldPair.comment })
|
|
211
|
+
value: newValueNode
|
|
155
212
|
});
|
|
156
213
|
return rebuildMap(node, newItems);
|
|
157
214
|
}
|
|
@@ -174,8 +231,7 @@ function modifyNode(node, path, depth, value) {
|
|
|
174
231
|
const newItems = [...node.items];
|
|
175
232
|
newItems[pairIndex] = YamlPair.make({
|
|
176
233
|
key: pair.key,
|
|
177
|
-
value: newValue
|
|
178
|
-
...definedFields({ comment: pair.comment })
|
|
234
|
+
value: newValue
|
|
179
235
|
});
|
|
180
236
|
return rebuildMap(node, newItems);
|
|
181
237
|
}
|
|
@@ -206,7 +262,9 @@ function rebuildMap(node, items) {
|
|
|
206
262
|
...definedFields({
|
|
207
263
|
tag: node.tag,
|
|
208
264
|
anchor: node.anchor,
|
|
265
|
+
commentBefore: node.commentBefore,
|
|
209
266
|
comment: node.comment,
|
|
267
|
+
spaceBefore: node.spaceBefore,
|
|
210
268
|
sourceMultiline: node.sourceMultiline
|
|
211
269
|
}),
|
|
212
270
|
offset: node.offset,
|
|
@@ -220,7 +278,9 @@ function rebuildSeq(node, items) {
|
|
|
220
278
|
...definedFields({
|
|
221
279
|
tag: node.tag,
|
|
222
280
|
anchor: node.anchor,
|
|
281
|
+
commentBefore: node.commentBefore,
|
|
223
282
|
comment: node.comment,
|
|
283
|
+
spaceBefore: node.spaceBefore,
|
|
224
284
|
sourceMultiline: node.sourceMultiline
|
|
225
285
|
}),
|
|
226
286
|
offset: node.offset,
|
|
@@ -239,6 +299,24 @@ function rebuildSeq(node, items) {
|
|
|
239
299
|
* `diagnostics: ReadonlyArray<YamlDiagnostic>`, never a collapsed `reason`
|
|
240
300
|
* string.
|
|
241
301
|
*
|
|
302
|
+
* `format`/`formatToString` handle multi-document streams whole — every
|
|
303
|
+
* document is re-emitted in order with its own framing, never a silently
|
|
304
|
+
* truncated stream. `modify`/`modifyToString` are **single-document**: a
|
|
305
|
+
* {@link YamlPath} carries no document index, so which document of a stream
|
|
306
|
+
* a path names is a rule the format does not define, and a multi-document
|
|
307
|
+
* stream fails typed with a `MultiDocumentStream` diagnostic rather than
|
|
308
|
+
* guessing. Parse a stream with {@link Yaml.parseAll} /
|
|
309
|
+
* `Yaml.parseAllResult`.
|
|
310
|
+
*
|
|
311
|
+
* **Directive-carrying input is refused on every path.** The stringifier does
|
|
312
|
+
* not re-emit `%YAML`/`%TAG` directive lines, and re-emitting a document
|
|
313
|
+
* without its `%TAG` while keeping the shorthand tags that depend on it
|
|
314
|
+
* (`!e!foo`) turns a valid file into one no parser can read. So
|
|
315
|
+
* `format`/`formatToString` leave such input byte-identical (no edits), and
|
|
316
|
+
* `modify`/`modifyToString` fail typed with a `DirectiveCarryingDocument`
|
|
317
|
+
* diagnostic. Directive re-emission is unimplemented, not undesired — the
|
|
318
|
+
* refusal is the floor that stops corruption until it lands.
|
|
319
|
+
*
|
|
242
320
|
* @public
|
|
243
321
|
*/
|
|
244
322
|
var YamlFormat = class YamlFormat {
|
|
@@ -249,6 +327,24 @@ var YamlFormat = class YamlFormat {
|
|
|
249
327
|
* Pure and total: malformed input (a fatal parse error) yields `[]` rather
|
|
250
328
|
* than corrupting the document.
|
|
251
329
|
*
|
|
330
|
+
* **Multi-document streams format whole.** Input containing more than one
|
|
331
|
+
* document (a `---`-separated stream — a Kubernetes manifest, a pnpm 11
|
|
332
|
+
* `pnpm-lock.yaml` with a config-dependency preamble) formats every
|
|
333
|
+
* document in order, re-emitting each document's own framing (`---`,
|
|
334
|
+
* `...`, comment blocks); no document is ever dropped. Document detection
|
|
335
|
+
* is CST-level, so a `---` inside a block scalar or quoted string is
|
|
336
|
+
* content, not a document boundary. Two multi-document shapes yield `[]`
|
|
337
|
+
* (untouched) because they cannot be re-emitted faithfully: a stream with
|
|
338
|
+
* a fatal diagnostic in any document (the same posture as single-document
|
|
339
|
+
* input) and a stream carrying `%YAML`/`%TAG` directives.
|
|
340
|
+
*
|
|
341
|
+
* **Directive-carrying documents yield `[]` on the single-document path
|
|
342
|
+
* too.** Directive lines are not re-emitted, and dropping a `%TAG` while
|
|
343
|
+
* keeping the shorthand tags that depend on it would turn a valid document
|
|
344
|
+
* into an unparseable one — so a document carrying `%YAML`/`%TAG`
|
|
345
|
+
* directives is left untouched. Detection is directive-token-level: a
|
|
346
|
+
* literal `%TAG` inside a scalar is content and formats normally.
|
|
347
|
+
*
|
|
252
348
|
* @remarks
|
|
253
349
|
* The positional `range` argument takes precedence over
|
|
254
350
|
* `options?.range` when both are given; either accepts a plain
|
|
@@ -278,6 +374,13 @@ var YamlFormat = class YamlFormat {
|
|
|
278
374
|
/**
|
|
279
375
|
* Format `text` and apply the resulting edits in one step
|
|
280
376
|
* (`YamlEdit.applyAll ∘ format`). Pure and total.
|
|
377
|
+
*
|
|
378
|
+
* Inherits the {@link YamlFormat.format} contract: a multi-document stream
|
|
379
|
+
* is formatted whole — every document re-emitted in order — and input
|
|
380
|
+
* that cannot be formatted faithfully (a fatal parse error, or any
|
|
381
|
+
* document — single or in a stream — carrying `%YAML`/`%TAG` directives)
|
|
382
|
+
* is returned byte-identical — never a truncated first document, never a
|
|
383
|
+
* document re-emitted without the directive its tags depend on.
|
|
281
384
|
*/
|
|
282
385
|
static formatToString(text, range, options) {
|
|
283
386
|
return YamlEdit.applyAll(text, YamlFormat.format(text, range, options));
|
|
@@ -291,18 +394,53 @@ var YamlFormat = class YamlFormat {
|
|
|
291
394
|
* {@link YamlModificationError} on a fatal parse error or a structural
|
|
292
395
|
* navigation mismatch.
|
|
293
396
|
*
|
|
397
|
+
* **Single-document contract.** A `path` carries no document index, so on
|
|
398
|
+
* a multi-document stream there is no rule for which document it names —
|
|
399
|
+
* `modify` fails with {@link YamlModificationError} carrying a
|
|
400
|
+
* `MultiDocumentStream` diagnostic rather than guessing document 1 (and
|
|
401
|
+
* unlike {@link YamlFormat.format}, which formats a stream whole because
|
|
402
|
+
* formatting needs no target). Detection is CST-level: a `---` inside a
|
|
403
|
+
* block scalar or quoted string is content, not a document boundary.
|
|
404
|
+
*
|
|
405
|
+
* **Directive-carrying documents are refused.** A document carrying
|
|
406
|
+
* `%YAML`/`%TAG` directives fails with {@link YamlModificationError}
|
|
407
|
+
* carrying a `DirectiveCarryingDocument` diagnostic: modify re-emits the
|
|
408
|
+
* whole document and does not re-emit directive lines, so applying it
|
|
409
|
+
* would drop the `%TAG` while keeping the shorthand tags that depend on
|
|
410
|
+
* it — unparseable output. A typed refusal beats silent corruption;
|
|
411
|
+
* directive re-emission is unimplemented, not undesired. A literal
|
|
412
|
+
* `%TAG` inside a scalar is content and does not trigger the refusal.
|
|
413
|
+
*
|
|
294
414
|
* @remarks
|
|
295
415
|
* `options` is a bare {@link YamlStringifyOptions} — it controls only the
|
|
296
416
|
* internal re-stringify step, not a range (there is no range to restrict
|
|
297
417
|
* for a path-targeted modification).
|
|
298
418
|
*/
|
|
299
419
|
static modify = Effect.fn("YamlFormat.modify")(function* (text, path, value, options) {
|
|
300
|
-
const doc =
|
|
420
|
+
const { document: doc, documentCount } = composeFirstDocumentCounted(text, {});
|
|
421
|
+
if (documentCount > 1) return yield* new YamlModificationError({
|
|
422
|
+
path,
|
|
423
|
+
diagnostics: [YamlDiagnostic.fromRaw({
|
|
424
|
+
code: "MultiDocumentStream",
|
|
425
|
+
message: `Cannot modify a multi-document stream (${documentCount} documents): modify re-emits exactly one document`,
|
|
426
|
+
offset: 0,
|
|
427
|
+
length: 0
|
|
428
|
+
}, text)]
|
|
429
|
+
});
|
|
301
430
|
const fatal = doc.errors.filter((e) => isFatalCode(e.code));
|
|
302
431
|
if (fatal.length > 0) return yield* new YamlModificationError({
|
|
303
432
|
path,
|
|
304
433
|
diagnostics: fatal.map((e) => YamlDiagnostic.fromRaw(e, text))
|
|
305
434
|
});
|
|
435
|
+
if (doc.directives.length > 0) return yield* new YamlModificationError({
|
|
436
|
+
path,
|
|
437
|
+
diagnostics: [YamlDiagnostic.fromRaw({
|
|
438
|
+
code: "DirectiveCarryingDocument",
|
|
439
|
+
message: "Cannot modify a document carrying %YAML/%TAG directives: modify does not re-emit directive lines, and dropping a %TAG orphans the shorthand tags that depend on it",
|
|
440
|
+
offset: 0,
|
|
441
|
+
length: 0
|
|
442
|
+
}, text)]
|
|
443
|
+
});
|
|
306
444
|
let newContents;
|
|
307
445
|
try {
|
|
308
446
|
newContents = modifyDocument(doc, path, value);
|
|
@@ -327,7 +465,10 @@ var YamlFormat = class YamlFormat {
|
|
|
327
465
|
});
|
|
328
466
|
/**
|
|
329
467
|
* Modify `text` and apply the resulting edits in one step
|
|
330
|
-
* (`YamlEdit.applyAll ∘ modify`).
|
|
468
|
+
* (`YamlEdit.applyAll ∘ modify`). Inherits the {@link YamlFormat.modify}
|
|
469
|
+
* error channel, including the single-document contract's
|
|
470
|
+
* `MultiDocumentStream` refusal and the `DirectiveCarryingDocument`
|
|
471
|
+
* refusal of `%YAML`/`%TAG`-carrying documents.
|
|
331
472
|
*/
|
|
332
473
|
static modifyToString = Effect.fn("YamlFormat.modifyToString")(function* (text, path, value, options) {
|
|
333
474
|
const edits = yield* YamlFormat.modify(text, path, value, options);
|