Skip to content

Commit

Permalink
feat: Add a toJS(doc, options?) method to nodes (#451)
Browse files Browse the repository at this point in the history
  • Loading branch information
eemeli committed Mar 27, 2023
1 parent 481761d commit eda2d8a
Show file tree
Hide file tree
Showing 36 changed files with 263 additions and 137 deletions.
5 changes: 1 addition & 4 deletions .eslintrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@ overrides:
rules:
camelcase: 0
'@typescript-eslint/no-non-null-assertion': off
'@typescript-eslint/no-unsafe-return': off

- files: [tests/doc/**]
rules:
'@typescript-eslint/no-unsafe-call': off
'@typescript-eslint/no-unsafe-return': off
'@typescript-eslint/unbound-method': off
2 changes: 1 addition & 1 deletion docs/03_options.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ parse('{[1, 2]: many}', { mapAsMap: true }) // Map { [ 1, 2 ] => 'many' }

These options influence how the document is transformed into "native" JavaScript representation.

Used by: `parse()` and `doc.toJS()`
Used by: `parse()`, `doc.toJS()` and `node.toJS()`

| Name | Type | Default | Description |
| ------------- | ------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
Expand Down
3 changes: 2 additions & 1 deletion docs/05_content_nodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ class NodeBase {
// a blank line before this node and its commentBefore
tag?: string // a fully qualified tag, if required
clone(): NodeBase // a copy of this node
toJSON(): any // a plain JS or JSON representation of this node
toJS(doc, options?): any // a plain JS representation of this node
toJSON(): any // a plain JSON representation of this node
}
```
Expand Down
3 changes: 2 additions & 1 deletion src/compose/compose-collection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { isMap, isNode, ParsedNode } from '../nodes/Node.js'
import { isMap, isNode } from '../nodes/identity.js'
import type { ParsedNode } from '../nodes/Node.js'
import { Scalar } from '../nodes/Scalar.js'
import type { YAMLMap } from '../nodes/YAMLMap.js'
import type { YAMLSeq } from '../nodes/YAMLSeq.js'
Expand Down
2 changes: 1 addition & 1 deletion src/compose/compose-scalar.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isScalar, SCALAR } from '../nodes/Node.js'
import { isScalar, SCALAR } from '../nodes/identity.js'
import { Scalar } from '../nodes/Scalar.js'
import type { BlockScalar, FlowScalar, SourceToken } from '../parse/cst.js'
import type { Schema } from '../schema/Schema.js'
Expand Down
3 changes: 2 additions & 1 deletion src/compose/composer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Directives } from '../doc/directives.js'
import { Document } from '../doc/Document.js'
import { ErrorCode, YAMLParseError, YAMLWarning } from '../errors.js'
import { isCollection, isPair, ParsedNode, Range } from '../nodes/Node.js'
import { isCollection, isPair } from '../nodes/identity.js'
import type { ParsedNode, Range } from '../nodes/Node.js'
import type {
DocumentOptions,
ParseOptions,
Expand Down
2 changes: 1 addition & 1 deletion src/compose/resolve-flow-collection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isPair } from '../nodes/Node.js'
import { isPair } from '../nodes/identity.js'
import { Pair } from '../nodes/Pair.js'
import { YAMLMap } from '../nodes/YAMLMap.js'
import { YAMLSeq } from '../nodes/YAMLSeq.js'
Expand Down
7 changes: 4 additions & 3 deletions src/compose/util-map-includes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { isScalar, ParsedNode } from '../nodes/Node'
import { Pair } from '../nodes/Pair'
import { ComposeContext } from './compose-node'
import { isScalar } from '../nodes/identity.js'
import type { ParsedNode } from '../nodes/Node.js'
import type { Pair } from '../nodes/Pair.js'
import type { ComposeContext } from './compose-node.js'

export function mapIncludes(
ctx: ComposeContext,
Expand Down
13 changes: 4 additions & 9 deletions src/doc/Document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@ import {
isCollection,
isNode,
isScalar,
Node,
NodeType,
NODE_TYPE,
ParsedNode,
Range
} from '../nodes/Node.js'
NODE_TYPE
} from '../nodes/identity.js'
import type { Node, NodeType, ParsedNode, Range } from '../nodes/Node.js'
import { Pair } from '../nodes/Pair.js'
import type { Scalar } from '../nodes/Scalar.js'
import { toJS, ToJSContext } from '../nodes/toJS.js'
Expand All @@ -26,7 +23,6 @@ import type {
ToStringOptions
} from '../options.js'
import { Schema } from '../schema/Schema.js'
import { stringify } from '../stringify/stringify.js'
import { stringifyDocument } from '../stringify/stringifyDocument.js'
import { anchorNames, createNodeAnchors, findNewAnchor } from './anchors.js'
import { applyReviver } from './applyReviver.js'
Expand Down Expand Up @@ -434,8 +430,7 @@ export class Document<
keep: !json,
mapAsMap: mapAsMap === true,
mapKeyWarned: false,
maxAliasCount: typeof maxAliasCount === 'number' ? maxAliasCount : 100,
stringify
maxAliasCount: typeof maxAliasCount === 'number' ? maxAliasCount : 100
}
const res = toJS(this.contents, jsonArg ?? '', ctx)
if (typeof onAnchor === 'function')
Expand Down
3 changes: 2 additions & 1 deletion src/doc/anchors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { isCollection, isScalar, Node } from '../nodes/Node.js'
import { isCollection, isScalar } from '../nodes/identity.js'
import type { Node } from '../nodes/Node.js'
import type { Scalar } from '../nodes/Scalar.js'
import type { YAMLMap } from '../nodes/YAMLMap.js'
import type { YAMLSeq } from '../nodes/YAMLSeq.js'
Expand Down
3 changes: 2 additions & 1 deletion src/doc/createNode.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Alias } from '../nodes/Alias.js'
import { isDocument, isNode, isPair, MAP, Node, SEQ } from '../nodes/Node.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'
Expand Down
2 changes: 1 addition & 1 deletion src/doc/directives.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isNode } from '../nodes/Node.js'
import { isNode } from '../nodes/identity.js'
import { visit } from '../visit.js'
import type { Document } from './Document.js'

Expand Down
8 changes: 3 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ export {
isNode,
isPair,
isScalar,
isSeq,
Node,
ParsedNode,
Range
} from './nodes/Node.js'
isSeq
} from './nodes/identity.js'
export { Node, ParsedNode, Range } from './nodes/Node.js'
export { Pair } from './nodes/Pair.js'
export { Scalar } from './nodes/Scalar.js'
export { YAMLMap } from './nodes/YAMLMap.js'
Expand Down
20 changes: 9 additions & 11 deletions src/nodes/Alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,10 @@ import type { Document } from '../doc/Document.js'
import type { FlowScalar } from '../parse/cst.js'
import type { StringifyContext } from '../stringify/stringify.js'
import { visit } from '../visit.js'
import {
ALIAS,
isAlias,
isCollection,
isPair,
Node,
NodeBase,
Range
} from './Node.js'
import { ALIAS, isAlias, isCollection, isPair } from './identity.js'
import { Node, NodeBase, Range } from './Node.js'
import type { Scalar } from './Scalar'
import type { ToJSContext } from './toJS.js'
import { toJS, ToJSContext } from './toJS.js'
import type { YAMLMap } from './YAMLMap.js'
import type { YAMLSeq } from './YAMLSeq.js'

Expand Down Expand Up @@ -62,7 +55,12 @@ export class Alias extends NodeBase {
const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`
throw new ReferenceError(msg)
}
const data = anchors.get(source)
let data = anchors.get(source)
if (!data) {
// Resolve anchors for Node.prototype.toJS()
toJS(source, null, ctx)
data = anchors.get(source)
}
/* istanbul ignore if */
if (!data || data.res === undefined) {
const msg = 'This should not happen: Alias anchor was not resolved?'
Expand Down
4 changes: 2 additions & 2 deletions src/nodes/Collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import {
isNode,
isPair,
isScalar,
NodeBase,
NODE_TYPE
} from './Node.js'
} from './identity.js'
import { NodeBase } from './Node.js'

export function collectionFromPath(
schema: Schema,
Expand Down
87 changes: 25 additions & 62 deletions src/nodes/Node.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { applyReviver } from '../doc/applyReviver.js'
import type { Document } from '../doc/Document.js'
import type { ToJSOptions } from '../options.js'
import { Token } from '../parse/cst.js'
import type { StringifyContext } from '../stringify/stringify.js'
import type { Alias } from './Alias.js'
import type { Pair } from './Pair.js'
import { NODE_TYPE } from './identity.js'
import type { Scalar } from './Scalar.js'
import { toJS, ToJSContext } from './toJS.js'
import type { YAMLMap } from './YAMLMap.js'
import type { YAMLSeq } from './YAMLSeq.js'

Expand Down Expand Up @@ -40,67 +43,6 @@ export type ParsedNode =

export type Range = [number, number, number]

export const ALIAS = Symbol.for('yaml.alias')
export const DOC = Symbol.for('yaml.document')
export const MAP = Symbol.for('yaml.map')
export const PAIR = Symbol.for('yaml.pair')
export const SCALAR = Symbol.for('yaml.scalar')
export const SEQ = Symbol.for('yaml.seq')
export const NODE_TYPE = Symbol.for('yaml.node.type')

export const isAlias = (node: any): node is Alias =>
!!node && typeof node === 'object' && node[NODE_TYPE] === ALIAS

export const isDocument = <T extends Node = Node>(
node: any
): node is Document<T> =>
!!node && typeof node === 'object' && node[NODE_TYPE] === DOC

export const isMap = <K = unknown, V = unknown>(
node: any
): node is YAMLMap<K, V> =>
!!node && typeof node === 'object' && node[NODE_TYPE] === MAP

export const isPair = <K = unknown, V = unknown>(
node: any
): node is Pair<K, V> =>
!!node && typeof node === 'object' && node[NODE_TYPE] === PAIR

export const isScalar = <T = unknown>(node: any): node is Scalar<T> =>
!!node && typeof node === 'object' && node[NODE_TYPE] === SCALAR

export const isSeq = <T = unknown>(node: any): node is YAMLSeq<T> =>
!!node && typeof node === 'object' && node[NODE_TYPE] === SEQ

export function isCollection<K = unknown, V = unknown>(
node: any
): node is YAMLMap<K, V> | YAMLSeq<V> {
if (node && typeof node === 'object')
switch (node[NODE_TYPE]) {
case MAP:
case SEQ:
return true
}
return false
}

export function isNode<T = unknown>(node: any): node is Node<T> {
if (node && typeof node === 'object')
switch (node[NODE_TYPE]) {
case ALIAS:
case MAP:
case SCALAR:
case SEQ:
return true
}
return false
}

export const hasAnchor = <K = unknown, V = unknown>(
node: unknown
): node is Scalar<V> | YAMLMap<K, V> | YAMLSeq<V> =>
(isScalar(node) || isCollection(node)) && !!node.anchor

export abstract class NodeBase {
declare readonly [NODE_TYPE]: symbol

Expand Down Expand Up @@ -149,4 +91,25 @@ export abstract class NodeBase {
if (this.range) copy.range = this.range.slice() as NodeBase['range']
return copy
}

/** A plain JavaScript representation of this node. */
toJS(
doc: Document<Node, boolean>,
{ mapAsMap, maxAliasCount, onAnchor, reviver }: ToJSOptions = {}
): any {
const ctx: ToJSContext = {
anchors: new Map(),
doc,
keep: true,
mapAsMap: mapAsMap === true,
mapKeyWarned: false,
maxAliasCount: typeof maxAliasCount === 'number' ? maxAliasCount : 100
}
const res = toJS(this, '', ctx)
if (typeof onAnchor === 'function')
for (const { count, res } of ctx.anchors.values()) onAnchor(res, count)
return typeof reviver === 'function'
? applyReviver(reviver, { '': res }, '', res)
: res
}
}
2 changes: 1 addition & 1 deletion src/nodes/Pair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { Schema } from '../schema/Schema.js'
import type { StringifyContext } from '../stringify/stringify.js'
import { stringifyPair } from '../stringify/stringifyPair.js'
import { addPairToJSMap } from './addPairToJSMap.js'
import { isNode, NODE_TYPE, PAIR } from './Node.js'
import { isNode, NODE_TYPE, PAIR } from './identity.js'
import type { ToJSContext } from './toJS.js'

export function createPair(
Expand Down
3 changes: 2 additions & 1 deletion src/nodes/Scalar.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { BlockScalar, FlowScalar } from '../parse/cst.js'
import { NodeBase, Range, SCALAR } from './Node.js'
import { SCALAR } from './identity.js'
import { NodeBase, Range } from './Node.js'
import { toJS, ToJSContext } from './toJS.js'

export const isScalarValue = (value: unknown) =>
Expand Down
3 changes: 2 additions & 1 deletion src/nodes/YAMLMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import type { StringifyContext } from '../stringify/stringify.js'
import { stringifyCollection } from '../stringify/stringifyCollection.js'
import { addPairToJSMap } from './addPairToJSMap.js'
import { Collection } from './Collection.js'
import { isPair, isScalar, MAP, ParsedNode, Range } from './Node.js'
import { isPair, isScalar, MAP } from './identity.js'
import type { ParsedNode, Range } from './Node.js'
import { Pair } from './Pair.js'
import { isScalarValue, Scalar } from './Scalar.js'
import type { ToJSContext } from './toJS.js'
Expand Down
3 changes: 2 additions & 1 deletion src/nodes/YAMLSeq.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import type { Schema } from '../schema/Schema.js'
import type { StringifyContext } from '../stringify/stringify.js'
import { stringifyCollection } from '../stringify/stringifyCollection.js'
import { Collection } from './Collection.js'
import { isScalar, ParsedNode, Range, SEQ } from './Node.js'
import { isScalar, SEQ } from './identity.js'
import type { ParsedNode, Range } from './Node.js'
import type { Pair } from './Pair.js'
import { isScalarValue, Scalar } from './Scalar.js'
import { toJS, ToJSContext } from './toJS.js'
Expand Down
2 changes: 1 addition & 1 deletion src/nodes/addPairToJSMap.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { warn } from '../log.js'
import { createStringifyContext } from '../stringify/stringify.js'
import { isAlias, isMap, isNode, isScalar, isSeq } from './Node.js'
import { isAlias, isMap, isNode, isScalar, isSeq } from './identity.js'
import type { Pair } from './Pair.js'
import { Scalar } from './Scalar.js'
import { toJS, ToJSContext } from './toJS.js'
Expand Down

0 comments on commit eda2d8a

Please sign in to comment.