diff --git a/src/doc/Document.ts b/src/doc/Document.ts index 8ee7bdcb..4eebe2ab 100644 --- a/src/doc/Document.ts +++ b/src/doc/Document.ts @@ -7,6 +7,7 @@ import { isNode, isScalar, Node, + NodeType, NODE_TYPE, ParsedNode, Range @@ -41,7 +42,7 @@ export declare namespace Document { } } -export class Document { +export class Document { readonly [NODE_TYPE]: symbol /** A comment before this Document */ @@ -192,12 +193,12 @@ export class Document { * Convert any value into a `Node` using the current schema, recursively * turning objects into collections. */ - createNode(value: unknown, options?: CreateNodeOptions): Node - createNode( - value: unknown, + createNode(value: T, options?: CreateNodeOptions): NodeType + createNode( + value: T, replacer: Replacer | CreateNodeOptions | null, options?: CreateNodeOptions - ): Node + ): NodeType createNode( value: unknown, replacer?: Replacer | CreateNodeOptions | null, @@ -264,7 +265,7 @@ export class Document { * Removes a value from the document. * @returns `true` if the item was found and removed. */ - delete(key: any) { + delete(key: unknown): boolean { return assertCollection(this.contents) ? this.contents.delete(key) : false } @@ -272,7 +273,7 @@ export class Document { * Removes a value from the document. * @returns `true` if the item was found and removed. */ - deleteIn(path: Iterable) { + deleteIn(path: Iterable | null): boolean { if (isEmptyPath(path)) { if (this.contents == null) return false this.contents = null @@ -288,7 +289,7 @@ export class Document { * scalar values from their surrounding node; to disable set `keepScalar` to * `true` (collections are always returned intact). */ - get(key: unknown, keepScalar?: boolean) { + get(key: unknown, keepScalar?: boolean): unknown { return isCollection(this.contents) ? this.contents.get(key, keepScalar) : undefined @@ -299,7 +300,7 @@ export class Document { * scalar values from their surrounding node; to disable set `keepScalar` to * `true` (collections are always returned intact). */ - getIn(path: Iterable, keepScalar?: boolean) { + getIn(path: Iterable | null, keepScalar?: boolean): unknown { if (isEmptyPath(path)) return !keepScalar && isScalar(this.contents) ? this.contents.value @@ -312,14 +313,14 @@ export class Document { /** * Checks if the document includes a value with the key `key`. */ - has(key: unknown) { + has(key: unknown): boolean { return isCollection(this.contents) ? this.contents.has(key) : false } /** * Checks if the document includes a value at `path`. */ - hasIn(path: Iterable) { + hasIn(path: Iterable | null): boolean { if (isEmptyPath(path)) return this.contents !== undefined return isCollection(this.contents) ? this.contents.hasIn(path) : false } @@ -328,7 +329,7 @@ export class Document { * 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) { + set(key: any, value: unknown): void { if (this.contents == null) { this.contents = collectionFromPath( this.schema, @@ -344,7 +345,7 @@ export class Document { * 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, value: unknown) { + setIn(path: Iterable | null, value: unknown): void { if (isEmptyPath(path)) this.contents = value as T else if (this.contents == null) { this.contents = collectionFromPath( diff --git a/src/index.ts b/src/index.ts index 8e9caa2e..76555122 100644 --- a/src/index.ts +++ b/src/index.ts @@ -48,6 +48,8 @@ export { export type { TagId, Tags } from './schema/tags' export type { CollectionTag, ScalarTag } from './schema/types' +export type { YAMLOMap } from './schema/yaml-1.1/omap' +export type { YAMLSet } from './schema/yaml-1.1/set' export { asyncVisitor, diff --git a/src/nodes/Collection.ts b/src/nodes/Collection.ts index abe1b360..27508b3f 100644 --- a/src/nodes/Collection.ts +++ b/src/nodes/Collection.ts @@ -36,8 +36,11 @@ export function collectionFromPath( }) } -// null, undefined, or an empty non-string iterable (e.g. []) -export const isEmptyPath = (path: Iterable | null | undefined) => +// 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) diff --git a/src/nodes/Node.ts b/src/nodes/Node.ts index ce347ecb..36bc34bd 100644 --- a/src/nodes/Node.ts +++ b/src/nodes/Node.ts @@ -7,7 +7,20 @@ import type { Scalar } from './Scalar.js' import type { YAMLMap } from './YAMLMap.js' import type { YAMLSeq } from './YAMLSeq.js' -export type Node = Alias | Scalar | YAMLMap | YAMLSeq +export type Node = + | Alias + | Scalar + | YAMLMap + | YAMLSeq + +/** Utility type mapper */ +export type NodeType = T extends string | number | bigint | boolean | null + ? Scalar + : T extends Array + ? YAMLSeq> + : T extends { [key: string | number]: any } + ? YAMLMap, NodeType> + : Node export type ParsedNode = | Alias.Parsed @@ -28,22 +41,30 @@ export const NODE_TYPE = 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 => +export const isDocument = ( + node: any +): node is Document => !!node && typeof node === 'object' && node[NODE_TYPE] === DOC -export const isMap = (node: any): node is YAMLMap => +export const isMap = ( + node: any +): node is YAMLMap => !!node && typeof node === 'object' && node[NODE_TYPE] === MAP -export const isPair = (node: any): node is Pair => +export const isPair = ( + node: any +): node is Pair => !!node && typeof node === 'object' && node[NODE_TYPE] === PAIR -export const isScalar = (node: any): node is Scalar => +export const isScalar = (node: any): node is Scalar => !!node && typeof node === 'object' && node[NODE_TYPE] === SCALAR -export const isSeq = (node: any): node is YAMLSeq => +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 { +export function isCollection( + node: any +): node is YAMLMap | YAMLSeq { if (node && typeof node === 'object') switch (node[NODE_TYPE]) { case MAP: @@ -53,7 +74,7 @@ export function isCollection(node: any): node is YAMLMap | YAMLSeq { return false } -export function isNode(node: any): node is Node { +export function isNode(node: any): node is Node { if (node && typeof node === 'object') switch (node[NODE_TYPE]) { case ALIAS: @@ -65,7 +86,9 @@ export function isNode(node: any): node is Node { return false } -export const hasAnchor = (node: unknown): node is Scalar | YAMLMap | YAMLSeq => +export const hasAnchor = ( + node: unknown +): node is Scalar | YAMLMap | YAMLSeq => (isScalar(node) || isCollection(node)) && !!node.anchor export abstract class NodeBase { diff --git a/src/nodes/YAMLMap.ts b/src/nodes/YAMLMap.ts index b50636cf..dcae8721 100644 --- a/src/nodes/YAMLMap.ts +++ b/src/nodes/YAMLMap.ts @@ -6,7 +6,7 @@ import { addPairToJSMap } from './addPairToJSMap.js' import { Collection } from './Collection.js' import { isPair, isScalar, MAP, ParsedNode, Range } from './Node.js' import { Pair } from './Pair.js' -import { isScalarValue } from './Scalar.js' +import { isScalarValue, Scalar } from './Scalar.js' import type { ToJSContext } from './toJS.js' export function findPair( @@ -51,12 +51,12 @@ export class YAMLMap extends Collection { * @param overwrite - If not set `true`, using a key that is already in the * collection will throw. Otherwise, overwrites the previous value. */ - add(pair: Pair | { key: K; value: V }, overwrite?: boolean) { + 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) + _pair = new Pair(pair as any, (pair as any)?.value) } else _pair = new Pair(pair.key, pair.value) const prev = findPair(this.items, _pair.key) @@ -76,24 +76,27 @@ export class YAMLMap extends Collection { } } - delete(key: K) { + delete(key: unknown): boolean { const it = findPair(this.items, key) if (!it) return false const del = this.items.splice(this.items.indexOf(it), 1) return del.length > 0 } - get(key: K, keepScalar?: boolean) { + 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 { const it = findPair(this.items, key) const node = it?.value - return !keepScalar && isScalar(node) ? node.value : node + return (!keepScalar && isScalar(node) ? node.value : node) ?? undefined } - has(key: K) { + has(key: unknown): boolean { return !!findPair(this.items, key) } - set(key: K, value: V) { + set(key: K, value: V): void { this.add(new Pair(key, value), true) } diff --git a/src/nodes/YAMLSeq.ts b/src/nodes/YAMLSeq.ts index 8b479703..c75619a8 100644 --- a/src/nodes/YAMLSeq.ts +++ b/src/nodes/YAMLSeq.ts @@ -5,7 +5,7 @@ import { stringifyCollection } from '../stringify/stringifyCollection.js' import { Collection } from './Collection.js' import { isScalar, ParsedNode, Range, SEQ } from './Node.js' import type { Pair } from './Pair.js' -import { isScalarValue } from './Scalar.js' +import { isScalarValue, Scalar } from './Scalar.js' import { toJS, ToJSContext } from './toJS.js' export declare namespace YAMLSeq { @@ -29,7 +29,7 @@ export class YAMLSeq extends Collection { super(SEQ, schema) } - add(value: T) { + add(value: T): void { this.items.push(value) } @@ -41,7 +41,7 @@ export class YAMLSeq extends Collection { * * @returns `true` if the item was found and removed. */ - delete(key: unknown) { + delete(key: unknown): boolean { const idx = asItemIndex(key) if (typeof idx !== 'number') return false const del = this.items.splice(idx, 1) @@ -56,11 +56,20 @@ export class YAMLSeq extends Collection { * `key` must contain a representation of an integer for this to succeed. * It may be wrapped in a `Scalar`. */ - get(key: unknown, keepScalar?: boolean) { + 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 + return !keepScalar && isScalar(it) ? it.value : it } /** @@ -69,7 +78,7 @@ export class YAMLSeq extends Collection { * `key` must contain a representation of an integer for this to succeed. * It may be wrapped in a `Scalar`. */ - has(key: unknown) { + has(key: unknown): boolean { const idx = asItemIndex(key) return typeof idx === 'number' && idx < this.items.length } @@ -81,7 +90,7 @@ export class YAMLSeq extends Collection { * If `key` does not contain a representation of an integer, this will throw. * It may be wrapped in a `Scalar`. */ - set(key: unknown, value: T) { + set(key: unknown, value: T): void { const idx = asItemIndex(key) if (typeof idx !== 'number') throw new Error(`Expected a valid index, not ${key}.`) diff --git a/src/schema/yaml-1.1/set.ts b/src/schema/yaml-1.1/set.ts index da64922c..64db9187 100644 --- a/src/schema/yaml-1.1/set.ts +++ b/src/schema/yaml-1.1/set.ts @@ -35,7 +35,11 @@ export class YAMLSet extends YAMLMap | null> { if (!prev) this.items.push(pair) } - get(key?: T, keepPair?: boolean) { + /** + * If `keepPair` is `true`, returns the Pair matching `key`. + * Otherwise, returns the value of that Pair's key. + */ + get(key: unknown, keepPair?: boolean): any { const pair = findPair(this.items, key) return !keepPair && isPair(pair) ? isScalar(pair.key) @@ -46,7 +50,7 @@ export class YAMLSet extends YAMLMap | null> { set(key: T, value: boolean): void - /** Will throw; `value` must be boolean */ + /** @deprecated Will throw; `value` must be boolean */ set(key: T, value: null): void set(key: T, value: boolean | null) { if (typeof value !== 'boolean') diff --git a/src/visit.ts b/src/visit.ts index e982ff3e..6ceb981a 100644 --- a/src/visit.ts +++ b/src/visit.ts @@ -320,7 +320,7 @@ function replaceNode( if (key === 'key') parent.key = node else parent.value = node } else if (isDocument(parent)) { - parent.contents = node + parent.contents = node as Node } else { const pt = isAlias(parent) ? 'alias' : 'scalar' throw new Error(`Cannot replace node with ${pt} parent`) diff --git a/tests/doc/collection-access.js b/tests/collection-access.ts similarity index 77% rename from tests/doc/collection-access.js rename to tests/collection-access.ts index 05a2817e..410d9e2f 100644 --- a/tests/doc/collection-access.js +++ b/tests/collection-access.ts @@ -1,10 +1,30 @@ -import { Document, parseDocument, Pair } from 'yaml' +/* eslint-disable @typescript-eslint/ban-ts-comment */ + +import { + Document, + parseDocument, + Pair, + Scalar, + YAMLMap, + YAMLOMap, + YAMLSeq, + YAMLSet, + isSeq, + isMap +} from 'yaml' describe('Map', () => { - let doc, map + let doc: Document + let map: YAMLMap< + string | Scalar, + | number + | string + | Scalar + | YAMLMap, number | Scalar> + > beforeEach(() => { doc = new Document({ a: 1, b: { c: 3, d: 4 } }) - map = doc.contents + map = doc.contents as any expect(map.items).toMatchObject([ { key: { value: 'a' }, value: { value: 1 } }, { @@ -22,6 +42,7 @@ describe('Map', () => { test('add', () => { map.add({ key: 'c', value: 'x' }) expect(map.get('c')).toBe('x') + // @ts-expect-error expect(() => map.add('a')).toThrow(/already set/) expect(() => map.add(new Pair('c', 'y'))).toThrow(/already set/) expect(map.items).toHaveLength(3) @@ -38,15 +59,28 @@ describe('Map', () => { test('get with value', () => { expect(map.get('a')).toBe(1) expect(map.get('a', true)).toMatchObject({ value: 1 }) - expect(map.get('b').toJSON()).toMatchObject({ c: 3, d: 4 }) - expect(map.get('c')).toBeUndefined() + 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') + } }) 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('b')).toJSON()).toMatchObject({ c: 3, d: 4 }) - expect(map.get(doc.createNode('c'))).toBeUndefined() + 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') + } }) test('has with value', () => { @@ -54,6 +88,7 @@ describe('Map', () => { expect(map.has('b')).toBe(true) expect(map.has('c')).toBe(false) expect(map.has('')).toBe(false) + // @ts-expect-error expect(map.has()).toBe(false) }) @@ -61,6 +96,7 @@ describe('Map', () => { expect(map.has(doc.createNode('a'))).toBe(true) expect(map.has(doc.createNode('b'))).toBe(true) expect(map.has(doc.createNode('c'))).toBe(false) + // @ts-expect-error expect(map.has(doc.createNode())).toBe(false) }) @@ -95,10 +131,11 @@ describe('Map', () => { }) describe('Seq', () => { - let doc, seq + let doc: Document + let seq: YAMLSeq | YAMLSeq>> beforeEach(() => { doc = new Document([1, [2, 3]]) - seq = doc.contents + seq = doc.contents as any expect(seq.items).toMatchObject([ { value: 1 }, { items: [{ value: 2 }, { value: 3 }] } @@ -106,8 +143,8 @@ describe('Seq', () => { }) test('add', () => { - seq.add('x') - expect(seq.get(2)).toBe('x') + seq.add(9) + expect(seq.get(2)).toBe(9) seq.add(1) expect(seq.items).toHaveLength(4) }) @@ -124,16 +161,26 @@ describe('Seq', () => { expect(seq.get(0)).toBe(1) expect(seq.get('0')).toBe(1) expect(seq.get(0, true)).toMatchObject({ value: 1 }) - expect(seq.get(1).toJSON()).toMatchObject([2, 3]) - expect(seq.get(2)).toBeUndefined() + 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') + } }) 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 }) - expect(seq.get(doc.createNode(1)).toJSON()).toMatchObject([2, 3]) - expect(seq.get(doc.createNode(2))).toBeUndefined() + 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('has with value', () => { @@ -142,6 +189,7 @@ describe('Seq', () => { expect(seq.has(2)).toBe(false) expect(seq.has('0')).toBe(true) expect(seq.has('')).toBe(false) + // @ts-expect-error expect(seq.has()).toBe(false) }) @@ -150,6 +198,7 @@ describe('Seq', () => { 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 expect(seq.has(doc.createNode())).toBe(false) }) @@ -177,10 +226,11 @@ describe('Seq', () => { }) describe('Set', () => { - let doc, set + let doc: Document + let set: YAMLSet | Pair> beforeEach(() => { doc = new Document(null, { version: '1.1' }) - set = doc.createNode([1, 2, 3], { tag: '!!set' }) + set = doc.createNode([1, 2, 3], { tag: '!!set' }) as any doc.contents = set expect(set.items).toMatchObject([ { key: { value: 1 }, value: { value: null } }, @@ -225,10 +275,13 @@ describe('Set', () => { }) describe('OMap', () => { - let doc, omap + let doc: Document + let omap: YAMLOMap beforeEach(() => { doc = new Document(null, { version: '1.1' }) - omap = doc.createNode([{ a: 1 }, { b: { c: 3, d: 4 } }], { tag: '!!omap' }) + omap = doc.createNode([{ a: 1 }, { b: { c: 3, d: 4 } }], { + tag: '!!omap' + }) as any doc.contents = omap expect(omap.items).toMatchObject([ { key: { value: 'a' }, value: { value: 1 } }, @@ -247,6 +300,7 @@ describe('OMap', () => { test('add', () => { omap.add({ key: 'c', value: 'x' }) expect(omap.get('c')).toBe('x') + // @ts-expect-error expect(() => omap.add('a')).toThrow(/already set/) expect(() => omap.add(new Pair('c', 'y'))).toThrow(/already set/) expect(omap.items).toHaveLength(3) @@ -263,8 +317,13 @@ describe('OMap', () => { test('get', () => { expect(omap.get('a')).toBe(1) expect(omap.get('a', true)).toMatchObject({ value: 1 }) - expect(omap.get('b').toJSON()).toMatchObject({ c: 3, d: 4 }) - expect(omap.get('c')).toBeUndefined() + 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') + } }) test('has', () => { @@ -272,6 +331,7 @@ describe('OMap', () => { expect(omap.has('b')).toBe(true) expect(omap.has('c')).toBe(false) expect(omap.has('')).toBe(false) + // @ts-expect-error expect(omap.has()).toBe(false) }) @@ -288,10 +348,11 @@ describe('OMap', () => { }) describe('Collection', () => { - let doc, map + let doc: Document + let map: YAMLMap | YAMLSeq>> beforeEach(() => { doc = new Document({ a: 1, b: [2, 3] }) - map = doc.contents + map = doc.contents as any }) test('addIn', () => { @@ -299,10 +360,15 @@ describe('Collection', () => { expect(map.getIn(['b', 2])).toBe(4) map.addIn([], new Pair('c', 5)) expect(map.get('c')).toBe(5) - expect(() => map.addIn(['a'])).toThrow(/Expected YAML collection/) + expect(() => map.addIn(['a'], -1)).toThrow(/Expected YAML collection/) map.addIn(['b', 3], 6) expect(map.items).toHaveLength(3) - expect(map.get('b').items).toHaveLength(4) + const seq = map.getIn(['b']) + if (isSeq(seq)) { + expect(seq.items).toHaveLength(4) + } else { + throw new Error('Expected seq to be a seq') + } }) test('deleteIn', () => { @@ -314,7 +380,12 @@ describe('Collection', () => { expect(map.deleteIn(['b', 2])).toBe(false) expect(() => map.deleteIn(['a', 'e'])).toThrow(/Expected YAML collection/) expect(map.items).toHaveLength(1) - expect(map.get('b').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') + } }) test('getIn', () => { @@ -351,15 +422,20 @@ describe('Collection', () => { expect(map.getIn(['e', 'e'])).toBe(7) expect(() => map.setIn(['a', 'e'], 8)).toThrow(/Expected YAML collection/) expect(map.items).toHaveLength(4) - expect(map.get('b').items).toHaveLength(3) + const subSeq = map.getIn(['b']) + if (isSeq(subSeq)) { + expect(subSeq.items).toHaveLength(3) + } else { + throw new Error('Expected subSeq to be a seq') + } }) }) describe('Document', () => { - let doc + let doc: Document | YAMLSeq>>> beforeEach(() => { doc = new Document({ a: 1, b: [2, 3] }) - expect(doc.contents.items).toMatchObject([ + expect(doc.contents?.items).toMatchObject([ { key: { value: 'a' }, value: { value: 1 } }, { key: { value: 'b' }, @@ -373,7 +449,7 @@ describe('Document', () => { 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) + expect(doc.contents?.items).toHaveLength(3) }) test('addIn', () => { @@ -381,21 +457,21 @@ describe('Document', () => { expect(doc.getIn(['b', 2])).toBe(4) doc.addIn([], new Pair('c', 5)) expect(doc.get('c')).toBe(5) - expect(() => doc.addIn(['a'])).toThrow(/Expected YAML collection/) + expect(() => doc.addIn(['a'], -1)).toThrow(/Expected YAML collection/) doc.addIn(['b', 3], 6) - expect(doc.contents.items).toHaveLength(3) - expect(doc.get('b').items).toHaveLength(4) + expect(doc.contents?.items).toHaveLength(3) + expect((doc.get('b') as any).items).toHaveLength(4) }) test('delete', () => { 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.contents?.items).toHaveLength(1) }) test('delete on scalar contents', () => { - doc.contents = doc.createNode('s') + const doc = new Document('s') expect(() => doc.set('a', 1)).toThrow(/document contents/) }) @@ -407,8 +483,8 @@ 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.get('b').items).toHaveLength(1) + expect(doc.contents?.items).toHaveLength(1) + expect((doc.get('b') as any).items).toHaveLength(1) expect(doc.deleteIn(null)).toBe(true) expect(doc.deleteIn(null)).toBe(false) }) @@ -420,7 +496,7 @@ describe('Document', () => { }) test('get on scalar contents', () => { - doc.contents = doc.createNode('s') + const doc = new Document('s') expect(doc.get('a')).toBeUndefined() }) @@ -435,7 +511,7 @@ describe('Document', () => { }) test('getIn scalar', () => { - doc.contents = doc.createNode('s') + const doc = new Document('s') expect(doc.getIn([])).toBe('s') expect(doc.getIn(null, true)).toMatchObject({ value: 's' }) expect(doc.getIn([0])).toBeUndefined() @@ -447,7 +523,7 @@ describe('Document', () => { }) test('has on scalar contents', () => { - doc.contents = doc.createNode('s') + const doc = new Document('s') expect(doc.has('a')).toBe(false) }) @@ -465,11 +541,11 @@ describe('Document', () => { expect(doc.get('a', true)).toMatchObject({ value: 2 }) doc.set('c', 6) expect(doc.get('c')).toBe(6) - expect(doc.contents.items).toHaveLength(3) + expect(doc.contents?.items).toHaveLength(3) }) test('set on scalar contents', () => { - doc.contents = doc.createNode('s') + const doc = new Document('s') expect(() => doc.set('a', 1)).toThrow(/document contents/) }) @@ -490,15 +566,15 @@ describe('Document', () => { doc.setIn(['e', 1, 'e'], 7) expect(doc.getIn(['e', 1, 'e'])).toBe(7) expect(() => doc.setIn(['a', 'e'], 8)).toThrow(/Expected YAML collection/) - expect(doc.contents.items).toHaveLength(4) - expect(doc.get('b').items).toHaveLength(2) + expect(doc.contents?.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', () => { - doc.contents = doc.createNode('s') + const doc = new Document('s') expect(() => doc.setIn(['a'], 1)).toThrow(/document contents/) }) @@ -511,7 +587,7 @@ describe('Document', () => { }) test('setIn on parsed document', () => { - doc = parseDocument('{ a: 1, b: [2, 3] }') + const doc = parseDocument('{ a: 1, b: [2, 3] }') doc.setIn(['c', 1], 9) expect(String(doc)).toBe('{ a: 1, b: [ 2, 3 ], c: [ null, 9 ] }\n') }) @@ -525,7 +601,8 @@ describe('Document', () => { doc.contents = null const foo = { foo: 'FOO' } doc.setIn([foo], 'BAR') - expect(doc.contents.items).toMatchObject([ + // @ts-expect-error - Doesn't see that setIn() changes contents + expect(doc.contents?.items).toMatchObject([ { key: { items: [{ key: { value: 'foo' }, value: { value: 'FOO' } }] }, value: { value: 'BAR' } @@ -537,7 +614,8 @@ describe('Document', () => { doc.contents = null const foo = { foo: 'FOO' } doc.setIn([foo, foo], 'BAR') - expect(doc.contents.items).toMatchObject([ + // @ts-expect-error - Doesn't see that setIn() changes contents + expect(doc.contents?.items).toMatchObject([ { key: { items: [{ key: { value: 'foo' }, value: { value: 'FOO' } }] }, value: {