Skip to content

Commit 09ed139

Browse files
lib: add experimental node:yaml module
Add a YAML 1.2 parser for the core schema as `node:yaml`, behind `--experimental-yaml`, with `yaml.parse(input)` following the shape of `Bun.YAML.parse()`. The parser is written from scratch, in one pass from text to values, with no new dependency. Anchors and aliases resolve to shared objects, core schema tags are applied, mappings become plain objects and duplicate keys are an error. The official yaml-test-suite is vendored as a JSON fixture and run by test-yaml-test-suite.js: 400 of its 402 cases pass, the two left are documents with duplicate keys. Refs: #53787 Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
1 parent d44625a commit 09ed139

19 files changed

Lines changed: 6579 additions & 0 deletions

‎doc/api/cli.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1780,6 +1780,16 @@ added:
17801780
17811781
Enable experimental support for the worker inspection with Chrome DevTools.
17821782

1783+
### `--experimental-yaml`
1784+
1785+
<!-- YAML
1786+
added: REPLACEME
1787+
-->
1788+
1789+
> Stability: 1.0 - Early development
1790+
1791+
Enable the experimental [`node:yaml`][] module.
1792+
17831793
### `--force-context-aware`
17841794

17851795
<!-- YAML
@@ -4323,6 +4333,7 @@ one is included in the list below.
43234333
* `--experimental-wasi-unstable-preview1`
43244334
* `--experimental-web-worker`
43254335
* `--experimental-websocket`
4336+
* `--experimental-yaml`
43264337
* `--force-context-aware`
43274338
* `--force-fips`
43284339
* `--force-node-api-uncaught-exceptions-policy`
@@ -4997,6 +5008,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
49975008
[`node:sqlite`]: sqlite.md
49985009
[`node:stream/iter`]: stream_iter.md
49995010
[`node:vfs`]: vfs.md
5011+
[`node:yaml`]: yaml.md
50005012
[`permission.drop()`]: permissions.md#permissiondropscope-reference
50015013
[`process.setUncaughtExceptionCaptureCallback()`]: process.md#processsetuncaughtexceptioncapturecallbackfn
50025014
[`session.connectToMainThread()`]: inspector.md#sessionconnecttomainthread

‎doc/api/errors.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2318,6 +2318,17 @@ specific purpose. It is only used in the [WHATWG URL API][] support in the
23182318
[`fs`][] module (which only accepts URLs with `'file'` scheme), but may be used
23192319
in other Node.js APIs as well in the future.
23202320

2321+
<a id="ERR_INVALID_YAML"></a>
2322+
2323+
### `ERR_INVALID_YAML`
2324+
2325+
<!-- YAML
2326+
added: REPLACEME
2327+
-->
2328+
2329+
The input passed to [`yaml.parse()`][] is not valid YAML. The message names
2330+
the problem and its line and column.
2331+
23212332
<a id="ERR_IPC_CHANNEL_CLOSED"></a>
23222333

23232334
### `ERR_IPC_CHANNEL_CLOSED`
@@ -4858,6 +4869,7 @@ An error occurred trying to allocate memory. This should never happen.
48584869
[`util.inspect()`]: util.md#utilinspectobject-options
48594870
[`util.parseArgs()`]: util.md#utilparseargsconfig
48604871
[`v8.startupSnapshot.setDeserializeMainFunction()`]: v8.md#v8startupsnapshotsetdeserializemainfunctioncallback-data
4872+
[`yaml.parse()`]: yaml.md#yamlparseinput
48614873
[`zlib`]: zlib.md
48624874
[crypto digest algorithm]: crypto.md#cryptogethashes
48634875
[debugger]: debugger.md

‎doc/api/index.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
* [Web Crypto API](webcrypto.md)
7272
* [Web Streams API](webstreams.md)
7373
* [Worker threads](worker_threads.md)
74+
* [YAML](yaml.md)
7475
* [Zlib](zlib.md)
7576

7677
<hr class="line"/>

‎doc/api/yaml.md‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# YAML
2+
3+
<!--introduced_in=REPLACEME-->
4+
5+
<!-- YAML
6+
added: REPLACEME
7+
-->
8+
9+
> Stability: 1.0 - Early development
10+
11+
<!-- source_link=lib/yaml.js -->
12+
13+
The `node:yaml` module parses [YAML 1.2][] into JavaScript values. Enable it
14+
with [`--experimental-yaml`][] and import it with `node:`:
15+
16+
```mjs
17+
import yaml from 'node:yaml';
18+
```
19+
20+
```cjs
21+
const yaml = require('node:yaml');
22+
```
23+
24+
Loading the module emits an `ExperimentalWarning`.
25+
26+
## `yaml.parse(input)`
27+
28+
<!-- YAML
29+
added: REPLACEME
30+
-->
31+
32+
* `input` {string|ArrayBuffer|SharedArrayBuffer|TypedArray|DataView} The YAML
33+
text. Binary input is decoded as UTF-8.
34+
* Returns: {any} The value of the document, or an array with the value of each
35+
document when the stream holds more than one. An empty stream yields `null`.
36+
37+
`parse()` uses the YAML 1.2 [core schema][]. Plain scalars resolve to null,
38+
booleans, numbers or strings. For example, `yes` and `no` are strings. Quoted
39+
and block scalars are strings unless a tag changes their type, as in
40+
`!!int "42"`. Mappings become plain objects and sequences become arrays, as
41+
with [`JSON.parse()`][].
42+
43+
```mjs
44+
import yaml from 'node:yaml';
45+
46+
const config = yaml.parse(`
47+
name: app
48+
ports:
49+
- 8080
50+
- 8443
51+
tls: true
52+
`);
53+
console.log(config);
54+
// { name: 'app', ports: [ 8080, 8443 ], tls: true }
55+
56+
console.log(yaml.parse('--- 1\n--- 2\n'));
57+
// [ 1, 2 ]
58+
```
59+
60+
Aliases to a collection refer to the same object. A collection may contain an
61+
alias to itself, so the result can contain cycles.
62+
63+
The parser applies the core tags `!!str`, `!!int`, `!!float`, `!!bool`,
64+
`!!null`, `!!map` and `!!seq`. A tag must match its value: `!!int abc` throws
65+
an error. Other tags leave collections unchanged and make scalars strings.
66+
For example, `!custom 123` becomes `'123'`.
67+
68+
Mapping keys become property names. Scalar keys use their string form, so
69+
`null`, `true` and `1` become `'null'`, `'true'` and `'1'`. Collection keys use
70+
their JSON text and are limited to 1024 characters. Duplicate keys throw.
71+
72+
Invalid YAML throws an [`ERR_INVALID_YAML`][] `SyntaxError` with the line and
73+
column. The parser rejects disallowed control characters and nesting deeper
74+
than 1000 levels.
75+
76+
[YAML 1.2]: https://yaml.org/spec/1.2.2/
77+
[`--experimental-yaml`]: cli.md#--experimental-yaml
78+
[`ERR_INVALID_YAML`]: errors.md#err_invalid_yaml
79+
[`JSON.parse()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
80+
[core schema]: https://yaml.org/spec/1.2.2/#103-core-schema

‎doc/node-config-schema.json‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,10 @@
418418
"type": "boolean",
419419
"description": "experimental Web Storage API"
420420
},
421+
"experimental-yaml": {
422+
"type": "boolean",
423+
"description": "experimental node:yaml module"
424+
},
421425
"extra-info-on-fatal-exception": {
422426
"type": "boolean",
423427
"description": "hide extra information on fatal exception that causes exit"

‎doc/node.1‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -955,6 +955,9 @@ Enable experimental support for the Web Worker API.
955955
.It Fl -experimental-worker-inspection
956956
Enable experimental support for the worker inspection with Chrome DevTools.
957957
.
958+
.It Fl -experimental-yaml
959+
Enable the experimental \fBnode:yaml\fR module.
960+
.
958961
.It Fl -force-context-aware
959962
Disable loading native addons that are not context-aware.
960963
.
@@ -2304,6 +2307,8 @@ one is included in the list below.
23042307
.It
23052308
\fB--experimental-websocket\fR
23062309
.It
2310+
\fB--experimental-yaml\fR
2311+
.It
23072312
\fB--force-context-aware\fR
23082313
.It
23092314
\fB--force-fips\fR

‎lib/internal/bootstrap/realm.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ const builtinModulePolicies = [
208208
['test', true, null],
209209
['test/reporters', true, null],
210210
['vfs', true, '--experimental-vfs'],
211+
['yaml', true, '--experimental-yaml'],
211212
['zlib/iter', false, '--experimental-stream-iter'],
212213
];
213214

‎lib/internal/errors.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,6 +1596,7 @@ E('ERR_INVALID_URL_SCHEME',
15961596
`of scheme ${expected[0]}`;
15971597
return `The URL must be ${res}`;
15981598
}, TypeError);
1599+
E('ERR_INVALID_YAML', '%s', SyntaxError);
15991600
E('ERR_IPC_CHANNEL_CLOSED', 'Channel closed', Error);
16001601
E('ERR_IPC_DISCONNECTED', 'IPC channel is already disconnected', Error);
16011602
E('ERR_IPC_ONE_PIPE', 'Child process can have only one IPC pipe', Error);

0 commit comments

Comments
 (0)