forked from eemeli/yaml
-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
createNode.ts
110 lines (100 loc) · 3.38 KB
/
createNode.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import { Alias } from '../nodes/Alias.js'
import { isDocument, isNode, isPair, MAP, SEQ } from '../nodes/identity.js'
import type { Node } from '../nodes/Node.js'
import { Scalar } from '../nodes/Scalar.js'
import type { YAMLMap } from '../nodes/YAMLMap.js'
import type { Schema } from '../schema/Schema.js'
import type { CollectionTag, ScalarTag } from '../schema/types.js'
import type { Replacer } from './Document.js'
const defaultTagPrefix = 'tag:yaml.org,2002:'
export interface CreateNodeContext {
aliasDuplicateObjects: boolean
keepUndefined: boolean
onAnchor: (source: unknown) => string
onTagObj?: (tagObj: ScalarTag | CollectionTag) => void
sourceObjects: Map<unknown, { anchor: string | null; node: Node | null }>
replacer?: Replacer
schema: Schema
}
function findTagObject(
value: unknown,
tagName: string | undefined,
tags: Array<ScalarTag | CollectionTag>
) {
if (tagName) {
const match = tags.filter(t => t.tag === tagName)
const tagObj = match.find(t => !t.format) ?? match[0]
if (!tagObj) throw new Error(`Tag ${tagName} not found`)
return tagObj
}
return tags.find(t => t.identify?.(value) && !t.format)
}
export function createNode(
value: unknown,
tagName: string | undefined,
ctx: CreateNodeContext
): Node {
if (isDocument(value)) value = value.contents
if (isNode(value)) return value
if (isPair(value)) {
const map = ctx.schema[MAP].createNode?.(ctx.schema, null, ctx) as YAMLMap
map.items.push(value)
return map
}
if (
value instanceof String ||
value instanceof Number ||
value instanceof Boolean ||
(typeof BigInt !== 'undefined' && value instanceof BigInt) // not supported everywhere
) {
// https://tc39.es/ecma262/#sec-serializejsonproperty
value = value.valueOf()
}
const { aliasDuplicateObjects, onAnchor, onTagObj, schema, sourceObjects } =
ctx
// Detect duplicate references to the same object & use Alias nodes for all
// after first. The `ref` wrapper allows for circular references to resolve.
let ref: { anchor: string | null; node: Node | null } | undefined = undefined
if (aliasDuplicateObjects && value && typeof value === 'object') {
ref = sourceObjects.get(value)
if (ref) {
if (!ref.anchor) ref.anchor = onAnchor(value)
return new Alias(ref.anchor)
} else {
ref = { anchor: null, node: null }
sourceObjects.set(value, ref)
}
}
if (tagName?.startsWith('!!')) tagName = defaultTagPrefix + tagName.slice(2)
let tagObj = findTagObject(value, tagName, schema.tags)
if (!tagObj) {
if (value && typeof (value as any).toJSON === 'function') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
value = (value as any).toJSON()
}
if (!value || typeof value !== 'object') {
const node = new Scalar(value)
if (ref) ref.node = node
return node
}
tagObj =
value instanceof Map
? schema[MAP]
: Symbol.iterator in Object(value)
? schema[SEQ]
: schema[MAP]
}
if (onTagObj) {
onTagObj(tagObj)
delete ctx.onTagObj
}
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
if (ref) ref.node = node
return node
}