@effected/yaml 0.10.0 → 0.12.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 +43 -7
- package/Yaml.js +32 -3
- package/YamlDocument.js +2 -0
- package/YamlFormat.js +77 -9
- package/YamlLint.js +0 -0
- package/YamlLintRule.js +50 -1
- package/YamlNode.js +16 -1
- package/index.d.ts +300 -7
- package/index.js +4 -4
- package/internal/requote.js +48 -0
- package/internal/rules/comments-spacing.js +48 -9
- package/internal/rules/document-end.js +48 -18
- package/internal/rules/document-start.js +31 -5
- package/internal/rules/empty-lines.js +21 -1
- package/internal/rules/indentation.js +76 -23
- package/internal/rules/line-length.js +9 -1
- package/internal/rules/quoted-strings.js +29 -11
- package/internal/stringifier.js +30 -7
- package/package.json +1 -1
- package/tsdoc-metadata.json +1 -1
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, 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
|
|
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 — inferring the lint config from files you already have — 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 prerelease. Packages graduate to
|
|
@@ -156,6 +156,23 @@ console.log(Result.isSuccess(fixed) ? fixed.success : "fatal parse error — not
|
|
|
156
156
|
|
|
157
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
158
|
|
|
159
|
+
A repo with no lint config yet can infer one from what its files already do. `YamlLint.inferLenient` observes a text's style — quote type, indentation width, document markers and the rest — and returns the config the dominant style implies, plus the residual: the diagnostics that config still produces on the same text.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { YamlLint } from "@effected/yaml";
|
|
163
|
+
|
|
164
|
+
const workflow = ["---", "name: ci", "on:", " push:", " branches: ['main']", "jobs:", " build:", " runs-on: ubuntu-latest", ""].join("\n");
|
|
165
|
+
|
|
166
|
+
const { config, residual } = YamlLint.inferLenient(workflow, YamlLint.builtins);
|
|
167
|
+
console.log(config.rules["quoted-strings"], config.rules["document-start"], config.rules.indentation);
|
|
168
|
+
// { quoteType: 'single' } { present: true } { spaces: 2 }
|
|
169
|
+
|
|
170
|
+
for (const d of residual) console.log(`${d.line}:${d.character} ${d.rule} ${d.message}`);
|
|
171
|
+
// 2:0 truthy Truthy value "on" is not in the allowed spellings
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`YamlLint.inferStrict` is the same step in `Result` form: it fails with a structured `YamlStyleConflictError` — naming every conflicting dimension, all observed spellings, counts and positions — when the observed style disagrees with itself, instead of picking a winner. Across many files, compose the primitives instead of the conveniences: `YamlLint.observe` each file, merge the `StyleEvidence` values with `StyleEvidence.combine` (evidence is a monoid, so the order does not matter), then resolve once with `YamlLint.resolveStrict` or `YamlLint.resolveLenient`. Dimensions never observed fall back to the base config rather than failing. Policy rules with no detectable style — `truthy`, `key-duplicates` — stay default-driven, as do `line-length` and `empty-lines`, whose observations are floor-only: the longest line proves the limit is at least that long, not what it should be, so the evidence is carried but never resolved into config. A custom rule participates by implementing the optional `infer` hook on `YamlRule`.
|
|
175
|
+
|
|
159
176
|
## Migrating from Prettier
|
|
160
177
|
|
|
161
178
|
`YamlFormat.formatToString(text, undefined, options)` replaces a `prettier --write` over YAML files. The option mapping:
|
|
@@ -163,34 +180,53 @@ Positions are zero-based throughout, so the first finding above sits on the seco
|
|
|
163
180
|
| Prettier | `YamlFormattingOptions` |
|
|
164
181
|
| --- | --- |
|
|
165
182
|
| `tabWidth` | `indent` |
|
|
166
|
-
| `singleQuote` | `quoteStyle: "single" \| "double"` |
|
|
183
|
+
| `singleQuote` | `quoteStyle: "single" \| "double"` + `requoteScalars: true` |
|
|
167
184
|
| indented block sequences (always on) | `indentSequences: true` |
|
|
168
185
|
| `printWidth` / `proseWrap` | no analog — see below |
|
|
169
186
|
| comments (best-effort) | `preserveComments: true` (the default) |
|
|
170
187
|
|
|
171
188
|
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
189
|
|
|
173
|
-
|
|
190
|
+
The `requoteScalars: true` half of the `singleQuote` row is what buys full parity. By default, `quoteStyle` on the format path governs only quotes the stringifier *introduces* — a scalar already quoted in your source keeps its own quote style, so formatting `a: 'x'` with `quoteStyle: "double"` leaves `'x'` untouched, where Prettier would have rewritten it to `"x"`. With `requoteScalars: true` (default `false`), `quoteStyle` applies to already-quoted source scalars too. A re-quote happens only when it provably preserves the parsed value: single→double applies proper double-quote escaping, and double→single is skipped when the value carries characters single quotes cannot express. Plain scalars stay plain, block scalars stay block, and tagged, anchored and multi-line scalars are never touched. One interaction to watch: `quoteStyle` omitted defaults to `"single"`, so `requoteScalars: true` on its own converts `"x"` to `'x'` — an ex-Prettier consumer who ran with `singleQuote: false` wants `quoteStyle: "double"` alongside it.
|
|
174
191
|
|
|
175
192
|
`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
193
|
|
|
177
194
|
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
195
|
|
|
196
|
+
## Emitting for a YAML 1.1 consumer
|
|
197
|
+
|
|
198
|
+
YAML 1.2's Core Schema resolves fewer plain scalars to non-strings than YAML 1.1 does. A plain `on` or `yes` round-trips as a string under this package's own parser but becomes a boolean the moment a YAML 1.1 consumer — js-yaml, PyYAML, libyaml, the `yaml` npm package's YAML 1.1 schema, most CI systems' native YAML support — reads it back: the "Norway problem". `quoteCompat: "yaml-1.1"` closes that gap on the way out, quoting every plain scalar a 1.1 resolver would re-type: the extended booleans (`y`/`yes`/`no`/`on`/`off` and case variants), 1.1 timestamps, sexagesimal numbers (`1:30`), underscore-grouped numbers (`1_000`) and the octal/hex/binary integer forms (`0777`, `0x_FF`, `0b1010_0111`). It is strictly additive — nothing the 1.2 rules already quote loses a quote, and no string outside that list gains one — and composes with `quoteStyle` for the quote character and `lineWidth` for folding:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import { Yaml, YamlStringifyOptions } from "@effected/yaml";
|
|
202
|
+
import { Effect } from "effect";
|
|
203
|
+
|
|
204
|
+
const frontmatter = { node: "on", released: "2024-01-15", port: "yes" };
|
|
205
|
+
const options = YamlStringifyOptions.make({ lineWidth: 0, quoteCompat: "yaml-1.1", quoteStyle: "double" });
|
|
206
|
+
|
|
207
|
+
Effect.runPromise(Yaml.stringify(frontmatter, options)).then(console.log);
|
|
208
|
+
// node: "on"
|
|
209
|
+
// released: "2024-01-15"
|
|
210
|
+
// port: "yes"
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
That combination — `lineWidth: 0` (no folding), `quoteCompat: "yaml-1.1"`, `quoteStyle: "double"` — is the shape to reach for when stringifying frontmatter destined for YAML-1.1 tooling: Markdown frontmatter parsers and most static-site generators still resolve on the older rules. The option applies on both the value path (`Yaml.stringify`, `Yaml.stringifyResult`) and the node path (`YamlDocument#stringify`, `YamlFormat`), and scalars carrying an explicit tag are exempt, exactly like the 1.2 type-conflict check it extends.
|
|
214
|
+
|
|
179
215
|
## Features
|
|
180
216
|
|
|
181
217
|
- `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`.
|
|
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.
|
|
218
|
+
- `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; `quoteCompat: "yaml-1.1"` additionally quotes plain scalars a YAML 1.1 resolver would re-type.
|
|
183
219
|
- `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.
|
|
184
220
|
- `Yaml.stripComments` — quote-aware comment removal that keeps line numbers stable, or every byte offset stable when given a replacement character.
|
|
185
221
|
- `Yaml.equals` / `Yaml.equalsValue` — semantic equality that ignores comments, whitespace, formatting and mapping key order while keeping sequence order significant.
|
|
186
222
|
- `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.
|
|
187
223
|
- `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
224
|
- `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.
|
|
225
|
+
- `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, and opt-in `requoteScalars` applies `quoteStyle` to scalars already quoted in the source, semantics-preserving or skipped. `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
226
|
- `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
227
|
- `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.
|
|
228
|
+
- `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. Plus config inference for adopting the linter on an existing corpus: `observe` produces mergeable `StyleEvidence`, `resolveStrict` fails typed with `YamlStyleConflictError` on conflicting spellings, `resolveLenient` picks the dominant style, and `inferStrict` / `inferLenient` do it in one step per text.
|
|
229
|
+
- `YamlLintConfig` / `YamlLintRule` / `YamlLintDiagnostic` — the `default` and `relaxed` presets, a config schema validated against the rule catalog, and the model a custom rule implements, including the optional `infer` hook that lets a custom rule participate in config inference.
|
|
194
230
|
- `YamlParseError` / `YamlStringifyError` / `YamlModificationError` — tagged errors carrying structured, positional `YamlDiagnostic` arrays rather than opaque messages.
|
|
195
231
|
|
|
196
232
|
## License
|
package/Yaml.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AliasExpansionBudgetExceeded, CollectionStyle, QuoteStyle, ScalarStyle, nodeToJsValue } from "./YamlNode.js";
|
|
1
|
+
import { AliasExpansionBudgetExceeded, CollectionStyle, QuoteCompat, QuoteStyle, ScalarStyle, nodeToJsValue } from "./YamlNode.js";
|
|
2
2
|
import { composeAllDocuments, composeFirstDocument } from "./internal/composer/document.js";
|
|
3
3
|
import { isFatalCode } from "./internal/diagnostics.js";
|
|
4
4
|
import { StringifyDepthExceeded, StringifyFailure, stringifyValue } from "./internal/stringifier.js";
|
|
@@ -35,8 +35,9 @@ var YamlParseOptions = class extends Schema.Class("YamlParseOptions")({
|
|
|
35
35
|
* Options controlling stringify behavior. All fields are omissible; absent
|
|
36
36
|
* fields resolve to `indent` `2`, `lineWidth` `0`, `defaultScalarStyle`
|
|
37
37
|
* `"plain"`, `defaultCollectionStyle` `"block"`, `sortKeys` `false`,
|
|
38
|
-
* `indentSequences` `false`, `quoteStyle` `"single"`, `
|
|
39
|
-
* and `forceDefaultStyles`
|
|
38
|
+
* `indentSequences` `false`, `quoteStyle` `"single"`, `quoteCompat` absent
|
|
39
|
+
* (no dialect-compat quoting), `finalNewline` `true` and `forceDefaultStyles`
|
|
40
|
+
* `false`.
|
|
40
41
|
*
|
|
41
42
|
* `lineWidth` controls column-based scalar folding. The default `0` (and any
|
|
42
43
|
* value `<= 0`) never wraps, emitting byte-identical output to the historic
|
|
@@ -114,6 +115,33 @@ var YamlStringifyOptions = class extends Schema.Class("YamlStringifyOptions")({
|
|
|
114
115
|
* `"double-quoted"` is unaffected.
|
|
115
116
|
*/
|
|
116
117
|
quoteStyle: Schema.optionalKey(QuoteStyle),
|
|
118
|
+
/**
|
|
119
|
+
* Additionally quote plain scalars a foreign resolution dialect would
|
|
120
|
+
* coerce to a non-string. Absent (the default) adds no quoting beyond the
|
|
121
|
+
* YAML 1.2 Core Schema rules — byte-identical to the released output.
|
|
122
|
+
*
|
|
123
|
+
* `"yaml-1.1"` quotes every plain scalar a YAML 1.1 parser (js-yaml,
|
|
124
|
+
* PyYAML, libyaml, and the `yaml` npm package's YAML 1.1 schema, whose
|
|
125
|
+
* lenient resolvers set the outer bound) would implicitly resolve to a
|
|
126
|
+
* non-string: the extended
|
|
127
|
+
* boolean spellings (`y`/`yes`/`on`/`off` and case variants — the "Norway
|
|
128
|
+
* problem"), timestamps (`2024-01-15`, `2001-12-15T02:59:43.1Z`, the
|
|
129
|
+
* space-separated 1.1 forms), sexagesimal numbers (`1:30`,
|
|
130
|
+
* `190:20:30.15`), underscore-separated numbers (`1_000`) and the
|
|
131
|
+
* base-2/8/16 integer forms (`0b1010_0111`, `0777`, `0x_FF`). Use it when
|
|
132
|
+
* emitted output is parsed downstream by a YAML 1.1 consumer and blanket
|
|
133
|
+
* `defaultScalarStyle` quoting is too noisy.
|
|
134
|
+
*
|
|
135
|
+
* Strictly additive: it never un-quotes anything the 1.2 rules require
|
|
136
|
+
* quoted, strings no 1.1 parser coerces stay plain, and the quote
|
|
137
|
+
* character stays governed by `quoteStyle`. Scalars carrying an explicit
|
|
138
|
+
* tag are exempt, exactly like the 1.2 type-conflict check. Applies
|
|
139
|
+
* wherever the plain fallback renders — values, mapping keys, flow items —
|
|
140
|
+
* on both the value path ({@link Yaml.stringify},
|
|
141
|
+
* {@link Yaml.stringifyResult}) and the node path (`YamlDocument#stringify`
|
|
142
|
+
* and the `YamlFormat` helpers).
|
|
143
|
+
*/
|
|
144
|
+
quoteCompat: Schema.optionalKey(QuoteCompat),
|
|
117
145
|
finalNewline: Schema.optionalKey(Schema.Boolean),
|
|
118
146
|
forceDefaultStyles: Schema.optionalKey(Schema.Boolean)
|
|
119
147
|
}) {};
|
|
@@ -167,6 +195,7 @@ const toStringifyInput = (options) => options === void 0 ? {} : {
|
|
|
167
195
|
sortKeys: options.sortKeys,
|
|
168
196
|
indentSequences: options.indentSequences,
|
|
169
197
|
quoteStyle: options.quoteStyle,
|
|
198
|
+
quoteCompat: options.quoteCompat,
|
|
170
199
|
finalNewline: options.finalNewline,
|
|
171
200
|
forceDefaultStyles: options.forceDefaultStyles
|
|
172
201
|
};
|
package/YamlDocument.js
CHANGED
|
@@ -163,6 +163,8 @@ const toStringifyInput = (options) => options === void 0 ? {} : {
|
|
|
163
163
|
defaultCollectionStyle: options.defaultCollectionStyle,
|
|
164
164
|
sortKeys: options.sortKeys,
|
|
165
165
|
indentSequences: options.indentSequences,
|
|
166
|
+
quoteStyle: options.quoteStyle,
|
|
167
|
+
quoteCompat: options.quoteCompat,
|
|
166
168
|
finalNewline: options.finalNewline,
|
|
167
169
|
forceDefaultStyles: options.forceDefaultStyles
|
|
168
170
|
};
|
package/YamlFormat.js
CHANGED
|
@@ -6,16 +6,33 @@ import { YamlDiagnostic } from "./YamlDiagnostic.js";
|
|
|
6
6
|
import { YamlStringifyOptions } from "./Yaml.js";
|
|
7
7
|
import { YamlEdit, YamlRange } from "./YamlEdit.js";
|
|
8
8
|
import { computeEdits } from "./internal/diff.js";
|
|
9
|
+
import { requoteScalarText } from "./internal/requote.js";
|
|
9
10
|
import { Effect, Schema } from "effect";
|
|
10
11
|
|
|
11
12
|
//#region src/YamlFormat.ts
|
|
12
13
|
/**
|
|
13
14
|
* Options controlling formatting behavior: every {@link YamlStringifyOptions}
|
|
14
|
-
* field (derived, not hand-duplicated — including `indentSequences
|
|
15
|
-
* `quoteStyle`) plus
|
|
16
|
-
* `preserveComments` (default `true`)
|
|
15
|
+
* field (derived, not hand-duplicated — including `indentSequences`,
|
|
16
|
+
* `quoteStyle` and `quoteCompat`) plus
|
|
17
|
+
* `preserveComments` (default `true`), `range` (restrict edits to a
|
|
17
18
|
* region; see the module-level remarks on the `range` parameter vs. this
|
|
18
|
-
* field).
|
|
19
|
+
* field) and `requoteScalars` (default `false`).
|
|
20
|
+
*
|
|
21
|
+
* `requoteScalars` makes `quoteStyle` apply to scalars **already quoted in
|
|
22
|
+
* the source** on the format path — by default formatting preserves an
|
|
23
|
+
* existing scalar's own quote style, and `quoteStyle` governs only quotes the
|
|
24
|
+
* stringifier introduces. When enabled, a re-quote happens only when it
|
|
25
|
+
* provably preserves the parsed value: single→double applies proper
|
|
26
|
+
* double-quote escaping, double→single is skipped whenever the value carries
|
|
27
|
+
* characters single quotes cannot express (newlines, tabs, control and other
|
|
28
|
+
* non-printable characters — single-quoted style can escape nothing but
|
|
29
|
+
* `'`). Plain scalars stay plain, block scalars stay block, and scalars
|
|
30
|
+
* carrying a tag or anchor — or spanning multiple source lines — are left
|
|
31
|
+
* untouched. The option exists only where a source quote exists to re-quote:
|
|
32
|
+
* it is read by {@link YamlFormat.format} / {@link YamlFormat.formatToString}
|
|
33
|
+
* and is deliberately absent from `Yaml.stringify` (which serializes plain
|
|
34
|
+
* values) and {@link YamlFormat.modify} (which takes a bare
|
|
35
|
+
* {@link YamlStringifyOptions}).
|
|
19
36
|
*
|
|
20
37
|
* Construct with the validated `YamlFormattingOptions.make({ ... })` static —
|
|
21
38
|
* the kit convention (never `new`). Call sites that take a
|
|
@@ -37,7 +54,8 @@ import { Effect, Schema } from "effect";
|
|
|
37
54
|
var YamlFormattingOptions = class extends Schema.Class("YamlFormattingOptions")({
|
|
38
55
|
...YamlStringifyOptions.fields,
|
|
39
56
|
preserveComments: Schema.optionalKey(Schema.Boolean),
|
|
40
|
-
range: Schema.optionalKey(YamlRange)
|
|
57
|
+
range: Schema.optionalKey(YamlRange),
|
|
58
|
+
requoteScalars: Schema.optionalKey(Schema.Boolean)
|
|
41
59
|
}) {};
|
|
42
60
|
/**
|
|
43
61
|
* Raised when `YamlFormat.modify` cannot navigate the requested path against
|
|
@@ -87,6 +105,7 @@ const toStringifyInput = (options) => options === void 0 ? {} : {
|
|
|
87
105
|
sortKeys: options.sortKeys,
|
|
88
106
|
indentSequences: options.indentSequences,
|
|
89
107
|
quoteStyle: options.quoteStyle,
|
|
108
|
+
quoteCompat: options.quoteCompat,
|
|
90
109
|
finalNewline: options.finalNewline,
|
|
91
110
|
forceDefaultStyles: options.forceDefaultStyles
|
|
92
111
|
};
|
|
@@ -104,6 +123,49 @@ function definedFields(fields) {
|
|
|
104
123
|
for (const key of Object.keys(fields)) if (fields[key] !== void 0) out[key] = fields[key];
|
|
105
124
|
return out;
|
|
106
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Walk a composed AST and flip the `style` of every scalar the shared
|
|
128
|
+
* escaping-mode helper deems re-quotable to the target quote's style. The
|
|
129
|
+
* stringifier then emits those scalars through the very renderers the helper
|
|
130
|
+
* used to prove the replacement value-preserving, so flip-and-stringify and
|
|
131
|
+
* the helper's replacement text cannot disagree. Everything the helper skips
|
|
132
|
+
* — plain scalars, block scalars, tagged/anchored nodes, multi-line source
|
|
133
|
+
* spans, impossible double→single escapes — keeps its node untouched, and
|
|
134
|
+
* the surrounding diff keeps the edit surgical per scalar span.
|
|
135
|
+
*/
|
|
136
|
+
function requoteNode(node, text, quote) {
|
|
137
|
+
if (node instanceof YamlScalar) {
|
|
138
|
+
if (requoteScalarText(text, node, quote, "escaping") === void 0) return node;
|
|
139
|
+
return YamlScalar.make({
|
|
140
|
+
value: node.value,
|
|
141
|
+
style: quote === "\"" ? "double-quoted" : "single-quoted",
|
|
142
|
+
...definedFields({
|
|
143
|
+
commentBefore: node.commentBefore,
|
|
144
|
+
comment: node.comment,
|
|
145
|
+
spaceBefore: node.spaceBefore,
|
|
146
|
+
raw: node.raw,
|
|
147
|
+
sourceMultiline: node.sourceMultiline
|
|
148
|
+
}),
|
|
149
|
+
offset: node.offset,
|
|
150
|
+
length: node.length
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
if (node instanceof YamlMap) return rebuildMap(node, node.items.map((pair) => YamlPair.make({
|
|
154
|
+
key: requoteNode(pair.key, text, quote),
|
|
155
|
+
value: pair.value === null ? null : requoteNode(pair.value, text, quote)
|
|
156
|
+
})));
|
|
157
|
+
if (node instanceof YamlSeq) return rebuildSeq(node, node.items.map((item) => requoteNode(item, text, quote)));
|
|
158
|
+
return node;
|
|
159
|
+
}
|
|
160
|
+
/** Apply the opt-in re-quoting pass to a composed document's contents. */
|
|
161
|
+
function requoteDocument(doc, text, options) {
|
|
162
|
+
if (options?.requoteScalars !== true || doc.contents === null) return doc;
|
|
163
|
+
const quote = (options.quoteStyle ?? "single") === "double" ? "\"" : "'";
|
|
164
|
+
return {
|
|
165
|
+
...doc,
|
|
166
|
+
contents: requoteNode(doc.contents, text, quote)
|
|
167
|
+
};
|
|
168
|
+
}
|
|
107
169
|
/** Rebuild a composed document for re-emission, stripping comments when asked. */
|
|
108
170
|
function toOutputDocument(doc, preserveComments) {
|
|
109
171
|
return {
|
|
@@ -132,12 +194,12 @@ function toOutputDocument(doc, preserveComments) {
|
|
|
132
194
|
*/
|
|
133
195
|
function formatDocument(text, options) {
|
|
134
196
|
const { documents, streamErrors } = composeAllDocuments(text, {});
|
|
135
|
-
if (documents.length > 1) return formatStream(documents, streamErrors, options);
|
|
197
|
+
if (documents.length > 1) return formatStream(text, documents, streamErrors, options);
|
|
136
198
|
if (streamErrors.some((e) => isFatalCode(e.code))) return void 0;
|
|
137
199
|
const doc = documents[0] ?? EMPTY_DOCUMENT;
|
|
138
200
|
if (doc.errors.some((e) => isFatalCode(e.code))) return void 0;
|
|
139
201
|
if (doc.directives.length > 0) return void 0;
|
|
140
|
-
return stringifyDocument(toOutputDocument(doc, options?.preserveComments ?? true), toStringifyInput(options));
|
|
202
|
+
return stringifyDocument(toOutputDocument(requoteDocument(doc, text, options), options?.preserveComments ?? true), toStringifyInput(options));
|
|
141
203
|
}
|
|
142
204
|
/**
|
|
143
205
|
* Format a multi-document stream: every document is re-emitted in order with
|
|
@@ -158,7 +220,7 @@ function formatDocument(text, options) {
|
|
|
158
220
|
* line while keeping the dependent shorthand tags produced unparseable
|
|
159
221
|
* output (probe-verified downstream against an independent oracle).
|
|
160
222
|
*/
|
|
161
|
-
function formatStream(documents, streamErrors, options) {
|
|
223
|
+
function formatStream(text, documents, streamErrors, options) {
|
|
162
224
|
if (streamErrors.some((e) => isFatalCode(e.code))) return void 0;
|
|
163
225
|
if (documents.some((d) => d.errors.some((e) => isFatalCode(e.code)))) return void 0;
|
|
164
226
|
if (documents.some((d) => d.directives.length > 0)) return void 0;
|
|
@@ -172,7 +234,7 @@ function formatStream(documents, streamErrors, options) {
|
|
|
172
234
|
...base,
|
|
173
235
|
finalNewline: true
|
|
174
236
|
} : base;
|
|
175
|
-
parts.push(stringifyDocument(toOutputDocument(doc, preserveComments), docOptions));
|
|
237
|
+
parts.push(stringifyDocument(toOutputDocument(requoteDocument(doc, text, options), preserveComments), docOptions));
|
|
176
238
|
}
|
|
177
239
|
return parts.join("");
|
|
178
240
|
}
|
|
@@ -351,6 +413,12 @@ var YamlFormat = class YamlFormat {
|
|
|
351
413
|
* `{ offset, length }` object as well as a {@link YamlRange} instance, so
|
|
352
414
|
* callers do not need `YamlRange.make(...)` for the common case.
|
|
353
415
|
*
|
|
416
|
+
* Formatting preserves an existing scalar's own quote style by default —
|
|
417
|
+
* `quoteStyle` governs only quotes the stringifier introduces. The opt-in
|
|
418
|
+
* `options.requoteScalars` makes `quoteStyle` apply to already-quoted
|
|
419
|
+
* source scalars too, re-quoting only where the parsed value is provably
|
|
420
|
+
* preserved; see {@link YamlFormattingOptions} for the exact skip rules.
|
|
421
|
+
*
|
|
354
422
|
* A plain `<<` mapping key is preserved unquoted, keeping its merge-key
|
|
355
423
|
* meaning (`tag:yaml.org,2002:merge`) — quoting it to `'<<'` would produce
|
|
356
424
|
* an ordinary string key that merges nothing, changing what the document
|
package/YamlLint.js
CHANGED
|
Binary file
|
package/YamlLintRule.js
CHANGED
|
@@ -31,6 +31,55 @@ var YamlLintDiagnostic = class extends Schema.Class("YamlLintDiagnostic")({
|
|
|
31
31
|
character: Schema.Number,
|
|
32
32
|
fix: Schema.optionalKey(YamlEdit)
|
|
33
33
|
}) {};
|
|
34
|
+
/**
|
|
35
|
+
* One categorical style observation (#345): a single occurrence of a style
|
|
36
|
+
* choice in the source, voting a `value` for an inference `dimension`.
|
|
37
|
+
*
|
|
38
|
+
* The `dimension` IS the rule's option key and the `value` IS that option's
|
|
39
|
+
* value (`"double"` for `quoteType`, `2` for `spaces`, `false` for
|
|
40
|
+
* `present`), so the inference resolvers can turn unanimous or dominant
|
|
41
|
+
* votes into a config entry with no per-rule knowledge — which is what lets
|
|
42
|
+
* custom rules participate in inference for free. The position points at the
|
|
43
|
+
* occurrence that voted, so a strict-resolution conflict can name where each
|
|
44
|
+
* spelling was seen.
|
|
45
|
+
*
|
|
46
|
+
* The `_tag` literal is the RUNTIME discriminator between the two
|
|
47
|
+
* observation kinds: class instances are structurally assignable, so a
|
|
48
|
+
* custom rule may yield a plain object shaped like a vote, and the evidence
|
|
49
|
+
* builder must still sort it into the right tally without `instanceof`.
|
|
50
|
+
* `.make` defaults it — construction sites never pass `_tag`.
|
|
51
|
+
*
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
var StyleVote = class extends Schema.TaggedClass()("StyleVote", {
|
|
55
|
+
dimension: Schema.String,
|
|
56
|
+
value: Schema.Union([
|
|
57
|
+
Schema.String,
|
|
58
|
+
Schema.Number,
|
|
59
|
+
Schema.Boolean
|
|
60
|
+
]),
|
|
61
|
+
offset: Schema.Number,
|
|
62
|
+
length: Schema.Number,
|
|
63
|
+
line: Schema.Number,
|
|
64
|
+
character: Schema.Number
|
|
65
|
+
}) {};
|
|
66
|
+
/**
|
|
67
|
+
* One measured style floor (#345): a value the source PROVES is at least
|
|
68
|
+
* `value`, without proving what the configured limit should be — the longest
|
|
69
|
+
* observed line proves `line-length.max` is at least that long, not what it
|
|
70
|
+
* is. Floors are carried in the evidence for callers that want them and are
|
|
71
|
+
* never resolved into config options; fabricating a max from the largest
|
|
72
|
+
* value one happened to see would be lying with a straight face.
|
|
73
|
+
*
|
|
74
|
+
* The `_tag` literal discriminates a floor from a {@link StyleVote} at
|
|
75
|
+
* runtime (see there); `.make` defaults it.
|
|
76
|
+
*
|
|
77
|
+
* @public
|
|
78
|
+
*/
|
|
79
|
+
var StyleFloor = class extends Schema.TaggedClass()("StyleFloor", {
|
|
80
|
+
dimension: Schema.String,
|
|
81
|
+
value: Schema.Number
|
|
82
|
+
}) {};
|
|
34
83
|
|
|
35
84
|
//#endregion
|
|
36
|
-
export { YamlLintDiagnostic, YamlLintSeverity };
|
|
85
|
+
export { StyleFloor, StyleVote, YamlLintDiagnostic, YamlLintSeverity };
|
package/YamlNode.js
CHANGED
|
@@ -30,6 +30,21 @@ const CollectionStyle = Schema.Literals(["block", "flow"]);
|
|
|
30
30
|
*/
|
|
31
31
|
const QuoteStyle = Schema.Literals(["single", "double"]);
|
|
32
32
|
/**
|
|
33
|
+
* Foreign resolution dialects the stringifier's plain-scalar fallback can
|
|
34
|
+
* defend against: setting the `quoteCompat` field of `YamlStringifyOptions`
|
|
35
|
+
* to `"yaml-1.1"` additionally quotes every plain scalar a YAML 1.1 parser
|
|
36
|
+
* (js-yaml, PyYAML, libyaml, and the `yaml` npm package's YAML 1.1 schema,
|
|
37
|
+
* whose lenient resolvers set the outer bound) would implicitly resolve to a
|
|
38
|
+
* non-string —
|
|
39
|
+
* `yes`/`no`/`on`/`off` booleans, ISO 8601 and space-separated timestamps,
|
|
40
|
+
* sexagesimal `1:30`, underscored `1_000` and base-2/8/16 numbers. Like
|
|
41
|
+
* `QuoteStyle` it is a stringify-option vocabulary, never a property of a
|
|
42
|
+
* composed node.
|
|
43
|
+
*
|
|
44
|
+
* @public
|
|
45
|
+
*/
|
|
46
|
+
const QuoteCompat = Schema.Literals(["yaml-1.1"]);
|
|
47
|
+
/**
|
|
33
48
|
* Block-scalar chomping indicators (`-` strip, default clip, `+` keep).
|
|
34
49
|
* Referenced by the {@link YamlScalar} `chomp` field schema.
|
|
35
50
|
*
|
|
@@ -442,4 +457,4 @@ function nodeToValue(node, anchors, budget, counting = false) {
|
|
|
442
457
|
}
|
|
443
458
|
|
|
444
459
|
//#endregion
|
|
445
|
-
export { AliasExpansionBudgetExceeded, CollectionStyle, QuoteStyle, ScalarChomp, ScalarStyle, YamlAlias, YamlMap, YamlNode, YamlPair, YamlScalar, YamlSeq, aliasExpansionLimit, nodeToJsValue };
|
|
460
|
+
export { AliasExpansionBudgetExceeded, CollectionStyle, QuoteCompat, QuoteStyle, ScalarChomp, ScalarStyle, YamlAlias, YamlMap, YamlNode, YamlPair, YamlScalar, YamlSeq, aliasExpansionLimit, nodeToJsValue };
|