Skip to content

Commit 3caba53

Browse files
committed
feat!: Drop the map & seq .add() methods, override .push() instead
1 parent 35462c8 commit 3caba53

16 files changed

Lines changed: 200 additions & 162 deletions

docs/04_documents.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,15 +117,14 @@ which is expected to always contain a `YAMLMap`, `YAMLSeq`, or `Scalar` value.
117117
```js
118118
const doc = parseDocument('a: 1\nb: [2, 3]\n')
119119
doc.get('a') // 1
120-
doc.get() // YAMLMap { items: [Pair, Pair], ... }
121120
doc.get('b').has(0) // true
122-
doc.get('b').add(4) // -> doc.get('b').items.length === 3
121+
doc.get('b').push(4) // -> doc.get('b').items.length === 3
123122
doc.get('b').delete(1) // true
124123
doc.get('b').get(1) // 4
125124
```
126125

127126
In addition to the above, the document object also provides the same **accessor methods** as [collections](#collections), based on the top-level collection:
128-
`add`, `delete`, `get`, `has`, and `set`.
127+
`delete`, `get`, `has`, and `set`.
129128

130129
#### `Document#toJS()`, `Document#toJSON()` and `Document#toString()`
131130

docs/05_content_nodes.md

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -61,21 +61,19 @@ interface CollectionBase extends NodeBase {
6161
clone(schema?: Schema): this // a deep copy of this collection
6262
}
6363

64-
class YAMLMap<K = unknown, V = unknown> implements CollectionBase {
65-
items: Pair<K, V>[]
66-
add(pair: Pair<K, V> | { key: K; value: V }, overwrite?: boolean): void
64+
class YAMLMap<K = unknown, V = unknown> extends Array<Pair<K, V>> implements CollectionBase {
6765
delete(key: K): boolean
6866
get(key: K, keepScalar?: boolean): unknown
6967
has(key: K): boolean
68+
push(...pairs: Pair<K, V>[]): number
7069
set(key: K, value: V): void
7170
}
7271

73-
class YAMLSeq<T = unknown> implements CollectionBase {
74-
items: T[]
75-
add(value: T): void
72+
class YAMLSeq<T = unknown> extends Array<NodeOf<T>> implements CollectionBase {
7673
delete(key: number | Scalar<number>): boolean
7774
get(key: number | Scalar<number>, keepScalar?: boolean): unknown
7875
has(key: number | Scalar<number>): boolean
76+
push(...items: Array<T | NodeOf<T>>): number
7977
set(key: number | Scalar<number>, value: T): void
8078
}
8179
```
@@ -93,21 +91,21 @@ All of the collections provide the following accessor methods:
9391

9492
| Method | Returns | Description |
9593
| --------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
96-
| add(value) | `void` | Adds a value to the collection. For `!!map` and `!!omap` the value must be a Pair instance, which must not have a key that already exists in the map. |
9794
| delete(key) | `boolean` | Removes a value from the collection. Returns `true` if the item was found and removed. |
9895
| get(key) | `Node` | Returns value at `key`, or `undefined` if not found. |
9996
| has(key) | `boolean` | Checks if the collection includes a value with the key `key`. |
97+
| push(...values) | `number` | Adds values to the collection. For `!!map` and `!!omap` the value must be a Pair instance, which must not have a key that already exists in the map. |
10098
| set(key, value) | `any` | Sets a value in this collection. For `!!set`, `value` needs to be a boolean to add/remove the item from the set. When overwriting a `Scalar` value with a scalar, the original node is retained. |
10199

102100
<!-- prettier-ignore -->
103101
```js
104102
const doc = new YAML.Document({ a: 1, b: [2, 3] }) // { a: 1, b: [ 2, 3 ] }
105-
doc.add(doc.createPair('c', 4)) // { a: 1, b: [ 2, 3 ], c: 4 }
106-
doc.get('b').add(5) // { a: 1, b: [ 2, 3, 5 ], c: 4 }
107-
doc.set('c', 42) // { a: 1, b: [ 2, 3, 5 ], c: 42 }
108-
doc.get('c').set('x') // TypeError: doc.get(...).set is not a function
109-
doc.delete('c') // { a: 1, b: [ 2, 3, 5 ] }
110-
doc.get('b').delete(1) // { a: 1, b: [ 2, 5 ] }
103+
doc.set('c', 4) // { a: 1, b: [ 2, 3 ], c: 4 }
104+
doc.get('b').push(5) // { a: 1, b: [ 2, 3, 5 ], c: 4 }
105+
doc.set('c', 42) // { a: 1, b: [ 2, 3, 5 ], c: 42 }
106+
doc.get('c').set('x') // TypeError: doc.get(...).set is not a function
107+
doc.delete('c') // { a: 1, b: [ 2, 3, 5 ] }
108+
doc.get('b').delete(1) // { a: 1, b: [ 2, 5 ] }
111109

112110
doc.get('a') // Scalar { value: 1 }
113111
doc.get('b').get(1) // Scalar { value: 5 }
@@ -118,7 +116,7 @@ doc.get('b').has(0) // true
118116

119117
For all of these methods, the keys may be nodes or their wrapped scalar values (i.e. `42` will match `Scalar { value: 42 }`).
120118
Keys for `!!seq` should be non-negative integers, or their string representations.
121-
`add()` and `set()` will internally call `doc.createNode()` to wrap the value.
119+
`set()` will internally call `doc.createNode()` to wrap the value.
122120

123121
## Alias Nodes
124122

@@ -165,8 +163,8 @@ const map = doc.createNode({ balloons: 99 })
165163
// key: Scalar { value: 'balloons' },
166164
// value: Scalar { value: 99 } } ] }
167165

168-
doc.add(map)
169-
doc.get(0, true).comment = ' A commented item'
166+
doc.value.push(map)
167+
doc.get(0).comment = ' A commented item'
170168
String(doc)
171169
// - some # A commented item
172170
// - values
@@ -197,8 +195,8 @@ To that end, you'll need to assign its return value to the `value` of a document
197195
<h4 style="clear:both"><code>doc.createAlias(node, name?): Alias</code></h4>
198196

199197
```js
200-
const alias = doc.createAlias(doc.get(1, true), 'foo')
201-
doc.add(alias)
198+
const alias = doc.createAlias(doc.get(1), 'foo')
199+
doc.value.push(alias)
202200
String(doc)
203201
// - some # A commented item
204202
// - &foo values
@@ -223,7 +221,7 @@ const doc = new Document([
223221
42,
224222
{ including: 'objects', 3: 'a string' }
225223
])
226-
doc.add(doc.createPair(1, 'a number'))
224+
doc.value.push(doc.createPair(1, 'a number'))
227225

228226
doc.toString()
229227
// - some values

src/compose/resolve-block-map.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export function resolveBlockMap(
116116
offset = valueNode.range![2]
117117
const pair = new Pair(keyNode, valueNode)
118118
if (ctx.options.keepSourceTokens) pair.srcToken = collItem
119-
map.push(pair)
119+
map._push(pair)
120120
} else {
121121
// key with no value
122122
if (implicitKey)
@@ -131,7 +131,7 @@ export function resolveBlockMap(
131131
}
132132
const pair = new Pair(keyNode)
133133
if (ctx.options.keepSourceTokens) pair.srcToken = collItem
134-
map.push(pair)
134+
map._push(pair)
135135
}
136136
}
137137

src/compose/resolve-block-seq.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function resolveBlockSeq(
5050
: composeEmptyNode(ctx, props.end, start, null, props, onError)
5151
if (ctx.schema.compat) flowIndentCheck(bs.indent, value, onError)
5252
offset = node.range![2]
53-
seq.push(node)
53+
seq._push(node)
5454
}
5555
seq.range = [bs.offset, offset, commentEnd ?? offset]
5656
return seq

src/compose/resolve-flow-collection.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export function resolveFlowCollection(
109109
const valueNode = value
110110
? composeNode(ctx, value, props, onError)
111111
: composeEmptyNode(ctx, props.end, sep, null, props, onError)
112-
;(coll as YAMLSeq).push(valueNode)
112+
;(coll as YAMLSeq)._push(valueNode)
113113
offset = valueNode.range![2]
114114
if (isBlock(value)) onError(valueNode.range!, 'BLOCK_IN_FLOW', blockMsg)
115115
} else {
@@ -193,14 +193,14 @@ export function resolveFlowCollection(
193193
const map = coll as YAMLMap
194194
if (mapIncludes(ctx, map, keyNode))
195195
onError(keyStart, 'DUPLICATE_KEY', 'Map keys must be unique')
196-
map.push(pair)
196+
map._push(pair)
197197
} else {
198198
const map = new YAMLMap(ctx.schema)
199199
map.flow = true
200-
map.push(pair)
200+
map._push(pair)
201201
const endRange = (valueNode ?? keyNode).range!
202202
map.range = [keyNode.range![0], endRange[1], endRange[2]]
203-
;(coll as YAMLSeq).push(map)
203+
;(coll as YAMLSeq)._push(map)
204204
}
205205
offset = valueNode ? valueNode.range![2] : valueProps.end
206206
}

src/doc/Document.ts

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,6 @@ export class Document<
151151
return copy
152152
}
153153

154-
/** Adds a value to the document. */
155-
add(value: any): void {
156-
assertCollection(this.value).add(value)
157-
}
158-
159154
/**
160155
* Create a new `Alias` node, ensuring that the target `node` has the required anchor.
161156
*
@@ -238,14 +233,13 @@ export class Document<
238233
* @returns `true` if the item was found and removed.
239234
*/
240235
delete(key: any): boolean {
241-
return assertCollection(this.value).delete(key)
236+
return isCollection(this.value) ? this.value.delete(key) : false
242237
}
243238

244239
/**
245240
* Returns item at `key`, or `undefined` if not found.
246241
*/
247-
get(key?: any): Strict extends true ? Node | Pair | undefined : any {
248-
if (key === undefined) return this.value
242+
get(key: any): Strict extends true ? Node | Pair | undefined : any {
249243
return isCollection(this.value) ? this.value.get(key) : undefined
250244
}
251245

@@ -261,7 +255,8 @@ export class Document<
261255
* boolean to add/remove the item from the set.
262256
*/
263257
set(key: any, value: any): void {
264-
assertCollection(this.value).set(key, value)
258+
if (isCollection(this.value)) this.value.set(key, value)
259+
else throw new Error('Expected a YAML collection as document value')
265260
}
266261

267262
/**
@@ -342,8 +337,3 @@ export class Document<
342337
return stringifyDocument(this, options)
343338
}
344339
}
345-
346-
function assertCollection(value: unknown) {
347-
if (isCollection(value)) return value
348-
throw new Error('Expected a YAML collection as document value')
349-
}

src/nodes/Collection.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@ export interface CollectionBase extends NodeBase {
3030
*/
3131
clone(schema?: Schema): this
3232

33-
/** Adds a value to the collection. */
34-
add(value: unknown): void
35-
3633
/**
3734
* Removes a value from the collection.
3835
* @returns `true` if the item was found and removed.

src/nodes/YAMLMap.ts

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -122,42 +122,57 @@ export class YAMLMap<
122122
return copyCollection(this, schema)
123123
}
124124

125+
/** @private */
126+
_push(pair: Pair<K, V>): void {
127+
super.push(pair)
128+
}
129+
125130
/**
126-
* Adds a key-value pair to the map.
131+
* Adds new key-value pairs to the mapping, and returns its new length.
127132
*
128-
* Using a key that is already in the collection overwrites the previous value.
133+
* Added pairs must not have the same keys as ones previously set in the map.
129134
*/
130-
add(pair: Pair<K, V>): void {
131-
if (!(pair instanceof Pair)) throw new TypeError('Expected a Pair')
135+
push(...pairs: Pair<K, V>[]): number {
136+
for (const pair of pairs) {
137+
if (!(pair instanceof Pair)) {
138+
const msg = `Expected a Pair, but found ${(pair as any).constructor?.name ?? pair}`
139+
throw new TypeError(msg)
140+
}
141+
if (findPair(this, pair.key)) {
142+
const msg = `Maps must not include duplicate keys: ${String(pair.key)}`
143+
throw new Error(msg)
144+
}
132145

133-
const prev = findPair(this, pair.key)
134-
const sortEntries = this.schema?.sortMapEntries
135-
if (prev) {
136-
// For scalars, keep the old node & its comments and anchors
137-
if (prev.value instanceof Scalar && pair.value instanceof Scalar)
138-
prev.value.value = pair.value.value
139-
else prev.value = pair.value
140-
} else if (sortEntries) {
141-
const i = this.findIndex(item => sortEntries(pair, item) < 0)
142-
if (i === -1) this.push(pair)
143-
else this.splice(i, 0, pair)
144-
} else {
145-
this.push(pair)
146+
if (this.schema?.sortMapEntries) {
147+
const sortEntries = this.schema.sortMapEntries
148+
const i = this.findIndex(item => sortEntries(pair, item) < 0)
149+
if (i === -1) super.push(pair)
150+
else this.splice(i, 0, pair)
151+
} else {
152+
super.push(pair)
153+
}
146154
}
155+
return this.length
147156
}
148157

158+
/**
159+
* Removes a value from the mapping.
160+
* @returns `true` if the item was found and removed.
161+
*/
149162
delete(key: unknown): boolean {
150163
const it = findPair(this, key)
151164
if (!it) return false
152165
const del = this.splice(this.indexOf(it), 1)
153166
return del.length > 0
154167
}
155168

169+
/** Returns item at `key`, or `undefined` if not found. */
156170
get(key: unknown): NodeOf<V> | undefined {
157171
const it = findPair(this, key)
158172
return it?.value ?? undefined
159173
}
160174

175+
/** Checks if the mapping includes a value with the key `key`. */
161176
has(key: unknown): boolean {
162177
return !!findPair(this, key)
163178
}
@@ -170,17 +185,31 @@ export class YAMLMap<
170185
let pair: Pair
171186
if (isNode(key) && (value === null || isNode(value))) {
172187
pair = new Pair(key, value)
173-
} else if (!this.schema) {
174-
throw new Error('Schema is required')
175188
} else {
189+
if (!this.schema) throw new Error('Schema is required')
176190
const nc = new NodeCreator(this.schema, {
177191
...options,
178192
aliasDuplicateObjects: false
179193
})
180194
pair = nc.createPair(key, value)
181195
nc.setAnchors()
182196
}
183-
this.add(pair as Pair<K, V>)
197+
198+
const prev = findPair(this, pair.key)
199+
if (prev) {
200+
const pv = pair.value as NodeOf<V>
201+
// For scalars, keep the old node & its comments and anchors
202+
if (prev.value instanceof Scalar && pv instanceof Scalar) {
203+
Object.assign(prev.value, pv)
204+
} else prev.value = pv
205+
} else if (this.schema?.sortMapEntries) {
206+
const sortEntries = this.schema.sortMapEntries
207+
const i = this.findIndex(item => sortEntries(pair, item) < 0)
208+
if (i === -1) super.push(pair as Pair<K, V>)
209+
else this.splice(i, 0, pair as Pair<K, V>)
210+
} else {
211+
super.push(pair as Pair<K, V>)
212+
}
184213
}
185214

186215
/**

0 commit comments

Comments
 (0)