Skip to content

Commit 35462c8

Browse files
committed
feat!: Have YAMLMap & YAMLSeq extend Array, rather than containing an .items Array
1 parent 6747f2f commit 35462c8

34 files changed

Lines changed: 545 additions & 637 deletions

.github/vitest.browserstack.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export default defineConfig({
4242
instances
4343
},
4444
globals: true,
45+
setupFiles: ['tests/_setup.ts'],
4546
include: ['tests/**/*.{js,ts}'],
4647
exclude: [
4748
'tests/_*',

src/compose/composer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ export class Composer<
111111
doc.comment = doc.comment ? `${doc.comment}\n${comment}` : comment
112112
} else if (afterEmptyLine || doc.directives.docStart) {
113113
doc.commentBefore = comment
114-
} else if (isCollection(dc) && !dc.flow && dc.items.length > 0) {
115-
let it = dc.items[0]
114+
} else if (isCollection(dc) && !dc.flow && dc.length > 0) {
115+
let it = dc[0]
116116
if (it instanceof Pair) it = it.key
117117
const cb = it.commentBefore
118118
it.commentBefore = cb ? `${comment}\n${cb}` : comment

src/compose/resolve-block-map.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export function resolveBlockMap(
7676
if (ctx.schema.compat) flowIndentCheck(bm.indent, key, onError)
7777
ctx.atKey = false
7878

79-
if (mapIncludes(ctx, map.items, keyNode))
79+
if (mapIncludes(ctx, map, keyNode))
8080
onError(keyStart, 'DUPLICATE_KEY', 'Map keys must be unique')
8181

8282
// value properties
@@ -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.items.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.items.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.items.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: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ export function resolveFlowCollection(
2323
): YAMLMap | YAMLSeq {
2424
const isMap = fc.start.source === '{'
2525
const fcName = isMap ? 'flow map' : 'flow sequence'
26-
const NodeClass = tag?.nodeClass ?? (isMap ? YAMLMap : YAMLSeq)
27-
const coll = new NodeClass(ctx.schema) as YAMLMap | YAMLSeq
26+
let coll
27+
if (tag?.nodeClass) coll = new tag.nodeClass(ctx.schema) as YAMLMap | YAMLSeq
28+
else coll = isMap ? new YAMLMap(ctx.schema) : new YAMLSeq(ctx.schema)
2829
coll.flow = true
2930
const atRoot = ctx.atRoot
3031
if (atRoot) ctx.atRoot = false
@@ -93,7 +94,7 @@ export function resolveFlowCollection(
9394
}
9495
}
9596
if (prevItemComment) {
96-
let prev = coll.items[coll.items.length - 1]
97+
let prev = coll[coll.length - 1]
9798
if (prev instanceof Pair) prev = prev.value ?? prev.key
9899
if (prev.comment) prev.comment += '\n' + prevItemComment
99100
else prev.comment = prevItemComment
@@ -108,7 +109,7 @@ export function resolveFlowCollection(
108109
const valueNode = value
109110
? composeNode(ctx, value, props, onError)
110111
: composeEmptyNode(ctx, props.end, sep, null, props, onError)
111-
;(coll as YAMLSeq).items.push(valueNode)
112+
;(coll as YAMLSeq).push(valueNode)
112113
offset = valueNode.range![2]
113114
if (isBlock(value)) onError(valueNode.range!, 'BLOCK_IN_FLOW', blockMsg)
114115
} else {
@@ -190,16 +191,16 @@ export function resolveFlowCollection(
190191
if (ctx.options.keepSourceTokens) pair.srcToken = collItem
191192
if (isMap) {
192193
const map = coll as YAMLMap
193-
if (mapIncludes(ctx, map.items, keyNode))
194+
if (mapIncludes(ctx, map, keyNode))
194195
onError(keyStart, 'DUPLICATE_KEY', 'Map keys must be unique')
195-
map.items.push(pair)
196+
map.push(pair)
196197
} else {
197198
const map = new YAMLMap(ctx.schema)
198199
map.flow = true
199-
map.items.push(pair)
200+
map.push(pair)
200201
const endRange = (valueNode ?? keyNode).range!
201202
map.range = [keyNode.range![0], endRange[1], endRange[2]]
202-
;(coll as YAMLSeq).items.push(map)
203+
;(coll as YAMLSeq).push(map)
203204
}
204205
offset = valueNode ? valueNode.range![2] : valueProps.end
205206
}

src/doc/NodeCreator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export class NodeCreator {
6262
if (isNode(value)) return value
6363
if (value instanceof Pair) {
6464
const map = this.schema.map.createNode(this, null)
65-
map.items.push(value)
65+
map.push(value)
6666
return map
6767
}
6868
if (

src/nodes/Alias.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,9 @@ function getAliasCount(
153153
const kc = getAliasCount(doc, node.key, anchors)
154154
const vc = getAliasCount(doc, node.value, anchors)
155155
return Math.max(kc, vc)
156-
} else if (node && 'items' in node) {
156+
} else if (Array.isArray(node)) {
157157
let count = 0
158-
for (const item of node.items) {
158+
for (const item of node) {
159159
const c = getAliasCount(doc, item, anchors)
160160
if (c > count) count = c
161161
}

src/nodes/Collection.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,19 @@ export interface CollectionBase extends NodeBase {
4848
/** Sets a value in this collection. */
4949
set(key: unknown, value: unknown): void
5050
}
51+
52+
export function copyCollection<T extends Collection>(
53+
orig: T,
54+
schema: Schema | undefined
55+
): T {
56+
const copy = (orig.constructor as typeof YAMLMap).from(orig, it =>
57+
it.clone(schema)
58+
) as typeof orig
59+
if (orig.range) copy.range = [...orig.range]
60+
const propDesc = Object.getOwnPropertyDescriptors(orig)
61+
for (const [name, prop] of Object.entries(propDesc)) {
62+
if (!(name in copy)) Object.defineProperty(copy, name, prop)
63+
}
64+
if (schema) copy.schema = schema
65+
return copy
66+
}

src/nodes/Pair.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ export class Pair<
1515
key: NodeOf<K>
1616
value: NodeOf<V> | null
1717

18+
declare comment?: never
19+
declare commentBefore?: never
20+
declare spaceBefore?: never
21+
1822
/** The CST token that was composed into this pair. */
1923
declare srcToken?: CollectionItem
2024

src/nodes/YAMLMap.ts

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ import type { Schema } from '../schema/Schema.ts'
66
import type { StringifyContext } from '../stringify/stringify.ts'
77
import { stringifyCollection } from '../stringify/stringifyCollection.ts'
88
import { addPairToJSMap } from './addPairToJSMap.ts'
9-
import type { CollectionBase, NodeOf, Primitive } from './Collection.ts'
9+
import {
10+
copyCollection,
11+
type CollectionBase,
12+
type NodeOf,
13+
type Primitive
14+
} from './Collection.ts'
1015
import { isNode } from './identity.ts'
1116
import type { Node, Range } from './Node.ts'
1217
import { Pair } from './Pair.ts'
@@ -33,9 +38,10 @@ export function findPair<
3338
export class YAMLMap<
3439
K extends Primitive | Node = Primitive | Node,
3540
V extends Primitive | Node = Primitive | Node
36-
> implements CollectionBase {
37-
items: Pair<K, V>[] = []
38-
41+
>
42+
extends Array<Pair<K, V>>
43+
implements CollectionBase
44+
{
3945
schema: Schema | undefined
4046

4147
/** An optional anchor on this collection. Used by alias nodes. */
@@ -84,20 +90,21 @@ export class YAMLMap<
8490
if (typeof replacer === 'function') value = replacer.call(obj, key, value)
8591
else if (Array.isArray(replacer) && !replacer.includes(key)) return
8692
if (value !== undefined || nc.keepUndefined)
87-
map.items.push(nc.createPair(key, value))
93+
map.push(nc.createPair(key, value))
8894
}
8995
if (obj instanceof Map) {
9096
for (const [key, value] of obj) add(key, value)
9197
} else if (obj && typeof obj === 'object') {
9298
for (const key of Object.keys(obj)) add(key, (obj as any)[key])
9399
}
94100
if (typeof nc.schema.sortMapEntries === 'function') {
95-
map.items.sort(nc.schema.sortMapEntries)
101+
map.sort(nc.schema.sortMapEntries)
96102
}
97103
return map
98104
}
99105

100-
constructor(schema?: Schema) {
106+
constructor(schema?: Schema, elements: Array<Pair<K, V>> = []) {
107+
super(...elements)
101108
Object.defineProperty(this, 'schema', {
102109
value: schema,
103110
configurable: true,
@@ -112,14 +119,7 @@ export class YAMLMap<
112119
* @param schema - If defined, overwrites the original's schema
113120
*/
114121
clone(schema?: Schema): this {
115-
const copy: this = Object.create(
116-
Object.getPrototypeOf(this),
117-
Object.getOwnPropertyDescriptors(this)
118-
)
119-
if (schema) copy.schema = schema
120-
copy.items = copy.items.map(it => it.clone(schema))
121-
if (this.range) copy.range = [...this.range]
122-
return copy
122+
return copyCollection(this, schema)
123123
}
124124

125125
/**
@@ -130,36 +130,36 @@ export class YAMLMap<
130130
add(pair: Pair<K, V>): void {
131131
if (!(pair instanceof Pair)) throw new TypeError('Expected a Pair')
132132

133-
const prev = findPair(this.items, pair.key)
133+
const prev = findPair(this, pair.key)
134134
const sortEntries = this.schema?.sortMapEntries
135135
if (prev) {
136136
// For scalars, keep the old node & its comments and anchors
137137
if (prev.value instanceof Scalar && pair.value instanceof Scalar)
138138
prev.value.value = pair.value.value
139139
else prev.value = pair.value
140140
} else if (sortEntries) {
141-
const i = this.items.findIndex(item => sortEntries(pair, item) < 0)
142-
if (i === -1) this.items.push(pair)
143-
else this.items.splice(i, 0, pair)
141+
const i = this.findIndex(item => sortEntries(pair, item) < 0)
142+
if (i === -1) this.push(pair)
143+
else this.splice(i, 0, pair)
144144
} else {
145-
this.items.push(pair)
145+
this.push(pair)
146146
}
147147
}
148148

149149
delete(key: unknown): boolean {
150-
const it = findPair(this.items, key)
150+
const it = findPair(this, key)
151151
if (!it) return false
152-
const del = this.items.splice(this.items.indexOf(it), 1)
152+
const del = this.splice(this.indexOf(it), 1)
153153
return del.length > 0
154154
}
155155

156156
get(key: unknown): NodeOf<V> | undefined {
157-
const it = findPair(this.items, key)
157+
const it = findPair(this, key)
158158
return it?.value ?? undefined
159159
}
160160

161161
has(key: unknown): boolean {
162-
return !!findPair(this.items, key)
162+
return !!findPair(this, key)
163163
}
164164

165165
set(
@@ -203,7 +203,7 @@ export class YAMLMap<
203203
ctx ??= new ToJSContext()
204204
const map = Type ? new Type() : ctx?.mapAsMap ? new Map() : {}
205205
if (this.anchor) ctx.setAnchor(this, map)
206-
for (const item of this.items) addPairToJSMap(doc, ctx, map, item)
206+
for (const item of this) addPairToJSMap(doc, ctx, map, item)
207207
return map
208208
}
209209

@@ -213,7 +213,7 @@ export class YAMLMap<
213213
onChompKeep?: () => void
214214
): string {
215215
if (!ctx) return JSON.stringify(this)
216-
for (const item of this.items) {
216+
for (const item of this) {
217217
if (!(item instanceof Pair))
218218
throw new Error(
219219
`Map items must all be pairs; found ${JSON.stringify(item)} instead`

0 commit comments

Comments
 (0)