From 06459bf42753f280ce457e72a512f84f3ed8e017 Mon Sep 17 00:00:00 2001 From: isaacs Date: Thu, 20 Apr 2023 10:40:32 -0700 Subject: [PATCH 01/11] feat: move tag.createNode to static nodeClass.from Re: https://github.com/eemeli/yaml/issues/457 --- src/doc/createNode.ts | 2 ++ src/nodes/YAMLMap.ts | 27 ++++++++++++++++++++++++++- src/nodes/YAMLSeq.ts | 17 +++++++++++++++++ src/schema/common/map.ts | 27 ++------------------------- src/schema/common/seq.ts | 22 ++-------------------- src/schema/types.ts | 8 +++++++- src/schema/yaml-1.1/omap.ts | 17 ++++++++++------- src/schema/yaml-1.1/set.ts | 37 +++++++++++++++++++------------------ 8 files changed, 85 insertions(+), 72 deletions(-) diff --git a/src/doc/createNode.ts b/src/doc/createNode.ts index 5a618239..ba415a7a 100644 --- a/src/doc/createNode.ts +++ b/src/doc/createNode.ts @@ -99,6 +99,8 @@ export function createNode( 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 diff --git a/src/nodes/YAMLMap.ts b/src/nodes/YAMLMap.ts index df2fb3f4..74852304 100644 --- a/src/nodes/YAMLMap.ts +++ b/src/nodes/YAMLMap.ts @@ -2,11 +2,12 @@ import type { BlockMap, FlowCollection } from '../parse/cst.js' import type { Schema } from '../schema/Schema.js' import type { StringifyContext } from '../stringify/stringify.js' import { stringifyCollection } from '../stringify/stringifyCollection.js' +import { CreateNodeContext } from '../util.js' import { addPairToJSMap } from './addPairToJSMap.js' import { Collection } from './Collection.js' import { isPair, isScalar, MAP } from './identity.js' import type { ParsedNode, Range } from './Node.js' -import { Pair } from './Pair.js' +import { createPair, Pair } from './Pair.js' import { isScalarValue, Scalar } from './Scalar.js' import type { ToJSContext } from './toJS.js' @@ -51,6 +52,30 @@ export class YAMLMap extends Collection { super(MAP, schema) } + /** + * 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) { + const { keepUndefined, replacer } = ctx + const map = new this(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 (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) + } + return map + } + /** * Adds a value to the collection. * diff --git a/src/nodes/YAMLSeq.ts b/src/nodes/YAMLSeq.ts index 6f45d589..922d765e 100644 --- a/src/nodes/YAMLSeq.ts +++ b/src/nodes/YAMLSeq.ts @@ -2,6 +2,7 @@ import type { BlockSequence, FlowCollection } from '../parse/cst.js' import type { Schema } from '../schema/Schema.js' import type { StringifyContext } from '../stringify/stringify.js' import { stringifyCollection } from '../stringify/stringifyCollection.js' +import { createNode, CreateNodeContext } from '../util.js' import { Collection } from './Collection.js' import { isScalar, SEQ } from './identity.js' import type { ParsedNode, Range } from './Node.js' @@ -116,6 +117,22 @@ export class YAMLSeq extends Collection { onComment }) } + + static from(schema: Schema, obj: unknown, ctx: CreateNodeContext) { + const { replacer } = ctx + const seq = new YAMLSeq(schema) + if (obj && Symbol.iterator in Object(obj)) { + let i = 0 + for (let it of obj as Iterable) { + if (typeof replacer === 'function') { + const key = obj instanceof Set ? it : String(i++) + it = replacer.call(obj, key, it) + } + seq.items.push(createNode(it, undefined, ctx)) + } + } + return seq + } } function asItemIndex(key: unknown): number | null { diff --git a/src/schema/common/map.ts b/src/schema/common/map.ts index 82159ed0..aecea3df 100644 --- a/src/schema/common/map.ts +++ b/src/schema/common/map.ts @@ -1,38 +1,15 @@ -import type { CreateNodeContext } from '../../doc/createNode.js' import { isMap } from '../../nodes/identity.js' -import { createPair } from '../../nodes/Pair.js' import { YAMLMap } from '../../nodes/YAMLMap.js' import type { CollectionTag } from '../types.js' -import type { Schema } from '../Schema.js' - -function createMap(schema: Schema, obj: unknown, ctx: CreateNodeContext) { - const { keepUndefined, replacer } = ctx - const map = new YAMLMap(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 (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) - } - return map -} export const map: CollectionTag = { collection: 'map', - createNode: createMap, default: true, nodeClass: YAMLMap, tag: 'tag:yaml.org,2002:map', resolve(map, onError) { if (!isMap(map)) 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 e908a7d6..df70dc7f 100644 --- a/src/schema/common/seq.ts +++ b/src/schema/common/seq.ts @@ -1,33 +1,15 @@ -import { CreateNodeContext, createNode } from '../../doc/createNode.js' import { isSeq } from '../../nodes/identity.js' import { YAMLSeq } from '../../nodes/YAMLSeq.js' -import type { Schema } from '../Schema.js' import type { CollectionTag } from '../types.js' -function createSeq(schema: Schema, obj: unknown, ctx: CreateNodeContext) { - const { replacer } = ctx - const seq = new YAMLSeq(schema) - if (obj && Symbol.iterator in Object(obj)) { - let i = 0 - for (let it of obj as Iterable) { - if (typeof replacer === 'function') { - const key = obj instanceof Set ? it : String(i++) - it = replacer.call(obj, key, it) - } - seq.items.push(createNode(it, undefined, ctx)) - } - } - return seq -} - export const seq: CollectionTag = { collection: 'seq', - createNode: createSeq, default: true, nodeClass: YAMLSeq, tag: 'tag:yaml.org,2002:seq', resolve(seq, onError) { if (!isSeq(seq)) 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 b20be459..611deca6 100644 --- a/src/schema/types.ts +++ b/src/schema/types.ts @@ -92,8 +92,14 @@ export interface CollectionTag extends TagBase { /** * The `Node` child class that implements this tag. * If set, used to select this tag when stringifying. + * + * If the class provides a static `from` method, then that + * will be used if the tag object doesn't have a `createNode` method. */ - nodeClass?: new () => Node + nodeClass?: { + new (): Node + from?: (schema: Schema, obj: unknown, ctx: CreateNodeContext) => Node + } /** * Turns a value into an AST node. diff --git a/src/schema/yaml-1.1/omap.ts b/src/schema/yaml-1.1/omap.ts index 94cc9342..f08b8fd0 100644 --- a/src/schema/yaml-1.1/omap.ts +++ b/src/schema/yaml-1.1/omap.ts @@ -2,6 +2,8 @@ import { isPair, isScalar } from '../../nodes/identity.js' import { toJS, ToJSContext } from '../../nodes/toJS.js' import { YAMLMap } from '../../nodes/YAMLMap.js' import { YAMLSeq } from '../../nodes/YAMLSeq.js' +import { CreateNodeContext } from '../../util.js' +import type { Schema } from '../Schema.js' import { CollectionTag } from '../types.js' import { createPairs, resolvePairs } from './pairs.js' @@ -41,6 +43,13 @@ export class YAMLOMap extends YAMLSeq { } return map as unknown as unknown[] } + + static from(schema: Schema, iterable: unknown, ctx: CreateNodeContext) { + const pairs = createPairs(schema, iterable, ctx) + const omap = new this() + omap.items = pairs.items + return omap + } } export const omap: CollectionTag = { @@ -64,11 +73,5 @@ export const omap: CollectionTag = { } return Object.assign(new YAMLOMap(), pairs) }, - - createNode(schema, iterable, ctx) { - const pairs = createPairs(schema, iterable, ctx) - const omap = new YAMLOMap() - omap.items = pairs.items - return omap - } + createNode: (schema, iterable, ctx) => YAMLOMap.from(schema, iterable, ctx) } diff --git a/src/schema/yaml-1.1/set.ts b/src/schema/yaml-1.1/set.ts index e877527c..33275a99 100644 --- a/src/schema/yaml-1.1/set.ts +++ b/src/schema/yaml-1.1/set.ts @@ -1,10 +1,11 @@ -import type { Schema } from '../../schema/Schema.js' import { isMap, isPair, isScalar } from '../../nodes/identity.js' -import { createPair, Pair } from '../../nodes/Pair.js' +import { Pair } from '../../nodes/Pair.js' import { Scalar } from '../../nodes/Scalar.js' import { ToJSContext } from '../../nodes/toJS.js' -import { YAMLMap, findPair } from '../../nodes/YAMLMap.js' +import { findPair, YAMLMap } from '../../nodes/YAMLMap.js' +import type { Schema } from '../../schema/Schema.js' import type { StringifyContext } from '../../stringify/stringify.js' +import { CreateNodeContext, createPair } from '../../util.js' import type { CollectionTag } from '../types.js' export class YAMLSet extends YAMLMap | null> { @@ -84,6 +85,20 @@ export class YAMLSet extends YAMLMap | null> { ) else throw new Error('Set items must all have null values') } + + static from(schema: Schema, iterable: unknown, ctx: CreateNodeContext) { + const { replacer } = ctx + const set = new this(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> + ) + } + return set + } } export const set: CollectionTag = { @@ -92,26 +107,12 @@ export const set: CollectionTag = { 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 - }, - - createNode(schema, iterable, ctx) { - const { replacer } = ctx - const set = new YAMLSet(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> - ) - } - return set } } From b9a5f07ad7d369e70cd4f8bfbaedd8c57ee7e05a Mon Sep 17 00:00:00 2001 From: isaacs Date: Fri, 21 Apr 2023 10:12:33 -0700 Subject: [PATCH 02/11] chore(YAMLSeq): import from createNode, not utils Prevents circular module reference --- src/nodes/YAMLSeq.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nodes/YAMLSeq.ts b/src/nodes/YAMLSeq.ts index 922d765e..9f935f94 100644 --- a/src/nodes/YAMLSeq.ts +++ b/src/nodes/YAMLSeq.ts @@ -1,8 +1,8 @@ +import { createNode, CreateNodeContext } from '../doc/createNode.js' import type { BlockSequence, FlowCollection } from '../parse/cst.js' import type { Schema } from '../schema/Schema.js' import type { StringifyContext } from '../stringify/stringify.js' import { stringifyCollection } from '../stringify/stringifyCollection.js' -import { createNode, CreateNodeContext } from '../util.js' import { Collection } from './Collection.js' import { isScalar, SEQ } from './identity.js' import type { ParsedNode, Range } from './Node.js' From ca08aa0bb53d3c3ee85a3ed10c9f07ff98c461d9 Mon Sep 17 00:00:00 2001 From: isaacs Date: Fri, 21 Apr 2023 10:16:04 -0700 Subject: [PATCH 03/11] feat(collections): use tagObj.nodeClass if present --- src/compose/resolve-block-map.ts | 7 +++++-- src/compose/resolve-block-seq.ts | 7 +++++-- src/compose/resolve-flow-collection.ts | 10 +++++++--- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/compose/resolve-block-map.ts b/src/compose/resolve-block-map.ts index 8e3a495a..ef923ee7 100644 --- a/src/compose/resolve-block-map.ts +++ b/src/compose/resolve-block-map.ts @@ -2,6 +2,7 @@ import type { ParsedNode } from '../nodes/Node.js' import { Pair } from '../nodes/Pair.js' import { YAMLMap } from '../nodes/YAMLMap.js' import type { BlockMap } from '../parse/cst.js' +import { CollectionTag } from '../schema/types.js' import type { ComposeContext, ComposeNode } from './compose-node.js' import type { ComposeErrorHandler } from './composer.js' import { resolveProps } from './resolve-props.js' @@ -15,9 +16,11 @@ export function resolveBlockMap( { composeNode, composeEmptyNode }: ComposeNode, ctx: ComposeContext, bm: BlockMap, - onError: ComposeErrorHandler + onError: ComposeErrorHandler, + tag?: CollectionTag ) { - const map = new YAMLMap(ctx.schema) + const NodeClass = tag?.nodeClass || YAMLMap + const map = new NodeClass(ctx.schema) as YAMLMap if (ctx.atRoot) ctx.atRoot = false let offset = bm.offset diff --git a/src/compose/resolve-block-seq.ts b/src/compose/resolve-block-seq.ts index a23cf248..6aaa3159 100644 --- a/src/compose/resolve-block-seq.ts +++ b/src/compose/resolve-block-seq.ts @@ -1,5 +1,6 @@ import { YAMLSeq } from '../nodes/YAMLSeq.js' import type { BlockSequence } from '../parse/cst.js' +import { CollectionTag } from '../schema/types.js' import type { ComposeContext, ComposeNode } from './compose-node.js' import type { ComposeErrorHandler } from './composer.js' import { resolveProps } from './resolve-props.js' @@ -9,9 +10,11 @@ export function resolveBlockSeq( { composeNode, composeEmptyNode }: ComposeNode, ctx: ComposeContext, bs: BlockSequence, - onError: ComposeErrorHandler + onError: ComposeErrorHandler, + tag?: CollectionTag ) { - const seq = new YAMLSeq(ctx.schema) + const NodeClass = tag?.nodeClass || YAMLSeq + const seq = new NodeClass(ctx.schema) as YAMLSeq if (ctx.atRoot) ctx.atRoot = false let offset = bs.offset diff --git a/src/compose/resolve-flow-collection.ts b/src/compose/resolve-flow-collection.ts index 64d280b7..5a863eef 100644 --- a/src/compose/resolve-flow-collection.ts +++ b/src/compose/resolve-flow-collection.ts @@ -3,6 +3,7 @@ import { Pair } from '../nodes/Pair.js' import { YAMLMap } from '../nodes/YAMLMap.js' import { YAMLSeq } from '../nodes/YAMLSeq.js' import type { FlowCollection, Token } from '../parse/cst.js' +import { CollectionTag } from '../schema/types.js' import type { ComposeContext, ComposeNode } from './compose-node.js' import type { ComposeErrorHandler } from './composer.js' import { resolveEnd } from './resolve-end.js' @@ -18,13 +19,16 @@ export function resolveFlowCollection( { composeNode, composeEmptyNode }: ComposeNode, ctx: ComposeContext, fc: FlowCollection, - onError: ComposeErrorHandler + onError: ComposeErrorHandler, + tag?: CollectionTag ) { const isMap = fc.start.source === '{' const fcName = isMap ? 'flow map' : 'flow sequence' + const MapClass = tag?.nodeClass || YAMLMap + const SeqClass = tag?.nodeClass || YAMLSeq const coll = isMap - ? (new YAMLMap(ctx.schema) as YAMLMap.Parsed) - : (new YAMLSeq(ctx.schema) as YAMLSeq.Parsed) + ? (new MapClass(ctx.schema) as YAMLMap.Parsed) + : (new SeqClass(ctx.schema) as YAMLSeq.Parsed) coll.flow = true const atRoot = ctx.atRoot if (atRoot) ctx.atRoot = false From 54c0d4d244cece8e02ae5f2e3763f077b4d36863 Mon Sep 17 00:00:00 2001 From: isaacs Date: Fri, 21 Apr 2023 10:28:09 -0700 Subject: [PATCH 04/11] feat: instantiate collection nodes with nodeClass --- src/compose/compose-collection.ts | 113 +++++++++++++++++++++--------- tests/doc/YAML-1.2.spec.ts | 4 +- 2 files changed, 80 insertions(+), 37 deletions(-) diff --git a/src/compose/compose-collection.ts b/src/compose/compose-collection.ts index cb6749ad..3c1e41de 100644 --- a/src/compose/compose-collection.ts +++ b/src/compose/compose-collection.ts @@ -1,8 +1,8 @@ -import { isMap, isNode } from '../nodes/identity.js' +import { isNode } from '../nodes/identity.js' import type { ParsedNode } from '../nodes/Node.js' import { Scalar } from '../nodes/Scalar.js' -import type { YAMLMap } from '../nodes/YAMLMap.js' -import type { YAMLSeq } from '../nodes/YAMLSeq.js' +import { YAMLMap } from '../nodes/YAMLMap.js' +import { YAMLSeq } from '../nodes/YAMLSeq.js' import type { BlockMap, BlockSequence, @@ -16,6 +16,35 @@ import { resolveBlockMap } from './resolve-block-map.js' import { resolveBlockSeq } from './resolve-block-seq.js' import { resolveFlowCollection } from './resolve-flow-collection.js' +function resolveCollection( + CN: ComposeNode, + ctx: ComposeContext, + token: BlockMap | BlockSequence | FlowCollection, + onError: ComposeErrorHandler, + tagName: string | null, + tag?: CollectionTag +) { + const coll = + token.type === 'block-map' + ? resolveBlockMap(CN, ctx, token, onError, tag) + : token.type === 'block-seq' + ? resolveBlockSeq(CN, ctx, token, onError, tag) + : resolveFlowCollection(CN, ctx, token, onError, tag) + + const Coll = coll.constructor as typeof YAMLMap | typeof YAMLSeq + + // If we got a tagName matching the class, or the tag name is '!', + // then use the tagName from the node class used to create it. + if (tagName === '!' || tagName === Coll.tagName) { + coll.tag = Coll.tagName + return coll + } else if (tagName) { + coll.tag = tagName + } + + return coll +} + export function composeCollection( CN: ComposeNode, ctx: ComposeContext, @@ -23,39 +52,50 @@ export function composeCollection( tagToken: SourceToken | null, onError: ComposeErrorHandler ) { + const tagName: string | null = !tagToken + ? null + : ctx.directives.tagName(tagToken.source, msg => + onError(tagToken, 'TAG_RESOLVE_FAILED', msg) + ) + let coll: YAMLMap.Parsed | YAMLSeq.Parsed - switch (token.type) { - case 'block-map': { - coll = resolveBlockMap(CN, ctx, token, onError) - break - } - case 'block-seq': { - coll = resolveBlockSeq(CN, ctx, token, onError) - break - } - case 'flow-collection': { - coll = resolveFlowCollection(CN, ctx, token, onError) - break - } - } + let expType: 'map' | 'seq' | undefined = + token.type === 'block-map' + ? 'map' + : token.type === 'block-seq' + ? 'seq' + : token.type === 'flow-collection' + ? token.start.source === '{' + ? 'map' + : 'seq' + : undefined - if (!tagToken) return coll - const tagName = ctx.directives.tagName(tagToken.source, msg => - onError(tagToken, 'TAG_RESOLVE_FAILED', msg) - ) - if (!tagName) return coll + if (!expType) { + onError( + token, + 'IMPOSSIBLE', + 'could not determine collection expression type', + true + ) + } - // Cast needed due to: https://github.com/Microsoft/TypeScript/issues/3841 - const Coll = coll.constructor as typeof YAMLMap | typeof YAMLSeq - if (tagName === '!' || tagName === Coll.tagName) { - coll.tag = Coll.tagName - return coll + // shortcut: check if it's a generic YAMLMap or YAMLSeq + // before jumping into the custom tag logic. + if ( + !tagToken || + !tagName || + tagName === '!' || + (tagName === YAMLMap.tagName && expType === 'map') || + (tagName === YAMLSeq.tagName && expType === 'seq') || + !expType + ) { + return resolveCollection(CN, ctx, token, onError, tagName) } - const expType = isMap(coll) ? 'map' : 'seq' let tag = ctx.schema.tags.find( t => t.collection === expType && t.tag === tagName ) as CollectionTag | undefined + if (!tag) { const kt = ctx.schema.knownTags[tagName] if (kt && kt.collection === expType) { @@ -68,16 +108,19 @@ export function composeCollection( `Unresolved tag: ${tagName}`, true ) - coll.tag = tagName - return coll + return resolveCollection(CN, ctx, token, onError, tagName) } } - const res = tag.resolve( - coll, - msg => onError(tagToken, 'TAG_RESOLVE_FAILED', msg), - ctx.options - ) + coll = resolveCollection(CN, ctx, token, onError, tagName, tag) + + const res = + tag.resolve?.( + coll, + msg => onError(tagToken, 'TAG_RESOLVE_FAILED', msg), + ctx.options + ) ?? coll + const node = isNode(res) ? (res as ParsedNode) : (new Scalar(res) as Scalar.Parsed) diff --git a/tests/doc/YAML-1.2.spec.ts b/tests/doc/YAML-1.2.spec.ts index d6c08748..ee9f756c 100644 --- a/tests/doc/YAML-1.2.spec.ts +++ b/tests/doc/YAML-1.2.spec.ts @@ -1827,8 +1827,8 @@ for (const section in spec) { expect(doc.errors.map(err => err.message)).toMatchObject( errors?.[i] ?? [] ) - expect(doc.warnings.map(err => err.message)).toMatchObject( - warnings?.[i] ?? [] + expect(new Set(doc.warnings.map(err => err.message))).toMatchObject( + new Set(warnings?.[i] ?? []) ) for (const err of doc.errors.concat(doc.warnings)) expect(err).toBeInstanceOf(YAMLError) From 7ba6f98552c8fd5680dda9229feb44be3ffe32de Mon Sep 17 00:00:00 2001 From: isaacs Date: Fri, 21 Apr 2023 16:14:55 -0700 Subject: [PATCH 05/11] feat: custom map test, collection type match error --- src/compose/compose-collection.ts | 39 +++++++++++++++++++------ src/compose/resolve-flow-collection.ts | 5 ++-- src/errors.ts | 1 + src/schema/types.ts | 8 ++++-- src/schema/yaml-1.1/timestamp.ts | 2 +- tests/doc/types.ts | 40 ++++++++++++++++++++++++-- 6 files changed, 78 insertions(+), 17 deletions(-) diff --git a/src/compose/compose-collection.ts b/src/compose/compose-collection.ts index 3c1e41de..3c21b44d 100644 --- a/src/compose/compose-collection.ts +++ b/src/compose/compose-collection.ts @@ -92,9 +92,21 @@ export function composeCollection( return resolveCollection(CN, ctx, token, onError, tagName) } - let tag = ctx.schema.tags.find( - t => t.collection === expType && t.tag === tagName - ) as CollectionTag | undefined + let tag = ctx.schema.tags.find(t => t.tag === tagName) as + | CollectionTag + | undefined + if (tag && tag.collection !== expType) { + if (tag.collection) { + onError( + tagToken, + 'BAD_COLLECTION_TYPE', + `${tag.tag} used for ${expType} collection, but expects ${tag.collection}`, + true + ) + return resolveCollection(CN, ctx, token, onError, tagName) + } + tag = undefined + } if (!tag) { const kt = ctx.schema.knownTags[tagName] @@ -102,12 +114,21 @@ export function composeCollection( ctx.schema.tags.push(Object.assign({}, kt, { default: false })) tag = kt } else { - onError( - tagToken, - 'TAG_RESOLVE_FAILED', - `Unresolved tag: ${tagName}`, - true - ) + if (kt?.collection) { + onError( + tagToken, + 'BAD_COLLECTION_TYPE', + `${kt.tag} used for ${expType} collection, but expects ${kt.collection}`, + true + ) + } else { + onError( + tagToken, + 'TAG_RESOLVE_FAILED', + `Unresolved tag: ${tagName}`, + true + ) + } return resolveCollection(CN, ctx, token, onError, tagName) } } diff --git a/src/compose/resolve-flow-collection.ts b/src/compose/resolve-flow-collection.ts index 5a863eef..10377ac9 100644 --- a/src/compose/resolve-flow-collection.ts +++ b/src/compose/resolve-flow-collection.ts @@ -24,8 +24,9 @@ export function resolveFlowCollection( ) { const isMap = fc.start.source === '{' const fcName = isMap ? 'flow map' : 'flow sequence' - const MapClass = tag?.nodeClass || YAMLMap - const SeqClass = tag?.nodeClass || YAMLSeq + let NodeClass = tag?.nodeClass + const MapClass = NodeClass || YAMLMap + const SeqClass = NodeClass || YAMLSeq const coll = isMap ? (new MapClass(ctx.schema) as YAMLMap.Parsed) : (new SeqClass(ctx.schema) as YAMLSeq.Parsed) diff --git a/src/errors.ts b/src/errors.ts index a1db8a9c..5a617425 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -21,6 +21,7 @@ export type ErrorCode = | 'TAB_AS_INDENT' | 'TAG_RESOLVE_FAILED' | 'UNEXPECTED_TOKEN' + | 'BAD_COLLECTION_TYPE' export type LinePos = { line: number; col: number } diff --git a/src/schema/types.ts b/src/schema/types.ts index 611deca6..d1cf15e1 100644 --- a/src/schema/types.ts +++ b/src/schema/types.ts @@ -1,11 +1,11 @@ import type { CreateNodeContext } from '../doc/createNode.js' -import type { Schema } from './Schema.js' import type { Node } from '../nodes/Node.js' import type { Scalar } from '../nodes/Scalar.js' import type { YAMLMap } from '../nodes/YAMLMap.js' import type { YAMLSeq } from '../nodes/YAMLSeq.js' import type { ParseOptions } from '../options.js' import type { StringifyContext } from '../stringify/stringify.js' +import type { Schema } from './Schema.js' interface TagBase { /** @@ -104,10 +104,12 @@ export interface CollectionTag extends TagBase { /** * Turns a value into an AST node. * If returning a non-`Node` value, the output will be wrapped as a `Scalar`. + * + * Note: this is required if nodeClass is not provided. */ - resolve( + resolve?: ( value: YAMLMap.Parsed | YAMLSeq.Parsed, onError: (message: string) => void, options: ParseOptions - ): unknown + ) => unknown } diff --git a/src/schema/yaml-1.1/timestamp.ts b/src/schema/yaml-1.1/timestamp.ts index bfe1f2e7..026c9efb 100644 --- a/src/schema/yaml-1.1/timestamp.ts +++ b/src/schema/yaml-1.1/timestamp.ts @@ -47,7 +47,7 @@ function stringifySexagesimal(node: Scalar) { return ( sign + parts - .map(n => (n < 10 ? '0' + String(n) : String(n))) + .map(n => String(n).padStart(2, '0')) //(n < 10 ? '0' + String(n) : String(n))) .join(':') .replace(/000000\d*$/, '') // % 60 may introduce error ) diff --git a/tests/doc/types.ts b/tests/doc/types.ts index b083ebc4..06217d22 100644 --- a/tests/doc/types.ts +++ b/tests/doc/types.ts @@ -1,4 +1,5 @@ import { + CollectionTag, Document, DocumentOptions, Node, @@ -12,9 +13,10 @@ import { SchemaOptions, stringify, YAMLMap, + YAMLParseError, YAMLSeq } from 'yaml' -import { seqTag, stringTag, stringifyString } from 'yaml/util' +import { seqTag, stringifyString, stringTag } from 'yaml/util' import { source } from '../_utils' const parseDocument = ( @@ -491,7 +493,7 @@ description: for (let i = 0; i < generic.length; ++i) genericStr += String.fromCharCode(generic[i]) expect(canonicalStr).toBe(genericStr) - expect(canonicalStr.substr(0, 5)).toBe('GIF89') + expect(canonicalStr.substring(0, 5)).toBe('GIF89') expect(String(doc)) .toBe(`canonical: !!binary "R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J\\ +fn5OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/++f/++f/++f/\\ @@ -981,6 +983,25 @@ describe('custom tags', () => { } } + class YAMLNullObject extends YAMLMap { + tag: string = '!nullobject' + toJSON(_?: unknown, ctx?: any): any { + const obj = super.toJSON>( + _, + { ...(ctx || {}), mapAsMap: false }, + Object + ) + return Object.assign(Object.create(null), obj) + } + } + const nullObject: CollectionTag = { + tag: '!nullobject', + collection: 'map', + identify: (value: any) => + !!value && typeof value === 'object' && !Object.getPrototypeOf(value), + nodeClass: YAMLNullObject + } + describe('RegExp', () => { test('stringify as plain scalar', () => { const str = stringify(/re/g, { customTags: [regexp] }) @@ -1025,6 +1046,21 @@ describe('custom tags', () => { }) }) + test('null prototyped object', () => { + const obj = Object.assign(Object.create(null), { x: 'y', z: 1 }) + const str = stringify(obj, { customTags: [nullObject] }) + expect(str).toBe('!nullobject\nx: y\nz: 1\n') + const res = parse(str, { customTags: [nullObject] }) + expect(Object.getPrototypeOf(res)).toBe(null) + expect(res).toMatchObject(obj) + }) + + test('cannot parse sequence as nullobject', () => { + const str = '!nullobject\n- y\n- 1\n' + const doc = origParseDocument(str) + expect(doc.warnings).toMatchObject([{ code: 'TAG_RESOLVE_FAILED' }]) + }) + test('array within customTags', () => { const obj = { re: /re/g, symbol: Symbol.for('foo') } // @ts-expect-error TS should complain here From a87d40bc706332f23665d2561f9ee9e0c8175313 Mon Sep 17 00:00:00 2001 From: isaacs Date: Fri, 21 Apr 2023 16:32:31 -0700 Subject: [PATCH 06/11] feat(docs): add collection examples to custom tag --- docs/06_custom_tags.md | 90 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 3 deletions(-) diff --git a/docs/06_custom_tags.md b/docs/06_custom_tags.md index c7c6425c..15bbe6a8 100644 --- a/docs/06_custom_tags.md +++ b/docs/06_custom_tags.md @@ -65,7 +65,7 @@ These tags are a part of the YAML 1.1 [language-independent types](https://yaml. ## Writing Custom Tags ```js -import { stringify } from 'yaml' +import { YAMLMap, stringify } from 'yaml' import { stringifyString } from 'yaml/util' const regexp = { @@ -89,18 +89,102 @@ const sharedSymbol = { } } +class YAMLNullObject extends YAMLMap { + tag = '!nullobject' + toJSON(_, ctx) { + const obj = super.toJSON(_, { ...ctx, mapAsMap: false }, Object) + return Object.assign(Object.create(null), obj) + } +} + +const nullObject = { + tag: '!nullobject', + collection: 'map', + nodeClass: YAMLNullObject, + identify: v => !!( + typeof v === 'object' && + v && + !Object.getPrototypeOf(v) + ) +} + +// slightly more complicated object type +class YAMLError extends YAMLMap { + tag = '!error' + toJSON(_, ctx) { + const { name, message, stack, ...rest } = super.toJSON(_, { + ...ctx, + mapAsMap: false, + }, Object) + // craft the appropriate error type + const Cls = + name === 'EvalError' ? EvalError + : name === 'RangeError' ? RangeError + : name === 'ReferenceError' ? ReferenceError + : name === 'SyntaxError' ? SyntaxError + : name === 'TypeError' ? TypeError + : name === 'URIError' ? URIError + : Error + if (Cls.name !== name) { + Object.defineProperty(er, 'name', { + value: name, + enumerable: false, + configurable: true, + }) + } + Object.defineProperty(er, 'stack', { + value: stack, + enumerable: false, + configurable: true, + }) + return Object.assign(er, rest) + } + + static from (schema, obj, ctx) { + const { name, message, stack } = obj + // ensure these props remain, even if not enumerable + return super.from(schema, { ...obj, name, message, stack }, ctx) + } +} + +const error = { + tag: '!error', + collection: 'map', + nodeClass: YAMLError, + identify: v => !!( + typeof v === 'object' && + v && + v instanceof Error + ) +} + stringify( - { regexp: /foo/gi, symbol: Symbol.for('bar') }, - { customTags: [regexp, sharedSymbol] } + { + regexp: /foo/gi, + symbol: Symbol.for('bar'), + nullobj: Object.assign(Object.create(null), { a: 1, b: 2 }), + error: new Error('This was an error'), + }, + { customTags: [regexp, sharedSymbol, nullObject, error] } ) // regexp: !re /foo/gi // symbol: !symbol/shared bar +// nullobj: !nullobject +// a: 1 +// b: 2 +// error: !error +// name: Error +// message: 'This was an error' +// stack: | +// at some-file.js:1:3 ``` In YAML-speak, a custom data type is represented by a _tag_. To define your own tag, you need to account for the ways that your data is both parsed and stringified. Furthermore, both of those processes are split into two stages by the intermediate AST node structure. If you wish to implement your own custom tags, the [`!!binary`](https://github.com/eemeli/yaml/blob/main/src/schema/yaml-1.1/binary.ts) and [`!!set`](https://github.com/eemeli/yaml/blob/main/src/schema/yaml-1.1/set.ts) tags provide relatively cohesive examples to study in addition to the simple examples in the sidebar here. +Custom collection types (ie, Maps, Sets, objects, and arrays; anything with child properties that may not be propertly serialized to a scalar value) may provide a `nodeClass` property that extends the [`YAMLMap`](https://github.com/eemeli/yaml/blob/main/src/nodes/YAMLMap.ts) and [`YAMLSeq`](https://github.com/eemeli/yaml/blob/main/src/nodes/YAMLSeq.ts) classes, which will be used for parsing and stringifying objects with the specified tag. + ### Parsing Custom Data At the lowest level, the [`Lexer`](#lexer) and [`Parser`](#parser) will take care of turning string input into a concrete syntax tree (CST). From 63fbadb7ee66212602bbf12679eb614511add977 Mon Sep 17 00:00:00 2001 From: isaacs Date: Fri, 21 Apr 2023 16:59:32 -0700 Subject: [PATCH 07/11] fix: remove unused variable in types test --- tests/doc/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/doc/types.ts b/tests/doc/types.ts index 06217d22..c03668be 100644 --- a/tests/doc/types.ts +++ b/tests/doc/types.ts @@ -13,7 +13,6 @@ import { SchemaOptions, stringify, YAMLMap, - YAMLParseError, YAMLSeq } from 'yaml' import { seqTag, stringifyString, stringTag } from 'yaml/util' From b963fc75dd4e45e7841a0fab9362bff7befc0a49 Mon Sep 17 00:00:00 2001 From: isaacs Date: Sat, 29 Apr 2023 13:36:24 -0700 Subject: [PATCH 08/11] chore: code style Updates from PR review --- src/compose/compose-collection.ts | 7 ++----- src/compose/resolve-flow-collection.ts | 10 ++++------ src/schema/yaml-1.1/timestamp.ts | 2 +- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/compose/compose-collection.ts b/src/compose/compose-collection.ts index 3c21b44d..a287bf1a 100644 --- a/src/compose/compose-collection.ts +++ b/src/compose/compose-collection.ts @@ -38,10 +38,8 @@ function resolveCollection( if (tagName === '!' || tagName === Coll.tagName) { coll.tag = Coll.tagName return coll - } else if (tagName) { - coll.tag = tagName } - + if (tagName) coll.tag = tagName return coll } @@ -58,7 +56,6 @@ export function composeCollection( onError(tagToken, 'TAG_RESOLVE_FAILED', msg) ) - let coll: YAMLMap.Parsed | YAMLSeq.Parsed let expType: 'map' | 'seq' | undefined = token.type === 'block-map' ? 'map' @@ -133,7 +130,7 @@ export function composeCollection( } } - coll = resolveCollection(CN, ctx, token, onError, tagName, tag) + const coll = resolveCollection(CN, ctx, token, onError, tagName, tag) const res = tag.resolve?.( diff --git a/src/compose/resolve-flow-collection.ts b/src/compose/resolve-flow-collection.ts index 10377ac9..33f7b2ba 100644 --- a/src/compose/resolve-flow-collection.ts +++ b/src/compose/resolve-flow-collection.ts @@ -24,12 +24,10 @@ export function resolveFlowCollection( ) { const isMap = fc.start.source === '{' const fcName = isMap ? 'flow map' : 'flow sequence' - let NodeClass = tag?.nodeClass - const MapClass = NodeClass || YAMLMap - const SeqClass = NodeClass || YAMLSeq - const coll = isMap - ? (new MapClass(ctx.schema) as YAMLMap.Parsed) - : (new SeqClass(ctx.schema) as YAMLSeq.Parsed) + let NodeClass = (tag?.nodeClass ?? (isMap ? YAMLMap : YAMLSeq)) as { + new (ctx: ComposeContext['schema']): YAMLMap.Parsed | YAMLSeq.Parsed + } + const coll = new NodeClass(ctx.schema) coll.flow = true const atRoot = ctx.atRoot if (atRoot) ctx.atRoot = false diff --git a/src/schema/yaml-1.1/timestamp.ts b/src/schema/yaml-1.1/timestamp.ts index 026c9efb..2093f3ae 100644 --- a/src/schema/yaml-1.1/timestamp.ts +++ b/src/schema/yaml-1.1/timestamp.ts @@ -47,7 +47,7 @@ function stringifySexagesimal(node: Scalar) { return ( sign + parts - .map(n => String(n).padStart(2, '0')) //(n < 10 ? '0' + String(n) : String(n))) + .map(n => String(n).padStart(2, '0')) .join(':') .replace(/000000\d*$/, '') // % 60 may introduce error ) From 524e1322c4b0ef54f660ac76464df3b8426c2478 Mon Sep 17 00:00:00 2001 From: isaacs Date: Mon, 1 May 2023 17:05:26 -0700 Subject: [PATCH 09/11] fix: various code review fixes - remove unnecessary check for undeterminable expType - lookup tag by both tagName and collection type - clearer NodeClass typecast - specify schema param to NodeClass constructor --- src/compose/compose-collection.ts | 35 +++++--------------------- src/compose/resolve-flow-collection.ts | 3 ++- src/schema/types.ts | 2 +- 3 files changed, 9 insertions(+), 31 deletions(-) diff --git a/src/compose/compose-collection.ts b/src/compose/compose-collection.ts index a287bf1a..eef9559a 100644 --- a/src/compose/compose-collection.ts +++ b/src/compose/compose-collection.ts @@ -61,20 +61,9 @@ export function composeCollection( ? 'map' : token.type === 'block-seq' ? 'seq' - : token.type === 'flow-collection' - ? token.start.source === '{' - ? 'map' - : 'seq' - : undefined - - if (!expType) { - onError( - token, - 'IMPOSSIBLE', - 'could not determine collection expression type', - true - ) - } + : token.start.source === '{' + ? 'map' + : 'seq' // shortcut: check if it's a generic YAMLMap or YAMLSeq // before jumping into the custom tag logic. @@ -89,21 +78,9 @@ export function composeCollection( return resolveCollection(CN, ctx, token, onError, tagName) } - let tag = ctx.schema.tags.find(t => t.tag === tagName) as - | CollectionTag - | undefined - if (tag && tag.collection !== expType) { - if (tag.collection) { - onError( - tagToken, - 'BAD_COLLECTION_TYPE', - `${tag.tag} used for ${expType} collection, but expects ${tag.collection}`, - true - ) - return resolveCollection(CN, ctx, token, onError, tagName) - } - tag = undefined - } + let tag = ctx.schema.tags.find( + t => t.tag === tagName && t.collection === expType + ) as CollectionTag | undefined if (!tag) { const kt = ctx.schema.knownTags[tagName] diff --git a/src/compose/resolve-flow-collection.ts b/src/compose/resolve-flow-collection.ts index 33f7b2ba..c9a3a6a6 100644 --- a/src/compose/resolve-flow-collection.ts +++ b/src/compose/resolve-flow-collection.ts @@ -3,6 +3,7 @@ import { Pair } from '../nodes/Pair.js' import { YAMLMap } from '../nodes/YAMLMap.js' import { YAMLSeq } from '../nodes/YAMLSeq.js' import type { FlowCollection, Token } from '../parse/cst.js' +import { Schema } from '../schema/Schema.js' import { CollectionTag } from '../schema/types.js' import type { ComposeContext, ComposeNode } from './compose-node.js' import type { ComposeErrorHandler } from './composer.js' @@ -25,7 +26,7 @@ export function resolveFlowCollection( const isMap = fc.start.source === '{' const fcName = isMap ? 'flow map' : 'flow sequence' let NodeClass = (tag?.nodeClass ?? (isMap ? YAMLMap : YAMLSeq)) as { - new (ctx: ComposeContext['schema']): YAMLMap.Parsed | YAMLSeq.Parsed + new (schema: Schema): YAMLMap.Parsed | YAMLSeq.Parsed } const coll = new NodeClass(ctx.schema) coll.flow = true diff --git a/src/schema/types.ts b/src/schema/types.ts index d1cf15e1..1bccc0bc 100644 --- a/src/schema/types.ts +++ b/src/schema/types.ts @@ -97,7 +97,7 @@ export interface CollectionTag extends TagBase { * will be used if the tag object doesn't have a `createNode` method. */ nodeClass?: { - new (): Node + new (schema?: Schema): Node from?: (schema: Schema, obj: unknown, ctx: CreateNodeContext) => Node } From 490842cd0a0ed65205678ea13fe022f73b9739cf Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sat, 6 May 2023 11:31:19 +0300 Subject: [PATCH 10/11] chore: Satisfy ESLint & Prettier --- docs/06_custom_tags.md | 47 +++++++++++++------------- src/compose/compose-collection.ts | 2 +- src/compose/resolve-block-map.ts | 2 +- src/compose/resolve-block-seq.ts | 2 +- src/compose/resolve-flow-collection.ts | 2 +- src/schema/common/map.ts | 2 +- src/schema/common/seq.ts | 2 +- 7 files changed, 29 insertions(+), 30 deletions(-) diff --git a/docs/06_custom_tags.md b/docs/06_custom_tags.md index 15bbe6a8..9bae8d09 100644 --- a/docs/06_custom_tags.md +++ b/docs/06_custom_tags.md @@ -101,46 +101,49 @@ const nullObject = { tag: '!nullobject', collection: 'map', nodeClass: YAMLNullObject, - identify: v => !!( - typeof v === 'object' && - v && - !Object.getPrototypeOf(v) - ) + identify: v => !!(typeof v === 'object' && v && !Object.getPrototypeOf(v)) } // slightly more complicated object type class YAMLError extends YAMLMap { tag = '!error' toJSON(_, ctx) { - const { name, message, stack, ...rest } = super.toJSON(_, { - ...ctx, - mapAsMap: false, - }, Object) + const { name, message, stack, ...rest } = super.toJSON( + _, + { ...ctx, mapAsMap: false }, + Object + ) // craft the appropriate error type const Cls = - name === 'EvalError' ? EvalError - : name === 'RangeError' ? RangeError - : name === 'ReferenceError' ? ReferenceError - : name === 'SyntaxError' ? SyntaxError - : name === 'TypeError' ? TypeError - : name === 'URIError' ? URIError + name === 'EvalError' + ? EvalError + : name === 'RangeError' + ? RangeError + : name === 'ReferenceError' + ? ReferenceError + : name === 'SyntaxError' + ? SyntaxError + : name === 'TypeError' + ? TypeError + : name === 'URIError' + ? URIError : Error if (Cls.name !== name) { Object.defineProperty(er, 'name', { value: name, enumerable: false, - configurable: true, + configurable: true }) } Object.defineProperty(er, 'stack', { value: stack, enumerable: false, - configurable: true, + configurable: true }) return Object.assign(er, rest) } - static from (schema, obj, ctx) { + static from(schema, obj, ctx) { const { name, message, stack } = obj // ensure these props remain, even if not enumerable return super.from(schema, { ...obj, name, message, stack }, ctx) @@ -151,11 +154,7 @@ const error = { tag: '!error', collection: 'map', nodeClass: YAMLError, - identify: v => !!( - typeof v === 'object' && - v && - v instanceof Error - ) + identify: v => !!(typeof v === 'object' && v && v instanceof Error) } stringify( @@ -163,7 +162,7 @@ stringify( regexp: /foo/gi, symbol: Symbol.for('bar'), nullobj: Object.assign(Object.create(null), { a: 1, b: 2 }), - error: new Error('This was an error'), + error: new Error('This was an error') }, { customTags: [regexp, sharedSymbol, nullObject, error] } ) diff --git a/src/compose/compose-collection.ts b/src/compose/compose-collection.ts index eef9559a..3cd8e756 100644 --- a/src/compose/compose-collection.ts +++ b/src/compose/compose-collection.ts @@ -56,7 +56,7 @@ export function composeCollection( onError(tagToken, 'TAG_RESOLVE_FAILED', msg) ) - let expType: 'map' | 'seq' | undefined = + const expType: 'map' | 'seq' = token.type === 'block-map' ? 'map' : token.type === 'block-seq' diff --git a/src/compose/resolve-block-map.ts b/src/compose/resolve-block-map.ts index ef923ee7..e0eef21d 100644 --- a/src/compose/resolve-block-map.ts +++ b/src/compose/resolve-block-map.ts @@ -19,7 +19,7 @@ export function resolveBlockMap( onError: ComposeErrorHandler, tag?: CollectionTag ) { - const NodeClass = tag?.nodeClass || YAMLMap + const NodeClass = tag?.nodeClass ?? YAMLMap const map = new NodeClass(ctx.schema) as YAMLMap if (ctx.atRoot) ctx.atRoot = false diff --git a/src/compose/resolve-block-seq.ts b/src/compose/resolve-block-seq.ts index 6aaa3159..6a1f84e7 100644 --- a/src/compose/resolve-block-seq.ts +++ b/src/compose/resolve-block-seq.ts @@ -13,7 +13,7 @@ export function resolveBlockSeq( onError: ComposeErrorHandler, tag?: CollectionTag ) { - const NodeClass = tag?.nodeClass || YAMLSeq + const NodeClass = tag?.nodeClass ?? YAMLSeq const seq = new NodeClass(ctx.schema) as YAMLSeq if (ctx.atRoot) ctx.atRoot = false diff --git a/src/compose/resolve-flow-collection.ts b/src/compose/resolve-flow-collection.ts index c9a3a6a6..5b418d72 100644 --- a/src/compose/resolve-flow-collection.ts +++ b/src/compose/resolve-flow-collection.ts @@ -25,7 +25,7 @@ export function resolveFlowCollection( ) { const isMap = fc.start.source === '{' const fcName = isMap ? 'flow map' : 'flow sequence' - let NodeClass = (tag?.nodeClass ?? (isMap ? YAMLMap : YAMLSeq)) as { + const NodeClass = (tag?.nodeClass ?? (isMap ? YAMLMap : YAMLSeq)) as { new (schema: Schema): YAMLMap.Parsed | YAMLSeq.Parsed } const coll = new NodeClass(ctx.schema) diff --git a/src/schema/common/map.ts b/src/schema/common/map.ts index aecea3df..8583d553 100644 --- a/src/schema/common/map.ts +++ b/src/schema/common/map.ts @@ -11,5 +11,5 @@ export const map: CollectionTag = { if (!isMap(map)) onError('Expected a mapping for this tag') return map }, - createNode: (schema, obj, ctx) => YAMLMap.from(schema, obj, ctx), + createNode: (schema, obj, ctx) => YAMLMap.from(schema, obj, ctx) } diff --git a/src/schema/common/seq.ts b/src/schema/common/seq.ts index df70dc7f..8bdcab74 100644 --- a/src/schema/common/seq.ts +++ b/src/schema/common/seq.ts @@ -11,5 +11,5 @@ export const seq: CollectionTag = { if (!isSeq(seq)) onError('Expected a sequence for this tag') return seq }, - createNode: (schema, obj, ctx) => YAMLSeq.from(schema, obj, ctx), + createNode: (schema, obj, ctx) => YAMLSeq.from(schema, obj, ctx) } From 3b9b19494e3a3f3f876adfe95b78409dbebd13ab Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sat, 6 May 2023 20:26:00 +0300 Subject: [PATCH 11/11] Update src/nodes/YAMLSeq.ts --- src/nodes/YAMLSeq.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nodes/YAMLSeq.ts b/src/nodes/YAMLSeq.ts index 9f935f94..ada8c488 100644 --- a/src/nodes/YAMLSeq.ts +++ b/src/nodes/YAMLSeq.ts @@ -120,7 +120,7 @@ export class YAMLSeq extends Collection { static from(schema: Schema, obj: unknown, ctx: CreateNodeContext) { const { replacer } = ctx - const seq = new YAMLSeq(schema) + const seq = new this(schema) if (obj && Symbol.iterator in Object(obj)) { let i = 0 for (let it of obj as Iterable) {