diff --git a/docs/05_content_nodes.md b/docs/05_content_nodes.md index 7fedeb5c..636ad674 100644 --- a/docs/05_content_nodes.md +++ b/docs/05_content_nodes.md @@ -164,13 +164,23 @@ String(doc) #### `YAML.Document#createNode(value, options?): Node` -To create a new node, use the `createNode(value, options?)` document method. This will recursively wrap any input with appropriate `Node` containers. Generic JS `Object` values as well as `Map` and its descendants become mappings, while arrays and other iterable objects result in sequences. With `Object`, entries that have an `undefined` value are dropped. +To create a new node, use the `createNode(value, options?)` document method. +This will recursively wrap any input with appropriate `Node` containers. +Generic JS `Object` values as well as `Map` and its descendants become mappings, while arrays and other iterable objects result in sequences. +With `Object`, entries that have an `undefined` value are dropped. -Use `options.replacer` to apply a replacer array or function, following the [JSON implementation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter). 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. +To force flow styling on a collection, use `options.flow = true` +Use `options.replacer` to apply a replacer array or function, following the [JSON implementation][replacer]. +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. -As a possible side effect, this method may add entries to the document's [`anchors`](#working-with-anchors) +[replacer]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter -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. +As a possible side effect, this method may add entries to the document's [`anchors`](#working-with-anchors). + +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)

diff --git a/src/doc/Document.ts b/src/doc/Document.ts index d667bd74..3007d522 100644 --- a/src/doc/Document.ts +++ b/src/doc/Document.ts @@ -5,8 +5,10 @@ import { collectionFromPath, isEmptyPath } from '../nodes/Collection.js' import { DOC, isCollection, + isMap, isNode, isScalar, + isSeq, Node, NODE_TYPE, ParsedNode @@ -157,7 +159,7 @@ export class Document { */ createNode( value: unknown, - { keepUndefined, onTagObj, replacer, tag }: CreateNodeOptions = {} + { flow, keepUndefined, onTagObj, replacer, tag }: CreateNodeOptions = {} ): Node { if (typeof replacer === 'function') value = replacer.call({ '': value }, '', value) @@ -183,6 +185,7 @@ export class Document { replacer, schema: this.schema } + const node = createNode(value, tag, ctx) for (const alias of aliasNodes) { // With circular references, the source node is only resolved after all of @@ -195,6 +198,11 @@ export class Document { this.anchors.map[name] = alias.source } } + if (flow) { + if (isMap(node)) node.type = Type.FLOW_MAP + else if (isSeq(node)) node.type = Type.FLOW_SEQ + } + return node } diff --git a/src/nodes/Scalar.ts b/src/nodes/Scalar.ts index f8737274..6c7c66cb 100644 --- a/src/nodes/Scalar.ts +++ b/src/nodes/Scalar.ts @@ -31,6 +31,7 @@ export class Scalar extends NodeBase { */ declare format?: string + /** If `value` is a number, use this value when stringifying this node. */ declare minFractionDigits?: number /** Set during parsing to the source string value */ diff --git a/src/options.ts b/src/options.ts index 0732fb30..fab3e969 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,9 +1,10 @@ -import { LogLevelId, Type } from './constants.js' +import type { LogLevelId } from './constants.js' import type { Reviver } from './doc/applyReviver.js' import type { Directives } from './doc/directives.js' import type { Replacer } from './doc/Document.js' import type { SchemaName } from './doc/Schema.js' import type { Pair } from './nodes/Pair.js' +import type { Scalar } from './nodes/Scalar.js' import type { LineCounter } from './parse/line-counter.js' import type { CollectionTag, ScalarTag, TagValue } from './tags/types.js' @@ -117,6 +118,15 @@ export type SchemaOptions = { } export type CreateNodeOptions = { + /** Force the top-level collection node to use flow style. */ + flow?: boolean + + /** + * Keep `undefined` object values when creating mappings, rather than + * discarding them. + * + * Default: `false` + */ keepUndefined?: boolean | null onTagObj?: (tagObj: ScalarTag | CollectionTag) => void @@ -174,13 +184,7 @@ export type ToStringOptions = { * * Default: `null` */ - defaultKeyType?: - | null - | Type.BLOCK_FOLDED - | Type.BLOCK_LITERAL - | Type.PLAIN - | Type.QUOTE_DOUBLE - | Type.QUOTE_SINGLE + defaultKeyType?: Scalar.Type | null /** * The default type of string literal used to stringify values in general. @@ -188,12 +192,7 @@ export type ToStringOptions = { * * Default: `'PLAIN'` */ - defaultStringType?: - | Type.BLOCK_FOLDED - | Type.BLOCK_LITERAL - | Type.PLAIN - | Type.QUOTE_DOUBLE - | Type.QUOTE_SINGLE + defaultStringType?: Scalar.Type /** * Restrict double-quoted strings to use JSON-compatible syntax. diff --git a/tests/doc/createNode.js b/tests/doc/createNode.js index b309adfa..f11bf22f 100644 --- a/tests/doc/createNode.js +++ b/tests/doc/createNode.js @@ -54,11 +54,23 @@ describe('arrays', () => { expect(s).toBeInstanceOf(YAMLSeq) expect(s.items).toHaveLength(0) }) + test('createNode([true])', () => { const s = doc.createNode([true]) expect(s).toBeInstanceOf(YAMLSeq) expect(s.items).toMatchObject([{ value: true }]) + doc.contents = s + expect(String(doc)).toBe('- true\n') + }) + + test('flow: true', () => { + const s = doc.createNode([true], { flow: true }) + expect(s).toBeInstanceOf(YAMLSeq) + expect(s.items).toMatchObject([{ value: true }]) + doc.contents = s + expect(String(doc)).toBe('[ true ]\n') }) + describe('[3, ["four", 5]]', () => { const array = [3, ['four', 5]] test('createNode(value)', () => { @@ -89,13 +101,27 @@ describe('objects', () => { expect(s).toBeInstanceOf(YAMLMap) expect(s.items).toHaveLength(0) }) + test('createNode({ x: true })', () => { const s = doc.createNode({ x: true }) expect(s).toBeInstanceOf(YAMLMap) expect(s.items).toMatchObject([ { key: { value: 'x' }, value: { value: true } } ]) + doc.contents = s + expect(String(doc)).toBe('x: true\n') + }) + + test('flow: true', () => { + const s = doc.createNode({ x: true }, { flow: true }) + expect(s).toBeInstanceOf(YAMLMap) + expect(s.items).toMatchObject([ + { key: { value: 'x' }, value: { value: true } } + ]) + doc.contents = s + expect(String(doc)).toBe('{ x: true }\n') }) + test('createNode({ x: true, y: undefined })', () => { const s = doc.createNode({ x: true, y: undefined }) expect(s).toBeInstanceOf(YAMLMap) @@ -103,6 +129,7 @@ describe('objects', () => { { type: PairType.PAIR, key: { value: 'x' }, value: { value: true } } ]) }) + test('createNode({ x: true, y: undefined }, { keepUndefined: true })', () => { const s = doc.createNode({ x: true, y: undefined }, { keepUndefined: true }) expect(s).toBeInstanceOf(YAMLMap) @@ -111,6 +138,7 @@ describe('objects', () => { { type: PairType.PAIR, key: { value: 'y' }, value: { value: null } } ]) }) + describe('{ x: 3, y: [4], z: { w: "five", v: 6 } }', () => { const object = { x: 3, y: [4], z: { w: 'five', v: 6 } } test('createNode(value)', () => {