From b67ffa8a115cd85c08097cbb6cee9ea6d8e92541 Mon Sep 17 00:00:00 2001 From: isaacs Date: Thu, 20 Apr 2023 10:40:32 -0700 Subject: [PATCH] move tagObj.createNode into nodeClass.from static method 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 | 24 ------------------------ src/schema/common/seq.ts | 19 ------------------- src/schema/types.ts | 8 +++++++- src/schema/yaml-1.1/omap.ts | 16 +++++++++------- src/schema/yaml-1.1/set.ts | 35 ++++++++++++++++++----------------- 8 files changed, 79 insertions(+), 69 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..6db66863 100644 --- a/src/schema/common/map.ts +++ b/src/schema/common/map.ts @@ -1,33 +1,9 @@ -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', diff --git a/src/schema/common/seq.ts b/src/schema/common/seq.ts index e908a7d6..bbf84201 100644 --- a/src/schema/common/seq.ts +++ b/src/schema/common/seq.ts @@ -1,28 +1,9 @@ -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', 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..3778a649 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 = { @@ -63,12 +72,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 } } diff --git a/src/schema/yaml-1.1/set.ts b/src/schema/yaml-1.1/set.ts index e877527c..929c7557 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 = { @@ -99,19 +114,5 @@ export const set: CollectionTag = { 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 } }