Skip to content

Commit

Permalink
Resolve circular dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
eemeli committed Aug 5, 2020
1 parent fae1614 commit 5d2b627
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 25 deletions.
3 changes: 3 additions & 0 deletions src/ast/Collection.js
Expand Up @@ -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
Expand Down
10 changes: 8 additions & 2 deletions src/doc/Document.js
@@ -1,4 +1,5 @@
import {
Alias,
Collection,
Node,
Pair,
Expand Down Expand Up @@ -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.
Expand Down
10 changes: 8 additions & 2 deletions src/doc/Schema.js
Expand Up @@ -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 : {}
}
}
18 changes: 5 additions & 13 deletions 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) {
Expand All @@ -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')
Expand All @@ -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)
}
Expand Down
11 changes: 7 additions & 4 deletions 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
Expand All @@ -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 {
Expand Down Expand Up @@ -66,7 +69,7 @@ function stringify(value, options) {
}

export const YAML = {
createNode,
createNode: _createNode,
defaultOptions,
Document,
parse,
Expand Down
8 changes: 4 additions & 4 deletions tests/doc/createNode.js
Expand Up @@ -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'
)
})

Expand All @@ -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'
)
})

Expand All @@ -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'
)
})

Expand All @@ -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'
)
})
})

0 comments on commit 5d2b627

Please sign in to comment.