Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions docs/05_content_nodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ It is valid to have an anchor associated with a node even if it has no aliases.
## Scalar Values

```js
class NodeBase {
interface NodeBase {
comment?: string // a comment on or immediately after this
commentBefore?: string // a comment before this
range?: [number, number, number]
Expand All @@ -19,16 +19,16 @@ class NodeBase {
// included in their respective ranges.
spaceBefore?: boolean
// a blank line before this node and its commentBefore
tag?: string // a fully qualified tag, if required
clone(): NodeBase // a copy of this node
toJS(doc, context?): any // a plain JS representation of this node
tag?: string // a fully qualified tag, if required
clone(): this // a copy of this node
toJS(doc): any // a plain JS representation of this node
}
```

For scalar values, the `tag` will not be set unless it was explicitly defined in the source document; this also applies for unsupported tags that have been resolved using a fallback tag (string, `YAMLMap`, or `YAMLSeq`).

```js
class Scalar<T = unknown> extends NodeBase {
class Scalar<T = unknown> implements NodeBase {
anchor?: string // an anchor associated with this node
format?: 'BIN' | 'HEX' | 'OCT' | 'TIME' | undefined
// By default (undefined), numbers use decimal notation.
Expand All @@ -54,12 +54,12 @@ class Pair {
value: Node | null
}

class Collection extends NodeBase {
class Collection implements NodeBase {
anchor?: string // an anchor associated with this node
flow?: boolean // use flow style when stringifying this
schema?: Schema
addIn(path: unknown[], value: unknown): void
clone(schema?: Schema): NodeBase // a deep copy of this collection
clone(schema?: Schema): this // a deep copy of this collection
deleteIn(path: unknown[]): boolean
getIn(path: unknown[], keepScalar?: boolean): unknown
hasIn(path: unknown[]): boolean
Expand Down Expand Up @@ -140,7 +140,7 @@ Note that for `addIn` the path argument points to the collection rather than the

<!-- prettier-ignore -->
```js
class Alias extends NodeBase {
class Alias implements NodeBase {
source: string
resolve(doc: Document): Scalar | YAMLMap | YAMLSeq | undefined
}
Expand Down
5 changes: 3 additions & 2 deletions src/compose/compose-collection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NodeBase, type Node } from '../nodes/Node.ts'
import { isNode } from '../nodes/identity.ts'
import type { Node } from '../nodes/Node.ts'
import { Scalar } from '../nodes/Scalar.ts'
import { YAMLMap } from '../nodes/YAMLMap.ts'
import { YAMLSeq } from '../nodes/YAMLSeq.ts'
Expand Down Expand Up @@ -135,7 +136,7 @@ export function composeCollection(
ctx.options
) ?? coll

const node = res instanceof NodeBase ? (res as Node) : new Scalar(res)
const node = isNode(res) ? res : new Scalar(res)
node.range = coll.range
node.tag = tagName
if (tag?.format) (node as Scalar).format = tag.format
Expand Down
6 changes: 3 additions & 3 deletions src/compose/util-map-includes.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import type { NodeBase } from '../nodes/Node.ts'
import type { Node } from '../nodes/Node.ts'
import type { Pair } from '../nodes/Pair.ts'
import { Scalar } from '../nodes/Scalar.ts'
import type { ComposeContext } from './compose-node.ts'

export function mapIncludes(
ctx: ComposeContext,
items: Pair[],
search: NodeBase
search: Node
): boolean {
const { uniqueKeys } = ctx.options
if (uniqueKeys === false) return false
const isEqual =
typeof uniqueKeys === 'function'
? uniqueKeys
: (a: NodeBase, b: NodeBase) =>
: (a: Node, b: Node) =>
a === b ||
(a instanceof Scalar && b instanceof Scalar && a.value === b.value)
return items.some(pair => isEqual(pair.key, search))
Expand Down
13 changes: 6 additions & 7 deletions src/doc/Document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { YAMLError, YAMLWarning } from '../errors.ts'
import { Alias } from '../nodes/Alias.ts'
import { Collection, type Primitive } from '../nodes/Collection.ts'
import type { Node, NodeType, Range } from '../nodes/Node.ts'
import type { NodeBase } from '../nodes/Node.ts'
import type { Pair } from '../nodes/Pair.ts'
import { Scalar } from '../nodes/Scalar.ts'
import { ToJSContext } from '../nodes/toJS.ts'
Expand Down Expand Up @@ -226,13 +225,13 @@ export class Document<
value: V,
options: CreateNodeOptions = {}
): Pair<
K extends Primitive | NodeBase ? K : NodeBase,
V extends Primitive | NodeBase ? V : NodeBase
K extends Primitive | Node ? K : Node,
V extends Primitive | Node ? V : Node
> {
const nc = new NodeCreator(this, options)
const pair = nc.createPair(key, value) as Pair<
K extends Primitive | NodeBase ? K : NodeBase,
V extends Primitive | NodeBase ? V : NodeBase
K extends Primitive | Node ? K : Node,
V extends Primitive | Node ? V : Node
>
nc.setAnchors()
return pair
Expand Down Expand Up @@ -261,7 +260,7 @@ export class Document<
/**
* Returns item at `key`, or `undefined` if not found.
*/
get(key: any): Strict extends true ? NodeBase | Pair | undefined : any {
get(key: any): Strict extends true ? Node | Pair | undefined : any {
return this.value instanceof Collection ? this.value.get(key) : undefined
}

Expand All @@ -270,7 +269,7 @@ export class Document<
*/
getIn(
path: unknown[]
): Strict extends true ? NodeBase | Pair | null | undefined : any {
): Strict extends true ? Node | Pair | null | undefined : any {
if (!path.length) return this.value
return this.value instanceof Collection ? this.value.getIn(path) : undefined
}
Expand Down
9 changes: 5 additions & 4 deletions src/doc/NodeCreator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Alias } from '../nodes/Alias.ts'
import { Collection } from '../nodes/Collection.ts'
import { type Node, NodeBase } from '../nodes/Node.ts'
import { isNode } from '../nodes/identity.ts'
import { type Node } from '../nodes/Node.ts'
import { Pair } from '../nodes/Pair.ts'
import { Scalar } from '../nodes/Scalar.ts'
import type { YAMLMap } from '../nodes/YAMLMap.ts'
Expand Down Expand Up @@ -60,7 +61,7 @@ export class NodeCreator {

create(value: unknown, tagName?: string): Node {
if (value instanceof Document) value = value.value
if (value instanceof NodeBase) return value as Node
if (isNode(value)) return value
if (value instanceof Pair) {
const map = (this.schema.map.nodeClass! as typeof YAMLMap).from(
this,
Expand Down Expand Up @@ -143,10 +144,10 @@ export class NodeCreator {
return node
}

createPair(key: unknown, value: unknown): Pair<NodeBase, NodeBase | null> {
createPair(key: unknown, value: unknown): Pair<Node, Node | null> {
const k = this.create(key)
const v = value == null ? null : this.create(value)
return new Pair<NodeBase, NodeBase | null>(k, v)
return new Pair<Node, Node | null>(k, v)
}

/**
Expand Down
5 changes: 2 additions & 3 deletions src/doc/directives.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { NodeBase } from '../nodes/Node.ts'
import { visit } from '../visit.ts'
import type { Document } from './Document.ts'

Expand Down Expand Up @@ -182,8 +181,8 @@ export class Directives {
let tagNames: string[]
if (doc && tagEntries.length > 0) {
const tags: Record<string, boolean> = {}
visit(doc.value, (_key, node) => {
if (node instanceof NodeBase && node.tag) tags[node.tag] = true
visit(doc.value, (_key, node: any) => {
if (node?.tag) tags[node.tag] = true
})
tagNames = Object.keys(tags)
} else tagNames = []
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export type { ErrorCode } from './errors.ts'
export { YAMLError, YAMLParseError, YAMLWarning } from './errors.ts'

export { Alias } from './nodes/Alias.ts'
export type { Node, Range } from './nodes/Node.ts'
export { isNode } from './nodes/identity.ts'
export type { Node, NodeBase, Range } from './nodes/Node.ts'
export { Pair } from './nodes/Pair.ts'
export { Scalar } from './nodes/Scalar.ts'
export { YAMLMap } from './nodes/YAMLMap.ts'
Expand Down
51 changes: 39 additions & 12 deletions src/nodes/Alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,40 @@ import type { Document, DocValue } from '../doc/Document.ts'
import type { FlowScalar } from '../parse/cst.ts'
import type { StringifyContext } from '../stringify/stringify.ts'
import { visit } from '../visit.ts'
import type { Node } from './Node.ts'
import { NodeBase } from './Node.ts'
import type { Node, NodeBase, Range } from './Node.ts'
import { Pair } from './Pair.ts'
import type { Scalar } from './Scalar.ts'
import { ToJSContext } from './toJS.ts'
import type { YAMLMap } from './YAMLMap.ts'
import type { YAMLSeq } from './YAMLSeq.ts'

export class Alias extends NodeBase {
export class Alias implements NodeBase {
source: string

declare anchor?: never

/** A comment on or immediately after this node. */
declare comment?: string | null

/** A comment before this node. */
declare commentBefore?: string | null

/**
* The `[start, value-end, node-end]` character offsets for
* the part of the source parsed into this node (undefined if not parsed).
* The `value-end` and `node-end` positions are themselves not included in their respective ranges.
*/
declare range?: Range | null

/** A blank line before this node and its commentBefore */
declare spaceBefore?: boolean

/** The CST token that was composed into this node. */
declare srcToken?: FlowScalar & { type: 'alias' }

declare tag?: never

constructor(source: string) {
super()
this.source = source
Object.defineProperty(this, 'tag', {
set() {
Expand All @@ -27,6 +45,16 @@ export class Alias extends NodeBase {
})
}

/** Create a copy of this node. */
clone(): this {
const copy: this = Object.create(
Object.getPrototypeOf(this),
Object.getOwnPropertyDescriptors(this)
)
if (this.range) copy.range = [...this.range]
return copy
}

/**
* Resolve the value of this alias within `doc`, finding the last
* instance of the `source` anchor before this node.
Expand Down Expand Up @@ -114,25 +142,24 @@ export class Alias extends NodeBase {

function getAliasCount(
doc: Document,
node: unknown,
node: Node | Pair | null,
anchors: ToJSContext['anchors']
): number {
if (node instanceof Alias) {
const source = node.resolve(doc)
const anchor = anchors && source && anchors.get(source)
return anchor ? anchor.count * anchor.aliasCount : 0
} else if (node instanceof NodeBase && 'items' in node) {
const coll = node as YAMLMap | YAMLSeq
} else if (node instanceof Pair) {
const kc = getAliasCount(doc, node.key, anchors)
const vc = getAliasCount(doc, node.value, anchors)
return Math.max(kc, vc)
} else if (node && 'items' in node) {
let count = 0
for (const item of coll.items) {
for (const item of node.items) {
const c = getAliasCount(doc, item, anchors)
if (c > count) count = c
}
return count
} else if (node instanceof Pair) {
const kc = getAliasCount(doc, node.key, anchors)
const vc = getAliasCount(doc, node.value, anchors)
return Math.max(kc, vc)
}
return 1
}
42 changes: 32 additions & 10 deletions src/nodes/Collection.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NodeCreator } from '../doc/NodeCreator.ts'
import type { Token } from '../parse/cst.ts'
import type { Schema } from '../schema/Schema.ts'
import { type Node, NodeBase } from './Node.ts'
import type { Node, Range } from './Node.ts'
import type { Pair } from './Pair.ts'
import type { Scalar } from './Scalar.ts'

Expand All @@ -26,12 +27,12 @@ export function collectionFromPath(
return new NodeCreator(schema, { aliasDuplicateObjects: false }).create(v)
}

export abstract class Collection extends NodeBase {
export abstract class Collection {
schema: Schema | undefined

declare items: (NodeBase | Pair)[]
declare items: (Node | Pair)[]

/** An optional anchor on this node. Used by alias nodes. */
/** An optional anchor on this collection. Used by alias nodes. */
declare anchor?: string

/**
Expand All @@ -40,8 +41,29 @@ export abstract class Collection extends NodeBase {
*/
declare flow?: boolean

/** A comment on or immediately after this collection. */
declare comment?: string | null

/** A comment before this collection. */
declare commentBefore?: string | null

/**
* The `[start, value-end, node-end]` character offsets for
* the part of the source parsed into this collection (undefined if not parsed).
* The `value-end` and `node-end` positions are themselves not included in their respective ranges.
*/
declare range?: Range | null

/** A blank line before this collection and its commentBefore */
declare spaceBefore?: boolean

/** The CST token that was composed into this collection. */
declare srcToken?: Token

/** A fully qualified tag, if required */
declare tag?: string

constructor(schema?: Schema) {
super()
Object.defineProperty(this, 'schema', {
value: schema,
configurable: true,
Expand All @@ -55,14 +77,14 @@ export abstract class Collection extends NodeBase {
*
* @param schema - If defined, overwrites the original's schema
*/
clone(schema?: Schema): Collection {
const copy: Collection = Object.create(
clone(schema?: Schema): this {
const copy: this = Object.create(
Object.getPrototypeOf(this),
Object.getOwnPropertyDescriptors(this)
)
if (schema) copy.schema = schema
copy.items = copy.items.map(it => it.clone(schema))
if (this.range) copy.range = this.range.slice() as NodeBase['range']
if (this.range) copy.range = [...this.range]
return copy
}

Expand All @@ -78,7 +100,7 @@ export abstract class Collection extends NodeBase {
/**
* Returns item at `key`, or `undefined` if not found.
*/
abstract get(key: unknown): NodeBase | Pair | undefined
abstract get(key: unknown): Node | Pair | undefined

/**
* Checks if the collection includes a value with the key `key`.
Expand Down Expand Up @@ -129,7 +151,7 @@ export abstract class Collection extends NodeBase {
/**
* Returns item at `key`, or `undefined` if not found.
*/
getIn(path: unknown[]): NodeBase | Pair | undefined {
getIn(path: unknown[]): Node | Pair | undefined {
const [key, ...rest] = path
const node = this.get(key)
if (rest.length === 0) return node
Expand Down
Loading
Loading