diff --git a/README.md b/README.md index 5914c993..c7dcb2e8 100644 --- a/README.md +++ b/README.md @@ -97,11 +97,10 @@ parse(file) - [`Document`](https://eemeli.org/yaml/#documents) - [`constructor(value, replacer?, options?)`](https://eemeli.org/yaml/#creating-documents) - - [`#contents`](https://eemeli.org/yaml/#content-nodes) + - [`#value`](https://eemeli.org/yaml/#content-nodes) - [`#directives`](https://eemeli.org/yaml/#stream-directives) - [`#errors`](https://eemeli.org/yaml/#errors) - [`#warnings`](https://eemeli.org/yaml/#errors) -- [`isDocument(foo): boolean`](https://eemeli.org/yaml/#identifying-node-types) - [`parseAllDocuments(str, options?): Document[]`](https://eemeli.org/yaml/#parsing-documents) - [`parseDocument(str, options?): Document`](https://eemeli.org/yaml/#parsing-documents) @@ -109,7 +108,6 @@ parse(file) ```js import { Document, - isDocument, parseAllDocuments, parseDocument } from 'yaml' @@ -117,13 +115,6 @@ import { ### Content Nodes -- [`isAlias(foo): boolean`](https://eemeli.org/yaml/#identifying-node-types) -- [`isCollection(foo): boolean`](https://eemeli.org/yaml/#identifying-node-types) -- [`isMap(foo): boolean`](https://eemeli.org/yaml/#identifying-node-types) -- [`isNode(foo): boolean`](https://eemeli.org/yaml/#identifying-node-types) -- [`isPair(foo): boolean`](https://eemeli.org/yaml/#identifying-node-types) -- [`isScalar(foo): boolean`](https://eemeli.org/yaml/#identifying-node-types) -- [`isSeq(foo): boolean`](https://eemeli.org/yaml/#identifying-node-types) - [`new Scalar(value)`](https://eemeli.org/yaml/#scalar-values) - [`new YAMLMap()`](https://eemeli.org/yaml/#collections) - [`new YAMLSeq()`](https://eemeli.org/yaml/#collections) @@ -136,9 +127,11 @@ import { ```js import { - isAlias, isCollection, isMap, isNode, - isPair, isScalar, isSeq, Scalar, - visit, visitAsync, YAMLMap, YAMLSeq + Scalar, + visit, + visitAsync, + YAMLMap, + YAMLSeq } from 'yaml' ``` diff --git a/docs/01_intro.md b/docs/01_intro.md index 319468b4..87f0f084 100644 --- a/docs/01_intro.md +++ b/docs/01_intro.md @@ -62,7 +62,6 @@ import { parse, stringify } from 'yaml' ```js import { Document, - isDocument, parseAllDocuments, parseDocument } from 'yaml' @@ -70,7 +69,7 @@ import { - [`Document`](#documents) - [`constructor(value, replacer?, options?)`](#creating-documents) - - [`#contents`](#content-nodes) + - [`#value`](#content-nodes) - [`#directives`](#stream-directives) - [`#errors`](#errors) - [`#warnings`](#errors) @@ -82,13 +81,14 @@ import { ```js import { - isAlias, isCollection, isMap, isNode, - isPair, isScalar, isSeq, Scalar, - visit, visitAsync, YAMLMap, YAMLSeq + Scalar, + visit, + visitAsync, + YAMLMap, + YAMLSeq } from 'yaml' ``` -- [`is*(foo): boolean`](#identifying-node-types) - [`new Scalar(value)`](#scalar-values) - [`new YAMLMap()`](#collections) - [`new YAMLSeq()`](#collections) diff --git a/docs/04_documents.md b/docs/04_documents.md index 17f15fd0..2332e41c 100644 --- a/docs/04_documents.md +++ b/docs/04_documents.md @@ -10,7 +10,7 @@ import { parseAllDocuments, parseDocument } from 'yaml' const file = fs.readFileSync('./file.yml', 'utf8') const doc = parseDocument(file) -doc.contents +doc.value // YAMLMap { // items: // [ Pair { @@ -44,9 +44,9 @@ These functions should never throw, provided that `str` is a string and the `options` are valid. Errors and warnings are included in the documents' `errors` and `warnings` arrays. In particular, if `errors` is not empty -it's likely that the document's parsed `contents` are not entirely correct. +it's likely that the document's parsed `value` are not entirely correct. -The `contents` of a parsed document will always consist of `Scalar`, `Map`, `Seq` or `null` values. +The `value` of a parsed document will always consist of `Scalar`, `Map`, or `Seq` values. #### `parseDocument(str, options = {}): Document` @@ -69,16 +69,15 @@ See [Options](#options) for more information on the second parameter. #### `new Document(value, replacer?, options = {})` Creates a new document. -If `value` is defined, the document `contents` are initialised with that value, wrapped recursively in appropriate [content nodes](#content-nodes). -If `value` is `undefined`, the document's `contents` is initialised as `null`. -If defined, a `replacer` may filter or modify the initial document contents, following the same algorithm as the [JSON implementation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter). +The document `value` is initialised with `value`, wrapped recursively in appropriate [content nodes](#content-nodes). +If defined, a `replacer` may filter or modify the initial document value, following the same algorithm as the [JSON implementation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter). See [Options](#options) for more information on the last argument. | Member | Type | Description | | ------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | commentBefore | `string?` | A comment at the very beginning of the document. If not empty, separated from the rest of the document by a blank line or the doc-start indicator when stringified. | | comment | `string?` | A comment at the end of the document. If not empty, separated from the rest of the document by a blank line when stringified. | -| contents | [`Node`](#content-nodes) `⎮ any` | The document contents. | +| value | [`Node`](#content-nodes) `⎮ any` | The document value. | | directives | [`Directives`](#stream-directives) | Controls for the `%YAML` and `%TAG` directives, as well as the doc-start marker `---`. | | errors | [`Error[]`](#errors) | Errors encountered during parsing. | | schema | `Schema` | The schema used with the document. | @@ -99,21 +98,21 @@ String(doc) ``` The Document members are all modifiable, though it's unlikely that you'll have reason to change `errors`, `schema` or `warnings`. -In particular you may be interested in both reading and writing **`contents`**. -Although `parseDocument()` and `parseAllDocuments()` will leave it with `YAMLMap`, `YAMLSeq`, `Scalar` or `null` contents, it can be set to anything. +In particular you may be interested in both reading and writing **`value`**, +which is expected to always contain a `YAMLMap`, `YAMLSeq`, or `Scalar` value. ## Document Methods -| Method | Returns | Description | -| ------------------------------------------ | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -| clone() | `Document` | Create a deep copy of this Document and its contents. Custom Node values that inherit from `Object` still refer to their original instances. | -| createAlias(node: Node, name?: string) | `Alias` | Create a new `Alias` node, adding the required anchor for `node`. If `name` is empty, a new anchor name will be generated. | -| 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. | -| setSchema(version, options?) | `void` | Change the YAML version and schema used by the document. `version` must be either `'1.1'` or `'1.2'`; accepts all Schema options. | -| toJS(options?) | `any` | A plain JavaScript representation of the document `contents`. | -| toJSON() | `any` | A JSON representation of the document `contents`. | -| toString(options?) | `string` | A YAML representation of the document. | +| Method | Returns | Description | +| ------------------------------------------ | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| clone() | `Document` | Create a deep copy of this Document and its value. Custom Node values that inherit from `Object` still refer to their original instances. | +| createAlias(node: Node, name?: string) | `Alias` | Create a new `Alias` node, adding the required anchor for `node`. If `name` is empty, a new anchor name will be generated. | +| 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. | +| setSchema(version, options?) | `void` | Change the YAML version and schema used by the document. `version` must be either `'1.1'` or `'1.2'`; accepts all Schema options. | +| toJS(options?) | `any` | A plain JavaScript representation of the document `value`. | +| toJSON() | `any` | A JSON representation of the document `value`. | +| toString(options?) | `string` | A YAML representation of the document. | ```js const doc = parseDocument('a: 1\nb: [2, 3]\n') @@ -127,7 +126,7 @@ doc.getIn(['b', 1]) // 4 In addition to the above, the document object also provides the same **accessor methods** as [collections](#collections), based on the top-level collection: `add`, `delete`, `get`, `has`, and `set`, along with their deeper variants `addIn`, `deleteIn`, `getIn`, `hasIn`, and `setIn`. -For the `*In` methods using an empty `path` value (i.e. `null`, `undefined`, or `[]`) will refer to the document's top-level `contents`. +For the `*In` methods using an empty `path` value (i.e. `[]`) will refer to the document's top-level `value`. #### `Document#toJS()`, `Document#toJSON()` and `Document#toString()` @@ -177,5 +176,5 @@ The contents of `doc.directives.tags` are used both for the `%TAG` directives an Each of the handles must start and end with a `!` character; `!` is by default the local tag and `!!` is used for default tags. See the section on [custom tags](#writing-custom-tags) for more on this topic. -`doc.contents.yaml` determines if an explicit `%YAML` directive should be included in the output, and what version it should use. +`doc.directives.yaml` determines if an explicit `%YAML` directive should be included in the output, and what version it should use. If changing the version after the document's creation, you'll probably want to use `doc.setSchema()` as it will also update the schema accordingly. diff --git a/docs/05_content_nodes.md b/docs/05_content_nodes.md index a1d9c749..8782137e 100644 --- a/docs/05_content_nodes.md +++ b/docs/05_content_nodes.md @@ -1,6 +1,6 @@ # Content Nodes -After parsing, the `contents` value of each `YAML.Document` is the root of an [Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) of nodes representing the document (or `null` for an empty document). +After parsing, the `value` value of each `YAML.Document` is the root of an [Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) of nodes representing the document. Both scalar and collection values may have an `anchor` associated with them; this is rendered in the string representation with a `&` prefix, so e.g. in `foo: &aa bar`, the value `bar` has the anchor `aa`. Anchors are used by [Alias nodes](#alias-nodes) to allow for the same value to be used in multiple places in the document. @@ -41,7 +41,7 @@ class Scalar extends NodeBase { } ``` -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 `YAMLMap` and `YAMLSeq` collections. +A parsed document's value will have all of its non-object values wrapped in `Scalar` objects, which themselves may be in some hierarchy of `YAMLMap` and `YAMLSeq` 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. @@ -50,21 +50,21 @@ On the other hand, `!!int` and `!!float` stringifiers will take `format` into ac ## Collections ```js -class Pair { - key: K // When parsed, key and value are always - value: V // Node or null, but can be set to anything +class Pair { + key: Node + value: Node | null } class Collection extends NodeBase { anchor?: string // an anchor associated with this node flow?: boolean // use flow style when stringifying this schema?: Schema - addIn(path: Iterable, value: unknown): void + addIn(path: unknown[], value: unknown): void clone(schema?: Schema): NodeBase // a deep copy of this collection - deleteIn(path: Iterable): boolean - getIn(path: Iterable, keepScalar?: boolean): unknown - hasIn(path: Iterable): boolean - setIn(path: Iterable, value: unknown): void + deleteIn(path: unknown[]): boolean + getIn(path: unknown[], keepScalar?: boolean): unknown + hasIn(path: unknown[]): boolean + setIn(path: unknown[], value: unknown): void } class YAMLMap extends Collection { @@ -87,8 +87,8 @@ class YAMLSeq extends Collection { ``` Within all YAML documents, two forms of collections are supported: sequential `YAMLSeq` collections and key-value `YAMLMap` collections. -The JavaScript representations of these collections both have an `items` array, which may (`YAMLSeq`) or must (`YAMLMap`) consist of `Pair` objects that contain a `key` and a `value` of any type, including `null`. -The `items` array of a `YAMLSeq` object may contain values of any type. +The JavaScript representations of these collections both have an `items` array, which may (`YAMLSeq`) or must (`YAMLMap`) consist of `Pair` objects that contain a `key` and a `value` of any node type, or `null` for `value`. +The `items` array of a `YAMLSeq` object may contain any node values. When stringifying collections, by default block notation will be used. Flow notation will be selected if `flow` is `true`, the collection is within a surrounding flow collection, or if the collection is in an implicit key. @@ -169,7 +169,7 @@ When nodes are constructed from JS structures (e.g. during `YAML.stringify()`), ```js const doc = new YAML.Document(['some', 'values']) // Document { -// contents: +// value: // YAMLSeq { // items: // [ Scalar { value: 'some' }, @@ -209,8 +209,7 @@ For all available options, see the [CreateNode Options](#createnode-options) sec [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. +To that end, you'll need to assign its return value to the `value` of a document (or somewhere within said value), as the document's schema is required for YAML string output.

doc.createAlias(node, name?): Alias

@@ -236,12 +235,11 @@ You should make sure to only add alias nodes to the document after the nodes to ```js import { Document, YAMLSeq } from 'yaml' -const doc = new Document(new YAMLSeq()) -doc.contents.items = [ +const doc = new Document([ 'some values', 42, { including: 'objects', 3: 'a string' } -] +]) doc.add(doc.createPair(1, 'a number')) doc.toString() @@ -320,7 +318,7 @@ The return value of the visitor may be used to control the traversal: - `number`: While iterating the items of a sequence or map, set the index of the next step. This is useful especially if the index of the current node has changed. -If `visitor` is a single function, it will be called with all values encountered in the tree, including e.g. `null` values. +If `visitor` is a single function, it will be called with all values encountered in the tree, including `null` values. Alternatively, separate visitor functions may be defined for each `Map`, `Pair`, `Seq`, `Alias` and `Scalar` node. To define the same visitor function for more than one node type, use the `Collection` (map and seq), `Value` (map, seq & scalar) and `Node` (alias, map, seq & scalar) targets. @@ -332,51 +330,6 @@ The same as `visit()`, but allows for visitor functions that return a promise which resolves to one of the above-defined control values. -## Identifying Node Types - -```js -import { - isAlias, - isCollection, // map or seq - isDocument, - isMap, - isNode, // alias, scalar, map or seq - isPair, - isScalar, - isSeq -} from 'yaml' - -const doc = new Document({ foo: [13, 42] }) -isDocument(doc) === true -isNode(doc) === false -isMap(doc.contents) === true -isNode(doc.contents) === true -isPair(doc.contents.items[0]) === true -isCollection(doc.get('foo')) === true -isScalar(doc.getIn(['foo', 1])) === true -``` - -#### `isAlias(x: unknown): boolean` - -#### `isCollection(x: unknown): boolean` - -#### `isDocument(x: unknown): boolean` - -#### `isMap(x: unknown): boolean` - -#### `isNode(x: unknown): boolean` - -#### `isPair(x: unknown): boolean` - -#### `isScalar(x: unknown): boolean` - -#### `isSeq(x: unknown): boolean` - -To find out what you've got, a family of custom type guard functions is provided. -These should be preferred over other methods such as `instanceof` checks, as they'll work even if the nodes have been created by a different instance of the library. - -Internally, node identification uses property symbols that are set on instances during their construction. - ## Comments and Blank Lines ```js diff --git a/docs/06_custom_tags.md b/docs/06_custom_tags.md index afa6fb58..d9c4ae9b 100644 --- a/docs/06_custom_tags.md +++ b/docs/06_custom_tags.md @@ -10,7 +10,7 @@ parse('!!timestamp 2001-12-15 2:59:43') // 2001-12-15T02:59:43.000Z (Date instance) const doc = parseDocument('2001-12-15 2:59:43', { customTags: ['timestamp'] }) -doc.contents.value.toDateString() +doc.value.value.toDateString() // 'Sat Dec 15 2001' ``` diff --git a/src/compose/compose-collection.ts b/src/compose/compose-collection.ts index c789ab58..4162f81e 100644 --- a/src/compose/compose-collection.ts +++ b/src/compose/compose-collection.ts @@ -1,5 +1,4 @@ -import { isNode } from '../nodes/identity.ts' -import type { ParsedNode } from '../nodes/Node.ts' +import { NodeBase, type Node } from '../nodes/Node.ts' import { Scalar } from '../nodes/Scalar.ts' import { YAMLMap } from '../nodes/YAMLMap.ts' import { YAMLSeq } from '../nodes/YAMLSeq.ts' @@ -55,7 +54,7 @@ export function composeCollection( token: BlockMap | BlockSequence | FlowCollection, props: Props, onError: ComposeErrorHandler -): ParsedNode { +): Node { const tagToken = props.tag const tagName: string | null = !tagToken ? null @@ -136,9 +135,7 @@ export function composeCollection( ctx.options ) ?? coll - const node = isNode(res) - ? (res as ParsedNode) - : (new Scalar(res) as Scalar.Parsed) + const node = res instanceof NodeBase ? (res as Node) : new Scalar(res) node.range = coll.range node.tag = tagName if (tag?.format) (node as Scalar).format = tag.format diff --git a/src/compose/compose-doc.ts b/src/compose/compose-doc.ts index cc304c48..5df947b7 100644 --- a/src/compose/compose-doc.ts +++ b/src/compose/compose-doc.ts @@ -1,6 +1,5 @@ import type { Directives } from '../doc/directives.ts' -import { Document } from '../doc/Document.ts' -import type { ParsedNode } from '../nodes/Node.ts' +import { Document, type DocValue } from '../doc/Document.ts' import type { DocumentOptions, ParseOptions, @@ -17,16 +16,16 @@ import { resolveEnd } from './resolve-end.ts' import { resolveProps } from './resolve-props.ts' export function composeDoc< - Contents extends ParsedNode = ParsedNode, + Value extends DocValue = DocValue, Strict extends boolean = true >( options: ParseOptions & DocumentOptions & SchemaOptions, directives: Directives, { offset, start, value, end }: CST.Document, onError: ComposeErrorHandler -): Document.Parsed { +): Document.Parsed { const opts = Object.assign({ _directives: directives }, options) - const doc = new Document(undefined, opts) as Document.Parsed + const doc = new Document(undefined, opts) as Document.Parsed const ctx: ComposeContext = { atKey: false, atRoot: true, @@ -55,12 +54,11 @@ export function composeDoc< 'Block collection cannot start on same line with directives-end marker' ) } - // @ts-expect-error If Contents is set, let's trust the user - doc.contents = value - ? composeNode(ctx, value, props, onError) - : composeEmptyNode(ctx, props.end, start, null, props, onError) + doc.value = value + ? (composeNode(ctx, value, props, onError) as Value) + : (composeEmptyNode(ctx, props.end, start, null, props, onError) as Value) - const contentEnd = doc.contents.range[2] + const contentEnd = doc.value.range![2] const re = resolveEnd(end, contentEnd, false, onError) if (re.comment) doc.comment = re.comment doc.range = [offset, contentEnd, re.offset] diff --git a/src/compose/compose-node.ts b/src/compose/compose-node.ts index 7c160263..9ff3327b 100644 --- a/src/compose/compose-node.ts +++ b/src/compose/compose-node.ts @@ -1,8 +1,7 @@ import type { Directives } from '../doc/directives.ts' import { Alias } from '../nodes/Alias.ts' -import { isScalar } from '../nodes/identity.ts' -import type { ParsedNode } from '../nodes/Node.ts' -import type { Scalar } from '../nodes/Scalar.ts' +import type { Node } from '../nodes/Node.ts' +import { Scalar } from '../nodes/Scalar.ts' import type { ParseOptions } from '../options.ts' import type { FlowScalar, SourceToken, Token } from '../parse/cst.ts' import type { Schema } from '../schema/Schema.ts' @@ -40,10 +39,10 @@ export function composeNode( token: Token, props: Props, onError: ComposeErrorHandler -): ParsedNode { +): Node { const atKey = ctx.atKey const { spaceBefore, comment, anchor, tag } = props - let node: ParsedNode + let node: Node let isSrcToken = true switch (token.type) { case 'alias': @@ -90,7 +89,7 @@ export function composeNode( if ( atKey && ctx.options.stringKeys && - (!isScalar(node) || + (!(node instanceof Scalar) || typeof node.value !== 'string' || (node.tag && node.tag !== 'tag:yaml.org,2002:str')) ) { @@ -114,7 +113,7 @@ export function composeEmptyNode( pos: number | null, { spaceBefore, comment, anchor, tag, end }: Props, onError: ComposeErrorHandler -): Scalar.Parsed { +): Scalar { const token: FlowScalar = { type: 'scalar', offset: emptyScalarPosition(offset, before, pos), @@ -130,7 +129,7 @@ export function composeEmptyNode( if (spaceBefore) node.spaceBefore = true if (comment) { node.comment = comment - node.range[2] = end + node.range![2] = end } return node } @@ -154,5 +153,5 @@ function composeAlias( const re = resolveEnd(end, valueEnd, options.strict, onError) alias.range = [offset, valueEnd, re.offset] if (re.comment) alias.comment = re.comment - return alias as Alias.Parsed + return alias } diff --git a/src/compose/compose-scalar.ts b/src/compose/compose-scalar.ts index e994ec0f..1d44f7b2 100644 --- a/src/compose/compose-scalar.ts +++ b/src/compose/compose-scalar.ts @@ -1,4 +1,3 @@ -import { isScalar, SCALAR } from '../nodes/identity.ts' import { Scalar } from '../nodes/Scalar.ts' import type { BlockScalar, FlowScalar, SourceToken } from '../parse/cst.ts' import type { Schema } from '../schema/Schema.ts' @@ -13,7 +12,7 @@ export function composeScalar( token: FlowScalar | BlockScalar, tagToken: SourceToken | null, onError: ComposeErrorHandler -) { +): Scalar { const { value, type, comment, range } = token.type === 'block-scalar' ? resolveBlockScalar(ctx, token, onError) @@ -27,12 +26,12 @@ export function composeScalar( let tag: ScalarTag if (ctx.options.stringKeys && ctx.atKey) { - tag = ctx.schema[SCALAR] + tag = ctx.schema.scalar } else if (tagName) tag = findScalarTagByName(ctx.schema, value, tagName, tagToken!, onError) else if (token.type === 'scalar') tag = findScalarTagByTest(ctx, value, token, onError) - else tag = ctx.schema[SCALAR] + else tag = ctx.schema.scalar let scalar: Scalar try { @@ -41,7 +40,7 @@ export function composeScalar( msg => onError(tagToken ?? token, 'TAG_RESOLVE_FAILED', msg), ctx.options ) - scalar = isScalar(res) ? res : new Scalar(res) + scalar = res instanceof Scalar ? res : new Scalar(res) } catch (error) { const msg = error instanceof Error ? error.message : String(error) onError(tagToken ?? token, 'TAG_RESOLVE_FAILED', msg) @@ -54,7 +53,7 @@ export function composeScalar( if (tag.format) scalar.format = tag.format if (comment) scalar.comment = comment - return scalar as Scalar.Parsed + return scalar } function findScalarTagByName( @@ -64,7 +63,7 @@ function findScalarTagByName( tagToken: SourceToken, onError: ComposeErrorHandler ) { - if (tagName === '!') return schema[SCALAR] // non-specific tag + if (tagName === '!') return schema.scalar // non-specific tag const matchWithTest: ScalarTag[] = [] for (const tag of schema.tags) { if (!tag.collection && tag.tag === tagName) { @@ -86,7 +85,7 @@ function findScalarTagByName( `Unresolved tag: ${tagName}`, tagName !== 'tag:yaml.org,2002:str' ) - return schema[SCALAR] + return schema.scalar } function findScalarTagByTest( @@ -100,12 +99,12 @@ function findScalarTagByTest( tag => (tag.default === true || (atKey && tag.default === 'key')) && tag.test?.test(value) - ) as ScalarTag) || schema[SCALAR] + ) as ScalarTag) || schema.scalar if (schema.compat) { const compat = schema.compat.find(tag => tag.default && tag.test?.test(value)) ?? - schema[SCALAR] + schema.scalar if (tag.tag !== compat.tag) { const ts = directives.tagString(tag.tag) const cs = directives.tagString(compat.tag) diff --git a/src/compose/composer.ts b/src/compose/composer.ts index 5d3c7062..990a4846 100644 --- a/src/compose/composer.ts +++ b/src/compose/composer.ts @@ -1,9 +1,10 @@ import { Directives } from '../doc/directives.ts' -import { Document } from '../doc/Document.ts' +import { Document, type DocValue } from '../doc/Document.ts' import type { ErrorCode } from '../errors.ts' import { YAMLParseError, YAMLWarning } from '../errors.ts' -import { isCollection, isPair } from '../nodes/identity.ts' -import type { ParsedNode, Range } from '../nodes/Node.ts' +import { Collection } from '../nodes/Collection.ts' +import type { Range } from '../nodes/Node.ts' +import { Pair } from '../nodes/Pair.ts' import type { DocumentOptions, ParseOptions, @@ -77,12 +78,12 @@ function parsePrelude(prelude: string[]) { * ``` */ export class Composer< - Contents extends ParsedNode = ParsedNode, + Value extends DocValue = DocValue, Strict extends boolean = true > { private directives: Directives - private doc: Document.Parsed | null = null - private docs: Document.Parsed[] = [] + private doc: Document.Parsed | null = null + private docs: Document.Parsed[] = [] private options: ParseOptions & DocumentOptions & SchemaOptions private atDirectives = false private prelude: string[] = [] @@ -101,18 +102,18 @@ export class Composer< else this.errors.push(new YAMLParseError(pos, code, message)) } - private decorate(doc: Document.Parsed, afterDoc: boolean) { + private decorate(doc: Document.Parsed, afterDoc: boolean) { const { comment, afterEmptyLine } = parsePrelude(this.prelude) //console.log({ dc: doc.comment, prelude, comment }) if (comment) { - const dc = doc.contents + const dc = doc.value if (afterDoc) { doc.comment = doc.comment ? `${doc.comment}\n${comment}` : comment - } else if (afterEmptyLine || doc.directives.docStart || !dc) { + } else if (afterEmptyLine || doc.directives.docStart) { doc.commentBefore = comment - } else if (isCollection(dc) && !dc.flow && dc.items.length > 0) { + } else if (dc instanceof Collection && !dc.flow && dc.items.length > 0) { let it = dc.items[0] - if (isPair(it)) it = it.key + if (it instanceof Pair) it = it.key const cb = it.commentBefore it.commentBefore = cb ? `${comment}\n${cb}` : comment } else { @@ -163,7 +164,7 @@ export class Composer< tokens: Iterable, forceDoc = false, endOffset = -1 - ): Document.Parsed[] { + ): Document.Parsed[] { this.docs = [] for (const token of tokens) this.next(token) return this.end(forceDoc, endOffset) @@ -183,7 +184,7 @@ export class Composer< this.atDirectives = true break case 'document': { - const doc = composeDoc( + const doc = composeDoc( this.options, this.directives, token, @@ -261,7 +262,7 @@ export class Composer< * @param forceDoc - If the stream contains no document, still emit a final document including any comments and directives that would be applied to a subsequent document. * @param endOffset - Should be set if `forceDoc` is also set, to set the document range end and to indicate errors correctly. */ - end(forceDoc = false, endOffset = -1): Document.Parsed[] { + end(forceDoc = false, endOffset = -1): Document.Parsed[] { if (this.doc) { this.decorate(this.doc, true) this.docs.push(this.doc) @@ -269,7 +270,7 @@ export class Composer< } else if (forceDoc) { const opts = Object.assign({ _directives: this.directives }, this.options) const doc = new Document(undefined, opts) as Document.Parsed< - Contents, + Value, Strict > if (this.atDirectives) diff --git a/src/compose/resolve-block-map.ts b/src/compose/resolve-block-map.ts index d01c7a66..8cdb03ff 100644 --- a/src/compose/resolve-block-map.ts +++ b/src/compose/resolve-block-map.ts @@ -1,4 +1,3 @@ -import type { ParsedNode } from '../nodes/Node.ts' import { Pair } from '../nodes/Pair.ts' import { YAMLMap } from '../nodes/YAMLMap.ts' import type { BlockMap } from '../parse/cst.ts' @@ -18,9 +17,9 @@ export function resolveBlockMap( bm: BlockMap, onError: ComposeErrorHandler, tag?: CollectionTag -) { +): YAMLMap { const NodeClass = tag?.nodeClass ?? YAMLMap - const map = new NodeClass(ctx.schema) as YAMLMap + const map = new NodeClass(ctx.schema) as YAMLMap if (ctx.atRoot) ctx.atRoot = false let offset = bm.offset @@ -84,7 +83,7 @@ export function resolveBlockMap( const valueProps = resolveProps(sep ?? [], { indicator: 'map-value-ind', next: value, - offset: keyNode.range[2], + offset: keyNode.range![2], onError, parentIndent: bm.indent, startOnNewline: !key || key.type === 'block-scalar' @@ -104,7 +103,7 @@ export function resolveBlockMap( keyProps.start < valueProps.found.offset - 1024 ) onError( - keyNode.range, + keyNode.range!, 'KEY_OVER_1024_CHARS', 'The : indicator must be at most 1024 chars after the start of an implicit block mapping key' ) @@ -114,7 +113,7 @@ export function resolveBlockMap( ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep, null, valueProps, onError) if (ctx.schema.compat) flowIndentCheck(bm.indent, value, onError) - offset = valueNode.range[2] + offset = valueNode.range![2] const pair = new Pair(keyNode, valueNode) if (ctx.options.keepSourceTokens) pair.srcToken = collItem map.items.push(pair) @@ -122,7 +121,7 @@ export function resolveBlockMap( // key with no value if (implicitKey) onError( - keyNode.range, + keyNode.range!, 'MISSING_CHAR', 'Implicit map keys need to be followed by map values' ) @@ -130,7 +129,7 @@ export function resolveBlockMap( if (keyNode.comment) keyNode.comment += '\n' + valueProps.comment else keyNode.comment = valueProps.comment } - const pair: Pair = new Pair(keyNode) + const pair = new Pair(keyNode) if (ctx.options.keepSourceTokens) pair.srcToken = collItem map.items.push(pair) } @@ -139,5 +138,5 @@ export function resolveBlockMap( if (commentEnd && commentEnd < offset) onError(commentEnd, 'IMPOSSIBLE', 'Map comment with trailing content') map.range = [bm.offset, offset, commentEnd ?? offset] - return map as YAMLMap.Parsed + return map } diff --git a/src/compose/resolve-block-seq.ts b/src/compose/resolve-block-seq.ts index 7339da16..a9617073 100644 --- a/src/compose/resolve-block-seq.ts +++ b/src/compose/resolve-block-seq.ts @@ -12,7 +12,7 @@ export function resolveBlockSeq( bs: BlockSequence, onError: ComposeErrorHandler, tag?: CollectionTag -) { +): YAMLSeq { const NodeClass = tag?.nodeClass ?? YAMLSeq const seq = new NodeClass(ctx.schema) as YAMLSeq @@ -49,9 +49,9 @@ export function resolveBlockSeq( ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, start, null, props, onError) if (ctx.schema.compat) flowIndentCheck(bs.indent, value, onError) - offset = node.range[2] + offset = node.range![2] seq.items.push(node) } seq.range = [bs.offset, offset, commentEnd ?? offset] - return seq as YAMLSeq.Parsed + return seq } diff --git a/src/compose/resolve-flow-collection.ts b/src/compose/resolve-flow-collection.ts index a8efd6ab..cecdc280 100644 --- a/src/compose/resolve-flow-collection.ts +++ b/src/compose/resolve-flow-collection.ts @@ -1,9 +1,7 @@ -import { isPair } from '../nodes/identity.ts' import { Pair } from '../nodes/Pair.ts' import { YAMLMap } from '../nodes/YAMLMap.ts' import { YAMLSeq } from '../nodes/YAMLSeq.ts' import type { FlowCollection, Token } from '../parse/cst.ts' -import type { Schema } from '../schema/Schema.ts' import type { CollectionTag } from '../schema/types.ts' import type { ComposeContext, ComposeNode } from './compose-node.ts' import type { ComposeErrorHandler } from './composer.ts' @@ -22,13 +20,11 @@ export function resolveFlowCollection( fc: FlowCollection, onError: ComposeErrorHandler, tag?: CollectionTag -): YAMLMap.Parsed | YAMLSeq.Parsed { +): YAMLMap | YAMLSeq { const isMap = fc.start.source === '{' const fcName = isMap ? 'flow map' : 'flow sequence' - const NodeClass = (tag?.nodeClass ?? (isMap ? YAMLMap : YAMLSeq)) as { - new (schema: Schema): YAMLMap.Parsed | YAMLSeq.Parsed - } - const coll = new NodeClass(ctx.schema) + const NodeClass = tag?.nodeClass ?? (isMap ? YAMLMap : YAMLSeq) + const coll = new NodeClass(ctx.schema) as YAMLMap | YAMLSeq coll.flow = true const atRoot = ctx.atRoot if (atRoot) ctx.atRoot = false @@ -98,7 +94,7 @@ export function resolveFlowCollection( } if (prevItemComment) { let prev = coll.items[coll.items.length - 1] - if (isPair(prev)) prev = prev.value ?? prev.key + if (prev instanceof Pair) prev = prev.value ?? prev.key if (prev.comment) prev.comment += '\n' + prevItemComment else prev.comment = prevItemComment props.comment = props.comment.substring(prevItemComment.length + 1) @@ -113,8 +109,8 @@ export function resolveFlowCollection( ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep, null, props, onError) ;(coll as YAMLSeq).items.push(valueNode) - offset = valueNode.range[2] - if (isBlock(value)) onError(valueNode.range, 'BLOCK_IN_FLOW', blockMsg) + offset = valueNode.range![2] + if (isBlock(value)) onError(valueNode.range!, 'BLOCK_IN_FLOW', blockMsg) } else { // item is a key+value pair @@ -124,7 +120,7 @@ export function resolveFlowCollection( const keyNode = key ? composeNode(ctx, key, props, onError) : composeEmptyNode(ctx, keyStart, start, null, props, onError) - if (isBlock(key)) onError(keyNode.range, 'BLOCK_IN_FLOW', blockMsg) + if (isBlock(key)) onError(keyNode.range!, 'BLOCK_IN_FLOW', blockMsg) ctx.atKey = false // value properties @@ -132,7 +128,7 @@ export function resolveFlowCollection( flow: fcName, indicator: 'map-value-ind', next: value, - offset: keyNode.range[2], + offset: keyNode.range![2], onError, parentIndent: fc.indent, startOnNewline: false @@ -184,7 +180,7 @@ export function resolveFlowCollection( ) : null if (valueNode) { - if (isBlock(value)) onError(valueNode.range, 'BLOCK_IN_FLOW', blockMsg) + if (isBlock(value)) onError(valueNode.range!, 'BLOCK_IN_FLOW', blockMsg) } else if (valueProps.comment) { if (keyNode.comment) keyNode.comment += '\n' + valueProps.comment else keyNode.comment = valueProps.comment @@ -193,7 +189,7 @@ export function resolveFlowCollection( const pair = new Pair(keyNode, valueNode) if (ctx.options.keepSourceTokens) pair.srcToken = collItem if (isMap) { - const map = coll as YAMLMap.Parsed + const map = coll as YAMLMap if (mapIncludes(ctx, map.items, keyNode)) onError(keyStart, 'DUPLICATE_KEY', 'Map keys must be unique') map.items.push(pair) @@ -201,11 +197,11 @@ export function resolveFlowCollection( const map = new YAMLMap(ctx.schema) map.flow = true map.items.push(pair) - const endRange = (valueNode ?? keyNode).range - map.range = [keyNode.range[0], endRange[1], endRange[2]] + const endRange = (valueNode ?? keyNode).range! + map.range = [keyNode.range![0], endRange[1], endRange[2]] ;(coll as YAMLSeq).items.push(map) } - offset = valueNode ? valueNode.range[2] : valueProps.end + offset = valueNode ? valueNode.range![2] : valueProps.end } } diff --git a/src/compose/util-map-includes.ts b/src/compose/util-map-includes.ts index d51a82bf..8d4e9b3a 100644 --- a/src/compose/util-map-includes.ts +++ b/src/compose/util-map-includes.ts @@ -1,19 +1,20 @@ -import { isScalar } from '../nodes/identity.ts' -import type { ParsedNode } from '../nodes/Node.ts' +import type { NodeBase } from '../nodes/Node.ts' import type { Pair } from '../nodes/Pair.ts' +import { Scalar } from '../nodes/Scalar.ts' import type { ComposeContext } from './compose-node.ts' export function mapIncludes( ctx: ComposeContext, - items: Pair[], - search: ParsedNode + items: Pair[], + search: NodeBase ): boolean { const { uniqueKeys } = ctx.options if (uniqueKeys === false) return false const isEqual = typeof uniqueKeys === 'function' ? uniqueKeys - : (a: ParsedNode, b: ParsedNode) => - a === b || (isScalar(a) && isScalar(b) && a.value === b.value) + : (a: NodeBase, b: NodeBase) => + a === b || + (a instanceof Scalar && b instanceof Scalar && a.value === b.value) return items.some(pair => isEqual(pair.key, search)) } diff --git a/src/doc/Document.ts b/src/doc/Document.ts index 492fe506..8abc5002 100644 --- a/src/doc/Document.ts +++ b/src/doc/Document.ts @@ -1,16 +1,10 @@ import type { YAMLError, YAMLWarning } from '../errors.ts' import { Alias } from '../nodes/Alias.ts' -import { collectionFromPath, isEmptyPath } from '../nodes/Collection.ts' -import { - DOC, - isCollection, - isNode, - isScalar, - NODE_TYPE -} from '../nodes/identity.ts' -import type { Node, NodeType, ParsedNode, Range } from '../nodes/Node.ts' -import { Pair } from '../nodes/Pair.ts' -import type { Scalar } from '../nodes/Scalar.ts' +import { Collection, type Primitive } from '../nodes/Collection.ts' +import type { Node, NodeType, Range } from '../nodes/Node.ts' +import type { NodeBase } from '../nodes/Node.ts' +import type { Pair } from '../nodes/Pair.ts' +import { Scalar } from '../nodes/Scalar.ts' import type { ToJSContext } from '../nodes/toJS.ts' import { toJS } from '../nodes/toJS.ts' import type { YAMLMap } from '../nodes/YAMLMap.ts' @@ -25,11 +19,12 @@ import type { } from '../options.ts' import { Schema } from '../schema/Schema.ts' import { stringifyDocument } from '../stringify/stringifyDocument.ts' -import { anchorNames, createNodeAnchors, findNewAnchor } from './anchors.ts' +import { anchorNames, findNewAnchor } from './anchors.ts' import { applyReviver } from './applyReviver.ts' -import type { CreateNodeContext } from './createNode.ts' -import { createNode } from './createNode.ts' import { Directives } from './directives.ts' +import { NodeCreator } from './NodeCreator.ts' + +export type DocValue = Scalar | YAMLSeq | YAMLMap export type Replacer = any[] | ((key: any, value: any) => unknown) @@ -37,29 +32,26 @@ export declare namespace Document { // eslint-disable-next-line @typescript-eslint/ban-ts-comment /** @ts-ignore The typing of directives fails in TS <= 4.2 */ interface Parsed< - Contents extends ParsedNode = ParsedNode, + Value extends DocValue = DocValue, Strict extends boolean = true - > extends Document { + > extends Document { directives: Directives range: Range } } export class Document< - Contents extends Node = Node, + Value extends DocValue = DocValue, Strict extends boolean = true > { - /** @internal */ - declare readonly [NODE_TYPE]: symbol - /** A comment before this Document */ commentBefore: string | null = null /** A comment immediately after this Document */ comment: string | null = null - /** The document contents. */ - contents: Strict extends true ? Contents | null : Contents + /** The document value. */ + value: Value directives: Strict extends true ? Directives | undefined : Directives @@ -109,7 +101,6 @@ export class Document< | null, options?: DocumentOptions & SchemaOptions & ParseOptions & CreateNodeOptions ) { - Object.defineProperty(this, NODE_TYPE, { value: DOC }) let _replacer: Replacer | null = null if (typeof replacer === 'function' || Array.isArray(replacer)) { _replacer = replacer @@ -139,20 +130,16 @@ export class Document< } else this.directives = new Directives({ version }) this.setSchema(version, options) - // @ts-expect-error We can't really know that this matches Contents. - this.contents = - value === undefined ? null : this.createNode(value, _replacer, options) + this.value = this.createNode(value, _replacer, options) as Value } /** - * Create a deep copy of this Document and its contents. + * Create a deep copy of this Document and its value. * * Custom Node values that inherit from `Object` still refer to their original instances. */ - clone(): Document { - const copy: Document = Object.create(Document.prototype, { - [NODE_TYPE]: { value: DOC } - }) + clone(): Document { + const copy: Document = Object.create(Document.prototype) copy.commentBefore = this.commentBefore copy.comment = this.comment copy.errors = this.errors.slice() @@ -160,22 +147,19 @@ export class Document< copy.options = Object.assign({}, this.options) if (this.directives) copy.directives = this.directives.clone() copy.schema = this.schema.clone() - // @ts-expect-error We can't really know that this matches Contents. - copy.contents = isNode(this.contents) - ? this.contents.clone(copy.schema) - : this.contents + copy.value = this.value.clone(copy.schema) as Value if (this.range) copy.range = this.range.slice() as Document['range'] return copy } /** Adds a value to the document. */ add(value: any): void { - if (assertCollection(this.contents)) this.contents.add(value) + assertCollection(this.value).add(value) } /** Adds a value to the document. */ - addIn(path: Iterable, value: unknown): void { - if (assertCollection(this.contents)) this.contents.addIn(path, value) + addIn(path: unknown[], value: unknown): void { + assertCollection(this.value).addIn(path, value) } /** @@ -188,7 +172,7 @@ export class Document< * If `name` is undefined, the generated anchor will use 'a' as a prefix. */ createAlias( - node: Strict extends true ? Scalar | YAMLMap | YAMLSeq : Node, + node: Strict extends true ? DocValue : Node, name?: string ): Alias { if (!node.anchor) { @@ -215,46 +199,22 @@ export class Document< replacer?: Replacer | CreateNodeOptions | null, options?: CreateNodeOptions ): Node { - let _replacer: Replacer | undefined = undefined + let nc: NodeCreator if (typeof replacer === 'function') { value = replacer.call({ '': value }, '', value) - _replacer = replacer + nc = new NodeCreator(this, options, replacer) } else if (Array.isArray(replacer)) { const keyToStr = (v: unknown) => typeof v === 'number' || v instanceof String || v instanceof Number const asStr = replacer.filter(keyToStr).map(String) if (asStr.length > 0) replacer = replacer.concat(asStr) - _replacer = replacer - } else if (options === undefined && replacer) { - options = replacer - replacer = undefined + nc = new NodeCreator(this, options, replacer) + } else { + options ??= replacer ?? undefined + nc = new NodeCreator(this, options) } - - const { - aliasDuplicateObjects, - anchorPrefix, - flow, - keepUndefined, - onTagObj, - tag - } = options ?? {} - const { onAnchor, setAnchors, sourceObjects } = createNodeAnchors( - this, - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - anchorPrefix || 'a' - ) - const ctx: CreateNodeContext = { - aliasDuplicateObjects: aliasDuplicateObjects ?? true, - keepUndefined: keepUndefined ?? false, - onAnchor, - onTagObj, - replacer: _replacer, - schema: this.schema, - sourceObjects - } - const node = createNode(value, tag, ctx) - if (flow && isCollection(node)) node.flow = true - setAnchors() + const node = nc.create(value, options?.tag) + nc.setAnchors() return node } @@ -262,110 +222,89 @@ export class Document< * Convert a key and a value into a `Pair` using the current schema, * recursively wrapping all values as `Scalar` or `Collection` nodes. */ - createPair( - key: unknown, - value: unknown, + createPair( + key: K, + value: V, options: CreateNodeOptions = {} - ): Pair { - const k = this.createNode(key, null, options) as K - const v = this.createNode(value, null, options) as V - return new Pair(k, v) + ) { + const nc = new NodeCreator(this, options) + const pair = nc.createPair(key, value) + nc.setAnchors() + return pair as Pair< + K extends Primitive | NodeBase ? K : NodeBase, + V extends Primitive | NodeBase ? V : NodeBase + > } /** * Removes a value from the document. * @returns `true` if the item was found and removed. */ - delete(key: unknown): boolean { - return assertCollection(this.contents) ? this.contents.delete(key) : false + delete(key: any): boolean { + return assertCollection(this.value).delete(key) } /** * Removes a value from the document. * @returns `true` if the item was found and removed. */ - deleteIn(path: Iterable | null): boolean { - if (isEmptyPath(path)) { - if (this.contents == null) return false - // @ts-expect-error Presumed impossible if Strict extends false - this.contents = null + deleteIn(path: unknown[]): boolean { + if (!path.length) { + this.value = new Scalar(null) as Value return true } - return assertCollection(this.contents) - ? this.contents.deleteIn(path) - : false + return assertCollection(this.value).deleteIn(path) } /** - * Returns item at `key`, or `undefined` if not found. By default unwraps - * scalar values from their surrounding node; to disable set `keepScalar` to - * `true` (collections are always returned intact). + * Returns item at `key`, or `undefined` if not found. */ - get(key: unknown, keepScalar?: boolean): Strict extends true ? unknown : any { - return isCollection(this.contents) - ? this.contents.get(key, keepScalar) - : undefined + get(key: any): Strict extends true ? NodeBase | Pair | undefined : any { + return this.value instanceof Collection ? this.value.get(key) : undefined } /** - * Returns item at `path`, or `undefined` if not found. By default unwraps - * scalar values from their surrounding node; to disable set `keepScalar` to - * `true` (collections are always returned intact). + * Returns item at `path`, or `undefined` if not found. */ getIn( - path: Iterable | null, - keepScalar?: boolean - ): Strict extends true ? unknown : any { - if (isEmptyPath(path)) - return !keepScalar && isScalar(this.contents) - ? this.contents.value - : this.contents - return isCollection(this.contents) - ? this.contents.getIn(path, keepScalar) - : undefined + path: unknown[] + ): Strict extends true ? NodeBase | Pair | null | undefined : any { + if (!path.length) return this.value + return this.value instanceof Collection ? this.value.getIn(path) : undefined } /** * Checks if the document includes a value with the key `key`. */ - has(key: unknown): boolean { - return isCollection(this.contents) ? this.contents.has(key) : false + has(key: any): boolean { + return this.value instanceof Collection ? this.value.has(key) : false } /** * Checks if the document includes a value at `path`. */ - hasIn(path: Iterable | null): boolean { - if (isEmptyPath(path)) return this.contents !== undefined - return isCollection(this.contents) ? this.contents.hasIn(path) : false + hasIn(path: unknown[]): boolean { + if (!path.length) return true + return this.value instanceof Collection ? this.value.hasIn(path) : false } /** * Sets a value in this document. For `!!set`, `value` needs to be a * boolean to add/remove the item from the set. */ - set(key: any, value: unknown): void { - if (this.contents == null) { - // @ts-expect-error We can't really know that this matches Contents. - this.contents = collectionFromPath(this.schema, [key], value) - } else if (assertCollection(this.contents)) { - this.contents.set(key, value) - } + set(key: any, value: any): void { + assertCollection(this.value).set(key, value) } /** * Sets a value in this document. For `!!set`, `value` needs to be a * boolean to add/remove the item from the set. */ - setIn(path: Iterable | null, value: unknown): void { - if (isEmptyPath(path)) { - // @ts-expect-error We can't really know that this matches Contents. - this.contents = value - } else if (this.contents == null) { - // @ts-expect-error We can't really know that this matches Contents. - this.contents = collectionFromPath(this.schema, Array.from(path), value) - } else if (assertCollection(this.contents)) { - this.contents.setIn(path, value) + setIn(path: unknown[], value: unknown): void { + if (!path.length) { + this.value = value as Value + } else { + assertCollection(this.value).setIn(path, value) } } @@ -416,7 +355,7 @@ export class Document< ) } - /** A plain JavaScript representation of the document `contents`. */ + /** A plain JavaScript representation of the document `value`. */ toJS(opt?: ToJSOptions & { [ignored: string]: unknown }): any // json & jsonArg are only used from toJSON() @@ -436,7 +375,7 @@ export class Document< mapKeyWarned: false, maxAliasCount: typeof maxAliasCount === 'number' ? maxAliasCount : 100 } - const res = toJS(this.contents, jsonArg ?? '', ctx) + const res = toJS(this.value, jsonArg ?? '', ctx) if (typeof onAnchor === 'function') for (const { count, res } of ctx.anchors.values()) onAnchor(res, count) return typeof reviver === 'function' @@ -445,7 +384,7 @@ export class Document< } /** - * A JSON representation of the document `contents`. + * A JSON representation of the document `value`. * * @param jsonArg Used by `JSON.stringify` to indicate the array index or * property name. @@ -469,7 +408,7 @@ export class Document< } } -function assertCollection(contents: unknown): contents is YAMLMap | YAMLSeq { - if (isCollection(contents)) return true - throw new Error('Expected a YAML collection as document contents') +function assertCollection(value: unknown) { + if (value instanceof Collection) return value as YAMLMap | YAMLSeq + throw new Error('Expected a YAML collection as document value') } diff --git a/src/doc/NodeCreator.ts b/src/doc/NodeCreator.ts new file mode 100644 index 00000000..57be20ee --- /dev/null +++ b/src/doc/NodeCreator.ts @@ -0,0 +1,172 @@ +import { Alias } from '../nodes/Alias.ts' +import { Collection } from '../nodes/Collection.ts' +import { type Node, NodeBase } from '../nodes/Node.ts' +import { Pair } from '../nodes/Pair.ts' +import { Scalar } from '../nodes/Scalar.ts' +import type { YAMLMap } from '../nodes/YAMLMap.ts' +import type { CreateNodeOptions } from '../options.ts' +import type { Schema } from '../schema/Schema.ts' +import type { CollectionTag, ScalarTag } from '../schema/types.ts' +import { anchorNames, findNewAnchor } from './anchors.ts' +import { Document, type DocValue, type Replacer } from './Document.ts' + +const defaultTagPrefix = 'tag:yaml.org,2002:' + +export class NodeCreator { + keepUndefined: boolean + replacer?: Replacer + schema: Schema + + #aliasDuplicateObjects: boolean + #anchorPrefix: string + #aliasObjects: unknown[] = [] + #doc?: Document + #flow: boolean + #onTagObj?: (tagObj: ScalarTag | CollectionTag) => void + #prevAnchors: Set | null = null + #sourceObjects: Map = + new Map() + + constructor( + doc: Document, + options?: CreateNodeOptions, + replacer?: Replacer + ) + constructor( + schema: Schema, + options: CreateNodeOptions & { aliasDuplicateObjects: false } + ) + constructor( + docOrSchema: Document | Schema, + options: CreateNodeOptions = {}, + replacer?: Replacer + ) { + this.keepUndefined = options.keepUndefined ?? false + this.replacer = replacer + + this.#flow = options.flow ?? false + this.#onTagObj = options.onTagObj + + this.#aliasDuplicateObjects = options.aliasDuplicateObjects ?? true + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + this.#anchorPrefix = options.anchorPrefix || 'a' + if (docOrSchema instanceof Document) { + this.#doc = docOrSchema + this.schema = docOrSchema.schema + } else { + this.schema = docOrSchema + } + } + + create(value: unknown, tagName?: string): Node { + if (value instanceof Document) value = value.value + if (value instanceof NodeBase) return value as Node + if (value instanceof Pair) { + const map = (this.schema.map.nodeClass! as typeof YAMLMap).from( + this, + null + ) + map.items.push(value) + return map + } + if ( + value instanceof String || + value instanceof Number || + value instanceof Boolean || + (typeof BigInt !== 'undefined' && value instanceof BigInt) // not supported everywhere + ) { + // https://tc39.es/ecma262/#sec-serializejsonproperty + value = value.valueOf() + } + + // Detect duplicate references to the same object & use Alias nodes for all + // after first. The `ref` wrapper allows for circular references to resolve. + let ref: { anchor: string | null; node: Node | null } | undefined = + undefined + if (this.#aliasDuplicateObjects && value && typeof value === 'object') { + ref = this.#sourceObjects.get(value) + if (ref) { + if (!ref.anchor) { + this.#aliasObjects.push(value) + this.#prevAnchors ??= anchorNames(this.#doc!) + ref.anchor = findNewAnchor(this.#anchorPrefix, this.#prevAnchors) + this.#prevAnchors.add(ref.anchor) + } + return new Alias(ref.anchor) + } else { + ref = { anchor: null, node: null } + this.#sourceObjects.set(value, ref) + } + } + + let tagObj: ScalarTag | CollectionTag | undefined + if (tagName) { + if (tagName.startsWith('!!')) + tagName = defaultTagPrefix + tagName.slice(2) + const match = this.schema.tags.filter(t => t.tag === tagName) + tagObj = match.find(t => !t.format) ?? match[0] + if (!tagObj) throw new Error(`Tag ${tagName} not found`) + } else { + tagObj = this.schema.tags.find(t => t.identify?.(value) && !t.format) + } + if (!tagObj) { + if (value && typeof (value as any).toJSON === 'function') { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + value = (value as any).toJSON() + } + if (!value || typeof value !== 'object') { + const node = new Scalar(value) + if (ref) ref.node = node + return node + } + tagObj = + value instanceof Map + ? this.schema.map + : Symbol.iterator in Object(value) + ? this.schema.seq + : this.schema.map + } + if (this.#onTagObj) { + this.#onTagObj(tagObj) + this.#onTagObj = undefined + } + + const node = + tagObj?.createNode?.(this, value) ?? + tagObj?.nodeClass?.from?.(this, value) ?? + new Scalar(value) + if (tagName) node.tag = tagName + else if (!tagObj.default) node.tag = tagObj.tag + + if (ref) ref.node = node + if (this.#flow && node instanceof Collection) node.flow = true + return node + } + + createPair(key: unknown, value: unknown): Pair { + const k = this.create(key) + const v = value == null ? null : this.create(value) + return new Pair(k, v) + } + + /** + * With circular references, the source node is only resolved after all + * of its child nodes are. This is why anchors are set only after all of + * the nodes have been created. + */ + setAnchors(): void { + for (const source of this.#aliasObjects) { + const ref = this.#sourceObjects.get(source) + if ( + typeof ref === 'object' && + ref.anchor && + (ref.node instanceof Scalar || ref.node instanceof Collection) + ) { + ref.node.anchor = ref.anchor + } else { + const msg = 'Failed to resolve repeated object (this should not happen)' + throw Object.assign(new Error(msg), { source }) + } + } + } +} diff --git a/src/doc/anchors.ts b/src/doc/anchors.ts index dce9481f..8bbe6926 100644 --- a/src/doc/anchors.ts +++ b/src/doc/anchors.ts @@ -1,11 +1,6 @@ -import { isCollection, isScalar } from '../nodes/identity.ts' import type { Node } from '../nodes/Node.ts' -import type { Scalar } from '../nodes/Scalar.ts' -import type { YAMLMap } from '../nodes/YAMLMap.ts' -import type { YAMLSeq } from '../nodes/YAMLSeq.ts' import { visit } from '../visit.ts' -import type { CreateNodeContext } from './createNode.ts' -import type { Document } from './Document.ts' +import type { Document, DocValue } from './Document.ts' /** * Verify that the input string is a valid anchor. @@ -21,10 +16,12 @@ export function anchorIsValid(anchor: string): true { return true } -export function anchorNames(root: Document | Node): Set { +export function anchorNames( + root: Document | Node +): Set { const anchors = new Set() visit(root, { - Value(_key: unknown, node: Scalar | YAMLMap | YAMLSeq) { + Value(_key, node) { if (node.anchor) anchors.add(node.anchor) } }) @@ -38,52 +35,3 @@ export function findNewAnchor(prefix: string, exclude: Set): string { if (!exclude.has(name)) return name } } - -export function createNodeAnchors( - doc: Document, - prefix: string -): { - onAnchor: (source: unknown) => string - setAnchors: () => void - sourceObjects: CreateNodeContext['sourceObjects'] -} { - const aliasObjects: unknown[] = [] - const sourceObjects: CreateNodeContext['sourceObjects'] = new Map() - let prevAnchors: Set | null = null - - return { - onAnchor: (source: unknown) => { - aliasObjects.push(source) - prevAnchors ??= anchorNames(doc) - const anchor = findNewAnchor(prefix, prevAnchors) - prevAnchors.add(anchor) - return anchor - }, - - /** - * With circular references, the source node is only resolved after all - * of its child nodes are. This is why anchors are set only after all of - * the nodes have been created. - */ - setAnchors: () => { - for (const source of aliasObjects) { - const ref = sourceObjects.get(source) - if ( - typeof ref === 'object' && - ref.anchor && - (isScalar(ref.node) || isCollection(ref.node)) - ) { - ref.node.anchor = ref.anchor - } else { - const error = new Error( - 'Failed to resolve repeated object (this should not happen)' - ) as Error & { source: unknown } - error.source = source - throw error - } - } - }, - - sourceObjects - } -} diff --git a/src/doc/createNode.ts b/src/doc/createNode.ts deleted file mode 100644 index 995a04ba..00000000 --- a/src/doc/createNode.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { Alias } from '../nodes/Alias.ts' -import { isDocument, isNode, isPair, MAP, SEQ } from '../nodes/identity.ts' -import type { Node } from '../nodes/Node.ts' -import { Scalar } from '../nodes/Scalar.ts' -import type { YAMLMap } from '../nodes/YAMLMap.ts' -import type { Schema } from '../schema/Schema.ts' -import type { CollectionTag, ScalarTag } from '../schema/types.ts' -import type { Replacer } from './Document.ts' - -const defaultTagPrefix = 'tag:yaml.org,2002:' - -export interface CreateNodeContext { - aliasDuplicateObjects: boolean - keepUndefined: boolean - onAnchor: (source: unknown) => string - onTagObj?: (tagObj: ScalarTag | CollectionTag) => void - sourceObjects: Map - replacer?: Replacer - schema: Schema -} - -function findTagObject( - value: unknown, - tagName: string | undefined, - tags: Array -) { - if (tagName) { - const match = tags.filter(t => t.tag === tagName) - const tagObj = match.find(t => !t.format) ?? match[0] - if (!tagObj) throw new Error(`Tag ${tagName} not found`) - return tagObj - } - return tags.find(t => t.identify?.(value) && !t.format) -} - -export function createNode( - value: unknown, - tagName: string | undefined, - ctx: CreateNodeContext -): Node { - if (isDocument(value)) value = value.contents - if (isNode(value)) return value - if (isPair(value)) { - const map = ctx.schema[MAP].createNode?.(ctx.schema, null, ctx) as YAMLMap - map.items.push(value) - return map - } - if ( - value instanceof String || - value instanceof Number || - value instanceof Boolean || - (typeof BigInt !== 'undefined' && value instanceof BigInt) // not supported everywhere - ) { - // https://tc39.es/ecma262/#sec-serializejsonproperty - value = value.valueOf() - } - - const { aliasDuplicateObjects, onAnchor, onTagObj, schema, sourceObjects } = - ctx - - // Detect duplicate references to the same object & use Alias nodes for all - // after first. The `ref` wrapper allows for circular references to resolve. - let ref: { anchor: string | null; node: Node | null } | undefined = undefined - if (aliasDuplicateObjects && value && typeof value === 'object') { - ref = sourceObjects.get(value) - if (ref) { - ref.anchor ??= onAnchor(value) - return new Alias(ref.anchor) - } else { - ref = { anchor: null, node: null } - sourceObjects.set(value, ref) - } - } - - if (tagName?.startsWith('!!')) tagName = defaultTagPrefix + tagName.slice(2) - - let tagObj = findTagObject(value, tagName, schema.tags) - if (!tagObj) { - if (value && typeof (value as any).toJSON === 'function') { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - value = (value as any).toJSON() - } - if (!value || typeof value !== 'object') { - const node = new Scalar(value) - if (ref) ref.node = node - return node - } - tagObj = - value instanceof Map - ? schema[MAP] - : Symbol.iterator in Object(value) - ? schema[SEQ] - : schema[MAP] - } - if (onTagObj) { - onTagObj(tagObj) - delete ctx.onTagObj - } - - const node = tagObj?.createNode - ? tagObj.createNode(ctx.schema, value, ctx) - : typeof tagObj?.nodeClass?.from === 'function' - ? tagObj.nodeClass.from(ctx.schema, value, ctx) - : new Scalar(value) - if (tagName) node.tag = tagName - else if (!tagObj.default) node.tag = tagObj.tag - - if (ref) ref.node = node - return node -} diff --git a/src/doc/directives.ts b/src/doc/directives.ts index 55227a86..aef96d06 100644 --- a/src/doc/directives.ts +++ b/src/doc/directives.ts @@ -1,4 +1,4 @@ -import { isNode } from '../nodes/identity.ts' +import { NodeBase } from '../nodes/Node.ts' import { visit } from '../visit.ts' import type { Document } from './Document.ts' @@ -180,10 +180,10 @@ export class Directives { const tagEntries = Object.entries(this.tags) let tagNames: string[] - if (doc && tagEntries.length > 0 && isNode(doc.contents)) { + if (doc && tagEntries.length > 0) { const tags: Record = {} - visit(doc.contents, (_key, node) => { - if (isNode(node) && node.tag) tags[node.tag] = true + visit(doc.value, (_key, node) => { + if (node instanceof NodeBase && node.tag) tags[node.tag] = true }) tagNames = Object.keys(tags) } else tagNames = [] diff --git a/src/index.ts b/src/index.ts index e4082837..e363e3a8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,23 +1,13 @@ export { Composer } from './compose/composer.ts' -export { Document } from './doc/Document.ts' +export { Document, type DocValue } from './doc/Document.ts' export { Schema } from './schema/Schema.ts' export type { ErrorCode } from './errors.ts' export { YAMLError, YAMLParseError, YAMLWarning } from './errors.ts' export { Alias } from './nodes/Alias.ts' -export { - isAlias, - isCollection, - isDocument, - isMap, - isNode, - isPair, - isScalar, - isSeq -} from './nodes/identity.ts' -export type { Node, ParsedNode, Range } from './nodes/Node.ts' +export type { Node, Range } from './nodes/Node.ts' export { Pair } from './nodes/Pair.ts' export { Scalar } from './nodes/Scalar.ts' export { YAMLMap } from './nodes/YAMLMap.ts' diff --git a/src/nodes/Alias.ts b/src/nodes/Alias.ts index dfc8583e..8f81e9e0 100644 --- a/src/nodes/Alias.ts +++ b/src/nodes/Alias.ts @@ -3,29 +3,23 @@ import type { Document } from '../doc/Document.ts' import type { FlowScalar } from '../parse/cst.ts' import type { StringifyContext } from '../stringify/stringify.ts' import { visit } from '../visit.ts' -import { ALIAS, hasAnchor, isAlias, isCollection, isPair } from './identity.ts' -import type { Node, Range } from './Node.ts' +import type { Node } from './Node.ts' import { NodeBase } from './Node.ts' +import { Pair } from './Pair.ts' import type { Scalar } from './Scalar.ts' import type { ToJSContext } from './toJS.ts' import { toJS } from './toJS.ts' import type { YAMLMap } from './YAMLMap.ts' import type { YAMLSeq } from './YAMLSeq.ts' -export declare namespace Alias { - interface Parsed extends Alias { - range: Range - srcToken?: FlowScalar & { type: 'alias' } - } -} - export class Alias extends NodeBase { source: string declare anchor?: never + declare srcToken?: FlowScalar & { type: 'alias' } constructor(source: string) { - super(ALIAS) + super() this.source = source Object.defineProperty(this, 'tag', { set() { @@ -49,7 +43,7 @@ export class Alias extends NodeBase { nodes = [] visit(doc, { Node: (_key: unknown, node: Node) => { - if (isAlias(node) || hasAnchor(node)) nodes.push(node) + if (node instanceof Alias || node.anchor) nodes.push(node) } }) if (ctx) ctx.aliasResolveCache = nodes @@ -119,18 +113,19 @@ function getAliasCount( node: unknown, anchors: ToJSContext['anchors'] ): number { - if (isAlias(node)) { + if (node instanceof Alias) { const source = node.resolve(doc) const anchor = anchors && source && anchors.get(source) return anchor ? anchor.count * anchor.aliasCount : 0 - } else if (isCollection(node)) { + } else if (node instanceof NodeBase && 'items' in node) { + const coll = node as YAMLMap | YAMLSeq let count = 0 - for (const item of node.items) { + for (const item of coll.items) { const c = getAliasCount(doc, item, anchors) if (c > count) count = c } return count - } else if (isPair(node)) { + } else if (node instanceof Pair) { const kc = getAliasCount(doc, node.key, anchors) const vc = getAliasCount(doc, node.value, anchors) return Math.max(kc, vc) diff --git a/src/nodes/Collection.ts b/src/nodes/Collection.ts index a73bece7..a78a8edc 100644 --- a/src/nodes/Collection.ts +++ b/src/nodes/Collection.ts @@ -1,13 +1,11 @@ -import { createNode } from '../doc/createNode.ts' +import { NodeCreator } from '../doc/NodeCreator.ts' import type { Schema } from '../schema/Schema.ts' -import { - isCollection, - isNode, - isPair, - isScalar, - NODE_TYPE -} from './identity.ts' import { type Node, NodeBase } from './Node.ts' +import type { Pair } from './Pair.ts' +import type { Scalar } from './Scalar.ts' + +export type Primitive = boolean | number | bigint | string | null +export type NodeOf = T extends Primitive ? Scalar : T export function collectionFromPath( schema: Schema, @@ -25,32 +23,13 @@ export function collectionFromPath( v = new Map([[k, v]]) } } - return createNode(v, undefined, { - aliasDuplicateObjects: false, - keepUndefined: false, - onAnchor: () => { - throw new Error('This should not happen, please report a bug.') - }, - schema, - sourceObjects: new Map() - }) + return new NodeCreator(schema, { aliasDuplicateObjects: false }).create(v) } -// Type guard is intentionally a little wrong so as to be more useful, -// as it does not cover untypable empty non-string iterables (e.g. []). -export const isEmptyPath = ( - path: Iterable | null | undefined -): path is null | undefined => - path == null || - (typeof path === 'object' && !!path[Symbol.iterator]().next().done) - export abstract class Collection extends NodeBase { - schema: Schema | undefined; - - /** @internal */ - declare [NODE_TYPE]: symbol + schema: Schema | undefined - declare items: unknown[] + declare items: (NodeBase | Pair)[] /** An optional anchor on this node. Used by alias nodes. */ declare anchor?: string @@ -61,8 +40,8 @@ export abstract class Collection extends NodeBase { */ declare flow?: boolean - constructor(type: symbol, schema?: Schema) { - super(type) + constructor(schema?: Schema) { + super() Object.defineProperty(this, 'schema', { value: schema, configurable: true, @@ -82,9 +61,7 @@ export abstract class Collection extends NodeBase { Object.getOwnPropertyDescriptors(this) ) if (schema) copy.schema = schema - copy.items = copy.items.map(it => - isNode(it) || isPair(it) ? it.clone(schema) : it - ) + copy.items = copy.items.map(it => it.clone(schema)) if (this.range) copy.range = this.range.slice() as NodeBase['range'] return copy } @@ -99,11 +76,9 @@ export abstract class Collection extends NodeBase { abstract delete(key: unknown): boolean /** - * Returns item at `key`, or `undefined` if not found. By default unwraps - * scalar values from their surrounding node; to disable set `keepScalar` to - * `true` (collections are always returned intact). + * Returns item at `key`, or `undefined` if not found. */ - abstract get(key: unknown, keepScalar?: boolean): unknown + abstract get(key: unknown): NodeBase | Pair | undefined /** * Checks if the collection includes a value with the key `key`. @@ -111,22 +86,21 @@ export abstract class Collection extends NodeBase { abstract has(key: unknown): boolean /** - * Sets a value in this collection. For `!!set`, `value` needs to be a - * boolean to add/remove the item from the set. + * Sets a value in this collection. */ abstract set(key: unknown, value: unknown): void /** - * Adds a value to the collection. For `!!map` and `!!omap` the value must - * be a Pair instance or a `{ key, value }` object, which may not have a key - * that already exists in the map. + * Adds a value to the collection. + * + * For `!!map` and `!!omap` the value must be a Pair instance. */ - addIn(path: Iterable, value: unknown): void { - if (isEmptyPath(path)) this.add(value) + addIn(path: unknown[], value: unknown): void { + if (!path.length) this.add(value) else { const [key, ...rest] = path - const node = this.get(key, true) - if (isCollection(node)) node.addIn(rest, value) + const node = this.get(key) + if (node instanceof Collection) node.addIn(rest, value) else if (node === undefined && this.schema) this.set(key, collectionFromPath(this.schema, rest, value)) else @@ -138,13 +112,14 @@ export abstract class Collection extends NodeBase { /** * Removes a value from the collection. + * * @returns `true` if the item was found and removed. */ - deleteIn(path: Iterable): boolean { + deleteIn(path: unknown[]): boolean { const [key, ...rest] = path if (rest.length === 0) return this.delete(key) - const node = this.get(key, true) - if (isCollection(node)) return node.deleteIn(rest) + const node = this.get(key) + if (node instanceof Collection) return node.deleteIn(rest) else throw new Error( `Expected YAML collection at ${key}. Remaining path: ${rest}` @@ -152,55 +127,35 @@ export abstract class Collection extends NodeBase { } /** - * Returns item at `key`, or `undefined` if not found. By default unwraps - * scalar values from their surrounding node; to disable set `keepScalar` to - * `true` (collections are always returned intact). + * Returns item at `key`, or `undefined` if not found. */ - getIn(path: Iterable, keepScalar?: boolean): unknown { + getIn(path: unknown[]): NodeBase | Pair | undefined { const [key, ...rest] = path - const node = this.get(key, true) - if (rest.length === 0) - return !keepScalar && isScalar(node) ? node.value : node - else return isCollection(node) ? node.getIn(rest, keepScalar) : undefined - } - - hasAllNullValues(allowScalar?: boolean): boolean { - return this.items.every(node => { - if (!isPair(node)) return false - const n = node.value - return ( - n == null || - (allowScalar && - isScalar(n) && - n.value == null && - !n.commentBefore && - !n.comment && - !n.tag) - ) - }) + const node = this.get(key) + if (rest.length === 0) return node + else return node instanceof Collection ? node.getIn(rest) : undefined } /** * Checks if the collection includes a value with the key `key`. */ - hasIn(path: Iterable): boolean { + hasIn(path: unknown[]): boolean { const [key, ...rest] = path if (rest.length === 0) return this.has(key) - const node = this.get(key, true) - return isCollection(node) ? node.hasIn(rest) : false + const node = this.get(key) + return node instanceof Collection ? node.hasIn(rest) : false } /** - * Sets a value in this collection. For `!!set`, `value` needs to be a - * boolean to add/remove the item from the set. + * Sets a value in this collection. */ - setIn(path: Iterable, value: unknown): void { + setIn(path: unknown[], value: unknown): void { const [key, ...rest] = path if (rest.length === 0) { this.set(key, value) } else { - const node = this.get(key, true) - if (isCollection(node)) node.setIn(rest, value) + const node = this.get(key) + if (node instanceof Collection) node.setIn(rest, value) else if (node === undefined && this.schema) this.set(key, collectionFromPath(this.schema, rest, value)) else diff --git a/src/nodes/Node.ts b/src/nodes/Node.ts index 403c157c..9988c7e1 100644 --- a/src/nodes/Node.ts +++ b/src/nodes/Node.ts @@ -2,20 +2,16 @@ import { applyReviver } from '../doc/applyReviver.ts' import type { Document } from '../doc/Document.ts' import type { ToJSOptions } from '../options.ts' import type { Token } from '../parse/cst.ts' +import type { Schema } from '../schema/Schema.ts' import type { StringifyContext } from '../stringify/stringify.ts' import type { Alias } from './Alias.ts' -import { isDocument, NODE_TYPE } from './identity.ts' import type { Scalar } from './Scalar.ts' import type { ToJSContext } from './toJS.ts' import { toJS } from './toJS.ts' import type { MapLike, YAMLMap } from './YAMLMap.ts' import type { YAMLSeq } from './YAMLSeq.ts' -export type Node = - | Alias - | Scalar - | YAMLMap - | YAMLSeq +export type Node = Alias | Scalar | YAMLSeq | YAMLMap /** Utility type mapper */ export type NodeType = T extends @@ -30,25 +26,14 @@ export type NodeType = T extends ? Scalar : T extends Array ? YAMLSeq> - : T extends { [key: string]: any } + : T extends { [key: string | number]: any } ? YAMLMap, NodeType> - : T extends { [key: number]: any } // Merge with previous once supported in all TS versions - ? YAMLMap, NodeType> - : Node - -export type ParsedNode = - | Alias.Parsed - | Scalar.Parsed - | YAMLMap.Parsed - | YAMLSeq.Parsed + : Node /** `[start, value-end, node-end]` */ export type Range = [number, number, number] export abstract class NodeBase { - /** @internal */ - declare readonly [NODE_TYPE]: symbol - /** A comment on or immediately after this */ declare comment?: string | null @@ -91,12 +76,8 @@ export abstract class NodeBase { onChompKeep?: () => void ): string - constructor(type: symbol) { - Object.defineProperty(this, NODE_TYPE, { value: type }) - } - /** Create a copy of this node. */ - clone(): NodeBase { + clone(_schema?: Schema): NodeBase { const copy: NodeBase = Object.create( Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this) @@ -107,10 +88,10 @@ export abstract class NodeBase { /** A plain JavaScript representation of this node. */ toJS( - doc: Document, + doc: Document, { mapAsMap, maxAliasCount, onAnchor, reviver }: ToJSOptions = {} ): any { - if (!isDocument(doc)) throw new TypeError('A document argument is required') + if (!doc?.schema) throw new TypeError('A document argument is required') const ctx: ToJSContext = { anchors: new Map(), doc, diff --git a/src/nodes/Pair.ts b/src/nodes/Pair.ts index 4b34f84f..321696e1 100644 --- a/src/nodes/Pair.ts +++ b/src/nodes/Pair.ts @@ -1,47 +1,30 @@ -import type { CreateNodeContext } from '../doc/createNode.ts' -import { createNode } from '../doc/createNode.ts' import type { CollectionItem } from '../parse/cst.ts' import type { Schema } from '../schema/Schema.ts' import type { StringifyContext } from '../stringify/stringify.ts' import { stringifyPair } from '../stringify/stringifyPair.ts' import { addPairToJSMap } from './addPairToJSMap.ts' -import { isNode, NODE_TYPE, PAIR } from './identity.ts' -import type { Node } from './Node.ts' +import type { NodeOf, Primitive } from './Collection.ts' +import type { NodeBase } from './Node.ts' import type { ToJSContext } from './toJS.ts' -export function createPair( - key: unknown, - value: unknown, - ctx: CreateNodeContext -): Pair { - const k = createNode(key, undefined, ctx) - const v = createNode(value, undefined, ctx) - return new Pair(k, v) -} - -export class Pair { - /** @internal */ - declare readonly [NODE_TYPE]: symbol - - /** Always Node or null when parsed, but can be set to anything. */ - key: K - - /** Always Node or null when parsed, but can be set to anything. */ - value: V | null +export class Pair< + K extends Primitive | NodeBase = Primitive | NodeBase, + V extends Primitive | NodeBase = Primitive | NodeBase +> { + key: NodeOf + value: NodeOf | null /** The CST token that was composed into this pair. */ declare srcToken?: CollectionItem - constructor(key: K, value: V | null = null) { - Object.defineProperty(this, NODE_TYPE, { value: PAIR }) + constructor(key: NodeOf, value: NodeOf | null = null) { this.key = key this.value = value } clone(schema?: Schema): Pair { - let { key, value } = this - if (isNode(key)) key = key.clone(schema) as unknown as K - if (isNode(value)) value = value.clone(schema) as unknown as V + const key = this.key.clone(schema) as NodeOf + const value = (this.value?.clone(schema) ?? null) as NodeOf return new Pair(key, value) } diff --git a/src/nodes/Scalar.ts b/src/nodes/Scalar.ts index 00eabfe6..58a69abf 100644 --- a/src/nodes/Scalar.ts +++ b/src/nodes/Scalar.ts @@ -1,20 +1,9 @@ import type { BlockScalar, FlowScalar } from '../parse/cst.ts' -import { SCALAR } from './identity.ts' -import type { Range } from './Node.ts' import { NodeBase } from './Node.ts' import type { ToJSContext } from './toJS.ts' import { toJS } from './toJS.ts' -export const isScalarValue = (value: unknown): boolean => - !value || (typeof value !== 'function' && typeof value !== 'object') - export declare namespace Scalar { - interface Parsed extends Scalar { - range: Range - source: string - srcToken?: FlowScalar | BlockScalar - } - type BLOCK_FOLDED = 'BLOCK_FOLDED' type BLOCK_LITERAL = 'BLOCK_LITERAL' type PLAIN = 'PLAIN' @@ -49,11 +38,13 @@ export class Scalar extends NodeBase { /** Set during parsing to the source string value */ declare source?: string + declare srcToken?: FlowScalar | BlockScalar + /** The scalar style used for the node's string representation */ declare type?: Scalar.Type constructor(value: T) { - super(SCALAR) + super() this.value = value } diff --git a/src/nodes/YAMLMap.ts b/src/nodes/YAMLMap.ts index 6169e14f..4df98059 100644 --- a/src/nodes/YAMLMap.ts +++ b/src/nodes/YAMLMap.ts @@ -1,110 +1,88 @@ +import type { CreateNodeOptions } from '../options.ts' import type { BlockMap, FlowCollection } from '../parse/cst.ts' -import type { Schema } from '../schema/Schema.ts' import type { StringifyContext } from '../stringify/stringify.ts' import { stringifyCollection } from '../stringify/stringifyCollection.ts' -import type { CreateNodeContext } from '../util.ts' +import { NodeCreator } from '../doc/NodeCreator.ts' import { addPairToJSMap } from './addPairToJSMap.ts' -import { Collection } from './Collection.ts' -import { isPair, isScalar, MAP } from './identity.ts' -import type { ParsedNode, Range } from './Node.ts' -import { createPair, Pair } from './Pair.ts' -import type { Scalar } from './Scalar.ts' -import { isScalarValue } from './Scalar.ts' +import { Collection, type NodeOf, type Primitive } from './Collection.ts' +import { NodeBase } from './Node.ts' +import { Pair } from './Pair.ts' import type { ToJSContext } from './toJS.ts' +import { Scalar } from './Scalar.ts' export type MapLike = - | Map - | Set - | Record + | Map + | Set + | Record -export function findPair( - items: Iterable>, - key: unknown -): Pair | undefined { - const k = isScalar(key) ? key.value : key +export function findPair< + K extends Primitive | NodeBase = Primitive | NodeBase, + V extends Primitive | NodeBase = Primitive | NodeBase +>(items: Iterable>, key: unknown): Pair | undefined { + const k = key instanceof Scalar ? key.value : key for (const it of items) { - if (isPair(it)) { - if (it.key === key || it.key === k) return it - if (isScalar(it.key) && it.key.value === k) return it - } + if (it.key === key || it.key === k) return it + if (it.key instanceof Scalar && it.key.value === k) return it } return undefined } -export declare namespace YAMLMap { - interface Parsed< - K extends ParsedNode = ParsedNode, - V extends ParsedNode | null = ParsedNode | null - > extends YAMLMap { - items: Pair[] - range: Range - srcToken?: BlockMap | FlowCollection - } -} - -export class YAMLMap extends Collection { +export class YAMLMap< + K extends Primitive | NodeBase = Primitive | NodeBase, + V extends Primitive | NodeBase = Primitive | NodeBase +> extends Collection { static get tagName(): 'tag:yaml.org,2002:map' { return 'tag:yaml.org,2002:map' } items: Pair[] = [] - - constructor(schema?: Schema) { - super(MAP, schema) - } + declare srcToken?: BlockMap | FlowCollection /** * A generic collection parsing method that can be extended * to other node classes that inherit from YAMLMap */ - static from(schema: Schema, obj: unknown, ctx: CreateNodeContext): YAMLMap { - const { keepUndefined, replacer } = ctx - const map = new this(schema) + static from(nc: NodeCreator, obj: unknown): YAMLMap { + const { replacer } = nc + const map = new this(nc.schema) const add = (key: unknown, value: unknown) => { if (typeof replacer === 'function') value = replacer.call(obj, key, value) else if (Array.isArray(replacer) && !replacer.includes(key)) return - if (value !== undefined || keepUndefined) - map.items.push(createPair(key, value, ctx)) + if (value !== undefined || nc.keepUndefined) + map.items.push(nc.createPair(key, value)) } if (obj instanceof Map) { for (const [key, value] of obj) add(key, value) } else if (obj && typeof obj === 'object') { for (const key of Object.keys(obj)) add(key, (obj as any)[key]) } - if (typeof schema.sortMapEntries === 'function') { - map.items.sort(schema.sortMapEntries) + if (typeof nc.schema.sortMapEntries === 'function') { + map.items.sort(nc.schema.sortMapEntries) } return map } /** - * Adds a value to the collection. + * Adds a key-value pair to the map. * - * @param overwrite - If not set `true`, using a key that is already in the - * collection will throw. Otherwise, overwrites the previous value. + * Using a key that is already in the collection overwrites the previous value. */ - add(pair: Pair | { key: K; value: V }, overwrite?: boolean): void { - let _pair: Pair - if (isPair(pair)) _pair = pair - else if (!pair || typeof pair !== 'object' || !('key' in pair)) { - // In TypeScript, this never happens. - _pair = new Pair(pair as any, (pair as any)?.value) - } else _pair = new Pair(pair.key, pair.value) + add(pair: Pair): void { + if (!(pair instanceof Pair)) throw new TypeError('Expected a Pair') - const prev = findPair(this.items, _pair.key) + const prev = findPair(this.items, pair.key) const sortEntries = this.schema?.sortMapEntries if (prev) { - if (!overwrite) throw new Error(`Key ${_pair.key} already set`) // For scalars, keep the old node & its comments and anchors - if (isScalar(prev.value) && isScalarValue(_pair.value)) - prev.value.value = _pair.value - else prev.value = _pair.value + if (prev.value instanceof Scalar && pair.value instanceof Scalar) + prev.value.value = pair.value.value + else prev.value = pair.value } else if (sortEntries) { - const i = this.items.findIndex(item => sortEntries(_pair, item) < 0) - if (i === -1) this.items.push(_pair) - else this.items.splice(i, 0, _pair) + const i = this.items.findIndex(item => sortEntries(pair, item) < 0) + if (i === -1) this.items.push(pair) + else this.items.splice(i, 0, pair) } else { - this.items.push(_pair) + this.items.push(pair) } } @@ -115,21 +93,37 @@ export class YAMLMap extends Collection { return del.length > 0 } - get(key: unknown, keepScalar: true): Scalar | undefined - get(key: unknown, keepScalar?: false): V | undefined - get(key: unknown, keepScalar?: boolean): V | Scalar | undefined - get(key: unknown, keepScalar?: boolean): V | Scalar | undefined { + get(key: unknown): NodeOf | undefined { const it = findPair(this.items, key) - const node = it?.value - return (!keepScalar && isScalar(node) ? node.value : node) ?? undefined + return it?.value ?? undefined } has(key: unknown): boolean { return !!findPair(this.items, key) } - set(key: K, value: V): void { - this.add(new Pair(key, value), true) + set( + key: unknown, + value: unknown, + options?: Omit + ): void { + let pair: Pair + if ( + key instanceof NodeBase && + (value instanceof NodeBase || value === null) + ) { + pair = new Pair(key, value) + } else if (!this.schema) { + throw new Error('Schema is required') + } else { + const nc = new NodeCreator(this.schema, { + ...options, + aliasDuplicateObjects: false + }) + pair = nc.createPair(key, value) + nc.setAnchors() + } + this.add(pair as Pair) } /** @@ -137,11 +131,17 @@ export class YAMLMap extends Collection { * @param {Class} Type - If set, forces the returned collection type * @returns Instance of Type, Map, or Object */ - toJSON>( + toJSON>( + _: unknown, + ctx: ToJSContext | undefined, + Type: { new (): T } + ): T + toJSON(_?: unknown, ctx?: ToJSContext): any + toJSON( _?: unknown, ctx?: ToJSContext, Type?: { new (): T } - ): any { + ) { const map = Type ? new Type() : ctx?.mapAsMap ? new Map() : {} if (ctx?.onCreate) ctx.onCreate(map) for (const item of this.items) addPairToJSMap(ctx, map, item) @@ -155,13 +155,11 @@ export class YAMLMap extends Collection { ): string { if (!ctx) return JSON.stringify(this) for (const item of this.items) { - if (!isPair(item)) + if (!(item instanceof Pair)) throw new Error( `Map items must all be pairs; found ${JSON.stringify(item)} instead` ) } - if (!ctx.allNullValues && this.hasAllNullValues(false)) - ctx = Object.assign({}, ctx, { allNullValues: true }) return stringifyCollection(this, ctx, { blockItemPrefix: '', flowChars: { start: '{', end: '}' }, diff --git a/src/nodes/YAMLSeq.ts b/src/nodes/YAMLSeq.ts index c3f96e99..d7073135 100644 --- a/src/nodes/YAMLSeq.ts +++ b/src/nodes/YAMLSeq.ts @@ -1,101 +1,109 @@ -import type { CreateNodeContext } from '../doc/createNode.ts' -import { createNode } from '../doc/createNode.ts' +import { NodeCreator } from '../doc/NodeCreator.ts' +import type { CreateNodeOptions } from '../options.ts' import type { BlockSequence, FlowCollection } from '../parse/cst.ts' -import type { Schema } from '../schema/Schema.ts' import type { StringifyContext } from '../stringify/stringify.ts' import { stringifyCollection } from '../stringify/stringifyCollection.ts' -import { Collection } from './Collection.ts' -import { isScalar, SEQ } from './identity.ts' -import type { ParsedNode, Range } from './Node.ts' +import { Collection, type NodeOf, type Primitive } from './Collection.ts' +import { NodeBase } from './Node.ts' import type { Pair } from './Pair.ts' -import type { Scalar } from './Scalar.ts' -import { isScalarValue } from './Scalar.ts' +import { Scalar } from './Scalar.ts' import type { ToJSContext } from './toJS.ts' import { toJS } from './toJS.ts' -export declare namespace YAMLSeq { - interface Parsed< - T extends ParsedNode | Pair = ParsedNode - > extends YAMLSeq { - items: T[] - range: Range - srcToken?: BlockSequence | FlowCollection - } -} +const isScalarValue = (value: unknown): boolean => + !value || (typeof value !== 'function' && typeof value !== 'object') -export class YAMLSeq extends Collection { +export class YAMLSeq< + T extends Primitive | NodeBase | Pair = Primitive | NodeBase | Pair +> extends Collection { static get tagName(): 'tag:yaml.org,2002:seq' { return 'tag:yaml.org,2002:seq' } - items: T[] = [] - - constructor(schema?: Schema) { - super(SEQ, schema) - } + items: NodeOf[] = [] + declare srcToken?: BlockSequence | FlowCollection - add(value: T): void { - this.items.push(value) + add( + value: T, + options?: Omit + ): void { + if (value instanceof NodeBase) this.items.push(value as NodeOf) + else if (!this.schema) throw new Error('Schema is required') + else { + const nc = new NodeCreator(this.schema, { + ...options, + aliasDuplicateObjects: false + }) + this.items.push(nc.create(value) as NodeOf) + nc.setAnchors() + } } /** * Removes a value from the collection. * - * `key` must contain a representation of an integer for this to succeed. - * It may be wrapped in a `Scalar`. + * Throws if `idx` is not a non-negative integer. * * @returns `true` if the item was found and removed. */ - delete(key: unknown): boolean { - const idx = asItemIndex(key) - if (typeof idx !== 'number') return false + delete(idx: number): boolean { + if (!Number.isInteger(idx)) + throw new TypeError(`Expected an integer, not ${idx}.`) + if (idx < 0) throw new RangeError(`Invalid negative index ${idx}`) const del = this.items.splice(idx, 1) return del.length > 0 } /** - * Returns item at `key`, or `undefined` if not found. By default unwraps - * scalar values from their surrounding node; to disable set `keepScalar` to - * `true` (collections are always returned intact). + * Returns item at `key`, or `undefined` if not found. * - * `key` must contain a representation of an integer for this to succeed. - * It may be wrapped in a `Scalar`. + * Throws if `idx` is not a non-negative integer. */ - get(key: unknown, keepScalar: true): Scalar | undefined - get(key: unknown, keepScalar?: false): T | undefined - get(key: unknown, keepScalar?: boolean): T | Scalar | undefined - get(key: unknown, keepScalar?: boolean): T | Scalar | undefined { - const idx = asItemIndex(key) - if (typeof idx !== 'number') return undefined - const it = this.items[idx] - return !keepScalar && isScalar(it) ? it.value : it + get(idx: number): NodeOf | undefined { + if (!Number.isInteger(idx)) + throw new TypeError(`Expected an integer, not ${JSON.stringify(idx)}.`) + if (idx < 0) throw new RangeError(`Invalid negative index ${idx}`) + return this.items[idx] } /** * Checks if the collection includes a value with the key `key`. * - * `key` must contain a representation of an integer for this to succeed. - * It may be wrapped in a `Scalar`. + * Throws if `idx` is not a non-negative integer. */ - has(key: unknown): boolean { - const idx = asItemIndex(key) - return typeof idx === 'number' && idx < this.items.length + has(idx: number): boolean { + if (!Number.isInteger(idx)) + throw new TypeError(`Expected an integer, not ${JSON.stringify(idx)}.`) + if (idx < 0) throw new RangeError(`Invalid negative index ${idx}`) + return idx < this.items.length } /** * Sets a value in this collection. For `!!set`, `value` needs to be a * boolean to add/remove the item from the set. * - * If `key` does not contain a representation of an integer, this will throw. - * It may be wrapped in a `Scalar`. + * Throws if `idx` is not a non-negative integer. */ - set(key: unknown, value: T): void { - const idx = asItemIndex(key) - if (typeof idx !== 'number') - throw new Error(`Expected a valid index, not ${key}.`) + set( + idx: number, + value: T, + options?: Omit + ): void { + if (!Number.isInteger(idx)) + throw new TypeError(`Expected an integer, not ${JSON.stringify(idx)}.`) + if (idx < 0) throw new RangeError(`Invalid negative index ${idx}`) const prev = this.items[idx] - if (isScalar(prev) && isScalarValue(value)) prev.value = value - else this.items[idx] = value + if (prev instanceof Scalar && isScalarValue(value)) prev.value = value + else if (value instanceof NodeBase) this.items[idx] = value as NodeOf + else if (!this.schema) throw new Error('Schema is required') + else { + const nc = new NodeCreator(this.schema, { + ...options, + aliasDuplicateObjects: false + }) + this.items[idx] = nc.create(value) as NodeOf + nc.setAnchors() + } } toJSON(_?: unknown, ctx?: ToJSContext): unknown[] { @@ -121,27 +129,18 @@ export class YAMLSeq extends Collection { }) } - static from(schema: Schema, obj: unknown, ctx: CreateNodeContext): YAMLSeq { - const { replacer } = ctx - const seq = new this(schema) + static from(nc: NodeCreator, obj: unknown): YAMLSeq { + const seq = new this(nc.schema) if (obj && Symbol.iterator in Object(obj)) { let i = 0 for (let it of obj as Iterable) { - if (typeof replacer === 'function') { + if (typeof nc.replacer === 'function') { const key = obj instanceof Set ? it : String(i++) - it = replacer.call(obj, key, it) + it = nc.replacer.call(obj, key, it) } - seq.items.push(createNode(it, undefined, ctx)) + seq.items.push(nc.create(it)) } } return seq } } - -function asItemIndex(key: unknown): number | null { - let idx = isScalar(key) ? key.value : key - if (idx && typeof idx === 'string') idx = Number(idx) - return typeof idx === 'number' && Number.isInteger(idx) && idx >= 0 - ? idx - : null -} diff --git a/src/nodes/addPairToJSMap.ts b/src/nodes/addPairToJSMap.ts index d429a35d..64049d8c 100644 --- a/src/nodes/addPairToJSMap.ts +++ b/src/nodes/addPairToJSMap.ts @@ -1,7 +1,7 @@ import { warn } from '../log.ts' import { addMergeToJSMap, isMergeKey } from '../schema/yaml-1.1/merge.ts' import { createStringifyContext } from '../stringify/stringify.ts' -import { isNode } from './identity.ts' +import { NodeBase } from './Node.ts' import type { Pair } from './Pair.ts' import type { ToJSContext } from './toJS.ts' import { toJS } from './toJS.ts' @@ -12,7 +12,7 @@ export function addPairToJSMap( map: MapLike, { key, value }: Pair ): MapLike { - if (isNode(key) && key.addToJSMap) key.addToJSMap(ctx, map, value) + if (key instanceof NodeBase && key.addToJSMap) key.addToJSMap(ctx, map, value) // TODO: Should drop this special case for bare << handling else if (isMergeKey(ctx, key)) addMergeToJSMap(ctx, map, value) else { @@ -45,7 +45,7 @@ function stringifyKey( if (jsKey === null) return '' // eslint-disable-next-line @typescript-eslint/no-base-to-string if (typeof jsKey !== 'object') return String(jsKey) - if (isNode(key) && ctx?.doc) { + if (key instanceof NodeBase && ctx?.doc) { const strCtx = createStringifyContext(ctx.doc, {}) strCtx.anchors = new Set() for (const node of ctx.anchors.keys()) diff --git a/src/nodes/identity.ts b/src/nodes/identity.ts deleted file mode 100644 index 9f1fba2f..00000000 --- a/src/nodes/identity.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type { Document } from '../doc/Document.ts' -import type { Alias } from './Alias.ts' -import type { Node } from './Node.ts' -import type { Pair } from './Pair.ts' -import type { Scalar } from './Scalar.ts' -import type { YAMLMap } from './YAMLMap.ts' -import type { YAMLSeq } from './YAMLSeq.ts' - -export const ALIAS: unique symbol = Symbol.for('yaml.alias') -export const DOC: unique symbol = Symbol.for('yaml.document') -export const MAP: unique symbol = Symbol.for('yaml.map') -export const PAIR: unique symbol = Symbol.for('yaml.pair') -export const SCALAR: unique symbol = Symbol.for('yaml.scalar') -export const SEQ: unique symbol = Symbol.for('yaml.seq') -export const NODE_TYPE: unique symbol = Symbol.for('yaml.node.type') - -export const isAlias = (node: any): node is Alias => - !!node && typeof node === 'object' && node[NODE_TYPE] === ALIAS - -export const isDocument = ( - node: any -): node is Document => - !!node && typeof node === 'object' && node[NODE_TYPE] === DOC - -export const isMap = ( - node: any -): node is YAMLMap => - !!node && typeof node === 'object' && node[NODE_TYPE] === MAP - -export const isPair = ( - node: any -): node is Pair => - !!node && typeof node === 'object' && node[NODE_TYPE] === PAIR - -export const isScalar = (node: any): node is Scalar => - !!node && typeof node === 'object' && node[NODE_TYPE] === SCALAR - -export const isSeq = (node: any): node is YAMLSeq => - !!node && typeof node === 'object' && node[NODE_TYPE] === SEQ - -export function isCollection( - node: any -): node is YAMLMap | YAMLSeq { - if (node && typeof node === 'object') - switch (node[NODE_TYPE]) { - case MAP: - case SEQ: - return true - } - return false -} - -export function isNode(node: any): node is Node { - if (node && typeof node === 'object') - switch (node[NODE_TYPE]) { - case ALIAS: - case MAP: - case SCALAR: - case SEQ: - return true - } - return false -} - -export const hasAnchor = ( - node: unknown -): node is Scalar | YAMLMap | YAMLSeq => - (isScalar(node) || isCollection(node)) && !!node.anchor diff --git a/src/nodes/toJS.ts b/src/nodes/toJS.ts index 2b9c6c14..b34fdb51 100644 --- a/src/nodes/toJS.ts +++ b/src/nodes/toJS.ts @@ -1,6 +1,5 @@ -import type { Document } from '../doc/Document.ts' -import { hasAnchor } from './identity.ts' -import type { Node } from './Node.ts' +import type { Document, DocValue } from '../doc/Document.ts' +import { NodeBase, type Node } from './Node.ts' export interface AnchorData { aliasCount: number @@ -12,7 +11,7 @@ export interface ToJSContext { anchors: Map /** Cached anchor and alias nodes in the order they occur in the document */ aliasResolveCache?: Node[] - doc: Document + doc: Document keep: boolean mapAsMap: boolean mapKeyWarned: boolean @@ -34,15 +33,18 @@ export function toJS(value: any, arg: string | null, ctx?: ToJSContext): any { // eslint-disable-next-line @typescript-eslint/no-unsafe-return if (Array.isArray(value)) return value.map((v, i) => toJS(v, String(i), ctx)) if (value && typeof value.toJSON === 'function') { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - if (!ctx || !hasAnchor(value)) return value.toJSON(arg, ctx) + const node = value as Node + if (!ctx || !(node instanceof NodeBase) || !node.anchor) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + return value.toJSON(arg, ctx) + } const data: AnchorData = { aliasCount: 0, count: 1, res: undefined } - ctx.anchors.set(value, data) + ctx.anchors.set(node, data) ctx.onCreate = res => { data.res = res delete ctx.onCreate } - const res = value.toJSON(arg, ctx) + const res = node.toJSON(arg, ctx) if (ctx.onCreate) ctx.onCreate(res) return res } diff --git a/src/options.ts b/src/options.ts index 2b8a0585..ef8a0aaf 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,7 +1,7 @@ import type { Reviver } from './doc/applyReviver.ts' import type { Directives } from './doc/directives.ts' import type { LogLevelId } from './log.ts' -import type { ParsedNode } from './nodes/Node.ts' +import type { NodeBase } from './nodes/Node.ts' import type { Pair } from './nodes/Pair.ts' import type { Scalar } from './nodes/Scalar.ts' import type { LineCounter } from './parse/line-counter.ts' @@ -62,12 +62,12 @@ export type ParseOptions = { * multiple `<<` keys are allowed. * * Set `false` to disable, or provide your own comparator function to - * customise. The comparator will be passed two `ParsedNode` values, and + * customise. The comparator will be passed two node values, and * is expected to return a `boolean` indicating their equality. * * Default: `true` */ - uniqueKeys?: boolean | ((a: ParsedNode, b: ParsedNode) => boolean) + uniqueKeys?: boolean | ((a: NodeBase, b: NodeBase) => boolean) } export type DocumentOptions = { diff --git a/src/public-api.ts b/src/public-api.ts index 1f8b8655..aee8e9c4 100644 --- a/src/public-api.ts +++ b/src/public-api.ts @@ -1,11 +1,9 @@ import { Composer } from './compose/composer.ts' import type { Reviver } from './doc/applyReviver.ts' -import type { Replacer } from './doc/Document.ts' +import type { DocValue, Replacer } from './doc/Document.ts' import { Document } from './doc/Document.ts' import { prettifyError, YAMLParseError } from './errors.ts' import { warn } from './log.ts' -import { isDocument } from './nodes/identity.ts' -import type { Node, ParsedNode } from './nodes/Node.ts' import type { CreateNodeOptions, DocumentOptions, @@ -39,18 +37,12 @@ function parseOptions(options: ParseOptions) { * TypeScript, you should use `'empty' in docs` as a type guard for it. */ export function parseAllDocuments< - Contents extends Node = ParsedNode, + Value extends DocValue = DocValue, Strict extends boolean = true >( source: string, options: ParseOptions & DocumentOptions & SchemaOptions = {} -): - | Array< - Contents extends ParsedNode - ? Document.Parsed - : Document - > - | EmptyStream { +): Document.Parsed[] | EmptyStream { const { lineCounter, prettyErrors } = parseOptions(options) const parser = new Parser(lineCounter?.addNewLine) const composer = new Composer(options) @@ -62,9 +54,7 @@ export function parseAllDocuments< doc.warnings.forEach(prettifyError(source, lineCounter)) } - type DocType = Contents extends ParsedNode - ? Document.Parsed - : Document + type DocType = Document.Parsed if (docs.length > 0) return docs as DocType[] return Object.assign< DocType[], @@ -75,21 +65,17 @@ export function parseAllDocuments< /** Parse an input string into a single YAML.Document */ export function parseDocument< - Contents extends Node = ParsedNode, + Value extends DocValue = DocValue, Strict extends boolean = true >( source: string, options: ParseOptions & DocumentOptions & SchemaOptions = {} -): Contents extends ParsedNode - ? Document.Parsed - : Document { +): Document.Parsed { const { lineCounter, prettyErrors } = parseOptions(options) const parser = new Parser(lineCounter?.addNewLine) const composer = new Composer(options) - type DocType = Contents extends ParsedNode - ? Document.Parsed - : Document + type DocType = Document.Parsed // `doc` is always set by compose.end(true) at the very latest let doc: DocType = null as any for (const _doc of composer.compose( @@ -226,6 +212,6 @@ export function stringify( const { keepUndefined } = options ?? (replacer as CreateNodeOptions) ?? {} if (!keepUndefined) return undefined } - if (isDocument(value) && !_replacer) return value.toString(options) + if (value instanceof Document && !_replacer) return value.toString(options) return new Document(value, _replacer, options).toString(options) } diff --git a/src/schema/Schema.ts b/src/schema/Schema.ts index c99468f4..47b400fa 100644 --- a/src/schema/Schema.ts +++ b/src/schema/Schema.ts @@ -1,4 +1,3 @@ -import { MAP, SCALAR, SEQ } from '../nodes/identity.ts' import type { Pair } from '../nodes/Pair.ts' import type { SchemaOptions, ToStringOptions } from '../options.ts' import { map } from './common/map.ts' @@ -20,11 +19,11 @@ export class Schema { // These are used by createNode() and composeScalar() /** @internal */ - declare readonly [MAP]: CollectionTag + declare readonly map: CollectionTag /** @internal */ - declare readonly [SCALAR]: ScalarTag + declare readonly scalar: ScalarTag /** @internal */ - declare readonly [SEQ]: CollectionTag + declare readonly seq: CollectionTag constructor({ compat, @@ -45,9 +44,9 @@ export class Schema { this.tags = getTags(customTags, this.name, merge) this.toStringOptions = toStringDefaults ?? null - Object.defineProperty(this, MAP, { value: map }) - Object.defineProperty(this, SCALAR, { value: string }) - Object.defineProperty(this, SEQ, { value: seq }) + Object.defineProperty(this, 'map', { value: map }) + Object.defineProperty(this, 'scalar', { value: string }) + Object.defineProperty(this, 'seq', { value: seq }) // Used by createMap() this.sortMapEntries = diff --git a/src/schema/common/map.ts b/src/schema/common/map.ts index 3f9a22e0..73c97d6c 100644 --- a/src/schema/common/map.ts +++ b/src/schema/common/map.ts @@ -1,4 +1,3 @@ -import { isMap } from '../../nodes/identity.ts' import { YAMLMap } from '../../nodes/YAMLMap.ts' import type { CollectionTag } from '../types.ts' @@ -8,8 +7,7 @@ export const map: CollectionTag = { nodeClass: YAMLMap, tag: 'tag:yaml.org,2002:map', resolve(map, onError) { - if (!isMap(map)) onError('Expected a mapping for this tag') + if (!(map instanceof YAMLMap)) onError('Expected a mapping for this tag') return map - }, - createNode: (schema, obj, ctx) => YAMLMap.from(schema, obj, ctx) + } } diff --git a/src/schema/common/seq.ts b/src/schema/common/seq.ts index 401e9c74..5a01192f 100644 --- a/src/schema/common/seq.ts +++ b/src/schema/common/seq.ts @@ -1,4 +1,3 @@ -import { isSeq } from '../../nodes/identity.ts' import { YAMLSeq } from '../../nodes/YAMLSeq.ts' import type { CollectionTag } from '../types.ts' @@ -8,8 +7,7 @@ export const seq: CollectionTag = { nodeClass: YAMLSeq, tag: 'tag:yaml.org,2002:seq', resolve(seq, onError) { - if (!isSeq(seq)) onError('Expected a sequence for this tag') + if (!(seq instanceof YAMLSeq)) onError('Expected a sequence for this tag') return seq - }, - createNode: (schema, obj, ctx) => YAMLSeq.from(schema, obj, ctx) + } } diff --git a/src/schema/types.ts b/src/schema/types.ts index 0583ffc3..042584df 100644 --- a/src/schema/types.ts +++ b/src/schema/types.ts @@ -1,4 +1,4 @@ -import type { CreateNodeContext } from '../doc/createNode.ts' +import type { NodeCreator } from '../doc/NodeCreator.ts' import type { Node } from '../nodes/Node.ts' import type { Scalar } from '../nodes/Scalar.ts' import type { YAMLMap } from '../nodes/YAMLMap.ts' @@ -11,7 +11,7 @@ interface TagBase { /** * An optional factory function, used e.g. by collections when wrapping JS objects as AST nodes. */ - createNode?: (schema: Schema, value: unknown, ctx: CreateNodeContext) => Node + createNode?: (nc: NodeCreator, value: unknown) => Node /** * If `true`, allows for values to be stringified without @@ -100,7 +100,7 @@ export interface CollectionTag extends TagBase { */ nodeClass?: { new (schema?: Schema): Node - from?: (schema: Schema, obj: unknown, ctx: CreateNodeContext) => Node + from?: (nc: NodeCreator, obj: unknown) => Node } /** @@ -110,7 +110,7 @@ export interface CollectionTag extends TagBase { * Note: this is required if nodeClass is not provided. */ resolve?: ( - value: YAMLMap.Parsed | YAMLSeq.Parsed, + value: YAMLMap | YAMLSeq, onError: (message: string) => void, options: ParseOptions ) => unknown diff --git a/src/schema/yaml-1.1/merge.ts b/src/schema/yaml-1.1/merge.ts index e8f449ac..de1d2e1c 100644 --- a/src/schema/yaml-1.1/merge.ts +++ b/src/schema/yaml-1.1/merge.ts @@ -1,7 +1,8 @@ -import { isAlias, isMap, isScalar, isSeq } from '../../nodes/identity.ts' +import { Alias } from '../../nodes/Alias.ts' import { Scalar } from '../../nodes/Scalar.ts' import type { ToJSContext } from '../../nodes/toJS.ts' -import type { MapLike } from '../../nodes/YAMLMap.ts' +import type { MapLike, YAMLMap } from '../../nodes/YAMLMap.ts' +import { YAMLSeq } from '../../nodes/YAMLSeq.ts' import type { ScalarTag } from '../types.ts' // If the value associated with a merge key is a single mapping node, each of @@ -36,7 +37,7 @@ export const isMergeKey = ( key: unknown ): boolean => (merge.identify(key) || - (isScalar(key) && + (key instanceof Scalar && (!key.type || key.type === Scalar.PLAIN) && merge.identify(key.value))) && Boolean( @@ -48,8 +49,9 @@ export function addMergeToJSMap( map: MapLike, value: unknown ): void { - value = ctx && isAlias(value) ? value.resolve(ctx.doc) : value - if (isSeq(value)) for (const it of value.items) mergeValue(ctx, map, it) + value = ctx && value instanceof Alias ? value.resolve(ctx.doc) : value + if (value instanceof YAMLSeq) + for (const it of value.items) mergeValue(ctx, map, it) else if (Array.isArray(value)) for (const it of value) mergeValue(ctx, map, it) else mergeValue(ctx, map, value) @@ -60,10 +62,10 @@ function mergeValue( map: MapLike, value: unknown ) { - const source = ctx && isAlias(value) ? value.resolve(ctx.doc) : value - if (!isMap(source)) + const source = ctx && value instanceof Alias ? value.resolve(ctx.doc) : value + const srcMap = (source as YAMLMap).toJSON(null, ctx, Map) + if (!(srcMap instanceof Map)) throw new Error('Merge sources must be maps or map aliases') - const srcMap = source.toJSON(null, ctx, Map) for (const [key, value] of srcMap) { if (map instanceof Map) { if (!map.has(key)) map.set(key, value) diff --git a/src/schema/yaml-1.1/omap.ts b/src/schema/yaml-1.1/omap.ts index cd14c3e4..7d2846bc 100644 --- a/src/schema/yaml-1.1/omap.ts +++ b/src/schema/yaml-1.1/omap.ts @@ -1,18 +1,24 @@ -import { isPair, isScalar } from '../../nodes/identity.ts' +import type { Primitive } from '../../nodes/Collection.ts' +import type { NodeBase } from '../../nodes/Node.ts' +import { Pair } from '../../nodes/Pair.ts' +import { Scalar } from '../../nodes/Scalar.ts' import type { ToJSContext } from '../../nodes/toJS.ts' import { toJS } from '../../nodes/toJS.ts' import { YAMLMap } from '../../nodes/YAMLMap.ts' import { YAMLSeq } from '../../nodes/YAMLSeq.ts' -import type { CreateNodeContext } from '../../util.ts' +import type { NodeCreator } from '../../util.ts' import type { Schema } from '../Schema.ts' import type { CollectionTag } from '../types.ts' import { createPairs, resolvePairs } from './pairs.ts' -export class YAMLOMap extends YAMLSeq { +export class YAMLOMap< + K extends Primitive | NodeBase = Primitive | NodeBase, + V extends Primitive | NodeBase = Primitive | NodeBase +> extends YAMLSeq> { static tag = 'tag:yaml.org,2002:omap' - constructor() { - super() + constructor(schema?: Schema) { + super(schema) this.tag = YAMLOMap.tag } @@ -32,7 +38,7 @@ export class YAMLOMap extends YAMLSeq { if (ctx?.onCreate) ctx.onCreate(map) for (const pair of this.items) { let key, value - if (isPair(pair)) { + if (pair instanceof Pair) { key = toJS(pair.key, '', ctx) value = toJS(pair.value, key, ctx) } else { @@ -45,13 +51,9 @@ export class YAMLOMap extends YAMLSeq { return map as unknown as unknown[] } - static from( - schema: Schema, - iterable: unknown, - ctx: CreateNodeContext - ): YAMLOMap { - const pairs = createPairs(schema, iterable, ctx) - const omap = new this() + static from(nc: NodeCreator, iterable: unknown): YAMLOMap { + const pairs = createPairs(nc, iterable) + const omap = new this(nc.schema) omap.items = pairs.items return omap } @@ -68,7 +70,7 @@ export const omap: CollectionTag = { const pairs = resolvePairs(seq, onError) const seenKeys: unknown[] = [] for (const { key } of pairs.items) { - if (isScalar(key)) { + if (key instanceof Scalar) { if (seenKeys.includes(key.value)) { onError(`Ordered maps must not include duplicate keys: ${key.value}`) } else { @@ -77,6 +79,5 @@ export const omap: CollectionTag = { } } return Object.assign(new YAMLOMap(), pairs) - }, - createNode: (schema, iterable, ctx) => YAMLOMap.from(schema, iterable, ctx) + } } diff --git a/src/schema/yaml-1.1/pairs.ts b/src/schema/yaml-1.1/pairs.ts index 3e98c8e8..6890e1be 100644 --- a/src/schema/yaml-1.1/pairs.ts +++ b/src/schema/yaml-1.1/pairs.ts @@ -1,28 +1,23 @@ -import type { CreateNodeContext } from '../../doc/createNode.ts' -import { isMap, isPair, isSeq } from '../../nodes/identity.ts' -import type { ParsedNode } from '../../nodes/Node.ts' -import { createPair, Pair } from '../../nodes/Pair.ts' +import type { NodeCreator } from '../../doc/NodeCreator.ts' +import type { NodeBase } from '../../nodes/Node.ts' +import { Pair } from '../../nodes/Pair.ts' import { Scalar } from '../../nodes/Scalar.ts' -import type { YAMLMap } from '../../nodes/YAMLMap.ts' +import { YAMLMap } from '../../nodes/YAMLMap.ts' import { YAMLSeq } from '../../nodes/YAMLSeq.ts' -import type { Schema } from '../../schema/Schema.ts' import type { CollectionTag } from '../types.ts' export function resolvePairs( - seq: - | YAMLSeq.Parsed> - | YAMLMap.Parsed, + seq: YAMLSeq | YAMLMap, onError: (message: string) => void -) { - if (isSeq(seq)) { +): YAMLSeq { + if (seq instanceof YAMLSeq) { for (let i = 0; i < seq.items.length; ++i) { - let item = seq.items[i] - if (isPair(item)) continue - else if (isMap(item)) { + const item = seq.items[i] + if (item instanceof Pair) continue + else if (item instanceof YAMLMap) { if (item.items.length > 1) onError('Each pair must have its own sequence indicator') - const pair = - item.items[0] || new Pair(new Scalar(null) as Scalar.Parsed) + const pair = item.items[0] || new Pair(new Scalar(null)) if (item.commentBefore) pair.key.commentBefore = pair.key.commentBefore ? `${item.commentBefore}\n${pair.key.commentBefore}` @@ -33,27 +28,23 @@ export function resolvePairs( ? `${item.comment}\n${cn.comment}` : item.comment } - item = pair + seq.items[i] = pair + } else { + seq.items[i] = new Pair(item, null) } - seq.items[i] = isPair(item) ? item : new Pair(item) } } else onError('Expected a sequence for this tag') - return seq as YAMLSeq.Parsed> + return seq as YAMLSeq } -export function createPairs( - schema: Schema, - iterable: unknown, - ctx: CreateNodeContext -): YAMLSeq { - const { replacer } = ctx - const pairs = new YAMLSeq(schema) +export function createPairs(nc: NodeCreator, iterable: unknown): YAMLSeq { + const pairs = new YAMLSeq(nc.schema) pairs.tag = 'tag:yaml.org,2002:pairs' let i = 0 if (iterable && Symbol.iterator in Object(iterable)) for (let it of iterable as Iterable) { - if (typeof replacer === 'function') - it = replacer.call(iterable, String(i++), it) + if (typeof nc.replacer === 'function') + it = nc.replacer.call(iterable, String(i++), it) let key: unknown, value: unknown if (Array.isArray(it)) { if (it.length === 2) { @@ -73,7 +64,7 @@ export function createPairs( } else { key = it } - pairs.items.push(createPair(key, value, ctx)) + pairs.items.push(nc.createPair(key, value)) } return pairs } diff --git a/src/schema/yaml-1.1/set.ts b/src/schema/yaml-1.1/set.ts index 11916935..1af04d9b 100644 --- a/src/schema/yaml-1.1/set.ts +++ b/src/schema/yaml-1.1/set.ts @@ -1,15 +1,18 @@ -import { isMap, isPair, isScalar } from '../../nodes/identity.ts' +import { NodeCreator } from '../../doc/NodeCreator.ts' +import type { NodeOf, Primitive } from '../../nodes/Collection.ts' +import { NodeBase } from '../../nodes/Node.ts' import { Pair } from '../../nodes/Pair.ts' -import type { Scalar } from '../../nodes/Scalar.ts' +import { Scalar } from '../../nodes/Scalar.ts' import type { ToJSContext } from '../../nodes/toJS.ts' import { findPair, YAMLMap } from '../../nodes/YAMLMap.ts' +import type { CreateNodeOptions } from '../../options.ts' import type { Schema } from '../../schema/Schema.ts' import type { StringifyContext } from '../../stringify/stringify.ts' -import type { CreateNodeContext } from '../../util.ts' -import { createPair } from '../../util.ts' import type { CollectionTag } from '../types.ts' -export class YAMLSet extends YAMLMap | null> { +export class YAMLSet< + T extends Primitive | NodeBase = Primitive | NodeBase +> extends YAMLMap { static tag = 'tag:yaml.org,2002:set' constructor(schema?: Schema) { @@ -17,54 +20,63 @@ export class YAMLSet extends YAMLMap | null> { this.tag = YAMLSet.tag } + /** + * Add a value to the set. + * + * If `value` is a Pair, its `.value` must be null and `options` is ignored. + * + * If the set already includes a matching value, no value is added. + */ add( - key: - | T - | Pair | null> - | { key: T; value: Scalar | null } + value: unknown, + options?: Omit ): void { - let pair: Pair | null> - if (isPair(key)) pair = key - else if ( - key && - typeof key === 'object' && - 'key' in key && - 'value' in key && - key.value === null - ) - pair = new Pair(key.key, null) - else pair = new Pair(key as T, null) - const prev = findPair(this.items, pair.key) - if (!prev) this.items.push(pair) + if (!(value instanceof Pair)) { + this.set(value, true, options) + } else if (value.value !== null) { + throw new TypeError('set pair values must be null') + } else { + const prev = findPair(this.items, value.key) + if (!prev) this.items.push(value as Pair) + } } /** - * If `keepPair` is `true`, returns the Pair matching `key`. - * Otherwise, returns the value of that Pair's key. + * Returns the value matching `key`. */ - get(key: unknown, keepPair?: boolean): any { + get(key: unknown): NodeOf | undefined { const pair = findPair(this.items, key) - return !keepPair && isPair(pair) - ? isScalar(pair.key) - ? pair.key.value - : pair.key - : pair + return pair?.key } - set(key: T, value: boolean): void - - /** @deprecated Will throw; `value` must be boolean */ - set(key: T, value: null): void - set(key: T, value: boolean | null) { + /** + * `value` needs to be true/false to add/remove the item from the set. + */ + set( + key: unknown, + value: boolean, + options?: Omit + ): void { if (typeof value !== 'boolean') - throw new Error( - `Expected boolean value for set(key, value) in a YAML set, not ${typeof value}` - ) + throw new Error(`Expected a boolean value, not ${typeof value}`) const prev = findPair(this.items, key) if (prev && !value) { this.items.splice(this.items.indexOf(prev), 1) } else if (!prev && value) { - this.items.push(new Pair(key)) + let node: NodeBase + if (key instanceof NodeBase) { + node = key + } else if (!this.schema) { + throw new Error('Schema is required') + } else { + const nc = new NodeCreator(this.schema, { + ...options, + aliasDuplicateObjects: false + }) + node = nc.create(key) + nc.setAnchors() + } + this.items.push(new Pair(node as NodeOf)) } } @@ -78,46 +90,49 @@ export class YAMLSet extends YAMLMap | null> { onChompKeep?: () => void ): string { if (!ctx) return JSON.stringify(this) - if (this.hasAllNullValues(true)) - return super.toString( - Object.assign({}, ctx, { allNullValues: true }), - onComment, - onChompKeep - ) - else throw new Error('Set items must all have null values') + return super.toString({ ...ctx, noValues: true }, onComment, onChompKeep) } - static from( - schema: Schema, - iterable: unknown, - ctx: CreateNodeContext - ): YAMLSet { - const { replacer } = ctx - const set = new this(schema) + static from(nc: NodeCreator, iterable: unknown): YAMLSet { + const set = new this(nc.schema) if (iterable && Symbol.iterator in Object(iterable)) for (let value of iterable as Iterable) { - if (typeof replacer === 'function') - value = replacer.call(iterable, value, value) - set.items.push( - createPair(value, null, ctx) as Pair> - ) + if (typeof nc.replacer === 'function') + value = nc.replacer.call(iterable, value, value) + set.items.push(nc.createPair(value, null) as Pair) } return set } } +const hasAllNullValues = (map: YAMLMap): boolean => + map.items.every( + ({ value }) => + value == null || + (value instanceof Scalar && + value.value == null && + !value.commentBefore && + !value.comment && + !value.tag) + ) + export const set: CollectionTag = { collection: 'map', identify: value => value instanceof Set, nodeClass: YAMLSet, default: false, tag: 'tag:yaml.org,2002:set', - createNode: (schema, iterable, ctx) => YAMLSet.from(schema, iterable, ctx), resolve(map, onError) { - if (isMap(map)) { - if (map.hasAllNullValues(true)) return Object.assign(new YAMLSet(), map) - else onError('Set items must all have null values') - } else onError('Expected a mapping for this tag') - return map + if (!(map instanceof YAMLMap)) { + onError('Expected a mapping for this tag') + return map + } else if (!hasAllNullValues(map)) { + onError('Set items must all have null values') + return map + } else { + const set = Object.assign(new YAMLSet(), map) + for (const pair of map.items) pair.value &&= null + return set + } } } diff --git a/src/stringify/stringify.ts b/src/stringify/stringify.ts index 533f5ff7..c44e0c47 100644 --- a/src/stringify/stringify.ts +++ b/src/stringify/stringify.ts @@ -1,15 +1,10 @@ import { anchorIsValid } from '../doc/anchors.ts' import type { Document } from '../doc/Document.ts' -import type { Alias } from '../nodes/Alias.ts' -import { - isAlias, - isCollection, - isNode, - isPair, - isScalar -} from '../nodes/identity.ts' -import type { Node } from '../nodes/Node.ts' -import type { Scalar } from '../nodes/Scalar.ts' +import { Alias } from '../nodes/Alias.ts' +import { Collection } from '../nodes/Collection.ts' +import { NodeBase, type Node } from '../nodes/Node.ts' +import { Pair } from '../nodes/Pair.ts' +import { Scalar } from '../nodes/Scalar.ts' import type { ToStringOptions } from '../options.ts' import type { CollectionTag, ScalarTag } from '../schema/types.ts' import { stringifyComment } from './stringifyComment.ts' @@ -17,7 +12,6 @@ import { stringifyString } from './stringifyString.ts' export type StringifyContext = { actualString?: boolean - allNullValues?: boolean anchors: Set doc: Document forceBlockIndent?: boolean @@ -28,6 +22,7 @@ export type StringifyContext = { inFlow: boolean | null inStringifyKey?: boolean flowCollectionPadding: string + noValues?: boolean options: Readonly< Required> > @@ -94,7 +89,7 @@ function getTagObject(tags: Array, item: Node) { let tagObj: ScalarTag | CollectionTag | undefined = undefined let obj: unknown - if (isScalar(item)) { + if (item instanceof Scalar) { obj = item.value let match = tags.filter(t => t.identify?.(obj)) if (match.length > 1) { @@ -124,7 +119,8 @@ function stringifyProps( ) { if (!doc.directives) return '' const props = [] - const anchor = (isScalar(node) || isCollection(node)) && node.anchor + const anchor = + (node instanceof Scalar || node instanceof Collection) && node.anchor if (anchor && anchorIsValid(anchor)) { anchors.add(anchor) props.push(`&${anchor}`) @@ -140,8 +136,8 @@ export function stringify( onComment?: () => void, onChompKeep?: () => void ): string { - if (isPair(item)) return item.toString(ctx, onComment, onChompKeep) - if (isAlias(item)) { + if (item instanceof Pair) return item.toString(ctx, onComment, onChompKeep) + if (item instanceof Alias) { if (ctx.doc.directives) return item.toString(ctx) if (ctx.resolvedAliases?.has(item)) { @@ -156,9 +152,10 @@ export function stringify( } let tagObj: ScalarTag | CollectionTag | undefined = undefined - const node = isNode(item) - ? item - : ctx.doc.createNode(item, { onTagObj: o => (tagObj = o) }) + const node = + item instanceof NodeBase + ? (item as Node) + : ctx.doc.createNode(item, { onTagObj: o => (tagObj = o) }) tagObj ??= getTagObject(ctx.doc.schema.tags, node) const props = stringifyProps(node, tagObj, ctx) @@ -168,11 +165,11 @@ export function stringify( const str = typeof tagObj.stringify === 'function' ? tagObj.stringify(node as Scalar, ctx, onComment, onChompKeep) - : isScalar(node) + : node instanceof Scalar ? stringifyString(node, ctx, onComment, onChompKeep) : node.toString(ctx, onComment, onChompKeep) if (!props) return str - return isScalar(node) || str[0] === '{' || str[0] === '[' + return node instanceof Scalar || str[0] === '{' || str[0] === '[' ? `${props} ${str}` : `${props}\n${ctx.indent}${str}` } diff --git a/src/stringify/stringifyCollection.ts b/src/stringify/stringifyCollection.ts index b7de321c..895b4c34 100644 --- a/src/stringify/stringifyCollection.ts +++ b/src/stringify/stringifyCollection.ts @@ -1,5 +1,6 @@ import type { Collection } from '../nodes/Collection.ts' -import { isNode, isPair } from '../nodes/identity.ts' +import { NodeBase } from '../nodes/Node.ts' +import { Pair } from '../nodes/Pair.ts' import type { StringifyContext } from './stringify.ts' import { stringify } from './stringify.ts' import { indentComment, lineComment } from './stringifyComment.ts' @@ -44,12 +45,12 @@ function stringifyBlockCollection( for (let i = 0; i < items.length; ++i) { const item = items[i] let comment: string | null = null - if (isNode(item)) { + if (item instanceof NodeBase) { if (!chompKeep && item.spaceBefore) lines.push('') addCommentBefore(ctx, lines, item.commentBefore, chompKeep) if (item.comment) comment = item.comment - } else if (isPair(item)) { - const ik = isNode(item.key) ? item.key : null + } else if (item instanceof Pair) { + const ik = item.key instanceof NodeBase ? item.key : null if (ik) { if (!chompKeep && ik.spaceBefore) lines.push('') addCommentBefore(ctx, lines, ik.commentBefore, chompKeep) @@ -111,19 +112,19 @@ function stringifyFlowCollection( for (let i = 0; i < items.length; ++i) { const item = items[i] let comment: string | null = null - if (isNode(item)) { + if (item instanceof NodeBase) { if (item.spaceBefore) lines.push('') addCommentBefore(ctx, lines, item.commentBefore, false) if (item.comment) comment = item.comment - } else if (isPair(item)) { - const ik = isNode(item.key) ? item.key : null + } else if (item instanceof Pair) { + const ik = item.key instanceof NodeBase ? item.key : null if (ik) { if (ik.spaceBefore) lines.push('') addCommentBefore(ctx, lines, ik.commentBefore, false) if (ik.comment) reqNewline = true } - const iv = isNode(item.value) ? item.value : null + const iv = item.value instanceof NodeBase ? item.value : null if (iv) { if (iv.comment) comment = iv.comment if (iv.commentBefore) reqNewline = true diff --git a/src/stringify/stringifyDocument.ts b/src/stringify/stringifyDocument.ts index 6d11bcea..655c82d2 100644 --- a/src/stringify/stringifyDocument.ts +++ b/src/stringify/stringifyDocument.ts @@ -1,6 +1,4 @@ -import type { Document } from '../doc/Document.ts' -import { isNode } from '../nodes/identity.ts' -import type { Node } from '../nodes/Node.ts' +import type { Document, DocValue } from '../doc/Document.ts' import type { ToStringOptions } from '../options.ts' import { createStringifyContext, @@ -10,7 +8,7 @@ import { import { indentComment, lineComment } from './stringifyComment.ts' export function stringifyDocument( - doc: Readonly>, + doc: Readonly>, options: ToStringOptions ): string { const lines: string[] = [] @@ -35,37 +33,31 @@ export function stringifyDocument( let chompKeep = false let contentComment = null - if (doc.contents) { - if (isNode(doc.contents)) { - if (doc.contents.spaceBefore && hasDirectives) lines.push('') - if (doc.contents.commentBefore) { - const cs = commentString(doc.contents.commentBefore) - lines.push(indentComment(cs, '')) - } - // top-level block scalars need to be indented if followed by a comment - ctx.forceBlockIndent = !!doc.comment - contentComment = doc.contents.comment - } - const onChompKeep = contentComment ? undefined : () => (chompKeep = true) - let body = stringify( - doc.contents, - ctx, - () => (contentComment = null), - onChompKeep - ) - if (contentComment) - body += lineComment(body, '', commentString(contentComment)) - if ( - (body[0] === '|' || body[0] === '>') && - lines[lines.length - 1] === '---' - ) { - // Top-level block scalars with a preceding doc marker ought to use the - // same line for their header. - lines[lines.length - 1] = `--- ${body}` - } else lines.push(body) - } else { - lines.push(stringify(doc.contents, ctx)) + if (doc.value.spaceBefore && hasDirectives) lines.push('') + if (doc.value.commentBefore) { + const cs = commentString(doc.value.commentBefore) + lines.push(indentComment(cs, '')) } + // top-level block scalars need to be indented if followed by a comment + ctx.forceBlockIndent = !!doc.comment + contentComment = doc.value.comment + const onChompKeep = contentComment ? undefined : () => (chompKeep = true) + let body = stringify( + doc.value, + ctx, + () => (contentComment = null), + onChompKeep + ) + if (contentComment) + body += lineComment(body, '', commentString(contentComment)) + if ( + (body[0] === '|' || body[0] === '>') && + lines[lines.length - 1] === '---' + ) { + // Top-level block scalars with a preceding doc marker ought to use the + // same line for their header. + lines[lines.length - 1] = `--- ${body}` + } else lines.push(body) if (doc.directives?.docEnd) { if (doc.comment) { const cs = commentString(doc.comment) diff --git a/src/stringify/stringifyPair.ts b/src/stringify/stringifyPair.ts index 5de8e874..b47ce38b 100644 --- a/src/stringify/stringifyPair.ts +++ b/src/stringify/stringifyPair.ts @@ -1,6 +1,8 @@ -import { isCollection, isNode, isScalar, isSeq } from '../nodes/identity.ts' +import { Collection } from '../nodes/Collection.ts' +import { NodeBase } from '../nodes/Node.ts' import type { Pair } from '../nodes/Pair.ts' import { Scalar } from '../nodes/Scalar.ts' +import { YAMLSeq } from '../nodes/YAMLSeq.ts' import type { StringifyContext } from './stringify.ts' import { stringify } from './stringify.ts' import { indentComment, lineComment } from './stringifyComment.ts' @@ -12,18 +14,21 @@ export function stringifyPair( onChompKeep?: () => void ): string { const { - allNullValues, doc, indent, indentStep, + noValues, options: { commentString, indentSeq, simpleKeys } } = ctx - let keyComment = (isNode(key) && key.comment) || null + let keyComment = (key instanceof NodeBase && key.comment) || null if (simpleKeys) { if (keyComment) { throw new Error('With simple keys, key nodes cannot have comments') } - if (isCollection(key) || (!isNode(key) && typeof key === 'object')) { + if ( + key instanceof Collection || + (!(key instanceof NodeBase) && typeof key === 'object') + ) { const msg = 'With simple keys, collection cannot be used as a key value' throw new Error(msg) } @@ -31,17 +36,16 @@ export function stringifyPair( let explicitKey = !simpleKeys && (!key || - (keyComment && value == null && !ctx.inFlow) || - isCollection(key) || - (isScalar(key) - ? key.type === Scalar.BLOCK_FOLDED || key.type === Scalar.BLOCK_LITERAL - : typeof key === 'object')) + !(key instanceof Scalar) || + key.type === Scalar.BLOCK_FOLDED || + key.type === Scalar.BLOCK_LITERAL) - ctx = Object.assign({}, ctx, { - allNullValues: false, - implicitKey: !explicitKey && (simpleKeys || !allNullValues), - indent: indent + indentStep - }) + ctx = { + ...ctx, + implicitKey: !explicitKey && (simpleKeys || !noValues), + indent: indent + indentStep, + noValues: false + } let keyCommentDone = false let chompKeep = false let str = stringify( @@ -60,11 +64,17 @@ export function stringifyPair( } if (ctx.inFlow) { - if (allNullValues || value == null) { + if (noValues || value == null) { if (keyCommentDone && onComment) onComment() - return str === '' ? '?' : explicitKey ? `? ${str}` : str + return str === '' + ? '?' + : explicitKey + ? `? ${str}` + : noValues + ? str + : `${str}:` } - } else if ((allNullValues && !simpleKeys) || (value == null && explicitKey)) { + } else if ((noValues && !simpleKeys) || (value == null && explicitKey)) { str = `? ${str}` if (keyComment && !keyCommentDone) { str += lineComment(str, ctx.indent, commentString(keyComment)) @@ -84,7 +94,7 @@ export function stringifyPair( } let vsb, vcb, valueComment - if (isNode(value)) { + if (value instanceof NodeBase) { vsb = !!value.spaceBefore vcb = value.commentBefore valueComment = value.comment @@ -95,7 +105,7 @@ export function stringifyPair( if (value && typeof value === 'object') value = doc.createNode(value) } ctx.implicitKey = false - if (!explicitKey && !keyComment && isScalar(value)) + if (!explicitKey && !keyComment && value instanceof Scalar) ctx.indentAtStart = str.length + 1 chompKeep = false if ( @@ -103,7 +113,7 @@ export function stringifyPair( indentStep.length >= 2 && !ctx.inFlow && !explicitKey && - isSeq(value) && + value instanceof YAMLSeq && !value.flow && !value.tag && !value.anchor @@ -131,7 +141,7 @@ export function stringifyPair( } else { ws += `\n${ctx.indent}` } - } else if (!explicitKey && isCollection(value)) { + } else if (!explicitKey && value instanceof Collection) { const vs0 = valueStr[0] const nl0 = valueStr.indexOf('\n') const hasNewline = nl0 !== -1 diff --git a/src/test-events.ts b/src/test-events.ts index 97b987fc..60770462 100644 --- a/src/test-events.ts +++ b/src/test-events.ts @@ -1,15 +1,11 @@ import type { Document } from './doc/Document.ts' -import { - isAlias, - isCollection, - isMap, - isNode, - isPair, - isScalar, - isSeq -} from './nodes/identity.ts' -import type { Node, ParsedNode } from './nodes/Node.ts' -import type { Pair } from './nodes/Pair.ts' +import { Alias } from './nodes/Alias.ts' +import { Collection } from './nodes/Collection.ts' +import { type Node, NodeBase } from './nodes/Node.ts' +import { Pair } from './nodes/Pair.ts' +import { Scalar } from './nodes/Scalar.ts' +import { YAMLMap } from './nodes/YAMLMap.ts' +import { YAMLSeq } from './nodes/YAMLSeq.ts' import { parseAllDocuments } from './public-api.ts' import { visit } from './visit.ts' @@ -46,19 +42,13 @@ export function testEvents(src: string): { try { for (let i = 0; i < docs.length; ++i) { const doc = docs[i] - let root = doc.contents - if (Array.isArray(root)) root = root[0] + const root = doc.value const [rootStart] = doc.range || [0] const error = doc.errors[0] if (error && (!error.pos || error.pos[0] < rootStart)) throw new Error() let docStart = '+DOC' if (doc.directives.docStart) docStart += ' ---' - else if ( - doc.contents && - doc.contents.range[2] === doc.contents.range[0] && - !doc.contents.anchor && - !doc.contents.tag - ) + else if (root.range![2] === root.range![0] && !root.anchor && !root.tag) continue events.push(docStart) addEvents(events, doc, error?.pos[0] ?? -1, root) @@ -78,16 +68,19 @@ function addEvents( events: string[], doc: Document, errPos: number, - node: ParsedNode | Pair | null + node: NodeBase | Pair | null ) { if (!node) { events.push('=VAL :') return } - if (errPos !== -1 && isNode(node) && node.range[0] >= errPos) + if (errPos !== -1 && node instanceof NodeBase && node.range![0] >= errPos) throw new Error() let props = '' - let anchor = isScalar(node) || isCollection(node) ? node.anchor : undefined + let anchor = + node instanceof Scalar || node instanceof Collection + ? node.anchor + : undefined if (anchor) { if (/\d$/.test(anchor)) { const alt = anchor.replace(/\d$/, '') @@ -95,9 +88,9 @@ function addEvents( } props = ` &${anchor}` } - if (isNode(node) && node.tag) props += ` <${node.tag}>` + if (node instanceof NodeBase && node.tag) props += ` <${node.tag}>` - if (isMap(node)) { + if (node instanceof YAMLMap) { const ev = node.flow ? '+MAP {}' : '+MAP' events.push(`${ev}${props}`) node.items.forEach(({ key, value }) => { @@ -105,30 +98,30 @@ function addEvents( addEvents(events, doc, errPos, value) }) events.push('-MAP') - } else if (isSeq(node)) { + } else if (node instanceof YAMLSeq) { const ev = node.flow ? '+SEQ []' : '+SEQ' events.push(`${ev}${props}`) node.items.forEach(item => { addEvents(events, doc, errPos, item) }) events.push('-SEQ') - } else if (isPair(node)) { + } else if (node instanceof Pair) { events.push(`+MAP${props}`) addEvents(events, doc, errPos, node.key) addEvents(events, doc, errPos, node.value) events.push('-MAP') - } else if (isAlias(node)) { + } else if (node instanceof Alias) { let alias = node.source if (alias && /\d$/.test(alias)) { const alt = alias.replace(/\d$/, '') if (anchorExists(doc, alt)) alias = alt } events.push(`=ALI${props} *${alias}`) - } else { + } else if (node instanceof Scalar) { const scalar = scalarChar[String(node.type)] if (!scalar) throw new Error(`Unexpected node type ${node.type}`) - const value = node.source - .replace(/\\/g, '\\\\') + const value = node + .source!.replace(/\\/g, '\\\\') .replace(/\0/g, '\\0') .replace(/\x07/g, '\\a') .replace(/\x08/g, '\\b') @@ -139,5 +132,7 @@ function addEvents( .replace(/\r/g, '\\r') .replace(/\x1b/g, '\\e') events.push(`=VAL${props} ${scalar}${value}`) + } else { + throw new Error(`Unexpected node ${node}`) } } diff --git a/src/util.ts b/src/util.ts index d1b9aa83..09d1f915 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,8 +1,6 @@ -export { createNode } from './doc/createNode.ts' -export type { CreateNodeContext } from './doc/createNode.ts' +export type { NodeCreator } from './doc/NodeCreator.ts' export { debug, warn } from './log.ts' export type { LogLevelId } from './log.ts' -export { createPair } from './nodes/Pair.ts' export { toJS } from './nodes/toJS.ts' export type { ToJSContext } from './nodes/toJS.ts' export { findPair } from './nodes/YAMLMap.ts' diff --git a/src/visit.ts b/src/visit.ts index 7ac9c1b9..2721022f 100644 --- a/src/visit.ts +++ b/src/visit.ts @@ -1,20 +1,10 @@ -import type { Document } from './doc/Document.ts' -import type { Alias } from './nodes/Alias.ts' -import { - isAlias, - isCollection, - isDocument, - isMap, - isNode, - isPair, - isScalar, - isSeq -} from './nodes/identity.ts' -import type { Node } from './nodes/Node.ts' -import type { Pair } from './nodes/Pair.ts' -import type { Scalar } from './nodes/Scalar.ts' -import type { YAMLMap } from './nodes/YAMLMap.ts' -import type { YAMLSeq } from './nodes/YAMLSeq.ts' +import { Document, type DocValue } from './doc/Document.ts' +import { Alias } from './nodes/Alias.ts' +import { NodeBase, type Node } from './nodes/Node.ts' +import { Pair } from './nodes/Pair.ts' +import { Scalar } from './nodes/Scalar.ts' +import { YAMLMap } from './nodes/YAMLMap.ts' +import { YAMLSeq } from './nodes/YAMLSeq.ts' const BREAK = Symbol('break visit') const SKIP = Symbol('skip children') @@ -107,9 +97,9 @@ export const visit: { REMOVE: symbol } = function visit(node, visitor) { const visitor_ = initVisitor(visitor) - if (isDocument(node)) { - const cd = visit_(null, node.contents, visitor_, Object.freeze([node])) - if (cd === REMOVE) node.contents = null + if (node instanceof Document) { + const cd = visit_(null, node.value, visitor_, Object.freeze([node])) + if (cd === REMOVE) node.value = new Scalar(null) } else visit_(null, node, visitor_, Object.freeze([])) } @@ -125,13 +115,13 @@ function visit_( ): number | symbol | void { const ctrl = callVisitor(key, node, visitor, path) - if (isNode(ctrl) || isPair(ctrl)) { + if (ctrl instanceof NodeBase || ctrl instanceof Pair) { replaceNode(key, path, ctrl) return visit_(key, ctrl, visitor, path) } if (typeof ctrl !== 'symbol') { - if (isCollection(node)) { + if (node instanceof YAMLMap || node instanceof YAMLSeq) { path = Object.freeze(path.concat(node)) for (let i = 0; i < node.items.length; ++i) { const ci = visit_(i, node.items[i], visitor, path) @@ -142,11 +132,11 @@ function visit_( i -= 1 } } - } else if (isPair(node)) { + } else if (node instanceof Pair) { path = Object.freeze(path.concat(node)) const ck = visit_('key', node.key, visitor, path) if (ck === BREAK) return BREAK - else if (ck === REMOVE) node.key = null + else if (ck === REMOVE) node.key = new Scalar(null) const cv = visit_('value', node.value, visitor, path) if (cv === BREAK) return BREAK else if (cv === REMOVE) node.value = null @@ -201,14 +191,14 @@ export const visitAsync: { REMOVE: symbol } = async function visitAsync(node, visitor) { const visitor_ = initVisitor(visitor) - if (isDocument(node)) { + if (node instanceof Document) { const cd = await visitAsync_( null, - node.contents, + node.value, visitor_, Object.freeze([node]) ) - if (cd === REMOVE) node.contents = null + if (cd === REMOVE) node.value = new Scalar(null) } else await visitAsync_(null, node, visitor_, Object.freeze([])) } @@ -224,13 +214,13 @@ async function visitAsync_( ): Promise { const ctrl = await callVisitor(key, node, visitor, path) - if (isNode(ctrl) || isPair(ctrl)) { + if (ctrl instanceof NodeBase || ctrl instanceof Pair) { replaceNode(key, path, ctrl) return visitAsync_(key, ctrl, visitor, path) } if (typeof ctrl !== 'symbol') { - if (isCollection(node)) { + if (node instanceof YAMLMap || node instanceof YAMLSeq) { path = Object.freeze(path.concat(node)) for (let i = 0; i < node.items.length; ++i) { const ci = await visitAsync_(i, node.items[i], visitor, path) @@ -241,11 +231,11 @@ async function visitAsync_( i -= 1 } } - } else if (isPair(node)) { + } else if (node instanceof Pair) { path = Object.freeze(path.concat(node)) const ck = await visitAsync_('key', node.key, visitor, path) if (ck === BREAK) return BREAK - else if (ck === REMOVE) node.key = null + else if (ck === REMOVE) node.key = new Scalar(null) const cv = await visitAsync_('value', node.value, visitor, path) if (cv === BREAK) return BREAK else if (cv === REMOVE) node.value = null @@ -302,11 +292,11 @@ function callVisitor( path: readonly (Document | Node | Pair)[] ): ReturnType> | ReturnType> { if (typeof visitor === 'function') return visitor(key, node, path) - if (isMap(node)) return visitor.Map?.(key, node, path) - if (isSeq(node)) return visitor.Seq?.(key, node, path) - if (isPair(node)) return visitor.Pair?.(key, node, path) - if (isScalar(node)) return visitor.Scalar?.(key, node, path) - if (isAlias(node)) return visitor.Alias?.(key, node, path) + if (node instanceof YAMLMap) return visitor.Map?.(key, node, path) + if (node instanceof YAMLSeq) return visitor.Seq?.(key, node, path) + if (node instanceof Pair) return visitor.Pair?.(key, node, path) + if (node instanceof Scalar) return visitor.Scalar?.(key, node, path) + if (node instanceof Alias) return visitor.Alias?.(key, node, path) return undefined } @@ -316,15 +306,19 @@ function replaceNode( node: Node | Pair ): number | symbol | void { const parent = path[path.length - 1] - if (isCollection(parent)) { + if (parent instanceof YAMLMap || parent instanceof YAMLSeq) { parent.items[key as number] = node - } else if (isPair(parent)) { - if (key === 'key') parent.key = node - else parent.value = node - } else if (isDocument(parent)) { - parent.contents = node as Node + } else if (parent instanceof Pair) { + if (node instanceof NodeBase) { + if (key === 'key') parent.key = node + else parent.value = node + } else { + throw new Error(`Cannot replace pair ${key} with non-node value`) + } + } else if (parent instanceof Document) { + parent.value = node as DocValue } else { - const pt = isAlias(parent) ? 'alias' : 'scalar' + const pt = parent instanceof Alias ? 'alias' : 'scalar' throw new Error(`Cannot replace node with ${pt} parent`) } } diff --git a/tests/cli.ts b/tests/cli.ts index e4f33238..177e14eb 100644 --- a/tests/cli.ts +++ b/tests/cli.ts @@ -243,18 +243,18 @@ const skip = Number(major) < 20 ) }) describe('--doc', () => { - ok('basic', 'hello: world', ['--doc'], [{ contents: { items: [{}] } }]) + ok('basic', 'hello: world', ['--doc'], [{ value: { items: [{}] } }]) ok( 'multiple', 'hello: world\n---\n42', ['--doc'], - [{ contents: { items: [{}] } }, { contents: { value: 42 } }] + [{ value: { items: [{}] } }, { value: { value: 42 } }] ) ok( 'error', 'hello: world: 2', ['--doc'], - [{ contents: { items: [{}] } }], + [{ value: { items: [{}] } }], [{ name: 'YAMLParseError' }] ) }) diff --git a/tests/clone.ts b/tests/clone.ts index 63dfb11a..7fb3f11e 100644 --- a/tests/clone.ts +++ b/tests/clone.ts @@ -1,5 +1,5 @@ -import type { Scalar, YAMLMap } from 'yaml' -import { isAlias, isScalar, parseDocument, visit } from 'yaml' +import { Alias, Scalar, type YAMLMap } from 'yaml' +import { parseDocument, visit } from 'yaml' import { source } from './_utils.ts' describe('doc.clone()', () => { @@ -21,20 +21,20 @@ describe('doc.clone()', () => { expect(copy.toJS()).toEqual({ foo: 'bar' }) const node = copy.createNode(42) - expect(isScalar(node)).toBe(true) + expect(node).toBeInstanceOf(Scalar) expect(node).toMatchObject({ value: 42 }) const alias = copy.createAlias(node as Scalar, 'foo') - expect(isAlias(alias)).toBe(true) + expect(alias).toBeInstanceOf(Alias) expect(alias).toMatchObject({ source: 'foo' }) }) - test('has separate contents from original', () => { + test('has separate value from original', () => { const doc = parseDocument('foo: bar') const copy = doc.clone() copy.set('foo', 'fizz') - expect(doc.get('foo')).toBe('bar') - expect(copy.get('foo')).toBe('fizz') + expect(doc.get('foo')).toMatchObject(new Scalar('bar')) + expect(copy.get('foo')).toMatchObject(new Scalar('fizz')) }) test('has separate directives from original', () => { diff --git a/tests/collection-access.ts b/tests/collection-access.ts index ec0ed50b..65ed2666 100644 --- a/tests/collection-access.ts +++ b/tests/collection-access.ts @@ -1,28 +1,20 @@ import { Document, Pair, - type Scalar, - type YAMLMap, + Scalar, + YAMLMap, type YAMLOMap, - type YAMLSeq, + YAMLSeq, type YAMLSet, - isMap, - isSeq, parseDocument } from 'yaml' describe('Map', () => { let doc: Document - let map: YAMLMap< - string | Scalar, - | number - | string - | Scalar - | YAMLMap, number | Scalar> - > + let map: YAMLMap> beforeEach(() => { doc = new Document({ a: 1, b: { c: 3, d: 4 } }) - map = doc.contents as any + map = doc.value as any expect(map.items).toMatchObject([ { key: { value: 'a' }, value: { value: 1 } }, { @@ -38,11 +30,10 @@ describe('Map', () => { }) test('add', () => { - map.add({ key: 'c', value: 'x' }) - expect(map.get('c')).toBe('x') - // @ts-expect-error TS should complain here - expect(() => map.add('a')).toThrow(/already set/) - expect(() => map.add(new Pair('c', 'y'))).toThrow(/already set/) + map.add(doc.createPair('c', 'x')) + expect(map.get('c')).toMatchObject({ value: 'x' }) + map.add(doc.createPair('c', 'y')) + expect(map.get('c')).toMatchObject({ value: 'y' }) expect(map.items).toHaveLength(3) }) @@ -55,30 +46,22 @@ describe('Map', () => { }) test('get with value', () => { - expect(map.get('a')).toBe(1) - expect(map.get('a', true)).toMatchObject({ value: 1 }) + expect(map.get('a')).toMatchObject({ value: 1 }) const subMap = map.get('b') - if (isMap(subMap)) { - expect(subMap.toJSON()).toMatchObject({ - c: 3, - d: 4 - }) - expect(map.get('c')).toBeUndefined() - } else { - throw new Error('Expected subMap to be a Map') - } + expect(subMap).toBeInstanceOf(YAMLMap) + expect(subMap!.toJSON()).toMatchObject({ + c: 3, + d: 4 + }) + expect(map.get('c')).toBeUndefined() }) test('get with node', () => { - expect(map.get(doc.createNode('a'))).toBe(1) - expect(map.get(doc.createNode('a'), true)).toMatchObject({ value: 1 }) + expect(map.get(doc.createNode('a'))).toMatchObject({ value: 1 }) const subMap = map.get(doc.createNode('b')) - if (isMap(subMap)) { - expect(subMap.toJSON()).toMatchObject({ c: 3, d: 4 }) - expect(map.get(doc.createNode('c'))).toBeUndefined() - } else { - throw new Error('Expected subMap to be a Map') - } + expect(subMap).toBeInstanceOf(YAMLMap) + expect(subMap!.toJSON()).toMatchObject({ c: 3, d: 4 }) + expect(map.get(doc.createNode('c'))).toBeUndefined() }) test('has with value', () => { @@ -100,30 +83,28 @@ describe('Map', () => { test('set with value', () => { map.set('a', 2) - expect(map.get('a')).toBe(2) - expect(map.get('a', true)).toMatchObject({ value: 2 }) + expect(map.get('a')).toMatchObject({ value: 2 }) map.set('b', 5) - expect(map.get('b')).toBe(5) + expect(map.get('b')).toMatchObject({ value: 5 }) map.set('c', 6) - expect(map.get('c')).toBe(6) + expect(map.get('c')).toMatchObject({ value: 6 }) expect(map.items).toHaveLength(3) }) test('set with node', () => { map.set(doc.createNode('a'), 2) - expect(map.get('a')).toBe(2) - expect(map.get('a', true)).toMatchObject({ value: 2 }) + expect(map.get('a')).toMatchObject({ value: 2 }) map.set(doc.createNode('b'), 5) - expect(map.get('b')).toBe(5) + expect(map.get('b')).toMatchObject({ value: 5 }) map.set(doc.createNode('c'), 6) - expect(map.get('c')).toBe(6) + expect(map.get('c')).toMatchObject({ value: 6 }) expect(map.items).toHaveLength(3) }) test('set scalar node with anchor', () => { doc = parseDocument('a: &A value\nb: *A\n') doc.set('a', 'foo') - expect(doc.get('a', true)).toMatchObject({ value: 'foo' }) + expect(doc.get('a')).toMatchObject({ value: 'foo' }) expect(String(doc)).toBe('a: &A foo\nb: *A\n') }) }) @@ -133,7 +114,7 @@ describe('Seq', () => { let seq: YAMLSeq | YAMLSeq>> beforeEach(() => { doc = new Document([1, [2, 3]]) - seq = doc.contents as any + seq = doc.value as any expect(seq.items).toMatchObject([ { value: 1 }, { items: [{ value: 2 }, { value: 3 }] } @@ -142,7 +123,7 @@ describe('Seq', () => { test('add', () => { seq.add(9) - expect(seq.get(2)).toBe(9) + expect(seq.get(2)).toMatchObject({ value: 9 }) seq.add(1) expect(seq.items).toHaveLength(4) }) @@ -150,124 +131,98 @@ describe('Seq', () => { test('delete', () => { expect(seq.delete(0)).toBe(true) expect(seq.delete(2)).toBe(false) - expect(seq.delete('a')).toBe(false) + expect(() => seq.delete('a' as any)).toThrow(TypeError) expect(seq.get(0)).toMatchObject({ items: [{ value: 2 }, { value: 3 }] }) expect(seq.items).toHaveLength(1) }) - test('get with value', () => { - expect(seq.get(0)).toBe(1) - expect(seq.get('0')).toBe(1) - expect(seq.get(0, true)).toMatchObject({ value: 1 }) + test('get with integer', () => { + expect(seq.get(0)).toMatchObject({ value: 1 }) const subSeq = seq.get(1) - if (isSeq(subSeq)) { - expect(subSeq.toJSON()).toMatchObject([2, 3]) - expect(seq.get(2)).toBeUndefined() - } else { - throw new Error('not a seq') - } + expect(subSeq).toBeInstanceOf(YAMLSeq) + expect(subSeq!.toJSON()).toMatchObject([2, 3]) + expect(seq.get(2)).toBeUndefined() }) - test('get with node', () => { - 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 }) - const subSeq = seq.get(doc.createNode(1)) - if (isSeq(subSeq)) { - expect(subSeq.toJSON()).toMatchObject([2, 3]) - expect(seq.get(doc.createNode(2))).toBeUndefined() - } else { - throw new Error('not a seq') - } + test('get with non-integer', () => { + expect(() => seq.get(-1)).toThrow(RangeError) + expect(() => seq.get(0.5)).toThrow(TypeError) + expect(() => seq.get('0' as any)).toThrow(TypeError) + expect(() => seq.get(doc.createNode(0) as any)).toThrow(TypeError) }) - test('has with value', () => { + test('has with integer', () => { expect(seq.has(0)).toBe(true) expect(seq.has(1)).toBe(true) expect(seq.has(2)).toBe(false) - expect(seq.has('0')).toBe(true) - expect(seq.has('')).toBe(false) - // @ts-expect-error TS should complain here - expect(seq.has()).toBe(false) }) - test('has with node', () => { - 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) - // @ts-expect-error TS should complain here - expect(seq.has(doc.createNode())).toBe(false) + test('has with non-integer', () => { + expect(() => seq.has(-1)).toThrow(RangeError) + expect(() => seq.has(0.5)).toThrow(TypeError) + expect(() => seq.has('0' as any)).toThrow(TypeError) + // @ts-expect-error TS complains, as it should. + expect(() => seq.has()).toThrow(TypeError) + expect(() => seq.has(doc.createNode(0) as any)).toThrow(TypeError) }) - test('set with value', () => { + test('set with integer', () => { seq.set(0, 2) - expect(seq.get(0)).toBe(2) - expect(seq.get(0, true)).toMatchObject({ value: 2 }) - seq.set('1', 5) - expect(seq.get(1)).toBe(5) + expect(seq.get(0)).toMatchObject({ value: 2 }) + seq.set(1, 5) + expect(seq.get(1)).toMatchObject({ value: 5 }) seq.set(2, 6) - expect(seq.get(2)).toBe(6) + expect(seq.get(2)).toMatchObject({ value: 6 }) expect(seq.items).toHaveLength(3) }) - test('set with node', () => { - seq.set(doc.createNode(0), 2) - expect(seq.get(0)).toBe(2) - expect(seq.get(0, true)).toMatchObject({ value: 2 }) - seq.set(doc.createNode('1'), 5) - expect(seq.get(1)).toBe(5) - seq.set(doc.createNode(2), 6) - expect(seq.get(2)).toBe(6) - expect(seq.items).toHaveLength(3) + test('set with non-integer', () => { + expect(() => seq.set(-1, 2)).toThrow(RangeError) + expect(() => seq.set(0.5, 2)).toThrow(TypeError) + expect(() => seq.set(doc.createNode(0) as any, 2)).toThrow(TypeError) }) }) describe('Set', () => { let doc: Document - let set: YAMLSet | Pair> + let set: YAMLSet beforeEach(() => { doc = new Document(null, { version: '1.1' }) set = doc.createNode([1, 2, 3], { tag: '!!set' }) as any - doc.contents = set + doc.value = set expect(set.items).toMatchObject([ - { key: { value: 1 }, value: { value: null } }, - { key: { value: 2 }, value: { value: null } }, - { key: { value: 3 }, value: { value: null } } + { key: { value: 1 }, value: null }, + { key: { value: 2 }, value: null }, + { key: { value: 3 }, value: null } ]) }) test('add', () => { set.add('x') - expect(set.get('x')).toBe('x') + expect(set.get('x')).toMatchObject({ value: 'x' }) set.add('x') - const y0 = new Pair('y') - set.add(y0) - set.add(new Pair('y')) - expect(set.get('y', true)).toBe(y0) + const y0 = new Scalar('y') + set.add(new Pair(y0)) + set.add(new Pair(new Scalar('y'))) + expect(set.get('y')).toBe(y0) expect(set.items).toHaveLength(5) }) test('get', () => { - expect(set.get(1)).toBe(1) - expect(set.get(1, true)).toMatchObject({ - key: { value: 1 }, - value: { value: null } - }) + expect(set.get(1)).toMatchObject({ value: 1 }) expect(set.get(0)).toBeUndefined() expect(set.get('1')).toBeUndefined() }) test('set', () => { set.set(1, true) - expect(set.get(1)).toBe(1) + expect(set.get(1)).toMatchObject({ value: 1 }) set.set(1, false) expect(set.get(1)).toBeUndefined() set.set(4, false) expect(set.get(4)).toBeUndefined() set.set(4, true) - expect(set.get(4)).toBe(4) - expect(set.get(4, true)).toMatchObject({ key: 4, value: null }) + expect(set.get(4)).toMatchObject(new Scalar(4)) expect(set.items).toHaveLength(3) }) }) @@ -280,7 +235,7 @@ describe('OMap', () => { omap = doc.createNode([{ a: 1 }, { b: { c: 3, d: 4 } }], { tag: '!!omap' }) as any - doc.contents = omap + doc.value = omap expect(omap.items).toMatchObject([ { key: { value: 'a' }, value: { value: 1 } }, { @@ -296,11 +251,9 @@ describe('OMap', () => { }) test('add', () => { - omap.add({ key: 'c', value: 'x' }) - expect(omap.get('c')).toBe('x') - // @ts-expect-error TS should complain here - expect(() => omap.add('a')).toThrow(/already set/) - expect(() => omap.add(new Pair('c', 'y'))).toThrow(/already set/) + omap.add(doc.createPair('c', 'x')) + expect(omap.get('c')).toMatchObject({ value: 'x' }) + omap.add(doc.createPair('c', 'y')) expect(omap.items).toHaveLength(3) }) @@ -313,15 +266,11 @@ describe('OMap', () => { }) test('get', () => { - expect(omap.get('a')).toBe(1) - expect(omap.get('a', true)).toMatchObject({ value: 1 }) + expect(omap.get('a')).toMatchObject({ value: 1 }) const subMap = omap.get('b') - if (isMap(subMap)) { - expect(subMap.toJSON()).toMatchObject({ c: 3, d: 4 }) - expect(omap.get('c')).toBeUndefined() - } else { - throw new Error('Expected subMap to be a map') - } + expect(subMap).toBeInstanceOf(YAMLMap) + expect(subMap.toJSON()).toMatchObject({ c: 3, d: 4 }) + expect(omap.get('c')).toBeUndefined() }) test('has', () => { @@ -335,12 +284,11 @@ describe('OMap', () => { test('set', () => { omap.set('a', 2) - expect(omap.get('a')).toBe(2) - expect(omap.get('a', true)).toMatchObject({ value: 2 }) + expect(omap.get('a')).toMatchObject({ value: 2 }) omap.set('b', 5) - expect(omap.get('b')).toBe(5) + expect(omap.get('b')).toMatchObject({ value: 5 }) omap.set('c', 6) - expect(omap.get('c')).toBe(6) + expect(omap.get('c')).toMatchObject({ value: 6 }) expect(omap.items).toHaveLength(3) }) }) @@ -350,23 +298,20 @@ describe('Collection', () => { let map: YAMLMap | YAMLSeq>> beforeEach(() => { doc = new Document({ a: 1, b: [2, 3] }) - map = doc.contents as any + map = doc.value as any }) test('addIn', () => { map.addIn(['b'], 4) - expect(map.getIn(['b', 2])).toBe(4) - map.addIn([], new Pair('c', 5)) - expect(map.get('c')).toBe(5) + expect(map.getIn(['b', 2])).toMatchObject({ value: 4 }) + map.addIn([], doc.createPair('c', 5)) + expect(map.get('c')).toMatchObject({ value: 5 }) expect(() => map.addIn(['a'], -1)).toThrow(/Expected YAML collection/) map.addIn(['b', 3], 6) expect(map.items).toHaveLength(3) const seq = map.getIn(['b']) - if (isSeq(seq)) { - expect(seq.items).toHaveLength(4) - } else { - throw new Error('Expected seq to be a seq') - } + expect(seq).toBeInstanceOf(YAMLSeq) + expect((seq as any).items).toHaveLength(4) }) test('deleteIn', () => { @@ -379,19 +324,14 @@ describe('Collection', () => { expect(() => map.deleteIn(['a', 'e'])).toThrow(/Expected YAML collection/) expect(map.items).toHaveLength(1) const subSeq = map.getIn(['b']) - if (isSeq(subSeq)) { - expect(subSeq.items).toHaveLength(1) - } else { - throw new Error('Expected subSeq to be a seq') - } + expect(subSeq).toBeInstanceOf(YAMLSeq) + expect((subSeq as any).items).toHaveLength(1) }) test('getIn', () => { - expect(map.getIn(['a'])).toBe(1) - expect(map.getIn(['a'], true)).toMatchObject({ value: 1 }) - expect(map.getIn(['b', 1])).toBe(3) - expect(map.getIn(['b', '1'])).toBe(3) - expect(map.getIn(['b', 1], true)).toMatchObject({ value: 3 }) + expect(map.getIn(['a'])).toMatchObject({ value: 1 }) + expect(map.getIn(['b', 1])).toMatchObject({ value: 3 }) + expect(() => map.getIn(['b', '1'])).toThrow(TypeError) expect(map.getIn(['b', 2])).toBeUndefined() expect(map.getIn(['c', 'e'])).toBeUndefined() expect(map.getIn(['a', 'e'])).toBeUndefined() @@ -400,7 +340,7 @@ describe('Collection', () => { test('hasIn', () => { expect(map.hasIn(['a'])).toBe(true) expect(map.hasIn(['b', 1])).toBe(true) - expect(map.hasIn(['b', '1'])).toBe(true) + expect(() => map.hasIn(['b', '1'])).toThrow(TypeError) expect(map.hasIn(['b', 2])).toBe(false) expect(map.hasIn(['c', 'e'])).toBe(false) expect(map.hasIn(['a', 'e'])).toBe(false) @@ -408,24 +348,20 @@ describe('Collection', () => { test('setIn', () => { map.setIn(['a'], 2) - expect(map.get('a')).toBe(2) - expect(map.get('a', true)).toMatchObject({ value: 2 }) + expect(map.get('a')).toMatchObject({ value: 2 }) map.setIn(['b', 1], 5) - expect(map.getIn(['b', 1])).toBe(5) + expect(map.getIn(['b', 1])).toMatchObject({ value: 5 }) map.setIn([1], 6) - expect(map.get(1)).toBe(6) + expect(map.get(1)).toMatchObject({ value: 6 }) map.setIn(['b', 2], 6) - expect(map.getIn(['b', 2])).toBe(6) + expect(map.getIn(['b', 2])).toMatchObject({ value: 6 }) map.setIn(['e', 'e'], 7) - expect(map.getIn(['e', 'e'])).toBe(7) + expect(map.getIn(['e', 'e'])).toMatchObject({ value: 7 }) expect(() => map.setIn(['a', 'e'], 8)).toThrow(/Expected YAML collection/) expect(map.items).toHaveLength(4) const subSeq = map.getIn(['b']) - if (isSeq(subSeq)) { - expect(subSeq.items).toHaveLength(3) - } else { - throw new Error('Expected subSeq to be a seq') - } + expect(subSeq).toBeInstanceOf(YAMLSeq) + expect((subSeq as any).items).toHaveLength(3) }) }) @@ -433,7 +369,7 @@ describe('Document', () => { let doc: Document | YAMLSeq>>> beforeEach(() => { doc = new Document({ a: 1, b: [2, 3] }) - expect(doc.contents?.items).toMatchObject([ + expect(doc.value.items).toMatchObject([ { key: { value: 'a' }, value: { value: 1 } }, { key: { value: 'b' }, @@ -443,21 +379,19 @@ describe('Document', () => { }) test('add', () => { - doc.add({ key: 'c', value: 'x' }) - expect(doc.get('c')).toBe('x') - expect(() => doc.add('a')).toThrow(/already set/) - expect(() => doc.add(new Pair('c', 'y'))).toThrow(/already set/) - expect(doc.contents?.items).toHaveLength(3) + doc.add(doc.createPair('c', 'x')) + expect(doc.get('c')).toMatchObject({ value: 'x' }) + expect(doc.value.items).toHaveLength(3) }) test('addIn', () => { doc.addIn(['b'], 4) - expect(doc.getIn(['b', 2])).toBe(4) - doc.addIn([], new Pair('c', 5)) - expect(doc.get('c')).toBe(5) + expect(doc.getIn(['b', 2])).toMatchObject({ value: 4 }) + doc.addIn([], doc.createPair('c', 5)) + expect(doc.get('c')).toMatchObject({ value: 5 }) expect(() => doc.addIn(['a'], -1)).toThrow(/Expected YAML collection/) doc.addIn(['b', 3], 6) - expect(doc.contents?.items).toHaveLength(3) + expect(doc.value.items).toHaveLength(3) expect((doc.get('b') as any).items).toHaveLength(4) }) @@ -465,12 +399,12 @@ describe('Document', () => { expect(doc.delete('a')).toBe(true) expect(doc.delete('a')).toBe(false) expect(doc.get('a')).toBeUndefined() - expect(doc.contents?.items).toHaveLength(1) + expect(doc.value.items).toHaveLength(1) }) - test('delete on scalar contents', () => { + test('delete on scalar value', () => { const doc = new Document('s') - expect(() => doc.set('a', 1)).toThrow(/document contents/) + expect(() => doc.set('a', 1)).toThrow(/document value/) }) test('deleteIn', () => { @@ -481,37 +415,33 @@ describe('Document', () => { expect(doc.deleteIn([1])).toBe(false) expect(doc.deleteIn(['b', 2])).toBe(false) expect(() => doc.deleteIn(['a', 'e'])).toThrow(/Expected/) - expect(doc.contents?.items).toHaveLength(1) + expect(doc.value.items).toHaveLength(1) expect((doc.get('b') as any).items).toHaveLength(1) - expect(doc.deleteIn(null)).toBe(true) - expect(doc.deleteIn(null)).toBe(false) + expect(() => doc.deleteIn(null as any)).toThrow() }) test('get', () => { - expect(doc.get('a')).toBe(1) - expect(doc.get('a', true)).toMatchObject({ value: 1 }) + expect(doc.get('a')).toMatchObject({ value: 1 }) expect(doc.get('c')).toBeUndefined() }) - test('get on scalar contents', () => { + test('get on scalar value', () => { const doc = new Document('s') expect(doc.get('a')).toBeUndefined() }) test('getIn collection', () => { - expect(doc.getIn(['a'])).toBe(1) - expect(doc.getIn(['a'], true)).toMatchObject({ value: 1 }) - expect(doc.getIn(['b', 1])).toBe(3) - expect(doc.getIn(['b', 1], true)).toMatchObject({ value: 3 }) - expect(doc.getIn(['b', 'e'])).toBeUndefined() + expect(doc.getIn(['a'])).toMatchObject({ value: 1 }) + expect(doc.getIn(['b', 1])).toMatchObject({ value: 3 }) + expect(() => doc.getIn(['b', 'e'])).toThrow(TypeError) expect(doc.getIn(['c', 'e'])).toBeUndefined() expect(doc.getIn(['a', 'e'])).toBeUndefined() }) test('getIn scalar', () => { const doc = new Document('s') - expect(doc.getIn([])).toBe('s') - expect(doc.getIn(null, true)).toMatchObject({ value: 's' }) + expect(doc.getIn([])).toMatchObject({ value: 's' }) + expect(() => doc.getIn(null as any)).toThrow() expect(doc.getIn([0])).toBeUndefined() }) @@ -520,7 +450,7 @@ describe('Document', () => { expect(doc.has('c')).toBe(false) }) - test('has on scalar contents', () => { + test('has on scalar value', () => { const doc = new Document('s') expect(doc.has('a')).toBe(false) }) @@ -528,60 +458,44 @@ describe('Document', () => { test('hasIn', () => { expect(doc.hasIn(['a'])).toBe(true) expect(doc.hasIn(['b', 1])).toBe(true) - expect(doc.hasIn(['b', 'e'])).toBe(false) + expect(() => doc.hasIn(['b', 'e'])).toThrow(TypeError) expect(doc.hasIn(['c', 'e'])).toBe(false) expect(doc.hasIn(['a', 'e'])).toBe(false) }) test('set', () => { doc.set('a', 2) - expect(doc.get('a')).toBe(2) - expect(doc.get('a', true)).toMatchObject({ value: 2 }) + expect(doc.get('a')).toMatchObject({ value: 2 }) doc.set('c', 6) - expect(doc.get('c')).toBe(6) - expect(doc.contents?.items).toHaveLength(3) + expect(doc.get('c')).toMatchObject({ value: 6 }) + expect(doc.value.items).toHaveLength(3) }) - test('set on scalar contents', () => { + test('set on scalar value', () => { const doc = new Document('s') - expect(() => doc.set('a', 1)).toThrow(/document contents/) - }) - - test('set on empty document', () => { - doc.contents = null - doc.set('a', 1) - expect(doc.get('a')).toBe(1) + expect(() => doc.set('a', 1)).toThrow(/document value/) }) test('setIn', () => { doc.setIn(['a'], 2) - expect(doc.getIn(['a'])).toBe(2) - expect(doc.getIn(['a'], true)).toMatchObject({ value: 2 }) + expect(doc.getIn(['a'])).toMatchObject({ value: 2 }) doc.setIn(['b', 1], 5) - expect(doc.getIn(['b', 1])).toBe(5) + expect(doc.getIn(['b', 1])).toMatchObject({ value: 5 }) doc.setIn(['c'], 6) - expect(doc.getIn(['c'])).toBe(6) + expect(doc.getIn(['c'])).toMatchObject({ value: 6 }) doc.setIn(['e', 1, 'e'], 7) - expect(doc.getIn(['e', 1, 'e'])).toBe(7) + expect(doc.getIn(['e', 1, 'e'])).toMatchObject({ value: 7 }) expect(() => doc.setIn(['a', 'e'], 8)).toThrow(/Expected YAML collection/) - expect(doc.contents?.items).toHaveLength(4) + expect(doc.value.items).toHaveLength(4) expect((doc.get('b') as any).items).toHaveLength(2) expect(String(doc)).toBe( 'a: 2\nb:\n - 2\n - 5\nc: 6\ne:\n - null\n - e: 7\n' ) }) - test('setIn on scalar contents', () => { + test('setIn on scalar value', () => { const doc = new Document('s') - expect(() => doc.setIn(['a'], 1)).toThrow(/document contents/) - }) - - test('setIn on empty document', () => { - doc.contents = null - doc.setIn(['a', 2], 1) - expect(doc.get('a')).toMatchObject({ - items: [{ value: null }, { value: null }, { value: 1 }] - }) + expect(() => doc.setIn(['a'], 1)).toThrow(/document value/) }) test('setIn on parsed document', () => { @@ -596,11 +510,10 @@ describe('Document', () => { }) test('setIn with object key', () => { - doc.contents = null + doc.value = doc.createNode({}) const foo = { foo: 'FOO' } doc.setIn([foo], 'BAR') - // @ts-expect-error - Doesn't see that setIn() changes contents - expect(doc.contents?.items).toMatchObject([ + expect(doc.value.items).toMatchObject([ { key: { items: [{ key: { value: 'foo' }, value: { value: 'FOO' } }] }, value: { value: 'BAR' } @@ -609,11 +522,10 @@ describe('Document', () => { }) test('setIn with repeated object key', () => { - doc.contents = null + doc.value = doc.createNode({}) const foo = { foo: 'FOO' } doc.setIn([foo, foo], 'BAR') - // @ts-expect-error - Doesn't see that setIn() changes contents - expect(doc.contents?.items).toMatchObject([ + expect(doc.value.items).toMatchObject([ { key: { items: [{ key: { value: 'foo' }, value: { value: 'FOO' } }] }, value: { diff --git a/tests/directives.ts b/tests/directives.ts index 058875ec..ed197f03 100644 --- a/tests/directives.ts +++ b/tests/directives.ts @@ -17,7 +17,7 @@ describe('%TAG', () => { '!': '!foo:', '!bar!': '!bar:' }) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [ { value: 'v1', tag: '!foo:bar' }, { value: 'v2', tag: '!bar:foo' } @@ -39,7 +39,7 @@ describe('%TAG', () => { '!': 'foo:', '!bar!': 'bar:bar#bar?' }) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [ { value: 'v1', tag: 'foo:bar' }, { value: 'v2', tag: 'bar:bar#bar?foo' } @@ -49,8 +49,8 @@ describe('%TAG', () => { test('create & stringify', () => { const doc = parseDocument('[ v1, v2 ]\n') - ;(doc.get(0, true) as Scalar).tag = '!foo:foo' - ;(doc.get(1, true) as Scalar).tag = '!bar:bar' + ;(doc.get(0) as Scalar).tag = '!foo:foo' + ;(doc.get(1) as Scalar).tag = '!bar:bar' doc.directives.tags['!'] = '!foo:' doc.directives.tags['!bar!'] = '!bar:' expect(String(doc)).toBe(source` @@ -74,7 +74,7 @@ describe('broken directives', () => { expect(doc.errors).toMatchObject([{ pos: [10, 11] }]) }) - test('missing separator before doc contents', () => { + test('missing separator before doc value', () => { const doc = parseDocument(`%YAML 1.2\nfoo\n`) expect(doc.errors).toMatchObject([{ pos: [10, 11] }]) }) diff --git a/tests/doc/YAML-1.2.spec.ts b/tests/doc/YAML-1.2.spec.ts index 03f3b240..fcd0d6d1 100644 --- a/tests/doc/YAML-1.2.spec.ts +++ b/tests/doc/YAML-1.2.spec.ts @@ -436,7 +436,7 @@ application specific tag: !something | warnings: [['Unresolved tag: !something']], special(src) { const doc = YAML.parseDocument(src, { schema: 'yaml-1.1' }) - const data = doc.contents.items[1].value.value + const data = doc.value.items[1].value.value expect(data).toBeInstanceOf(Uint8Array) expect(data.byteLength).toBe(65) } @@ -673,7 +673,7 @@ mapping: { sky: blue, sea: green }`, tgt: [], special(src) { const doc = YAML.parseDocument(src) - expect(doc.commentBefore).toBe(' Comment only.') + expect(doc.value.commentBefore).toBe(' Comment only.') } }, @@ -1717,12 +1717,12 @@ mapping: !!map ], special(src) { const doc = YAML.parseDocument, false>(src) - expect(doc.contents.tag).toBeUndefined() - expect(doc.contents.items[0].value.tag).toBe('tag:yaml.org,2002:seq') - expect(doc.contents.items[0].value.items[1].tag).toBe( + expect(doc.value.tag).toBeUndefined() + expect(doc.value.items[0].value.tag).toBe('tag:yaml.org,2002:seq') + expect(doc.value.items[0].value.items[1].tag).toBe( 'tag:yaml.org,2002:seq' ) - expect(doc.contents.items[1].value.tag).toBe('tag:yaml.org,2002:map') + expect(doc.value.items[1].value.tag).toBe('tag:yaml.org,2002:map') } } }, diff --git a/tests/doc/anchors.ts b/tests/doc/anchors.ts index 20bd259d..f16338d9 100644 --- a/tests/doc/anchors.ts +++ b/tests/doc/anchors.ts @@ -5,7 +5,7 @@ test('basic', () => { const src = `- &a 1\n- *a\n` const doc = parseDocument(src) expect(doc.errors).toHaveLength(0) - expect(doc.contents?.items).toMatchObject([ + expect(doc.value.items).toMatchObject([ { anchor: 'a', value: 1 }, { source: 'a' } ]) @@ -16,7 +16,7 @@ test('re-defined anchor', () => { const src = '- &a 1\n- &a 2\n- *a\n' const doc = parseDocument(src) expect(doc.errors).toHaveLength(0) - expect(doc.contents?.items).toMatchObject([ + expect(doc.value.items).toMatchObject([ { anchor: 'a', value: 1 }, { anchor: 'a', value: 2 }, { source: 'a' } @@ -30,7 +30,7 @@ test('circular reference', () => { const doc = parseDocument(src) expect(doc.errors).toHaveLength(0) expect(doc.warnings).toHaveLength(0) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ anchor: 'a', items: [{ value: 1 }, { source: 'a' }] }) @@ -60,16 +60,16 @@ describe('create', () => { test('node.anchor', () => { const doc = parseDocument('[{ a: A }, { b: B }]') doc.get(0).anchor = 'AA' - doc.getIn([0, 'a'], true).anchor = 'a' - doc.getIn([0, 'a'], true).anchor = '' - doc.getIn([1, 'b'], true).anchor = 'BB' + doc.getIn([0, 'a']).anchor = 'a' + doc.getIn([0, 'a']).anchor = '' + doc.getIn([1, 'b']).anchor = 'BB' expect(String(doc)).toBe('[ &AA { a: A }, { b: &BB B } ]\n') }) test('doc.createAlias', () => { const doc = parseDocument('[{ a: A }, { b: B }]') const alias = doc.createAlias(doc.get(0), 'AA') - doc.contents?.items.push(alias) + doc.value.items.push(alias) expect(doc.toJS()).toMatchObject([{ a: 'A' }, { b: 'B' }, { a: 'A' }]) const alias2 = doc.createAlias(doc.get(1), 'AA') expect(doc.get(1).anchor).toBe('AA1') @@ -114,7 +114,7 @@ describe('errors', () => { test('set tag on alias', () => { const doc = parseDocument, false>('[{ a: A }, { b: B }]') - const node = doc.contents.items[0] + const node = doc.value.items[0] const alias = doc.createAlias(node, 'AA') expect(() => { alias.tag = 'tag:yaml.org,2002:alias' @@ -126,7 +126,7 @@ describe('errors', () => { '[{ a: A }, { b: B }]' ) const alias = doc.createAlias(doc.get(0), 'AA') - doc.contents.items.unshift(alias) + doc.value.items.unshift(alias) expect(() => String(doc)).toThrow( 'Unresolved alias (the anchor must be set before the alias): AA' ) @@ -167,7 +167,7 @@ describe('__proto__ as anchor name', () => { const src = `- &__proto__ 1\n- *__proto__\n` const doc = parseDocument(src) expect(doc.errors).toHaveLength(0) - expect(doc.contents?.items).toMatchObject([ + expect(doc.value.items).toMatchObject([ { anchor: '__proto__', value: 1 }, { source: '__proto__' } ]) @@ -179,8 +179,8 @@ describe('__proto__ as anchor name', () => { const doc = parseDocument, false>( '[{ a: A }, { b: B }]' ) - const alias = doc.createAlias(doc.contents.items[0], '__proto__') - doc.contents.items.push(alias) + const alias = doc.createAlias(doc.value.items[0], '__proto__') + doc.value.items.push(alias) expect(doc.toJSON()).toMatchObject([{ a: 'A' }, { b: 'B' }, { a: 'A' }]) expect(String(doc)).toMatch( '[ &__proto__ { a: A }, { b: B }, *__proto__ ]\n' @@ -249,13 +249,13 @@ describe('merge <<', () => { test('YAML.parseDocument', () => { const doc = parseDocument, false>(src, { merge: true }) - expect( - doc.contents.items.slice(5).map(it => it.items[0].value) - ).toMatchObject([ - { source: 'CENTER' }, - { items: [{ source: 'CENTER' }, { source: 'BIG' }] }, - { items: [{ source: 'BIG' }, { source: 'LEFT' }, { source: 'SMALL' }] } - ]) + expect(doc.value.items.slice(5).map(it => it.items[0].value)).toMatchObject( + [ + { source: 'CENTER' }, + { items: [{ source: 'CENTER' }, { source: 'BIG' }] }, + { items: [{ source: 'BIG' }, { source: 'LEFT' }, { source: 'SMALL' }] } + ] + ) }) test('alias is associated with a sequence', () => { @@ -292,7 +292,7 @@ describe('merge <<', () => { '[{ a: A }, { b: B }]', { merge: true } ) - const [a, b] = doc.contents.items + const [a, b] = doc.value.items const merge = doc.createPair('<<', doc.createAlias(a)) b.items.push(merge) expect(doc.toJS()).toMatchObject([{ a: 'A' }, { a: 'A', b: 'B' }]) @@ -304,7 +304,7 @@ describe('merge <<', () => { '[{ a: A }, { b: B }]', { customTags: ['merge'] } ) - const [a, b] = doc.contents.items + const [a, b] = doc.value.items const merge = doc.createPair('<<', doc.createAlias(a)) b.items.push(merge) expect(doc.toJS()).toMatchObject([{ a: 'A' }, { a: 'A', b: 'B' }]) @@ -316,7 +316,7 @@ describe('merge <<', () => { '[{ a: A }, { b: B }]', { merge: true } ) - const [a, b] = doc.contents.items + const [a, b] = doc.value.items const merge = doc.createPair(Symbol('<<'), doc.createAlias(a)) b.items.push(merge) expect(doc.toJS()).toMatchObject([{ a: 'A' }, { a: 'A', b: 'B' }]) @@ -328,7 +328,7 @@ describe('merge <<', () => { '[{ a: A }, { b: B }]', { merge: true } ) - const [a, b] = doc.contents.items + const [a, b] = doc.value.items const alias = doc.createAlias(a, 'AA') const merge = doc.createPair('<<', alias) b.items.push(merge) @@ -340,7 +340,7 @@ describe('merge <<', () => { const doc = parseDocument('[{ a: A }, { b: B }]', { merge: true }) - const alias = doc.createAlias(doc.getIn([0, 'a'], true)) + const alias = doc.createAlias(doc.getIn([0, 'a'])) doc.addIn([1], doc.createPair('<<', alias)) expect(String(doc)).toBe('[ { a: &a1 A }, { b: B, <<: *a1 } ]\n') expect(() => doc.toJS()).toThrow( diff --git a/tests/doc/comments.ts b/tests/doc/comments.ts index 630afbdc..224d40fb 100644 --- a/tests/doc/comments.ts +++ b/tests/doc/comments.ts @@ -18,7 +18,7 @@ describe('parse comments', () => { string ` const doc = YAML.parseDocument(src) - expect(doc.contents.commentBefore).toBe('comment\ncomment') + expect(doc.value.commentBefore).toBe('comment\ncomment') expect(String(doc)).toBe(src) }) @@ -31,7 +31,7 @@ describe('parse comments', () => { string ` const doc = YAML.parseDocument(src) - expect(doc.contents.commentBefore).toBe('comment\n \ncomment') + expect(doc.value.commentBefore).toBe('comment\n \ncomment') expect(String(doc)).toBe(source` --- #comment @@ -53,31 +53,31 @@ describe('parse comments', () => { test('plain', () => { const src = '#c0\nvalue #c1\n#c2' const doc = YAML.parseDocument(src) - expect(doc.contents.commentBefore).toBe('c0') - expect(doc.contents.comment).toBe('c1') + expect(doc.value.commentBefore).toBe('c0') + expect(doc.value.comment).toBe('c1') expect(doc.comment).toBe('c2') - expect(doc.contents.value).toBe('value') - expect(doc.contents.range).toMatchObject([4, 9, 14]) + expect(doc.value.value).toBe('value') + expect(doc.value.range).toMatchObject([4, 9, 14]) }) test('"quoted"', () => { const src = '#c0\n"value" #c1\n#c2' const doc = YAML.parseDocument(src) - expect(doc.contents.commentBefore).toBe('c0') - expect(doc.contents.comment).toBe('c1') + expect(doc.value.commentBefore).toBe('c0') + expect(doc.value.comment).toBe('c1') expect(doc.comment).toBe('c2') - expect(doc.contents.value).toBe('value') - expect(doc.contents.range).toMatchObject([4, 11, 16]) + expect(doc.value.value).toBe('value') + expect(doc.value.range).toMatchObject([4, 11, 16]) }) test('block', () => { const src = '#c0\n>- #c1\n value\n#c2\n' const doc = YAML.parseDocument(src) - expect(doc.contents.commentBefore).toBe('c0') - expect(doc.contents.comment).toBe('c1') + expect(doc.value.commentBefore).toBe('c0') + expect(doc.value.comment).toBe('c1') expect(doc.comment).toBe('c2') - expect(doc.contents.value).toBe('value') - expect(doc.contents.range).toMatchObject([4, 18, 18]) + expect(doc.value.value).toBe('value') + expect(doc.value.range).toMatchObject([4, 18, 18]) }) }) @@ -94,7 +94,7 @@ describe('parse comments', () => { ` const doc = YAML.parseDocument(src) expect(doc).toMatchObject({ - contents: { + value: { items: [ { commentBefore: 'c0', value: 'value 1', comment: 'c1' }, { value: 'value 2' } @@ -119,7 +119,7 @@ describe('parse comments', () => { ` const doc = YAML.parseDocument(src) expect(doc).toMatchObject({ - contents: { + value: { items: [{ comment: 'c0' }, { commentBefore: 'c1\n\nc2' }] }, comment: 'c3\nc4' @@ -140,7 +140,7 @@ describe('parse comments', () => { ` const doc = YAML.parseDocument(src) expect(doc).toMatchObject({ - contents: { + value: { items: [ { key: { commentBefore: 'c0' }, value: { comment: 'c1' } }, { key: {}, value: {} } @@ -164,7 +164,7 @@ describe('parse comments', () => { ` const doc = YAML.parseDocument(src) expect(doc).toMatchObject({ - contents: { + value: { items: [ { value: { comment: 'c0' } }, { key: { commentBefore: 'c1\n\nc2' } } @@ -189,7 +189,7 @@ describe('parse comments', () => { ` const doc = YAML.parseDocument(src) expect(doc).toMatchObject({ - contents: { + value: { items: [ { commentBefore: 'c0\nc1', @@ -232,7 +232,7 @@ describe('parse comments', () => { ` const doc = YAML.parseDocument(src) expect(doc).toMatchObject({ - contents: { + value: { items: [ { key: { commentBefore: 'c0', value: 'k1' }, @@ -272,7 +272,7 @@ describe('parse comments', () => { [ a, #c0 b #c1 ]`) - expect(doc.contents.items).toMatchObject([ + expect(doc.value.items).toMatchObject([ { value: 'a', comment: 'c0' }, { value: 'b', comment: 'c1' } ]) @@ -284,7 +284,7 @@ describe('parse comments', () => { b: c, #c1 d #c2 }`) - expect(doc.contents.items).toMatchObject([ + expect(doc.value.items).toMatchObject([ { key: { value: 'a', comment: 'c0' } }, { key: { value: 'b' }, value: { value: 'c', comment: 'c1' } }, { key: { value: 'd', comment: 'c2' } } @@ -293,7 +293,7 @@ describe('parse comments', () => { test('multi-line comments', () => { const doc = YAML.parseDocument('{ a,\n#c0\n#c1\nb }') - expect(doc.contents.items).toMatchObject([ + expect(doc.value.items).toMatchObject([ { key: { value: 'a' } }, { key: { commentBefore: 'c0\nc1', value: 'b' } } ]) @@ -320,21 +320,21 @@ describe('stringify comments', () => { test('plain', () => { const src = 'string' const doc = YAML.parseDocument(src) - doc.contents.comment = 'comment' + doc.value.comment = 'comment' expect(String(doc)).toBe('string #comment\n') }) test('"quoted"', () => { const src = '"string\\u0000"' const doc = YAML.parseDocument(src) - doc.contents.comment = 'comment' + doc.value.comment = 'comment' expect(String(doc)).toBe('"string\\0" #comment\n') }) test('block', () => { const src = '>\nstring\n' const doc = YAML.parseDocument(src) - doc.contents.comment = 'comment' + doc.value.comment = 'comment' expect(String(doc)).toBe('> #comment\nstring\n') }) }) @@ -343,21 +343,21 @@ describe('stringify comments', () => { test('plain', () => { const src = 'string' const doc = YAML.parseDocument(src) - doc.contents.comment = 'comment\nlines' + doc.value.comment = 'comment\nlines' expect(String(doc)).toBe('string\n#comment\n#lines\n') }) test('"quoted"', () => { const src = '"string\\u0000"' const doc = YAML.parseDocument(src) - doc.contents.comment = 'comment\nlines' + doc.value.comment = 'comment\nlines' expect(String(doc)).toBe('"string\\0"\n#comment\n#lines\n') }) test('block', () => { const src = '>\nstring\n' const doc = YAML.parseDocument(src) - doc.contents.comment = 'comment\nlines' + doc.value.comment = 'comment\nlines' expect(String(doc)).toBe('> #comment lines\nstring\n') }) }) @@ -385,10 +385,10 @@ describe('stringify comments', () => { test('plain', () => { const src = '- value 1\n- value 2\n' const doc = YAML.parseDocument, false>(src) - doc.contents.commentBefore = 'c0' - doc.contents.items[0].commentBefore = 'c1' - doc.contents.items[1].commentBefore = 'c2' - doc.contents.comment = 'c3' + doc.value.commentBefore = 'c0' + doc.value.items[0].commentBefore = 'c1' + doc.value.items[1].commentBefore = 'c2' + doc.value.comment = 'c3' expect(String(doc)).toBe(source` #c0 #c1 @@ -402,9 +402,9 @@ describe('stringify comments', () => { test('multiline', () => { const src = '- value 1\n- value 2\n' const doc = YAML.parseDocument, false>(src) - doc.contents.items[0].commentBefore = 'c0\nc1' - doc.contents.items[1].commentBefore = ' \nc2\n\nc3' - doc.contents.comment = 'c4\nc5' + doc.value.items[0].commentBefore = 'c0\nc1' + doc.value.items[1].commentBefore = ' \nc2\n\nc3' + doc.value.comment = 'c4\nc5' expect(String(doc)).toBe(source` #c0 #c1 @@ -425,9 +425,9 @@ describe('stringify comments', () => { YAML.YAMLMap>, false >(src) - doc.contents.items[0].key.commentBefore = 'c0' - doc.contents.items[0].key.comment = 'c1' - const seq = doc.contents.items[0].value! + doc.value.items[0].key.commentBefore = 'c0' + doc.value.items[0].key.comment = 'c1' + const seq = doc.value.items[0].value! seq.commentBefore = 'c2' seq.items[0].commentBefore = 'c3' seq.items[1].commentBefore = 'c4' @@ -448,9 +448,9 @@ describe('stringify comments', () => { const doc = YAML.parseDocument, false>( '- a\n- b\n' ) - doc.contents.commentBefore = 'c0' - doc.contents.items[0].commentBefore = 'c1' - doc.contents.items[1].commentBefore = 'c2\nc3' + doc.value.commentBefore = 'c0' + doc.value.items[0].commentBefore = 'c1' + doc.value.items[1].commentBefore = 'c2\nc3' expect(doc.toString({ commentString: str => str.replace(/^/gm, '// ') })) .toBe(source` // c0 @@ -470,11 +470,11 @@ describe('stringify comments', () => { YAML.YAMLMap, false >(src) - doc.contents.items[0].key.commentBefore = 'c0' - doc.contents.items[1].key.commentBefore = 'c1' - doc.contents.items[1].key.comment = 'c2' - doc.contents.items[1].value!.spaceBefore = true - doc.contents.comment = 'c3' + doc.value.items[0].key.commentBefore = 'c0' + doc.value.items[1].key.commentBefore = 'c1' + doc.value.items[1].key.comment = 'c2' + doc.value.items[1].value!.spaceBefore = true + doc.value.comment = 'c3' expect(String(doc)).toBe(source` #c0 key1: value 1 @@ -492,12 +492,12 @@ describe('stringify comments', () => { YAML.YAMLMap, false >(src) - doc.contents.items[0].key.commentBefore = 'c0\nc1' - doc.contents.items[1].key.commentBefore = ' \nc2\n\nc3' - doc.contents.items[1].key.comment = 'c4\nc5' - doc.contents.items[1].value!.spaceBefore = true - doc.contents.items[1].value!.commentBefore = 'c6' - doc.contents.comment = 'c7\nc8' + doc.value.items[0].key.commentBefore = 'c0\nc1' + doc.value.items[1].key.commentBefore = ' \nc2\n\nc3' + doc.value.items[1].key.comment = 'c4\nc5' + doc.value.items[1].value!.spaceBefore = true + doc.value.items[1].value!.commentBefore = 'c6' + doc.value.comment = 'c7\nc8' expect(String(doc)).toBe(source` #c0 #c1 @@ -575,9 +575,9 @@ describe('stringify comments', () => { }`) expect(String(doc)).toBe(source` { - a, #c0 + a:, #c0 b: c, #c1 - d #c2 + d: #c2 } `) }) @@ -592,7 +592,7 @@ describe('stringify comments', () => { { ? { a: 1 }, #c0 b: { c: 2 }, #c1 - d #c2 + d: #c2 } `) }) @@ -636,25 +636,25 @@ describe('blank lines', () => { }) describe('drop trailing blank lines', () => { - test('empty contents', () => { + test('empty value', () => { const src = '\n\n\n' const doc = YAML.parseDocument(src) expect(String(doc)).toBe('null\n') }) - test('scalar contents', () => { + test('scalar value', () => { const src = 'str\n\n\n' const doc = YAML.parseDocument(src) expect(String(doc)).toBe('str\n') }) - test('seq contents', () => { + test('seq value', () => { const src = '- a\n- b\n\n\n' const doc = YAML.parseDocument(src) expect(String(doc)).toBe('- a\n- b\n') }) - test('empty/comment contents', () => { + test('empty/comment value', () => { const src = '#cc\n\n\n' const doc = YAML.parseDocument(src) expect(String(doc)).toBe('#cc\n\nnull\n') @@ -675,7 +675,7 @@ describe('blank lines', () => { test('before first node in document with directives', () => { const doc = YAML.parseDocument('str\n') - doc.contents.spaceBefore = true + doc.value.spaceBefore = true expect(doc.toString({ directives: true })).toBe('---\n\nstr\n') }) @@ -745,7 +745,7 @@ describe('blank lines', () => { test(name, () => { const doc = YAML.parseDocument(src) expect(String(doc)).toBe(src) - let it = doc.contents.items[1] + let it = doc.value.items[1] if (it.key) it = it.key expect(it).not.toHaveProperty('spaceBefore', true) it.spaceBefore = true @@ -755,12 +755,12 @@ describe('blank lines', () => { }) } - test('as contents', () => { + test('as value', () => { const src = '|+\n a\n\n#c\n' const doc = YAML.parseDocument(src) expect(doc).toMatchObject({ comment: 'c', - contents: { value: 'a\n\n' } + value: { value: 'a\n\n' } }) expect(String(doc)).toBe(src) doc.comment = '\n\nc' @@ -771,7 +771,7 @@ describe('blank lines', () => { test('before block map values', () => { const src = 'a:\n\n 1\nb:\n\n #c\n 2\n' const doc = YAML.parseDocument(src) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [ { key: { value: 'a' }, @@ -806,7 +806,7 @@ describe('blank lines', () => { test('flow seq', () => { const src = '[1,\n\n2,\n3,\n\n4\n\n]' const doc = YAML.parseDocument(src) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [ { value: 1 }, { value: 2, spaceBefore: true }, @@ -820,7 +820,7 @@ describe('blank lines', () => { test('flow map', () => { const src = '{\n\na: 1,\n\nb: 2 }' const doc = YAML.parseDocument(src) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [ { key: { value: 'a', spaceBefore: true }, value: { value: 1 } }, { key: { value: 'b', spaceBefore: true }, value: { value: 2 } } @@ -831,7 +831,7 @@ describe('blank lines', () => { test('flow map value comments & spaces', () => { const src = '{\n a:\n #c\n 1,\n b:\n\n #d\n 2\n}\n' const doc = YAML.parseDocument(src) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [ { key: { value: 'a' }, @@ -931,7 +931,7 @@ map: # c4 ` const doc = YAML.parseDocument(src) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [ { value: { commentBefore: ' c1\n \n c2\n', comment: ' c3\n \n c4' } } ] @@ -963,7 +963,7 @@ map: # c4 ` const doc = YAML.parseDocument(src) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [ { value: { commentBefore: ' c1\n\n c2\n', comment: ' c3\n\n c4' } } ] @@ -993,7 +993,7 @@ map: - v2 ` const doc = YAML.parseDocument(src) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [ { commentBefore: ' c1\n \n c2', comment: ' c3\n ' }, { commentBefore: ' c4' } @@ -1023,7 +1023,7 @@ map: - v2 ` const doc = YAML.parseDocument(src) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [ { commentBefore: ' c1\n\n c2', comment: ' c3' }, { commentBefore: ' c4', spaceBefore: true } @@ -1046,7 +1046,7 @@ map: const doc = YAML.parseDocument, false>( '- v1\n- v2\n' ) - const [v1, v2] = doc.contents.items + const [v1, v2] = doc.value.items v1.commentBefore = '\n' v1.comment = '\n' v2.commentBefore = '\n' @@ -1066,7 +1066,7 @@ map: YAML.YAMLMap, false >('k1: v1\nk2: v2') - const [p1, p2] = doc.contents.items + const [p1, p2] = doc.value.items p1.key.commentBefore = '\n' p1.value!.commentBefore = '\n' p1.value!.comment = '\n' @@ -1189,7 +1189,7 @@ describe('collection end comments', () => { - d ` const doc = YAML.parseDocument(src) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [ { items: [{ value: 'a' }, { value: 'b' }], comment: '1\n\n2' }, { value: 'd' } @@ -1217,7 +1217,7 @@ describe('collection end comments', () => { - d ` const doc = YAML.parseDocument(src) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [ { items: [ @@ -1252,7 +1252,7 @@ describe('collection end comments', () => { d: 1 ` const doc = YAML.parseDocument(src) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [ { key: { value: 'a' }, @@ -1285,7 +1285,7 @@ describe('collection end comments', () => { d: 1 ` const doc = YAML.parseDocument(src) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [ { key: { value: 'a' }, @@ -1322,7 +1322,7 @@ a: #2 - e\n` const doc = YAML.parseDocument(src) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [ { key: { value: 'a' }, diff --git a/tests/doc/createNode.ts b/tests/doc/createNode.ts index 5b070fd8..9635e11e 100644 --- a/tests/doc/createNode.ts +++ b/tests/doc/createNode.ts @@ -56,7 +56,7 @@ describe('arrays', () => { const s = doc.createNode([true]) expect(s).toBeInstanceOf(YAMLSeq) expect(s.items).toMatchObject([{ value: true }]) - doc.contents = s + doc.value = s expect(String(doc)).toBe('- true\n') }) @@ -65,7 +65,7 @@ describe('arrays', () => { const s = doc.createNode([true], { flow: true }) expect(s).toBeInstanceOf(YAMLSeq) expect(s.items).toMatchObject([{ value: true }]) - doc.contents = s + doc.value = s expect(String(doc)).toBe('[ true ]\n') }) @@ -81,13 +81,13 @@ describe('arrays', () => { expect(s.items[1].items[0].value).toBe('four') expect(s.items[1].items[1].value).toBe(5) }) - test('set doc contents', () => { + test('set doc value', () => { const res = '- 3\n- - four\n - 5\n' const doc = new Document(array) expect(String(doc)).toBe(res) - doc.contents = array as any + doc.value = array as any expect(String(doc)).toBe(res) - doc.contents = doc.createNode(array) + doc.value = doc.createNode(array) expect(String(doc)).toBe(res) }) }) @@ -107,7 +107,7 @@ describe('objects', () => { expect(s.items).toMatchObject([ { key: { value: 'x' }, value: { value: true } } ]) - doc.contents = s + doc.value = s expect(String(doc)).toBe('x: true\n') }) @@ -118,7 +118,7 @@ describe('objects', () => { expect(s.items).toMatchObject([ { key: { value: 'x' }, value: { value: true } } ]) - doc.contents = s + doc.value = s expect(String(doc)).toBe('{ x: true }\n') }) @@ -138,7 +138,16 @@ describe('objects', () => { expect(s).toBeInstanceOf(YAMLMap) expect(s.items).toMatchObject([ { key: { value: 'x' }, value: { value: true } }, - { key: { value: 'y' }, value: { value: null } } + { key: { value: 'y' }, value: null } + ]) + }) + + test('createNode(pair)', () => { + const pair = new Document().createPair('x', true) + const s = new Document().createNode(pair) + expect(s).toBeInstanceOf(YAMLMap) + expect(s.items).toMatchObject([ + { key: { value: 'x' }, value: { value: true } } ]) }) @@ -162,7 +171,7 @@ describe('objects', () => { } ]) }) - test('set doc contents', () => { + test('set doc value', () => { const res = `x: 3 y: - 4 @@ -171,9 +180,9 @@ z: v: 6\n` const doc = new Document(object) expect(String(doc)).toBe(res) - doc.contents = object as any + doc.value = object as any expect(String(doc)).toBe(res) - doc.contents = doc.createNode(object) + doc.value = doc.createNode(object) expect(String(doc)).toBe(res) }) }) @@ -202,13 +211,13 @@ describe('Set', () => { expect(s.items[1].items[0].value).toBe('four') expect(s.items[1].items[1].value).toBe(5) }) - test('set doc contents', () => { + test('set doc value', () => { const res = '- 3\n- - four\n - 5\n' const doc = new Document(set) expect(String(doc)).toBe(res) - doc.contents = set as any + doc.value = set as any expect(String(doc)).toBe(res) - doc.contents = doc.createNode(set) + doc.value = doc.createNode(set) expect(String(doc)).toBe(res) }) test('Schema#createNode() - YAML 1.2', () => { @@ -275,7 +284,7 @@ describe('Map', () => { } ]) }) - test('set doc contents', () => { + test('set doc value', () => { const res = `x: 3 y: - 4 @@ -284,9 +293,9 @@ y: : z\n` const doc = new Document(map) expect(String(doc)).toBe(res) - doc.contents = map as any + doc.value = map as any expect(String(doc)).toBe(res) - doc.contents = doc.createNode(map) + doc.value = doc.createNode(map) expect(String(doc)).toBe(res) }) }) @@ -332,7 +341,7 @@ describe('circular references', () => { const map: any = { foo: 'bar' } map.map = map const doc = new Document(map) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ anchor: 'a1', items: [ { key: { value: 'foo' }, value: { value: 'bar' } }, @@ -371,7 +380,7 @@ describe('circular references', () => { const two = ['two'] const seq = [one, two, one, one, two] const doc = new Document(seq) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [ { items: [{ value: 'one' }] }, { items: [{ value: 'two' }] }, diff --git a/tests/doc/errors.ts b/tests/doc/errors.ts index 9691218f..a518d50c 100644 --- a/tests/doc/errors.ts +++ b/tests/doc/errors.ts @@ -66,7 +66,7 @@ describe('block collections', () => { expect(doc.errors[0].message).toMatch( 'All mapping items must start at the same column' ) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [ { key: { value: 'foo' }, value: { value: '1' } }, { key: { value: 'bar' }, value: { value: 2 } } @@ -81,7 +81,7 @@ describe('block collections', () => { expect(doc.errors[0].message).toMatch( 'All sequence items must start at the same column' ) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [{ value: 'foo' }, { items: [{ value: 'bar' }] }] }) }) @@ -94,7 +94,7 @@ describe('block collections', () => { { code: 'UNEXPECTED_TOKEN' }, { code: 'MISSING_CHAR' } ]) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [ { key: { value: 'foo' }, value: { value: '1' } }, { key: { value: null }, value: null } @@ -430,7 +430,7 @@ test('broken document with comment before first node', () => { test('multiple tags on one node', () => { const doc = YAML.parseDocument('!foo !bar baz\n') - expect(doc.contents).toMatchObject({ value: 'baz', type: 'PLAIN' }) + expect(doc.value).toMatchObject({ value: 'baz', type: 'PLAIN' }) expect(doc.errors).toHaveLength(1) expect(doc.warnings).toHaveLength(1) }) diff --git a/tests/doc/foldFlowLines.ts b/tests/doc/foldFlowLines.ts index 5636a296..c95bb1c9 100644 --- a/tests/doc/foldFlowLines.ts +++ b/tests/doc/foldFlowLines.ts @@ -160,7 +160,7 @@ describe('double-quoted', () => { const str = YAML.stringify({ x }) const doc = YAML.parseDocument(str) expect(doc.errors).toHaveLength(0) - expect(doc.contents.items[0].value.value).toBe(x) + expect(doc.value.items[0].value.value).toBe(x) }) }) @@ -188,7 +188,7 @@ describe('double-quoted', () => { const str = YAML.stringify({ key: [[value]] }) const doc = YAML.parseDocument(str) expect(doc.errors).toHaveLength(0) - expect(doc.contents.items[0].value.items[0].items[0].value).toBe(value) + expect(doc.value.items[0].value.items[0].items[0].value).toBe(value) }) }) @@ -304,7 +304,7 @@ describe('end-to-end', () => { Unfolded paragraph. ` const doc = YAML.parseDocument(src) - expect(doc.contents.value).toBe(source` + expect(doc.value.value).toBe(source` Text on a line that should get folded with a line width of 20 characters. Indented text @@ -332,10 +332,10 @@ describe('end-to-end', () => { - plain with comment # that won't get folded ` const doc = YAML.parseDocument, false>(src) - expect(doc.contents.items[0].value).toBe( + expect(doc.value.items[0].value).toBe( 'plain value with enough length to fold twice' ) - expect(doc.contents.items[1].value).toBe('plain with comment') + expect(doc.value.items[1].value).toBe('plain with comment') expect(doc.toString(foldOptions)).toBe(src) }) diff --git a/tests/doc/parse.ts b/tests/doc/parse.ts index 627b9d91..28edc02f 100644 --- a/tests/doc/parse.ts +++ b/tests/doc/parse.ts @@ -175,7 +175,7 @@ describe('flow collection keys', () => { test('block map with flow collection key as explicit key', () => { const doc = YAML.parseDocument(`? []: x`) expect(doc.errors).toHaveLength(0) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [ { key: { @@ -194,7 +194,7 @@ describe('flow collection keys', () => { c: d `) expect(doc.errors).toHaveLength(0) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [ { key: { value: 'a' }, @@ -217,7 +217,7 @@ describe('flow collection keys', () => { c: d `) expect(doc.errors).toHaveLength(0) - expect(doc.contents).toMatchObject({ + expect(doc.value).toMatchObject({ items: [ { key: { value: 'x' }, value: { value: 'y' } }, { @@ -235,14 +235,14 @@ describe('flow collection keys', () => { test('empty scalar as last flow collection value (#550)', () => { const doc = YAML.parseDocument('{c:}') - expect(doc.contents.items).toMatchObject([ + expect(doc.value.items).toMatchObject([ { key: { value: 'c' }, value: { value: null } } ]) }) test('plain key with no space before flow collection value (#550)', () => { const doc = YAML.parseDocument('{c:[]}') - expect(doc.contents.items).toMatchObject([ + expect(doc.value.items).toMatchObject([ { key: { value: 'c' }, value: { items: [] } } ]) }) @@ -321,8 +321,8 @@ describe('empty(ish) nodes', () => { const src = '{ ? : 123 }' const doc = YAML.parseDocument(src) expect(doc.errors).toHaveLength(0) - expect(doc.contents.items[0].key.value).toBeNull() - expect(doc.contents.items[0].value.value).toBe(123) + expect(doc.value.items[0].key.value).toBeNull() + expect(doc.value.items[0].value.value).toBe(123) }) describe('comment on empty pair value (#19)', () => { @@ -358,7 +358,7 @@ describe('empty(ish) nodes', () => { test('empty node position', () => { const doc = YAML.parseDocument('\r\na: # 123\r\n') - const empty = doc.contents.items[0].value + const empty = doc.value.items[0].value expect(empty.range).toEqual([5, 5, 12]) }) @@ -384,7 +384,7 @@ describe('maps with no values', () => { test('block map', () => { const src = `a: null\n? b #c` const doc = YAML.parseDocument(src) - expect(String(doc)).toBe(`a: null\n? b #c\n`) + expect(String(doc)).toBe(`a: null\nb: #c\n null\n`) doc.set('b', 'x') expect(String(doc)).toBe(`a: null\nb: #c\n x\n`) }) @@ -392,9 +392,9 @@ describe('maps with no values', () => { test('flow map', () => { const src = `{\na: null,\n? b\n}` const doc = YAML.parseDocument(src) - expect(String(doc)).toBe(`{ a: null, b }\n`) - doc.contents.items[1].key.comment = 'c' - expect(String(doc)).toBe(`{\n a: null,\n b #c\n}\n`) + expect(String(doc)).toBe(`{ a: null, b: }\n`) + doc.value.items[1].key.comment = 'c' + expect(String(doc)).toBe(`{\n a: null,\n b: #c\n}\n`) doc.set('b', 'x') expect(String(doc)).toBe(`{\n a: null,\n b: #c\n x\n}\n`) }) @@ -412,7 +412,7 @@ describe('maps with no values', () => { test('implicit scalar key after explicit key with no value', () => { const doc = YAML.parseDocument('? - 1\nx:\n') - expect(doc.contents.items).toMatchObject([ + expect(doc.value.items).toMatchObject([ { key: { items: [{ value: 1 }] }, value: null }, { key: { value: 'x' }, value: { value: null } } ]) @@ -420,7 +420,7 @@ describe('maps with no values', () => { test('implicit flow collection key after explicit key with no value', () => { const doc = YAML.parseDocument('? - 1\n[x]: y\n') - expect(doc.contents.items).toMatchObject([ + expect(doc.value.items).toMatchObject([ { key: { items: [{ value: 1 }] }, value: null }, { key: { items: [{ value: 'x' }] }, value: { value: 'y' } } ]) @@ -431,7 +431,7 @@ describe('odd indentations', () => { test('Block map with empty explicit key (#551)', () => { const doc = YAML.parseDocument('?\n? a') expect(doc.errors).toHaveLength(0) - expect(doc.contents.items).toMatchObject([ + expect(doc.value.items).toMatchObject([ { key: { value: null }, value: null }, { key: { value: 'a' }, value: null } ]) @@ -455,7 +455,7 @@ describe('odd indentations', () => { test('comment after top-level block scalar with indentation indicator (#547)', () => { const doc = YAML.parseDocument('|1\n x\n#c') expect(doc.errors).toHaveLength(0) - expect(doc.contents).toMatchObject({ value: 'x\n' }) + expect(doc.value).toMatchObject({ value: 'x\n' }) }) test('tab after indent spaces for flow-in-block (#604)', () => { @@ -691,32 +691,32 @@ describe('keepSourceTokens', () => { ]) { test(`${type}: default false`, () => { const doc = YAML.parseDocument(src) - expect(doc.contents).not.toHaveProperty('srcToken') - expect(doc.contents.items[0]).not.toHaveProperty('srcToken') - expect(doc.get('foo', true)).not.toHaveProperty('srcToken') + expect(doc.value).not.toHaveProperty('srcToken') + expect(doc.value.items[0]).not.toHaveProperty('srcToken') + expect(doc.get('foo')).not.toHaveProperty('srcToken') }) test(`${type}: included when set`, () => { const doc = YAML.parseDocument(src, { keepSourceTokens: true }) - expect(doc.contents.srcToken).toMatchObject({ type }) - expect(doc.contents.items[0].srcToken).toMatchObject({ + expect(doc.value.srcToken).toMatchObject({ type }) + expect(doc.value.items[0].srcToken).toMatchObject({ key: { type: 'scalar' }, value: { type: 'scalar' } }) - expect(doc.get('foo', true).srcToken).toMatchObject({ type: 'scalar' }) + expect(doc.get('foo').srcToken).toMatchObject({ type: 'scalar' }) }) } test('allow for CST modifications (#903)', () => { const src = 'foo:\n [ 42 ]' const tokens = Array.from(new YAML.Parser().parse(src)) - const docs = new YAML.Composer({ + const docs = new YAML.Composer({ keepSourceTokens: true }).compose(tokens) const doc = Array.from(docs)[0] - const node = doc.get('foo', true) + const node = doc.get('foo') YAML.CST.setScalarValue(node.srcToken, 'eek') const res = tokens.map(YAML.CST.stringify).join('') expect(res).toBe('foo:\n eek') @@ -923,7 +923,7 @@ describe('stringKeys', () => { `, { stringKeys: true } ) - expect(doc.contents.items).toMatchObject([ + expect(doc.value.items).toMatchObject([ { key: { value: 'x' }, value: { value: 'x' } }, { key: { value: 'y' }, value: { value: 'y' } }, { key: { value: '42' }, value: { value: 42 } }, diff --git a/tests/doc/stringify.ts b/tests/doc/stringify.ts index 4aba165f..f91e565d 100644 --- a/tests/doc/stringify.ts +++ b/tests/doc/stringify.ts @@ -1,6 +1,6 @@ import { source } from '../_utils.ts' import * as YAML from 'yaml' -import { Pair, Scalar } from 'yaml' +import { Scalar } from 'yaml' for (const [name, version] of [ ['YAML 1.1', '1.1'], @@ -49,29 +49,29 @@ for (const [name, version] of [ test('float with trailing zeros', () => { const doc = new YAML.Document(3, { version }) - doc.contents.minFractionDigits = 2 + doc.value.minFractionDigits = 2 expect(String(doc)).toBe('3.00\n') }) test('scientific float ignores minFractionDigits', () => { const doc = new YAML.Document(3, { version }) - doc.contents.format = 'EXP' - doc.contents.minFractionDigits = 2 + doc.value.format = 'EXP' + doc.value.minFractionDigits = 2 expect(String(doc)).toBe('3e+0\n') }) test('integer with HEX format', () => { const doc = new YAML.Document(42, { version }) - doc.contents.format = 'HEX' + doc.value.format = 'HEX' expect(String(doc)).toBe('0x2a\n') }) test('float with HEX format', () => { const doc = new YAML.Document(4.2, { version }) - doc.contents.format = 'HEX' + doc.value.format = 'HEX' expect(String(doc)).toBe('4.2\n') }) test('negative integer with HEX format', () => { const doc = new YAML.Document(-42, { version }) - doc.contents.format = 'HEX' + doc.value.format = 'HEX' const exp = version === '1.2' ? '-42\n' : '-0x2a\n' expect(String(doc)).toBe(exp) }) @@ -86,14 +86,14 @@ for (const [name, version] of [ const doc = new YAML.Document(BigInt('42'), { version }) - doc.contents.format = 'HEX' + doc.value.format = 'HEX' expect(String(doc)).toBe('0x2a\n') }) test('BigInt with OCT format', () => { const doc = new YAML.Document(BigInt('42'), { version }) - doc.contents.format = 'OCT' + doc.value.format = 'OCT' const exp = version === '1.2' ? '0o52\n' : '052\n' expect(String(doc)).toBe(exp) }) @@ -101,7 +101,7 @@ for (const [name, version] of [ const doc = new YAML.Document(BigInt('-42'), { version }) - doc.contents.format = 'OCT' + doc.value.format = 'OCT' const exp = version === '1.2' ? '-42\n' : '-052\n' expect(String(doc)).toBe(exp) }) @@ -150,7 +150,7 @@ blah blah\n`) YAML.YAMLMap, false >({ foo }, { version }) - for (const node of doc.contents.items) + for (const node of doc.value.items) node.value!.type = Scalar.QUOTE_DOUBLE expect( doc @@ -165,7 +165,7 @@ blah blah\n`) const doc = new YAML.Document, false>([foo], { version }) - for (const node of doc.contents.items) node.type = Scalar.QUOTE_DOUBLE + for (const node of doc.value.items) node.type = Scalar.QUOTE_DOUBLE expect( doc .toString(opt) @@ -180,7 +180,7 @@ blah blah\n`) YAML.YAMLMap>, false >({ foo: [foo] }, { version }) - const seq = doc.contents.items[0].value! + const seq = doc.value.items[0].value! for (const node of seq.items) node.type = Scalar.QUOTE_DOUBLE expect( doc @@ -334,7 +334,7 @@ z: const doc = new YAML.Document({ x: 3, y: 4 }) expect(String(doc)).toBe('x: 3\ny: 4\n') // @ts-expect-error This should fail. - doc.contents.items.push('TEST') + doc.value.items.push('TEST') expect(() => String(doc)).toThrow(/^Map items must all be pairs.*TEST/) }) @@ -345,7 +345,7 @@ z: test('Document as key', () => { const doc = new YAML.Document({ a: 1 }) - doc.add(new YAML.Document({ b: 2, c: 3 })) + doc.set(new YAML.Document({ b: 2, c: 3 }), null) expect(String(doc)).toBe('a: 1\n? b: 2\n c: 3\n') }) @@ -356,7 +356,7 @@ z: describe('No extra whitespace for empty values', () => { const getDoc = () => - new YAML.Document, false>({ + new YAML.Document({ a: null, b: null }) @@ -367,33 +367,36 @@ z: test('Block map, with key.comment', () => { const doc = getDoc() - doc.contents.items[0].key.comment = 'c' + doc.set('a', new Scalar(null)) + doc.value.items[0].key.comment = 'c' expect(doc.toString({ nullStr: '' })).toBe('a: #c\nb:\n') }) test('Block map, with value.commentBefore', () => { const doc = getDoc() - doc.get('a', true).commentBefore = 'c' + doc.set('a', new Scalar(null)) + doc.get('a').commentBefore = 'c' expect(doc.toString({ nullStr: '' })).toBe('a:\n #c\nb:\n') }) test('Flow map, no comments', () => { const doc = getDoc() - doc.contents.flow = true + doc.value.flow = true expect(doc.toString({ nullStr: '' })).toBe('{ a:, b: }\n') }) test('Flow map, with key.comment', () => { const doc = getDoc() - doc.contents.flow = true - doc.contents.items[0].key.comment = 'c' - expect(doc.toString({ nullStr: '' })).toBe('{\n a: #c\n ,\n b:\n}\n') + doc.value.flow = true + doc.value.items[0].key.comment = 'c' + expect(doc.toString({ nullStr: '' })).toBe('{\n a:, #c\n b:\n}\n') }) test('Flow map, with value.commentBefore', () => { const doc = getDoc() - doc.contents.flow = true - doc.get('a', true).commentBefore = 'c' + doc.value.flow = true + doc.set('a', new Scalar(null)) + doc.get('a').commentBefore = 'c' expect(doc.toString({ nullStr: '' })).toBe( '{\n a:\n #c\n ,\n b:\n}\n' ) @@ -497,7 +500,7 @@ z: expect(String(doc)).toBe(src) }) test('explicit tag on empty mapping', () => { - const doc = new YAML.Document({ key: {} }) + const doc = new YAML.Document({ key: {} }) doc.get('key').tag = '!tag' expect(String(doc)).toBe(source` key: !tag {} @@ -557,7 +560,7 @@ test('Quoting item markers (#52)', () => { const str = String(doc) expect(() => YAML.parse(str)).not.toThrow() expect(str).toBe('key: "-"\n') - doc.contents = doc.createNode({ key: '?' }) + doc.value = doc.createNode({ key: '?' }) const str2 = String(doc) expect(() => YAML.parse(str2)).not.toThrow() expect(str2).toBe('key: "?"\n') @@ -721,20 +724,20 @@ describe('scalar styles', () => { describe('simple keys', () => { test('key with no value', () => { const doc = YAML.parseDocument('? ~') - expect(doc.toString()).toBe('? ~\n') + expect(doc.toString()).toBe('~: null\n') expect(doc.toString({ simpleKeys: true })).toBe('~: null\n') }) test('key with block scalar value', () => { const doc = YAML.parseDocument('foo: bar') - doc.contents.items[0].key.type = 'BLOCK_LITERAL' + doc.value.items[0].key.type = 'BLOCK_LITERAL' expect(doc.toString()).toBe('? |-\n foo\n: bar\n') expect(doc.toString({ simpleKeys: true })).toBe('"foo": bar\n') }) test('key with comment', () => { const doc = YAML.parseDocument('foo: bar') - doc.contents.items[0].key.comment = 'FOO' + doc.value.items[0].key.comment = 'FOO' expect(doc.toString()).toBe('foo: #FOO\n bar\n') expect(() => doc.toString({ simpleKeys: true })).toThrow( /With simple keys, key nodes cannot have comments/ @@ -751,7 +754,7 @@ describe('simple keys', () => { test('key with JS object value', () => { const doc = YAML.parseDocument('[foo]: bar') - doc.contents.items[0].key = { foo: 42 } + doc.value.items[0].key = { foo: 42 } expect(doc.toString()).toBe('? foo: 42\n: bar\n') expect(() => doc.toString({ simpleKeys: true })).toThrow( /With simple keys, collection cannot be used as a key value/ @@ -760,7 +763,7 @@ describe('simple keys', () => { test('key with JS null value', () => { const doc = YAML.parseDocument('[foo]: bar') - doc.contents.items[0].key = null + doc.value.items[0].key = null expect(doc.toString()).toBe('? null\n: bar\n') expect(() => doc.toString({ simpleKeys: true })).toThrow( /With simple keys, collection cannot be used as a key value/ @@ -815,7 +818,7 @@ describe('sortMapEntries', () => { }) test('doc.add', () => { const doc = new YAML.Document(obj, { sortMapEntries: true }) - doc.add(new Pair('bb', 4)) + doc.add(doc.createPair('bb', 4)) expect(String(doc)).toBe('a: 1\nb: 2\nbb: 4\nc: 3\n') }) test('doc.set', () => { @@ -960,11 +963,11 @@ describe('collectionStyle', () => { const doc = new YAML.Document({ foo: ['bar'] }) expect(doc.toString()).toBe('foo:\n - bar\n') - doc.contents.flow = false + doc.value.flow = false doc.get('foo').flow = true expect(doc.toString()).toBe('foo: [ bar ]\n') - doc.contents.flow = true + doc.value.flow = true doc.get('foo').flow = false expect(doc.toString()).toBe('{ foo: [ bar ] }\n') }) @@ -973,11 +976,11 @@ describe('collectionStyle', () => { const doc = new YAML.Document({ foo: ['bar'] }) expect(doc.toString({ collectionStyle: 'any' })).toBe('foo:\n - bar\n') - doc.contents.flow = false + doc.value.flow = false doc.get('foo').flow = true expect(doc.toString({ collectionStyle: 'any' })).toBe('foo: [ bar ]\n') - doc.contents.flow = true + doc.value.flow = true doc.get('foo').flow = false expect(doc.toString({ collectionStyle: 'any' })).toBe('{ foo: [ bar ] }\n') }) @@ -986,11 +989,11 @@ describe('collectionStyle', () => { const doc = new YAML.Document({ foo: ['bar'] }) expect(doc.toString({ collectionStyle: 'block' })).toBe('foo:\n - bar\n') - doc.contents.flow = false + doc.value.flow = false doc.get('foo').flow = true expect(doc.toString({ collectionStyle: 'block' })).toBe('foo:\n - bar\n') - doc.contents.flow = true + doc.value.flow = true doc.get('foo').flow = false expect(doc.toString({ collectionStyle: 'block' })).toBe('foo:\n - bar\n') }) @@ -1002,7 +1005,7 @@ describe('collectionStyle', () => { doc.get('foo').flow = true expect(doc.toString({ collectionStyle: 'flow' })).toBe('{ foo: [ bar ] }\n') - doc.contents.flow = true + doc.value.flow = true doc.get('foo').flow = false expect(doc.toString({ collectionStyle: 'flow' })).toBe('{ foo: [ bar ] }\n') }) @@ -1050,13 +1053,15 @@ describe('Scalar options', () => { expect(YAML.stringify({ foo: 'bar' }, opt)).toBe('"foo": "bar"\n') }) - test('Use defaultType for explicit keys', () => { + test('Use defaultStringType for explicit keys', () => { const opt = { + blockQuote: false, defaultStringType: Scalar.QUOTE_DOUBLE, defaultKeyType: Scalar.QUOTE_SINGLE } as const const doc = new YAML.Document({ foo: null }) - doc.contents.items[0].value = null + const key = doc.value.items[0].key as Scalar + key.type = Scalar.BLOCK_LITERAL expect(doc.toString(opt)).toBe('? "foo"\n') }) }) @@ -1197,7 +1202,7 @@ describe('Document markers in top-level scalars', () => { test("'foo\\n...'", () => { const doc = new YAML.Document('foo\n...') - doc.contents.type = Scalar.QUOTE_SINGLE + doc.value.type = Scalar.QUOTE_SINGLE const str = String(doc) expect(str).toBe("'foo\n\n ...'\n") expect(YAML.parse(str)).toBe('foo\n...') @@ -1205,7 +1210,7 @@ describe('Document markers in top-level scalars', () => { test('"foo\\n..."', () => { const doc = new YAML.Document('foo\n...') - doc.contents.type = Scalar.QUOTE_DOUBLE + doc.value.type = Scalar.QUOTE_DOUBLE const str = doc.toString({ doubleQuotedMinMultiLineLength: 0 }) expect(str).toBe('"foo\n\n ..."\n') expect(YAML.parse(str)).toBe('foo\n...') @@ -1441,9 +1446,9 @@ describe('YAML.stringify on ast Document', () => { describe('flow collection padding', () => { const doc = new YAML.Document() - doc.contents = new YAML.YAMLSeq() - doc.contents.items = [1, 2] - doc.contents.flow = true + doc.value = new YAML.YAMLSeq() + doc.value.items = [new Scalar(1), new Scalar(2)] + doc.value.flow = true test('default', () => { expect(doc.toString()).toBe('[ 1, 2 ]\n') diff --git a/tests/doc/types.ts b/tests/doc/types.ts index 4f91a23e..e46eaf60 100644 --- a/tests/doc/types.ts +++ b/tests/doc/types.ts @@ -2,10 +2,9 @@ import { type CollectionTag, Document, type DocumentOptions, - type Node, + type DocValue, parseDocument as origParseDocument, parse, - type ParsedNode, type ParseOptions, Scalar, type ScalarTag, @@ -18,7 +17,7 @@ import { import { seqTag, stringifyString, stringTag } from 'yaml/util' import { source } from '../_utils.ts' -const parseDocument = ( +const parseDocument = ( source: string, options?: ParseOptions & DocumentOptions & SchemaOptions ) => origParseDocument(source, options) @@ -27,66 +26,66 @@ describe('tags', () => { describe('implicit tags', () => { test('plain string', () => { const doc = parseDocument('foo') - expect(doc.contents.tag).toBeUndefined() - expect(doc.contents.value).toBe('foo') + expect(doc.value.tag).toBeUndefined() + expect(doc.value.value).toBe('foo') }) test('quoted string', () => { const doc = parseDocument('"foo"') - expect(doc.contents.tag).toBeUndefined() - expect(doc.contents.value).toBe('foo') + expect(doc.value.tag).toBeUndefined() + expect(doc.value.value).toBe('foo') }) test('flow map', () => { const doc = parseDocument('{ foo }') - expect(doc.contents.tag).toBeUndefined() - expect(doc.contents.toJSON()).toMatchObject({ foo: null }) + expect(doc.value.tag).toBeUndefined() + expect(doc.value.toJSON()).toMatchObject({ foo: null }) }) test('flow seq', () => { const doc = parseDocument('[ foo ]') - expect(doc.contents.tag).toBeUndefined() - expect(doc.contents.toJSON()).toMatchObject(['foo']) + expect(doc.value.tag).toBeUndefined() + expect(doc.value.toJSON()).toMatchObject(['foo']) }) test('block map', () => { const doc = parseDocument('foo:\n') - expect(doc.contents.tag).toBeUndefined() - expect(doc.contents.toJSON()).toMatchObject({ foo: null }) + expect(doc.value.tag).toBeUndefined() + expect(doc.value.toJSON()).toMatchObject({ foo: null }) }) test('block seq', () => { const doc = parseDocument('- foo') - expect(doc.contents.tag).toBeUndefined() - expect(doc.contents.toJSON()).toMatchObject(['foo']) + expect(doc.value.tag).toBeUndefined() + expect(doc.value.toJSON()).toMatchObject(['foo']) }) }) describe('explicit tags', () => { test('plain string', () => { const doc = parseDocument('!!str foo') - expect(doc.contents.tag).toBe('tag:yaml.org,2002:str') - expect(doc.contents.value).toBe('foo') + expect(doc.value.tag).toBe('tag:yaml.org,2002:str') + expect(doc.value.value).toBe('foo') }) test('quoted string', () => { const doc = parseDocument('!!str "foo"') - expect(doc.contents.tag).toBe('tag:yaml.org,2002:str') - expect(doc.contents.value).toBe('foo') + expect(doc.value.tag).toBe('tag:yaml.org,2002:str') + expect(doc.value.value).toBe('foo') }) test('flow map', () => { const doc = parseDocument('!!map { foo }') - expect(doc.contents.tag).toBe('tag:yaml.org,2002:map') - expect(doc.contents.toJSON()).toMatchObject({ foo: null }) + expect(doc.value.tag).toBe('tag:yaml.org,2002:map') + expect(doc.value.toJSON()).toMatchObject({ foo: null }) }) test('flow seq', () => { const doc = parseDocument('!!seq [ foo ]') - expect(doc.contents.tag).toBe('tag:yaml.org,2002:seq') - expect(doc.contents.toJSON()).toMatchObject(['foo']) + expect(doc.value.tag).toBe('tag:yaml.org,2002:seq') + expect(doc.value.toJSON()).toMatchObject(['foo']) }) test('block map', () => { const doc = parseDocument('!!map\nfoo:\n') - expect(doc.contents.tag).toBe('tag:yaml.org,2002:map') - expect(doc.contents.toJSON()).toMatchObject({ foo: null }) + expect(doc.value.tag).toBe('tag:yaml.org,2002:map') + expect(doc.value.toJSON()).toMatchObject({ foo: null }) }) test('block seq', () => { const doc = parseDocument('!!seq\n- foo') - expect(doc.contents.tag).toBe('tag:yaml.org,2002:seq') - expect(doc.contents.toJSON()).toMatchObject(['foo']) + expect(doc.value.tag).toBe('tag:yaml.org,2002:seq') + expect(doc.value.toJSON()).toMatchObject(['foo']) }) }) @@ -144,7 +143,7 @@ describe('number types', () => { intAsBigInt: false, version: '1.1' }) - expect(doc.contents.items).toMatchObject([ + expect(doc.value.items).toMatchObject([ { value: 10, format: 'BIN' }, { value: 83, format: 'OCT' }, { value: -0, format: 'OCT' }, @@ -156,10 +155,10 @@ describe('number types', () => { { value: 0.42 }, { value: 0.4 } ]) - expect(doc.contents.items[3]).not.toHaveProperty('format') - expect(doc.contents.items[6]).not.toHaveProperty('format') - expect(doc.contents.items[6]).not.toHaveProperty('minFractionDigits') - expect(doc.contents.items[7]).not.toHaveProperty('format') + expect(doc.value.items[3]).not.toHaveProperty('format') + expect(doc.value.items[6]).not.toHaveProperty('format') + expect(doc.value.items[6]).not.toHaveProperty('minFractionDigits') + expect(doc.value.items[7]).not.toHaveProperty('format') }) test('Version 1.2', () => { @@ -177,7 +176,7 @@ describe('number types', () => { intAsBigInt: false, version: '1.2' }) - expect(doc.contents.items).toMatchObject([ + expect(doc.value.items).toMatchObject([ { value: 83, format: 'OCT' }, { value: 0, format: 'OCT' }, { value: 123456 }, @@ -188,10 +187,10 @@ describe('number types', () => { { value: 0.42 }, { value: 0.4 } ]) - expect(doc.contents.items[2]).not.toHaveProperty('format') - expect(doc.contents.items[5]).not.toHaveProperty('format') - expect(doc.contents.items[5]).not.toHaveProperty('minFractionDigits') - expect(doc.contents.items[6]).not.toHaveProperty('format') + expect(doc.value.items[2]).not.toHaveProperty('format') + expect(doc.value.items[5]).not.toHaveProperty('format') + expect(doc.value.items[5]).not.toHaveProperty('minFractionDigits') + expect(doc.value.items[6]).not.toHaveProperty('format') }) }) @@ -209,7 +208,7 @@ describe('number types', () => { intAsBigInt: true, version: '1.1' }) - expect(doc.contents.items).toMatchObject([ + expect(doc.value.items).toMatchObject([ { value: 10n, format: 'BIN' }, { value: 83n, format: 'OCT' }, { value: 0n, format: 'OCT' }, @@ -218,9 +217,9 @@ describe('number types', () => { { value: 0.5123, format: 'EXP' }, { value: 4.02 } ]) - expect(doc.contents.items[3]).not.toHaveProperty('format') - expect(doc.contents.items[6]).not.toHaveProperty('format') - expect(doc.contents.items[6]).not.toHaveProperty('minFractionDigits') + expect(doc.value.items[3]).not.toHaveProperty('format') + expect(doc.value.items[6]).not.toHaveProperty('format') + expect(doc.value.items[6]).not.toHaveProperty('minFractionDigits') }) test('Version 1.2', () => { @@ -235,7 +234,7 @@ describe('number types', () => { intAsBigInt: true, version: '1.2' }) - expect(doc.contents.items).toMatchObject([ + expect(doc.value.items).toMatchObject([ { value: 83n, format: 'OCT' }, { value: 0n, format: 'OCT' }, { value: 123456n }, @@ -243,9 +242,9 @@ describe('number types', () => { { value: 0.5123, format: 'EXP' }, { value: 4.02 } ]) - expect(doc.contents.items[2]).not.toHaveProperty('format') - expect(doc.contents.items[5]).not.toHaveProperty('format') - expect(doc.contents.items[5]).not.toHaveProperty('minFractionDigits') + expect(doc.value.items[2]).not.toHaveProperty('format') + expect(doc.value.items[5]).not.toHaveProperty('format') + expect(doc.value.items[5]).not.toHaveProperty('minFractionDigits') }) }) }) @@ -268,7 +267,7 @@ describe('failsafe schema', () => { test('empty scalars (#616)', () => { const doc = parseDocument('empty:\n', { schema: 'failsafe' }) expect(doc.errors).toMatchObject([]) - expect(doc.get('empty', true)).toMatchObject({ value: '' }) + expect(doc.get('empty')).toMatchObject({ value: '' }) expect(doc.toJS()).toMatchObject({ empty: '' }) expect(doc.toString()).toEqual('empty:\n') }) @@ -330,7 +329,7 @@ describe('json schema', () => { }) expect(doc.errors).toHaveLength(2) doc.errors = [] - doc.contents.items[1].value!.tag = 'tag:yaml.org,2002:float' + doc.value.items[1].value!.tag = 'tag:yaml.org,2002:float' expect(String(doc)).toBe( '"canonical": 685230.15\n"fixed": !!float 685230.15\n"negative infinity": "-.inf"\n"not a number": ".NaN"\n' ) @@ -472,11 +471,11 @@ one: 1 '{ 3: 4 }': 'many' }) expect(doc.errors).toHaveLength(0) - doc.contents.items[2].key = { 3: 4 } + doc.value.items[2].key = doc.createNode({ 3: 4 }) expect(doc.toJS()).toMatchObject({ one: 1, 2: 'two', - '{"3":4}': 'many' + '{ "3": 4 }': 'many' }) }) @@ -494,12 +493,12 @@ one: 1 ]) ) expect(doc.errors).toHaveLength(0) - doc.contents.items[2].key = { 3: 4 } + doc.value.items[2].key = doc.createNode({ 5: 6 }) expect(doc.toJS({ mapAsMap: true })).toMatchObject( new Map([ ['one', 1], [2, 'two'], - [{ 3: 4 }, 'many'] + [new Map([[5, 6]]), 'many'] ]) ) }) @@ -524,8 +523,8 @@ description: const doc = parseDocument>>(src, { schema: 'yaml-1.1' }) - const canonical = doc.contents.items[0].value!.value - const generic = doc.contents.items[1].value!.value + const canonical = doc.value.items[0].value!.value + const generic = doc.value.items[1].value!.value expect(canonical).toBeInstanceOf(Uint8Array) expect(generic).toBeInstanceOf(Uint8Array) expect(canonical).toHaveLength(185) @@ -696,7 +695,7 @@ no time zone (Z): 2001-12-15 2:59:43.10 date (00:00:00Z): 2002-12-14` const doc = parseDocument>(src) - doc.contents.items.forEach(item => { + doc.value.items.forEach(item => { expect(item.value!.value).toBeInstanceOf(Date) }) expect(doc.toJSON()).toMatchObject({ @@ -733,8 +732,8 @@ date (00:00:00Z): 2002-12-14\n`) ]) test(name, () => { const doc = parseDocument(src, { version: '1.1' }) - expect(doc.contents).toBeInstanceOf(YAMLSeq) - expect(doc.contents.items).toMatchObject([ + expect(doc.value).toBeInstanceOf(YAMLSeq) + expect(doc.value.items).toMatchObject([ { key: { value: 'a' }, value: { value: 1 } }, { key: { value: 'b' }, value: { value: 2 } }, { key: { value: 'a' }, value: { value: 3 } } @@ -746,7 +745,7 @@ date (00:00:00Z): 2002-12-14\n`) test('stringify', () => { const doc = new Document(null, { version: '1.1' }) - doc.contents = doc.createNode( + doc.value = doc.createNode( [ ['a', 1], ['b', 2], @@ -754,7 +753,7 @@ date (00:00:00Z): 2002-12-14\n`) ], { tag: '!!pairs' } ) - expect(doc.contents.tag).toBe('tag:yaml.org,2002:pairs') + expect(doc.value.tag).toBe('tag:yaml.org,2002:pairs') expect(String(doc)).toBe(`!!pairs\n- a: 1\n- b: 2\n- a: 3\n`) }) }) @@ -766,7 +765,7 @@ date (00:00:00Z): 2002-12-14\n`) ]) test(name, () => { const doc = parseDocument(src, { version: '1.1' }) - expect(doc.contents.constructor.tag).toBe('tag:yaml.org,2002:omap') + expect(doc.value.constructor.tag).toBe('tag:yaml.org,2002:omap') expect(doc.toJS()).toBeInstanceOf(Map) expect(doc.toJS()).toMatchObject( new Map([ @@ -806,7 +805,7 @@ date (00:00:00Z): 2002-12-14\n`) test('stringify Array', () => { const doc = new Document(null, { version: '1.1' }) - doc.contents = doc.createNode( + doc.value = doc.createNode( [ ['a', 1], ['b', 2], @@ -814,7 +813,7 @@ date (00:00:00Z): 2002-12-14\n`) ], { tag: '!!omap' } ) - expect(doc.contents.constructor.tag).toBe('tag:yaml.org,2002:omap') + expect(doc.value.constructor.tag).toBe('tag:yaml.org,2002:omap') expect(String(doc)).toBe(`!!omap\n- a: 1\n- b: 2\n- a: 3\n`) }) }) @@ -826,7 +825,7 @@ date (00:00:00Z): 2002-12-14\n`) ]) test(name, () => { const doc = parseDocument(src, { version: '1.1' }) - expect(doc.contents.constructor.tag).toBe('tag:yaml.org,2002:set') + expect(doc.value.constructor.tag).toBe('tag:yaml.org,2002:set') expect(doc.toJS()).toBeInstanceOf(Set) expect(doc.toJS()).toMatchObject(new Set(['a', 'b', 'c'])) expect(String(doc)).toBe(src) @@ -929,9 +928,9 @@ describe('custom tags', () => { test('parse', () => { const doc = parseDocument>(src) - expect(doc.contents).toBeInstanceOf(YAMLSeq) - expect(doc.contents.tag).toBe('tag:example.com,2000:test/x') - const { items } = doc.contents + expect(doc.value).toBeInstanceOf(YAMLSeq) + expect(doc.value.tag).toBe('tag:example.com,2000:test/x') + const { items } = doc.value expect(items).toHaveLength(4) items.forEach(item => expect(typeof item.value).toBe('string')) expect(items[0].tag).toBe('!y') @@ -962,12 +961,12 @@ describe('custom tags', () => { '!f!': prefix }) - doc.contents.commentBefore = 'c' - doc.contents.items[3].comment = 'cc' + doc.value.commentBefore = 'c' + doc.value.items[3].comment = 'cc' const s = new Scalar(6) s.tag = '!g' // @ts-expect-error TS should complain here - doc.contents.items.splice(1, 1, s, '7') + doc.value.items.splice(1, 1, s, '7') expect(String(doc)).toBe(source` %TAG !e! tag:example.com,2000:test/ %TAG !f! tag:example.com,2000:other/ @@ -1033,12 +1032,12 @@ describe('custom tags', () => { } } - class YAMLNullObject extends YAMLMap { + class YAMLNullObject extends YAMLMap { tag: string = '!nullobject' toJSON(_?: unknown, ctx?: any): any { const obj = super.toJSON>( _, - { ...(ctx || {}), mapAsMap: false }, + { ...ctx, mapAsMap: false }, Object ) return Object.assign(Object.create(null), obj) diff --git a/tests/is-node.ts b/tests/is-node.ts deleted file mode 100644 index ed26183f..00000000 --- a/tests/is-node.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { - Alias, - Document, - isAlias, - isCollection, - isDocument, - isMap, - isNode, - isPair, - isScalar, - isSeq, - Pair, - parseDocument, - Scalar, - YAMLMap, - YAMLSeq -} from 'yaml' - -for (const { fn, exp } of [ - { - fn: isAlias, - exp: { - doc: false, - scalar: false, - map: false, - seq: false, - alias: true, - pair: false - } - }, - { - fn: isCollection, - exp: { - doc: false, - scalar: false, - map: true, - seq: true, - alias: false, - pair: false - } - }, - { - fn: isDocument, - exp: { - doc: true, - scalar: false, - map: false, - seq: false, - alias: false, - pair: false - } - }, - { - fn: isMap, - exp: { - doc: false, - scalar: false, - map: true, - seq: false, - alias: false, - pair: false - } - }, - { - fn: isNode, - exp: { - doc: false, - scalar: true, - map: true, - seq: true, - alias: true, - pair: false - } - }, - { - fn: isPair, - exp: { - doc: false, - scalar: false, - map: false, - seq: false, - alias: false, - pair: true - } - }, - { - fn: isScalar, - exp: { - doc: false, - scalar: true, - map: false, - seq: false, - alias: false, - pair: false - } - }, - { - fn: isSeq, - exp: { - doc: false, - scalar: false, - map: false, - seq: true, - alias: false, - pair: false - } - } -] as { fn: (x: unknown) => boolean; exp: Record }[]) { - describe(fn.name, () => { - test('parsed doc', () => { - const doc = parseDocument('foo') - expect(fn(doc)).toBe(exp.doc) - }) - - test('parsed scalar', () => { - const doc = parseDocument('foo') - expect(fn(doc.contents)).toBe(exp.scalar) - }) - - test('parsed map', () => { - const doc = parseDocument('foo: bar') - expect(fn(doc.contents)).toBe(exp.map) - }) - - test('parsed seq', () => { - const doc = parseDocument('[foo, bar]') - expect(fn(doc.contents)).toBe(exp.seq) - }) - - test('parsed alias', () => { - const doc = parseDocument('[ &a foo, *a ]') - expect(fn(doc.contents?.items[1])).toBe(exp.alias) - }) - - test('parsed pair', () => { - const doc = parseDocument('foo: bar') - expect(fn(doc.contents?.items[0])).toBe(exp.pair) - }) - - test('created doc', () => { - expect(fn(new Document())).toBe(exp.doc) - }) - - test('created scalar', () => { - expect(fn(new Scalar(42))).toBe(exp.scalar) - }) - - test('created map', () => { - expect(fn(new YAMLMap())).toBe(exp.map) - }) - - test('created seq', () => { - expect(fn(new YAMLSeq())).toBe(exp.seq) - }) - - test('created alias', () => { - expect(fn(new Alias('42'))).toBe(exp.alias) - }) - - test('created pair', () => { - expect(fn(new Pair(null))).toBe(exp.pair) - }) - - test('null', () => { - expect(fn(null)).toBe(false) - }) - - test('string', () => { - expect(fn('foo')).toBe(false) - }) - - test('object', () => { - expect(fn({ type: 'SCALAR', value: 42 })).toBe(false) - }) - }) -} diff --git a/tests/node-to-js.ts b/tests/node-to-js.ts index 93ff7ad7..2728a653 100644 --- a/tests/node-to-js.ts +++ b/tests/node-to-js.ts @@ -5,19 +5,19 @@ import { source } from './_utils.ts' describe('scalars', () => { test('plain', () => { const doc = parseDocument('42') - expect(doc.contents?.toJS(doc)).toBe(42) + expect(doc.value.toJS(doc)).toBe(42) }) test('plain in map', () => { const doc = parseDocument('key: 42') - expect(doc.get('key', true).toJS(doc)).toBe(42) + expect(doc.get('key').toJS(doc)).toBe(42) }) }) describe('collections', () => { test('map', () => { const doc = parseDocument('key: 42') - expect(doc.contents?.toJS(doc)).toMatchObject({ key: 42 }) + expect(doc.value.toJS(doc)).toMatchObject({ key: 42 }) }) test('map in seq', () => { @@ -64,14 +64,9 @@ describe('alias', () => { }) describe('options', () => { - test('doc is required', () => { - const doc = parseDocument('key: 42') - expect(() => doc.contents?.toJS({} as any)).toThrow(TypeError) - }) - test('mapAsMap', () => { const doc = parseDocument('key: 42') - expect(doc.contents?.toJS(doc, { mapAsMap: true })).toMatchObject( + expect(doc.value.toJS(doc, { mapAsMap: true })).toMatchObject( new Map([['key', 42]]) ) }) diff --git a/tests/visit.ts b/tests/visit.ts index 197f8576..5b669627 100644 --- a/tests/visit.ts +++ b/tests/visit.ts @@ -1,5 +1,11 @@ -import type { Scalar } from 'yaml' -import { Document, isSeq, parseDocument, visit, visitAsync } from 'yaml' +import { + Document, + parseDocument, + visit, + visitAsync, + YAMLSeq, + type Scalar +} from 'yaml' const coll = { items: {} } @@ -27,9 +33,9 @@ for (const [visit_, title] of [ const fn = vi.fn() await visit_(doc, { Map: fn, Pair: fn, Seq: fn, Alias: fn, Scalar: fn }) expect(fn.mock.calls).toMatchObject([ - [null, coll, [{ contents: {} }]], - [0, { type: 'PLAIN', value: 1 }, [{ contents: {} }, coll]], - [1, { type: 'PLAIN', value: 'two' }, [{ contents: {} }, coll]] + [null, coll, [{ value: {} }]], + [0, { type: 'PLAIN', value: 1 }, [{ value: {} }, coll]], + [1, { type: 'PLAIN', value: 'two' }, [{ value: {} }, coll]] ]) }) @@ -147,7 +153,9 @@ for (const [visit_, title] of [ test('Do not visit block seq items', async () => { const doc = parseDocument('foo:\n - one\n - two\nbar:\n') - const fn = vi.fn((_, node) => (isSeq(node) ? visit_.SKIP : undefined)) + const fn = vi.fn((_, node) => + node instanceof YAMLSeq ? visit_.SKIP : undefined + ) await visit_(doc, { Map: fn, Pair: fn, Seq: fn, Scalar: fn }) expect(fn.mock.calls).toMatchObject([ [null, coll, [{}]], @@ -230,11 +238,11 @@ for (const [visit_, title] of [ const doc = parseDocument('- one\n- two\n- three\n') const Seq = vi.fn(() => doc.createNode(42)) if (visit_ === visit) { - expect(() => visit_(doc.contents, { Seq })).toThrow( + expect(() => visit_(doc.value, { Seq })).toThrow( 'Cannot replace node with scalar parent' ) } else { - await expect(visit_(doc.contents, { Seq })).rejects.toMatchObject({ + await expect(visit_(doc.value, { Seq })).rejects.toMatchObject({ message: 'Cannot replace node with scalar parent' }) }