diff --git a/src/ast/Collection.js b/src/ast/Collection.js index f4aee56b..6965cec5 100644 --- a/src/ast/Collection.js +++ b/src/ast/Collection.js @@ -13,6 +13,9 @@ function collectionFromPath(schema, path, value) { v = o } return createNode(v, null, { + onAlias() { + throw new Error('Repeated objects are not supported here') + }, prevObjects: new Map(), schema, wrapScalars: false diff --git a/src/doc/Document.js b/src/doc/Document.js index 31808f67..58815c3e 100644 --- a/src/doc/Document.js +++ b/src/doc/Document.js @@ -1,4 +1,5 @@ import { + Alias, Collection, Node, Pair, @@ -62,15 +63,20 @@ export class Document { createNode(value, { onTagObj, tag, wrapScalars } = {}) { this.setSchema() + const aliasNodes = [] const ctx = { - aliasNodes: [], + onAlias(source) { + const alias = new Alias(source) + aliasNodes.push(alias) + return alias + }, onTagObj, prevObjects: new Map(), schema: this.schema, wrapScalars: wrapScalars !== false } const node = createNode(value, tag, ctx) - for (const alias of ctx.aliasNodes) { + for (const alias of aliasNodes) { // 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. diff --git a/src/doc/Schema.js b/src/doc/Schema.js index 47bae1cd..50bd3f1d 100644 --- a/src/doc/Schema.js +++ b/src/doc/Schema.js @@ -16,9 +16,15 @@ export class Schema { constructor({ customTags, merge, resolveKnownTags, schema, sortMapEntries }) { this.merge = !!merge this.name = schema + this.knownTags = resolveKnownTags ? coreKnownTags : {} + this.tags = getSchemaTags(schemas, tags, customTags, schema) + + // Used by createNode(), to avoid circular dependencies + this.map = tags.map + this.seq = tags.seq + + // Used by createMap() this.sortMapEntries = sortMapEntries === true ? sortMapEntriesByKey : sortMapEntries || null - this.tags = getSchemaTags(schemas, tags, customTags, schema) - this.knownTags = resolveKnownTags ? coreKnownTags : {} } } diff --git a/src/doc/createNode.js b/src/doc/createNode.js index 66d5bbe7..48d9b7aa 100644 --- a/src/doc/createNode.js +++ b/src/doc/createNode.js @@ -1,9 +1,6 @@ -import { Alias } from '../ast/Alias.js' import { Node } from '../ast/Node.js' import { Scalar } from '../ast/Scalar.js' import { defaultTagPrefix } from '../constants.js' -import { map } from '../tags/failsafe/map.js' -import { seq } from '../tags/failsafe/seq.js' function findTagObject(value, tagName, tags) { if (tagName) { @@ -17,11 +14,12 @@ function findTagObject(value, tagName, tags) { export function createNode(value, tagName, ctx) { if (value instanceof Node) return value - const { aliasNodes, onTagObj, prevObjects, schema, wrapScalars } = ctx + const { onAlias, onTagObj, prevObjects, wrapScalars } = ctx + const { map, seq, tags } = ctx.schema if (tagName && tagName.startsWith('!!')) tagName = defaultTagPrefix + tagName.slice(2) - let tagObj = findTagObject(value, tagName, schema.tags) + let tagObj = findTagObject(value, tagName, tags) if (!tagObj) { if (typeof value.toJSON === 'function') value = value.toJSON() if (typeof value !== 'object') @@ -36,15 +34,9 @@ export function createNode(value, tagName, ctx) { // Detect duplicate references to the same object & use Alias nodes for all // after first. The `obj` wrapper allows for circular references to resolve. const obj = {} - if (value && typeof value === 'object' && prevObjects) { + if (value && typeof value === 'object') { const prev = prevObjects.get(value) - if (prev) { - if (!aliasNodes) - throw new Error('Circular references are not supported here') - const alias = new Alias(prev) // leaves source dirty; must be cleaned by caller - aliasNodes.push(alias) - return alias - } + if (prev) return onAlias(prev) obj.value = value prevObjects.set(value, obj) } diff --git a/src/index.js b/src/index.js index 808cd373..74beba3c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,12 @@ import { parse as parseCST } from './cst/parse.js' -import { createNode as _createNode } from './doc/createNode.js' +import { createNode } from './doc/createNode.js' import { Document as YAMLDocument } from './doc/Document.js' import { Schema } from './doc/Schema.js' import { YAMLSemanticError } from './errors.js' import { defaultOptions, scalarOptions } from './options.js' import { warn } from './warnings.js' -function createNode(value, wrapScalars = true, tag) { +function _createNode(value, wrapScalars = true, tag) { if (tag === undefined && typeof wrapScalars === 'string') { tag = wrapScalars wrapScalars = true @@ -17,11 +17,14 @@ function createNode(value, wrapScalars = true, tag) { defaultOptions ) const ctx = { + onAlias() { + throw new Error('Repeated objects are not supported here') + }, prevObjects: new Map(), schema: new Schema(options), wrapScalars } - return _createNode(value, tag, ctx) + return createNode(value, tag, ctx) } class Document extends YAMLDocument { @@ -66,7 +69,7 @@ function stringify(value, options) { } export const YAML = { - createNode, + createNode: _createNode, defaultOptions, Document, parse, diff --git a/tests/doc/createNode.js b/tests/doc/createNode.js index 7a5c7fad..e1308151 100644 --- a/tests/doc/createNode.js +++ b/tests/doc/createNode.js @@ -361,7 +361,7 @@ describe('circular references', () => { a1: { items: [{ key: 'foo' }, { key: 'map' }] } }) expect(() => YAML.createNode(map)).toThrow( - /Circular references are not supported here/ + 'Repeated objects are not supported here' ) }) @@ -379,7 +379,7 @@ describe('circular references', () => { a1: { items: [{ key: 'foo' }] } }) expect(() => YAML.createNode(map)).toThrow( - /Circular references are not supported here/ + 'Repeated objects are not supported here' ) }) @@ -402,7 +402,7 @@ describe('circular references', () => { a2: { items: ['two'] } }) expect(() => YAML.createNode(seq)).toThrow( - /Circular references are not supported here/ + 'Repeated objects are not supported here' ) }) @@ -417,7 +417,7 @@ describe('circular references', () => { expect(alias).toMatchObject({ type: 'ALIAS' }) expect(alias.source).toBe(source) expect(() => YAML.createNode(seq)).toThrow( - /Circular references are not supported here/ + 'Repeated objects are not supported here' ) }) })