From b101145b71812e4c5c8e0bf588bb10ea92eb5925 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Wed, 5 Aug 2020 20:16:38 +0300 Subject: [PATCH] Remove YAML.createNode() BREAKING CHANGE: Recursively creating nodes now requires a document instance. --- docs/01_intro.md | 2 +- docs/04_documents.md | 45 ++++++------ docs/05_content_nodes.md | 61 ++++++++-------- docs/06_custom_tags.md | 22 +++--- src/index.js | 24 ------- tests/doc/collection-access.js | 75 ++++++++++---------- tests/doc/comments.js | 4 +- tests/doc/createNode.js | 124 ++++++++++++++++----------------- tests/doc/stringify.js | 14 ++-- 9 files changed, 177 insertions(+), 194 deletions(-) diff --git a/docs/01_intro.md b/docs/01_intro.md index c8fa7fa3..e7959ab2 100644 --- a/docs/01_intro.md +++ b/docs/01_intro.md @@ -36,11 +36,11 @@ const YAML = require('yaml')

Documents

-- [`YAML.createNode(value, wrapScalars, tag): Node`](#creating-nodes) - [`YAML.defaultOptions`](#options) - [`YAML.Document`](#documents) - [`constructor(value, options)`](#creating-documents) - [`defaults`](#options) + - [`#createNode(value, options): Node`](#creating-nodes) - [`#anchors`](#working-with-anchors) - [`#contents`](#content-nodes) - [`#errors`](#errors) diff --git a/docs/04_documents.md b/docs/04_documents.md index 0f9c90b8..83c03c05 100644 --- a/docs/04_documents.md +++ b/docs/04_documents.md @@ -98,16 +98,16 @@ During stringification, a document with a true-ish `version` value will include ## Document Methods -| Method | Returns | Description | -| ------------------------------------------ | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| createNode(value, options?) | `Node` | Recursively turn objects into [collections](#collections). Generic objects as well as `Map` and its descendants become mappings, while arrays and other iterable objects result in sequences. If `options.wrapScalars` is undefined or `true`, it also wraps plain values in `Scalar` objects. To specify the collection type, set `options.tag` to its identifying string, e.g. `"!!omap"`. | -| createPair(key, value, options?) | `Pair` | Recursively wrap `key` and `value` into a `Pair` object. See `createNode()` for the available options. | -| listNonDefaultTags() | `string[]` | List the tags used in the document that are not in the default `tag:yaml.org,2002:` namespace. | -| parse(cst) | `Document` | Parse a CST into this document. Mostly an internal method, modifying the document according to the contents of the parsed `cst`. Calling this multiple times on a Document is not recommended. | -| setSchema(id?, customTags?) | `void` | Set the schema used by the document. `id` may either be a YAML version, or the identifier of a YAML 1.2 schema; if set, `customTags` should have the same shape as the similarly-named option. | -| setTagPrefix(handle, prefix) | `void` | Set `handle` as a shorthand string for the `prefix` tag namespace. | -| toJSON() | `any` | A plain JavaScript representation of the document `contents`. | -| toString() | `string` | A YAML representation of the document. | +| Method | Returns | Description | +| ------------------------------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| createNode(value, options?) | `Node` | Recursively wrap any input with appropriate `Node` containers. See [Creating Nodes](#creating-nodes) for more information. | +| createPair(key, value, options?) | `Pair` | Recursively wrap `key` and `value` into a `Pair` object. See [Creating Nodes](#creating-nodes) for more information. | +| listNonDefaultTags() | `string[]` | List the tags used in the document that are not in the default `tag:yaml.org,2002:` namespace. | +| parse(cst) | `Document` | Parse a CST into this document. Mostly an internal method, modifying the document according to the contents of the parsed `cst`. Calling this multiple times on a Document is not recommended. | +| setSchema(id?, customTags?) | `void` | Set the schema used by the document. `id` may either be a YAML version, or the identifier of a YAML 1.2 schema; if set, `customTags` should have the same shape as the similarly-named option. | +| setTagPrefix(handle, prefix) | `void` | Set `handle` as a shorthand string for the `prefix` tag namespace. | +| toJSON() | `any` | A plain JavaScript representation of the document `contents`. | +| toString() | `string` | A YAML representation of the document. | ```js const doc = YAML.parseDocument('a: 1\nb: [2, 3]\n') @@ -163,33 +163,34 @@ A description of [alias and merge nodes](#alias-nodes) is included in the next s ```js const src = '[{ a: A }, { b: B }]' const doc = YAML.parseDocument(src) -const { anchors, contents } = doc -const [a, b] = contents.items -anchors.setAnchor(a.items[0].value) // 'a1' -anchors.setAnchor(b.items[0].value) // 'a2' -anchors.setAnchor(null, 'a1') // 'a1' -anchors.getName(a) // undefined -anchors.getNode('a2') +doc.anchors.setAnchor(doc.getIn([0, 'a'], true)) // 'a1' +doc.anchors.setAnchor(doc.getIn([1, 'b'], true)) // 'a2' +doc.anchors.setAnchor(null, 'a1') // 'a1' +doc.anchors.getNode('a2') // { value: 'B', range: [ 16, 18 ], type: 'PLAIN' } String(doc) // [ { a: A }, { b: &a2 B } ] -const alias = anchors.createAlias(a, 'AA') -contents.items.push(alias) +const alias = doc.anchors.createAlias(doc.get(0, true), 'AA') +// Alias { source: YAMLMap { items: [ [Pair] ] } } +doc.add(alias) doc.toJSON() // [ { a: 'A' }, { b: 'B' }, { a: 'A' } ] String(doc) // [ &AA { a: A }, { b: &a2 B }, *AA ] -const merge = anchors.createMergePair(alias) -b.items.push(merge) +const merge = doc.anchors.createMergePair(alias) +// Merge { +// key: Scalar { value: '<<' }, +// value: YAMLSeq { items: [ [Alias] ] } } +doc.addIn([1], merge) doc.toJSON() // [ { a: 'A' }, { b: 'B', a: 'A' }, { a: 'A' } ] String(doc) // [ &AA { a: A }, { b: &a2 B, <<: *AA }, *AA ] // This creates a circular reference -merge.value.items.push(anchors.createAlias(b)) +merge.value.add(doc.anchors.createAlias(doc.get(1, true))) doc.toJSON() // [RangeError: Maximum call stack size exceeded] String(doc) // [ diff --git a/docs/05_content_nodes.md b/docs/05_content_nodes.md index fcda5258..fbaafaa7 100644 --- a/docs/05_content_nodes.md +++ b/docs/05_content_nodes.md @@ -32,7 +32,7 @@ class Scalar extends Node { } ``` -A parsed document's contents will have all of its non-object values wrapped in `Scalar` objects, which themselves may be in some hierarchy of `Map` and `Seq` collections. However, this is not a requirement for the document's stringification, which is rather tolerant regarding its input values, and will use [`YAML.createNode`](#yaml-createnode) when encountering an unwrapped value. +A parsed document's contents will have all of its non-object values wrapped in `Scalar` objects, which themselves may be in some hierarchy of `Map` and `Seq` collections. However, this is not a requirement for the document's stringification, which is rather tolerant regarding its input values, and will use [`doc.createNode()`](#creating-nodes) when encountering an unwrapped value. When stringifying, the node `type` will be taken into account by `!!str` and `!!binary` values, and ignored by other scalars. On the other hand, `!!int` and `!!float` stringifiers will take `format` into account. @@ -74,14 +74,15 @@ All of the collections provide the following accessor methods: ```js -const map = YAML.createNode({ a: 1, b: [2, 3] }) +const doc = new YAML.Document() +const map = doc.createNode({ a: 1, b: [2, 3] }) map.add({ key: 'c', value: 4 }) // => map.get('c') === 4 && map.has('c') === true map.addIn(['b'], 5) // -> map.getIn(['b', 2]) === 5 map.delete('c') // true map.deleteIn(['c', 'f']) // false map.get('a') // 1 -map.get(YAML.createNode('a'), true) // Scalar { value: 1 } +map.get(doc.createNode('a'), true) // Scalar { value: 1 } map.getIn(['b', 1]) // 3 map.has('c') // false map.hasIn(['b', '0']) // true @@ -92,7 +93,7 @@ map.setIn(['c', 'x']) // Expected YAML collection at c. Remaining path: x ``` -For all of these methods, the keys may be nodes or their wrapped scalar values (i.e. `42` will match `Scalar { value: 42 }`) . Keys for `!!seq` should be positive integers, or their string representations. `add()` and `set()` do not automatically call `createNode()` to wrap the value. +For all of these methods, the keys may be nodes or their wrapped scalar values (i.e. `42` will match `Scalar { value: 42 }`) . Keys for `!!seq` should be positive integers, or their string representations. `add()` and `set()` do not automatically call `doc.createNode()` to wrap the value. Each of the methods also has a variant that requires an iterable as the first parameter, and allows fetching or modifying deeper collections: `addIn(path, value)`, `deleteIn(path)`, `getIn(path, keepScalar)`, `hasIn(path)`, `setIn(path, value)`. If any intermediate node in `path` is a scalar rather than a collection, an error will be thrown. If any of the intermediate collections is not found: @@ -124,7 +125,7 @@ YAML.stringify(obj) `Alias` nodes provide a way to include a single node in multiple places in a document; the `source` of an alias node must be a preceding node in the document. Circular references are fully supported, and where possible the JS representation of alias nodes will be the actual source object. -When directly stringifying JS structures with `YAML.stringify()`, multiple references to the same object will result in including an autogenerated anchor at its first instance, and alias nodes to that anchor at later references. Directly calling `YAML.createNode()` will not create anchors or alias nodes, allowing for greater manual control. +When nodes are constructed from JS structures (e.g. during `YAML.stringify()`), multiple references to the same object will result in including an autogenerated anchor at its first instance, and alias nodes to that anchor at later references. ```js class Merge extends Pair { @@ -141,48 +142,52 @@ To create and work with alias and merge nodes, you should use the [`YAML.Documen ## Creating Nodes ```js -const seq = YAML.createNode(['some', 'values', { balloons: 99 }]) -// YAMLSeq { +const doc = new YAML.Document(['some', 'values']) +// Document { +// contents: +// YAMLSeq { +// items: +// [ Scalar { value: 'some' }, +// Scalar { value: 'values' } ] } } + +const map = doc.createNode({ balloons: 99 }) +// YAMLMap { // items: -// [ Scalar { value: 'some' }, -// Scalar { value: 'values' }, -// YAMLMap { -// items: -// [ Pair { -// key: Scalar { value: 'balloons' }, -// value: Scalar { value: 99 } } ] } ] } +// [ Pair { +// key: Scalar { value: 'balloons' }, +// value: Scalar { value: 99 } } ] } -const doc = new YAML.Document() -doc.contents = seq -seq.items[0].comment = ' A commented item' +doc.add(map) +doc.get(0, true).comment = ' A commented item' String(doc) // - some # A commented item // - values // - balloons: 99 ``` -#### `YAML.createNode(value, wrapScalars?, tag?): Node` +#### `YAML.Document#createNode(value, options?): Node` -`YAML.createNode` recursively turns objects into [collections](#collections). Generic objects as well as `Map` and its descendants become mappings, while arrays and other iterable objects result in sequences. If `wrapScalars` is undefined or `true`, it also wraps plain values in `Scalar` objects; if it is false and `value` is not an object, it will be returned directly. +To create a new node, use the `createNode(value, options?)` document method. This will recursively wrap any input with appropriate `Node` containers. Generic objects as well as `Map` and its descendants become mappings, while arrays and other iterable objects result in sequences. -To specify the collection type, set `tag` to its identifying string, e.g. `"!!omap"`. Note that this requires the corresponding tag to be available based on the default options. To use a specific document's schema, use the Document method `createNode(value, options?)`. +To specify the collection type, set `options.tag` to its identifying string, e.g. `"!!omap"`. Note that this requires the corresponding tag to be available in the document's schema. If `options.wrapScalars` is undefined or `true`, plain values are wrapped in `Scalar` objects. -The primary purpose of this function is to enable attaching comments or other metadata to a value, or to otherwise exert more fine-grained control over the stringified output. To that end, you'll need to assign its return value to the `contents` of a Document (or somewhere within said contents), as the document's schema is required for YAML string output. +As a possible side effect, this method may add entries to the document's [`anchors`](#working-with-anchors) -

new Map(), new Seq(), new Pair(key, value)

+The primary purpose of this method is to enable attaching comments or other metadata to a value, or to otherwise exert more fine-grained control over the stringified output. To that end, you'll need to assign its return value to the `contents` of a document (or somewhere within said contents), as the document's schema is required for YAML string output. If you're not interested in working with such metadata, document `contents` may also include non-`Node` values at any level. + +

new YAMLMap(), new YAMLSeq(), doc.createPair(key, value)

```js import YAML from 'yaml' -import { Pair, YAMLSeq } from 'yaml/types' +import { YAMLSeq } from 'yaml/types' -const doc = new YAML.Document() -doc.contents = new YAMLSeq() +const doc = new YAML.Document(new YAMLSeq()) doc.contents.items = [ 'some values', 42, { including: 'objects', 3: 'a string' } ] -doc.contents.items.push(new Pair(1, 'a number')) +doc.add(doc.createPair(1, 'a number')) doc.toString() // - some values @@ -192,9 +197,9 @@ doc.toString() // - 1: a number ``` -To construct a `YAMLSeq` or `YAMLMap`, use [`YAML.createNode()`](#yaml-createnode) with array, object or iterable input, or create the collections directly by importing the classes from `yaml/types`. +To construct a `YAMLSeq` or `YAMLMap`, use `doc.createNode()` with array, object or iterable input, or create the collections directly by importing the classes from `yaml/types`. -Once created, normal array operations may be used to modify the `items` array. New `Pair` objects may created either by importing the class from `yaml/types` and using its `new Pair(key, value)` constructor, or by using the `doc.createPair(key, value)` method. The latter will recursively wrap the `key` and `value` as nodes. +Once created, normal array operations may be used to modify the `items` array. New `Pair` objects may created either by importing the class from `yaml/types` and using its `new Pair(key, value)` constructor, or by using the `doc.createPair(key, value, options?)` method. The latter will recursively wrap the `key` and `value` as nodes, and accepts the same options as `doc.createNode()` ## Comments diff --git a/docs/06_custom_tags.md b/docs/06_custom_tags.md index da06374e..d5ae72ef 100644 --- a/docs/06_custom_tags.md +++ b/docs/06_custom_tags.md @@ -57,15 +57,15 @@ If including more than one custom tag from this set, make sure that the `'float' These tags are a part of the YAML 1.1 [language-independent types](https://yaml.org/type/), but are not a part of any default YAML 1.2 schema. -| Identifier | YAML Type | JS Type | Description | -| ------------- | ----------------------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `'binary'` | [`!!binary`](https://yaml.org/type/binary.html) | `Uint8Array` | Binary data, represented in YAML as base64 encoded characters. | -| `'floatTime'` | [`!!float`](https://yaml.org/type/float.html) | `Number` | Sexagesimal floating-point number format, e.g. `190:20:30.15`. To stringify with this tag, the node `format` must be `'TIME'`. | -| `'intTime'` | [`!!int`](https://yaml.org/type/int.html) | `Number` | Sexagesimal integer number format, e.g. `190:20:30`. To stringify with this tag, the node `format` must be `'TIME'`. | -| `'omap'` | [`!!omap`](https://yaml.org/type/omap.html) | `Map` | Ordered sequence of key: value pairs without duplicates. Using `mapAsMap: true` together with this tag is not recommended, as it makes the parse → stringify loop non-idempotent. | -| `'pairs'` | [`!!pairs`](https://yaml.org/type/pairs.html) | `Array` | Ordered sequence of key: value pairs allowing duplicates. To create from JS, you'll need to explicitly use `'!!pairs'` as the third argument of [`createNode()`](#creating-nodes). | -| `'set'` | [`!!set`](https://yaml.org/type/set.html) | `Set` | Unordered set of non-equal values. | -| `'timestamp'` | [`!!timestamp`](https://yaml.org/type/timestamp.html) | `Date` | A point in time, e.g. `2001-12-15T02:59:43`. | +| Identifier | YAML Type | JS Type | Description | +| ------------- | ----------------------------------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `'binary'` | [`!!binary`](https://yaml.org/type/binary.html) | `Uint8Array` | Binary data, represented in YAML as base64 encoded characters. | +| `'floatTime'` | [`!!float`](https://yaml.org/type/float.html) | `Number` | Sexagesimal floating-point number format, e.g. `190:20:30.15`. To stringify with this tag, the node `format` must be `'TIME'`. | +| `'intTime'` | [`!!int`](https://yaml.org/type/int.html) | `Number` | Sexagesimal integer number format, e.g. `190:20:30`. To stringify with this tag, the node `format` must be `'TIME'`. | +| `'omap'` | [`!!omap`](https://yaml.org/type/omap.html) | `Map` | Ordered sequence of key: value pairs without duplicates. Using `mapAsMap: true` together with this tag is not recommended, as it makes the parse → stringify loop non-idempotent. | +| `'pairs'` | [`!!pairs`](https://yaml.org/type/pairs.html) | `Array` | Ordered sequence of key: value pairs allowing duplicates. To create from JS, use `doc.createNode(array, { tag: '!!pairs' })`. | +| `'set'` | [`!!set`](https://yaml.org/type/set.html) | `Set` | Unordered set of non-equal values. | +| `'timestamp'` | [`!!timestamp`](https://yaml.org/type/timestamp.html) | `Date` | A point in time, e.g. `2001-12-15T02:59:43`. | ## Writing Custom Tags @@ -118,7 +118,7 @@ Note that during the CST -> AST parsing, the anchors and comments attached to ea As with parsing, turning input data into its YAML string representation is a two-stage process as the input is first turned into an AST tree before stringifying it. This allows for metadata and comments to be attached to each node, and for e.g. circular references to be resolved. For scalar values, this means just wrapping the value within a `Scalar` class while keeping it unchanged. -As values may be wrapped within objects and arrays, `YAML.createNode()` uses each tag's `identify(value): boolean` function to detect custom data types. For the same reason, collections need to define their own `createNode(schema, value, ctx): Collection` functions that may recursively construct their equivalent collection class instances. +As values may be wrapped within objects and arrays, `doc.createNode()` uses each tag's `identify(value): boolean` function to detect custom data types. For the same reason, collections need to define their own `createNode(schema, value, ctx): Collection` functions that may recursively construct their equivalent collection class instances. Finally, `stringify(item, ctx, ...): string` defines how your data should be represented as a YAML string, in case the default stringifiers aren't enough. For collections in particular, the default stringifier should be perfectly sufficient. `'yaml/util'` exports `stringifyNumber(item)` and `stringifyString(item, ctx, ...)`, which may be of use for custom scalar data. @@ -142,7 +142,7 @@ To define your own tag, you'll need to define an object comprising of some of th - `createNode(schema, value, ctx): Node` is an optional factory function, used e.g. by collections when wrapping JS objects as AST nodes. - `format: string` If a tag has multiple forms that should be parsed and/or stringified differently, use `format` to identify them. Used by `!!int` and `!!float`. -- **`identify(value): boolean`** is used by `YAML.createNode` to detect your data type, e.g. using `typeof` or `instanceof`. Required. +- **`identify(value): boolean`** is used by `doc.createNode()` to detect your data type, e.g. using `typeof` or `instanceof`. Required. - `nodeClass: Node` is the `Node` child class that implements this tag. Required for collections and tags that have overlapping JS representations. - `options: Object` is used by some tags to configure their stringification. - **`resolve(doc, cstNode): Node | any`** turns a CST node into an AST node; `doc` is the resulting `YAML.Document` instance. If returning a non-`Node` value, the output will be wrapped as a `Scalar`. Required. diff --git a/src/index.js b/src/index.js index 74beba3c..11c84776 100644 --- a/src/index.js +++ b/src/index.js @@ -1,32 +1,9 @@ import { parse as parseCST } from './cst/parse.js' -import { createNode } from './doc/createNode.js' import { Document as YAMLDocument } from './doc/Document.js' -import { Schema } from './doc/Schema.js' import { YAMLSemanticError } from './errors.js' import { defaultOptions, scalarOptions } from './options.js' import { warn } from './warnings.js' -function _createNode(value, wrapScalars = true, tag) { - if (tag === undefined && typeof wrapScalars === 'string') { - tag = wrapScalars - wrapScalars = true - } - const options = Object.assign( - {}, - YAMLDocument.defaults[defaultOptions.version], - defaultOptions - ) - const ctx = { - onAlias() { - throw new Error('Repeated objects are not supported here') - }, - prevObjects: new Map(), - schema: new Schema(options), - wrapScalars - } - return createNode(value, tag, ctx) -} - class Document extends YAMLDocument { constructor(contents, options) { super(contents, Object.assign({}, defaultOptions, options)) @@ -69,7 +46,6 @@ function stringify(value, options) { } export const YAML = { - createNode: _createNode, defaultOptions, Document, parse, diff --git a/tests/doc/collection-access.js b/tests/doc/collection-access.js index e48e7415..9b8c83be 100644 --- a/tests/doc/collection-access.js +++ b/tests/doc/collection-access.js @@ -2,9 +2,10 @@ import YAML from '../../index.js' import { Pair } from '../../types.js' describe('Map', () => { - let map + let doc, map beforeEach(() => { - map = YAML.createNode({ a: 1, b: { c: 3, d: 4 } }) + doc = new YAML.Document() + map = doc.createNode({ a: 1, b: { c: 3, d: 4 } }) expect(map.items).toMatchObject([ { key: { value: 'a' }, value: { value: 1 } }, { @@ -43,10 +44,10 @@ describe('Map', () => { }) test('get with node', () => { - expect(map.get(YAML.createNode('a'))).toBe(1) - expect(map.get(YAML.createNode('a'), true)).toMatchObject({ value: 1 }) - expect(map.get(YAML.createNode('b')).toJSON()).toMatchObject({ c: 3, d: 4 }) - expect(map.get(YAML.createNode('c'))).toBeUndefined() + expect(map.get(doc.createNode('a'))).toBe(1) + expect(map.get(doc.createNode('a'), true)).toMatchObject({ value: 1 }) + expect(map.get(doc.createNode('b')).toJSON()).toMatchObject({ c: 3, d: 4 }) + expect(map.get(doc.createNode('c'))).toBeUndefined() }) test('has with value', () => { @@ -58,10 +59,10 @@ describe('Map', () => { }) test('has with node', () => { - expect(map.has(YAML.createNode('a'))).toBe(true) - expect(map.has(YAML.createNode('b'))).toBe(true) - expect(map.has(YAML.createNode('c'))).toBe(false) - expect(map.has(YAML.createNode())).toBe(false) + expect(map.has(doc.createNode('a'))).toBe(true) + expect(map.has(doc.createNode('b'))).toBe(true) + expect(map.has(doc.createNode('c'))).toBe(false) + expect(map.has(doc.createNode())).toBe(false) }) test('set with value', () => { @@ -76,21 +77,22 @@ describe('Map', () => { }) test('set with node', () => { - map.set(YAML.createNode('a'), 2) + map.set(doc.createNode('a'), 2) expect(map.get('a')).toBe(2) expect(map.get('a', true)).toBe(2) - map.set(YAML.createNode('b'), 5) + map.set(doc.createNode('b'), 5) expect(map.get('b')).toBe(5) - map.set(YAML.createNode('c'), 6) + map.set(doc.createNode('c'), 6) expect(map.get('c')).toBe(6) expect(map.items).toHaveLength(3) }) }) describe('Seq', () => { - let seq + let doc, seq beforeEach(() => { - seq = YAML.createNode([1, [2, 3]]) + doc = new YAML.Document() + seq = doc.createNode([1, [2, 3]]) expect(seq.items).toMatchObject([ { value: 1 }, { items: [{ value: 2 }, { value: 3 }] } @@ -121,11 +123,11 @@ describe('Seq', () => { }) test('get with node', () => { - expect(seq.get(YAML.createNode(0))).toBe(1) - expect(seq.get(YAML.createNode('0'))).toBe(1) - expect(seq.get(YAML.createNode(0), true)).toMatchObject({ value: 1 }) - expect(seq.get(YAML.createNode(1)).toJSON()).toMatchObject([2, 3]) - expect(seq.get(YAML.createNode(2))).toBeUndefined() + expect(seq.get(doc.createNode(0))).toBe(1) + expect(seq.get(doc.createNode('0'))).toBe(1) + expect(seq.get(doc.createNode(0), true)).toMatchObject({ value: 1 }) + expect(seq.get(doc.createNode(1)).toJSON()).toMatchObject([2, 3]) + expect(seq.get(doc.createNode(2))).toBeUndefined() }) test('has with value', () => { @@ -138,11 +140,11 @@ describe('Seq', () => { }) test('has with node', () => { - expect(seq.has(YAML.createNode(0))).toBe(true) - expect(seq.has(YAML.createNode('0'))).toBe(true) - expect(seq.has(YAML.createNode(2))).toBe(false) - expect(seq.has(YAML.createNode(''))).toBe(false) - expect(seq.has(YAML.createNode())).toBe(false) + expect(seq.has(doc.createNode(0))).toBe(true) + expect(seq.has(doc.createNode('0'))).toBe(true) + expect(seq.has(doc.createNode(2))).toBe(false) + expect(seq.has(doc.createNode(''))).toBe(false) + expect(seq.has(doc.createNode())).toBe(false) }) test('set with value', () => { @@ -157,12 +159,12 @@ describe('Seq', () => { }) test('set with node', () => { - seq.set(YAML.createNode(0), 2) + seq.set(doc.createNode(0), 2) expect(seq.get(0)).toBe(2) expect(seq.get(0, true)).toBe(2) - seq.set(YAML.createNode('1'), 5) + seq.set(doc.createNode('1'), 5) expect(seq.get(1)).toBe(5) - seq.set(YAML.createNode(2), 6) + seq.set(doc.createNode(2), 6) expect(seq.get(2)).toBe(6) expect(seq.items).toHaveLength(3) }) @@ -286,9 +288,10 @@ describe('OMap', () => { }) describe('Collection', () => { - let map + let doc, map beforeEach(() => { - map = YAML.createNode({ a: 1, b: [2, 3] }) + doc = new YAML.Document() + map = doc.createNode({ a: 1, b: [2, 3] }) }) test('addIn', () => { @@ -390,7 +393,7 @@ describe('Document', () => { expect(doc.get('a')).toBeUndefined() expect(doc.contents.items).toHaveLength(1) - doc.contents = YAML.createNode('s') + doc.contents = doc.createNode('s') expect(() => doc.set('a', 1)).toThrow(/document contents/) }) @@ -413,7 +416,7 @@ describe('Document', () => { expect(doc.get('a', true)).toMatchObject({ value: 1 }) expect(doc.get('c')).toBeUndefined() - doc.contents = YAML.createNode('s') + doc.contents = doc.createNode('s') expect(doc.get('a')).toBeUndefined() }) @@ -428,7 +431,7 @@ describe('Document', () => { }) test('getIn scalar', () => { - doc.contents = YAML.createNode('s') + doc.contents = doc.createNode('s') expect(doc.getIn([])).toBe('s') expect(doc.getIn(null, true)).toMatchObject({ value: 's' }) expect(doc.getIn([0])).toBeUndefined() @@ -438,7 +441,7 @@ describe('Document', () => { expect(doc.has('a')).toBe(true) expect(doc.has('c')).toBe(false) - doc.contents = YAML.createNode('s') + doc.contents = doc.createNode('s') expect(doc.has('a')).toBe(false) }) @@ -458,7 +461,7 @@ describe('Document', () => { expect(doc.get('c')).toBe(6) expect(doc.contents.items).toHaveLength(3) - doc.contents = YAML.createNode('s') + doc.contents = doc.createNode('s') expect(() => doc.set('a', 1)).toThrow(/document contents/) }) @@ -479,7 +482,7 @@ describe('Document', () => { 'a: 2\nb:\n - 2\n - 5\nc: 6\ne:\n - null\n - e: 7\n' ) - doc.contents = YAML.createNode('s') + doc.contents = doc.createNode('s') expect(() => doc.setIn(['a'], 1)).toThrow(/document contents/) }) }) diff --git a/tests/doc/comments.js b/tests/doc/comments.js index 1889c6f4..66290257 100644 --- a/tests/doc/comments.js +++ b/tests/doc/comments.js @@ -844,14 +844,14 @@ a: describe('Pair.commentBefore', () => { test('Should get key comment', () => { - const key = YAML.createNode('foo', true) + const key = new YAML.Document().createNode('foo') const pair = new Pair(key, 42) key.commentBefore = 'cc' expect(pair.commentBefore).toBe('cc') }) test('Should set key comment', () => { - const key = YAML.createNode('foo', true) + const key = new YAML.Document().createNode('foo') const pair = new Pair(key, 42) pair.commentBefore = 'cc' expect(key.commentBefore).toBe('cc') diff --git a/tests/doc/createNode.js b/tests/doc/createNode.js index e1308151..b05989fa 100644 --- a/tests/doc/createNode.js +++ b/tests/doc/createNode.js @@ -2,54 +2,59 @@ import { YAML } from '../../src/index.js' import { Pair, Scalar, YAMLMap, YAMLSeq } from '../../src/ast/index.js' import { YAMLSet } from '../../src/tags/yaml-1.1/set.js' +let doc +beforeEach(() => { + doc = new YAML.Document() +}) + describe('scalars', () => { - describe('createNode(value, false)', () => { + describe('createNode(value, { wrapScalars: false })', () => { test('boolean', () => { - const s = YAML.createNode(false, false) + const s = doc.createNode(false, { wrapScalars: false }) expect(s).toBe(false) }) test('null', () => { - const s = YAML.createNode(null, false) + const s = doc.createNode(null, { wrapScalars: false }) expect(s).toBeNull() }) test('undefined', () => { - const s = YAML.createNode(undefined, false) + const s = doc.createNode(undefined, { wrapScalars: false }) expect(s).toBeNull() }) test('number', () => { - const s = YAML.createNode(3, false) + const s = doc.createNode(3, { wrapScalars: false }) expect(s).toBe(3) }) test('string', () => { - const s = YAML.createNode('test', false) + const s = doc.createNode('test', { wrapScalars: false }) expect(s).toBe('test') }) }) }) -describe('createNode(value, true)', () => { +describe('createNode(value)', () => { test('boolean', () => { - const s = YAML.createNode(false, true) + const s = doc.createNode(false) expect(s).toBeInstanceOf(Scalar) expect(s.value).toBe(false) }) test('null', () => { - const s = YAML.createNode(null, true) + const s = doc.createNode(null) expect(s).toBeInstanceOf(Scalar) expect(s.value).toBe(null) }) test('undefined', () => { - const s = YAML.createNode(undefined, true) + const s = doc.createNode(undefined) expect(s).toBeInstanceOf(Scalar) expect(s.value).toBe(null) }) test('number', () => { - const s = YAML.createNode(3, true) + const s = doc.createNode(3) expect(s).toBeInstanceOf(Scalar) expect(s.value).toBe(3) }) test('string', () => { - const s = YAML.createNode('test', true) + const s = doc.createNode('test') expect(s).toBeInstanceOf(Scalar) expect(s.value).toBe('test') }) @@ -57,18 +62,21 @@ describe('createNode(value, true)', () => { describe('explicit tags', () => { test('wrapScalars: false', () => { - const s = YAML.createNode(3, false, 'tag:yaml.org,2002:str') + const s = doc.createNode(3, { + tag: 'tag:yaml.org,2002:str', + wrapScalars: false + }) expect(s).toBe(3) }) test('wrapScalars: true', () => { - const s = YAML.createNode(3, true, '!!str') + const s = doc.createNode(3, { tag: '!!str' }) expect(s).toBeInstanceOf(Scalar) expect(s).toMatchObject({ value: 3, tag: 'tag:yaml.org,2002:str' }) }) test('unknown tag', () => { - expect(() => YAML.createNode('3', true, '!foo')).toThrow( + expect(() => doc.createNode('3', { tag: '!foo' })).toThrow( 'Tag !foo not found' ) }) @@ -76,27 +84,27 @@ describe('explicit tags', () => { describe('arrays', () => { test('createNode([])', () => { - const s = YAML.createNode([]) + const s = doc.createNode([]) expect(s).toBeInstanceOf(YAMLSeq) expect(s.items).toHaveLength(0) }) - test('createNode([true], false)', () => { - const s = YAML.createNode([true], false) + test('createNode([true], { wrapScalars: false })', () => { + const s = doc.createNode([true], { wrapScalars: false }) expect(s).toBeInstanceOf(YAMLSeq) expect(s.items).toMatchObject([true]) }) describe('[3, ["four", 5]]', () => { const array = [3, ['four', 5]] - test('createNode(value, false)', () => { - const s = YAML.createNode(array, false) + test('createNode(value, { wrapScalars: false })', () => { + const s = doc.createNode(array, { wrapScalars: false }) expect(s).toBeInstanceOf(YAMLSeq) expect(s.items).toHaveLength(2) expect(s.items[0]).toBe(3) expect(s.items[1]).toBeInstanceOf(YAMLSeq) expect(s.items[1].items).toMatchObject(['four', 5]) }) - test('createNode(value, true)', () => { - const s = YAML.createNode(array, true) + test('createNode(value)', () => { + const s = doc.createNode(array) expect(s).toBeInstanceOf(YAMLSeq) expect(s.items).toHaveLength(2) expect(s.items[0].value).toBe(3) @@ -111,9 +119,9 @@ describe('arrays', () => { expect(String(doc)).toBe(res) doc.contents = array expect(String(doc)).toBe(res) - doc.contents = YAML.createNode(array, false) + doc.contents = doc.createNode(array, { wrapScalars: false }) expect(String(doc)).toBe(res) - doc.contents = YAML.createNode(array, true) + doc.contents = doc.createNode(array) expect(String(doc)).toBe(res) }) }) @@ -121,12 +129,12 @@ describe('arrays', () => { describe('objects', () => { test('createNode({})', () => { - const s = YAML.createNode({}) + const s = doc.createNode({}) expect(s).toBeInstanceOf(YAMLMap) expect(s.items).toHaveLength(0) }) - test('createNode({ x: true }, false)', () => { - const s = YAML.createNode({ x: true }, false) + test('createNode({ x: true }, { wrapScalars: false })', () => { + const s = doc.createNode({ x: true }, { wrapScalars: false }) expect(s).toBeInstanceOf(YAMLMap) expect(s.items).toHaveLength(1) expect(s.items[0]).toBeInstanceOf(Pair) @@ -134,8 +142,8 @@ describe('objects', () => { }) describe('{ x: 3, y: [4], z: { w: "five", v: 6 } }', () => { const object = { x: 3, y: [4], z: { w: 'five', v: 6 } } - test('createNode(value, false)', () => { - const s = YAML.createNode(object, false) + test('createNode(value, { wrapScalars: false })', () => { + const s = doc.createNode(object, { wrapScalars: false }) expect(s).toBeInstanceOf(YAMLMap) expect(s.items).toHaveLength(3) expect(s.items).toMatchObject([ @@ -152,8 +160,8 @@ describe('objects', () => { } ]) }) - test('createNode(value, true)', () => { - const s = YAML.createNode(object, true) + test('createNode(value)', () => { + const s = doc.createNode(object) expect(s).toBeInstanceOf(YAMLMap) expect(s.items).toHaveLength(3) expect(s.items).toMatchObject([ @@ -181,9 +189,9 @@ z: expect(String(doc)).toBe(res) doc.contents = object expect(String(doc)).toBe(res) - doc.contents = YAML.createNode(object, false) + doc.contents = doc.createNode(object, { wrapScalars: false }) expect(String(doc)).toBe(res) - doc.contents = YAML.createNode(object, true) + doc.contents = doc.createNode(object) expect(String(doc)).toBe(res) }) }) @@ -191,27 +199,27 @@ z: describe('Set', () => { test('createNode(new Set)', () => { - const s = YAML.createNode(new Set()) + const s = doc.createNode(new Set()) expect(s).toBeInstanceOf(YAMLSeq) expect(s.items).toHaveLength(0) }) - test('createNode(new Set([true]), false)', () => { - const s = YAML.createNode(new Set([true]), false) + test('createNode(new Set([true]), { wrapScalars: false })', () => { + const s = doc.createNode(new Set([true]), { wrapScalars: false }) expect(s).toBeInstanceOf(YAMLSeq) expect(s.items).toMatchObject([true]) }) describe("Set { 3, Set { 'four', 5 } }", () => { const set = new Set([3, new Set(['four', 5])]) - test('createNode(set, false)', () => { - const s = YAML.createNode(set, false) + test('createNode(set, { wrapScalars: false })', () => { + const s = doc.createNode(set, { wrapScalars: false }) expect(s).toBeInstanceOf(YAMLSeq) expect(s.items).toHaveLength(2) expect(s.items[0]).toBe(3) expect(s.items[1]).toBeInstanceOf(YAMLSeq) expect(s.items[1].items).toMatchObject(['four', 5]) }) - test('createNode(set, true)', () => { - const s = YAML.createNode(set, true) + test('createNode(set)', () => { + const s = doc.createNode(set) expect(s).toBeInstanceOf(YAMLSeq) expect(s.items).toHaveLength(2) expect(s.items[0].value).toBe(3) @@ -226,9 +234,9 @@ describe('Set', () => { expect(String(doc)).toBe(res) doc.contents = set expect(String(doc)).toBe(res) - doc.contents = YAML.createNode(set, false) + doc.contents = doc.createNode(set, { wrapScalars: false }) expect(String(doc)).toBe(res) - doc.contents = YAML.createNode(set, true) + doc.contents = doc.createNode(set) expect(String(doc)).toBe(res) }) test('Schema#createNode() - YAML 1.2', () => { @@ -254,12 +262,12 @@ describe('Set', () => { describe('Map', () => { test('createNode(new Map)', () => { - const s = YAML.createNode(new Map()) + const s = doc.createNode(new Map()) expect(s).toBeInstanceOf(YAMLMap) expect(s.items).toHaveLength(0) }) - test('createNode(new Map([["x", true]]), false)', () => { - const s = YAML.createNode(new Map([['x', true]]), false) + test('createNode(new Map([["x", true]]), { wrapScalars: false })', () => { + const s = doc.createNode(new Map([['x', true]]), { wrapScalars: false }) expect(s).toBeInstanceOf(YAMLMap) expect(s.items).toHaveLength(1) expect(s.items[0]).toBeInstanceOf(Pair) @@ -277,8 +285,8 @@ describe('Map', () => { 'z' ] ]) - test('createNode(map, false)', () => { - const s = YAML.createNode(map, false) + test('createNode(map, { wrapScalars: false })', () => { + const s = doc.createNode(map, { wrapScalars: false }) expect(s).toBeInstanceOf(YAMLMap) expect(s.items).toHaveLength(3) expect(s.items).toMatchObject([ @@ -295,8 +303,8 @@ describe('Map', () => { } ]) }) - test('createNode(map, true)', () => { - const s = YAML.createNode(map, true) + test('createNode(map)', () => { + const s = doc.createNode(map) expect(s).toBeInstanceOf(YAMLMap) expect(s.items).toHaveLength(3) expect(s.items).toMatchObject([ @@ -324,9 +332,9 @@ y: expect(String(doc)).toBe(res) doc.contents = map expect(String(doc)).toBe(res) - doc.contents = YAML.createNode(map, false) + doc.contents = doc.createNode(map, { wrapScalars: false }) expect(String(doc)).toBe(res) - doc.contents = YAML.createNode(map, true) + doc.contents = doc.createNode(map) expect(String(doc)).toBe(res) }) }) @@ -335,7 +343,7 @@ y: describe('toJSON()', () => { test('Date', () => { const date = new Date('2018-12-22T08:02:52Z') - const node = YAML.createNode(date) + const node = doc.createNode(date) expect(node.value).toBe(date.toJSON()) }) }) @@ -360,9 +368,6 @@ describe('circular references', () => { expect(doc.anchors.map).toMatchObject({ a1: { items: [{ key: 'foo' }, { key: 'map' }] } }) - expect(() => YAML.createNode(map)).toThrow( - 'Repeated objects are not supported here' - ) }) test('ancestor at root', () => { @@ -378,9 +383,6 @@ describe('circular references', () => { expect(doc.anchors.map).toMatchObject({ a1: { items: [{ key: 'foo' }] } }) - expect(() => YAML.createNode(map)).toThrow( - 'Repeated objects are not supported here' - ) }) test('sibling sequences', () => { @@ -401,9 +403,6 @@ describe('circular references', () => { a1: { items: ['one'] }, a2: { items: ['two'] } }) - expect(() => YAML.createNode(seq)).toThrow( - 'Repeated objects are not supported here' - ) }) test('further relatives', () => { @@ -416,8 +415,5 @@ describe('circular references', () => { expect(source).toMatchObject({ items: [{ key: 'a', value: 1 }] }) expect(alias).toMatchObject({ type: 'ALIAS' }) expect(alias.source).toBe(source) - expect(() => YAML.createNode(seq)).toThrow( - 'Repeated objects are not supported here' - ) }) }) diff --git a/tests/doc/stringify.js b/tests/doc/stringify.js index e48f56f8..480b0ad2 100644 --- a/tests/doc/stringify.js +++ b/tests/doc/stringify.js @@ -313,7 +313,7 @@ test('eemeli/yaml#52: Quoting item markers', () => { const str = String(doc) expect(() => YAML.parse(str)).not.toThrow() expect(str).toBe('key: "-"\n') - doc.contents = YAML.createNode({ key: '?' }) + doc.contents = doc.createNode({ key: '?' }) const str2 = String(doc) expect(() => YAML.parse(str2)).not.toThrow() expect(str2).toBe('key: "?"\n') @@ -458,9 +458,10 @@ describe('simple keys', () => { }) test('eemeli/yaml#128: YAML node inside object', () => { - const seq = YAML.createNode(['a']) + const doc = new YAML.Document() + const seq = doc.createNode(['a']) seq.commentBefore = 'sc' - const map = YAML.createNode({ foo: 'bar', seq }) + const map = doc.createNode({ foo: 'bar', seq }) map.commentBefore = 'mc' const obj = { array: [1], map } expect(YAML.stringify(obj)).toBe( @@ -507,9 +508,10 @@ describe('sortMapEntries', () => { describe('custom indent', () => { let obj beforeEach(() => { - const seq = YAML.createNode(['a']) + const doc = new YAML.Document() + const seq = doc.createNode(['a']) seq.commentBefore = 'sc' - const map = YAML.createNode({ foo: 'bar', seq }) + const map = doc.createNode({ foo: 'bar', seq }) map.commentBefore = 'mc' obj = { array: [{ a: 1, b: 2 }], map } }) @@ -556,7 +558,7 @@ describe('custom indent', () => { describe('indentSeq: false', () => { let obj beforeEach(() => { - const seq = YAML.createNode(['a']) + const seq = new YAML.Document().createNode(['a']) seq.commentBefore = 'sc' obj = { array: [{ a: 1, b: 2 }], map: { seq } } })