Skip to content

Commit

Permalink
feat: Refactor Node range as [start, value-end, node-end] (#259)
Browse files Browse the repository at this point in the history
  • Loading branch information
eemeli committed Apr 17, 2021
1 parent 8c01824 commit 16d01cc
Show file tree
Hide file tree
Showing 23 changed files with 100 additions and 80 deletions.
21 changes: 9 additions & 12 deletions docs/04_documents.md
Expand Up @@ -14,33 +14,30 @@ doc.contents
// YAMLMap {
// items:
// [ Pair {
// key: Scalar { value: 'YAML', range: [ 0, 4 ] },
// key: Scalar { value: 'YAML', range: [ 0, 4, 4 ] },
// value:
// YAMLSeq {
// items:
// [ Scalar {
// value: 'A human-readable data serialization language',
// range: [ 10, 55 ] },
// range: [ 10, 54, 55 ] },
// Scalar {
// value: 'https://en.wikipedia.org/wiki/YAML',
// range: [ 59, 94 ] } ],
// tag: 'tag:yaml.org,2002:seq',
// range: [ 8, 94 ] } },
// range: [ 59, 93, 94 ] } ],
// range: [ 8, 94, 94 ] } },
// Pair {
// key: Scalar { value: 'yaml', range: [ 94, 98 ] },
// key: Scalar { value: 'yaml', range: [ 94, 98, 98 ] },
// value:
// YAMLSeq {
// items:
// [ Scalar {
// value: 'A complete JavaScript implementation',
// range: [ 104, 141 ] },
// range: [ 104, 140, 141 ] },
// Scalar {
// value: 'https://www.npmjs.com/package/yaml',
// range: [ 145, 180 ] } ],
// tag: 'tag:yaml.org,2002:seq',
// range: [ 102, 180 ] } } ],
// tag: 'tag:yaml.org,2002:map',
// range: [ 0, 180 ] }
// range: [ 145, 180, 180 ] } ],
// range: [ 102, 180, 180 ] } } ],
// range: [ 0, 180, 180 ] }
```

#### `parseDocument(str, options = {}): Document`
Expand Down
6 changes: 3 additions & 3 deletions docs/05_content_nodes.md
Expand Up @@ -12,9 +12,9 @@ It is valid to have an anchor associated with a node even if it has no aliases.
class NodeBase {
comment?: string // a comment on or immediately after this
commentBefore?: string // a comment before this
range?: [number, number]
// the [start, end] range of characters of the source parsed
// into this node (undefined for pairs or if not parsed)
range?: [number, number, number]
// The [start, value-end, node-end] character offsets for the part
// of the source parsed into this node (undefined if not parsed).
spaceBefore?: boolean
// a blank line before this node and its commentBefore
tag?: string // a fully qualified tag, if required
Expand Down
2 changes: 1 addition & 1 deletion docs/07_parsing_yaml.md
Expand Up @@ -308,7 +308,7 @@ const item = doc.value.items[0].value
}

YAML.resolveAsScalar(item)
> { value: 'bar', type: 'QUOTE_DOUBLE', comment: 'comment', length: 14 }
> { value: 'bar', type: 'QUOTE_DOUBLE', comment: 'comment', range: [5, 9, 19] }
```

#### `CST.isCollection(token?: Token): boolean`
Expand Down
5 changes: 3 additions & 2 deletions src/compose/compose-doc.ts
Expand Up @@ -52,8 +52,9 @@ export function composeDoc(
? composeNode(ctx, value, props, onError)
: composeEmptyNode(ctx, props.end, start, null, props, onError)

const re = resolveEnd(end, doc.contents.range[1], false, onError)
const contentEnd = doc.contents.range[2]
const re = resolveEnd(end, contentEnd, false, onError)
if (re.comment) doc.comment = re.comment
doc.range = [offset, re.offset]
doc.range = [offset, contentEnd, re.offset]
return doc
}
5 changes: 3 additions & 2 deletions src/compose/compose-node.ts
Expand Up @@ -102,8 +102,9 @@ function composeAlias(
onError: ComposeErrorHandler
) {
const alias = new Alias(source.substring(1))
const re = resolveEnd(end, offset + source.length, options.strict, onError)
alias.range = [offset, re.offset]
const valueEnd = offset + source.length
const re = resolveEnd(end, valueEnd, options.strict, onError)
alias.range = [offset, valueEnd, re.offset]
if (re.comment) alias.comment = re.comment
return alias as Alias.Parsed
}
9 changes: 4 additions & 5 deletions src/compose/compose-scalar.ts
Expand Up @@ -14,8 +14,7 @@ export function composeScalar(
tagName: string | null,
onError: ComposeErrorHandler
) {
const { offset } = token
const { value, type, comment, length } =
const { value, type, comment, range } =
token.type === 'block-scalar'
? resolveBlockScalar(token, ctx.options.strict, onError)
: resolveFlowScalar(token, ctx.options.strict, onError)
Expand All @@ -28,15 +27,15 @@ export function composeScalar(
try {
const res = tag.resolve(
value,
msg => onError(offset, 'TAG_RESOLVE_FAILED', msg),
msg => onError(token.offset, 'TAG_RESOLVE_FAILED', msg),
ctx.options
)
scalar = isScalar(res) ? res : new Scalar(res)
} catch (error) {
onError(offset, 'TAG_RESOLVE_FAILED', error.message)
onError(token.offset, 'TAG_RESOLVE_FAILED', error.message)
scalar = new Scalar(value)
}
scalar.range = [offset, offset + length]
scalar.range = range
scalar.source = value
if (type) scalar.type = type
if (tagName) scalar.tag = tagName
Expand Down
4 changes: 2 additions & 2 deletions src/compose/composer.ts
Expand Up @@ -206,7 +206,7 @@ export class Composer {
const dc = this.doc.comment
this.doc.comment = dc ? `${dc}\n${end.comment}` : end.comment
}
this.doc.range[1] = end.offset
this.doc.range[2] = end.offset
break
}
default:
Expand Down Expand Up @@ -240,7 +240,7 @@ export class Composer {
'MISSING_CHAR',
'Missing directives-end indicator line'
)
doc.range = [0, endOffset]
doc.range = [0, endOffset, endOffset]
this.decorate(doc, false)
yield doc
}
Expand Down
6 changes: 3 additions & 3 deletions src/compose/resolve-block-map.ts
Expand Up @@ -65,7 +65,7 @@ export function resolveBlockMap(
const valueProps = resolveProps(sep || [], {
ctx,
indicator: 'map-value-ind',
offset: keyNode.range[1],
offset: keyNode.range[2],
onError,
startOnNewline: !key || key.type === 'block-scalar'
})
Expand Down Expand Up @@ -93,7 +93,7 @@ export function resolveBlockMap(
const valueNode = value
? composeNode(ctx, value, valueProps, onError)
: composeEmptyNode(ctx, offset, sep, null, valueProps, onError)
offset = valueNode.range[1]
offset = valueNode.range[2]
map.items.push(new Pair(keyNode, valueNode))
} else {
// key with no value
Expand All @@ -111,6 +111,6 @@ export function resolveBlockMap(
}
}

map.range = [bm.offset, offset]
map.range = [bm.offset, offset, offset]
return map as YAMLMap.Parsed
}
21 changes: 10 additions & 11 deletions src/compose/resolve-block-scalar.ts
@@ -1,3 +1,4 @@
import { Range } from '../nodes/Node.js'
import { Scalar } from '../nodes/Scalar.js'
import type { BlockScalar } from '../parse/cst.js'
import type { ComposeErrorHandler } from './composer.js'
Expand All @@ -10,10 +11,12 @@ export function resolveBlockScalar(
value: string
type: Scalar.BLOCK_FOLDED | Scalar.BLOCK_LITERAL | null
comment: string
length: number
range: Range
} {
const start = scalar.offset
const header = parseBlockScalarHeader(scalar, strict, onError)
if (!header) return { value: '', type: null, comment: '', length: 0 }
if (!header)
return { value: '', type: null, comment: '', range: [start, start, start] }
const type = header.mode === '>' ? Scalar.BLOCK_FOLDED : Scalar.BLOCK_LITERAL
const lines = scalar.source ? splitLines(scalar.source) : []

Expand All @@ -29,9 +32,9 @@ export function resolveBlockScalar(
if (!scalar.source || chompStart === 0) {
const value =
header.chomp === '+' ? lines.map(line => line[0]).join('\n') : ''
let length = header.length
if (scalar.source) length += scalar.source.length
return { value, type, comment: header.comment, length }
let end = start + header.length
if (scalar.source) end += scalar.source.length
return { value, type, comment: header.comment, range: [start, end, end] }
}

// find the indentation level to trim from start
Expand Down Expand Up @@ -113,12 +116,8 @@ export function resolveBlockScalar(
value += '\n'
}

return {
value,
type,
comment: header.comment,
length: header.length + scalar.source.length
}
const end = start + header.length + scalar.source.length
return { value, type, comment: header.comment, range: [start, end, end] }
}

function parseBlockScalarHeader(
Expand Down
4 changes: 2 additions & 2 deletions src/compose/resolve-block-seq.ts
Expand Up @@ -40,9 +40,9 @@ export function resolveBlockSeq(
const node = value
? composeNode(ctx, value, props, onError)
: composeEmptyNode(ctx, offset, start, null, props, onError)
offset = node.range[1]
offset = node.range[2]
seq.items.push(node)
}
seq.range = [bs.offset, offset]
seq.range = [bs.offset, offset, offset]
return seq as YAMLSeq.Parsed
}
19 changes: 11 additions & 8 deletions src/compose/resolve-flow-collection.ts
Expand Up @@ -113,7 +113,7 @@ export function resolveFlowCollection(
? composeNode(ctx, value, props, onError)
: composeEmptyNode(ctx, props.end, sep, null, props, onError)
;(coll as YAMLSeq).items.push(valueNode)
offset = valueNode.range[1]
offset = valueNode.range[2]
} else {
// item is a key+value pair

Expand All @@ -128,7 +128,7 @@ export function resolveFlowCollection(
ctx,
flow: fcName,
indicator: 'map-value-ind',
offset: keyNode.range[1],
offset: keyNode.range[2],
onError,
startOnNewline: false
})
Expand Down Expand Up @@ -188,29 +188,32 @@ export function resolveFlowCollection(
map.items.push(pair)
;(coll as YAMLSeq).items.push(map)
}
offset = valueNode ? valueNode.range[1] : valueProps.end
offset = valueNode ? valueNode.range[2] : valueProps.end
}
}

const expectedEnd = isMap ? '}' : ']'
const [ce, ...ee] = fc.end
if (!ce || ce.source !== expectedEnd) {
let cePos = offset
if (ce && ce.source === expectedEnd) cePos += ce.source.length
else {
onError(
offset + 1,
'MISSING_CHAR',
`Expected ${fcName} to end with ${expectedEnd}`
)
if (ce && ce.source.length !== 1) ee.unshift(ce)
}
if (ce) offset += ce.source.length
if (ee.length > 0) {
const end = resolveEnd(ee, offset, ctx.options.strict, onError)
const end = resolveEnd(ee, cePos, ctx.options.strict, onError)
if (end.comment) {
if (coll.comment) coll.comment += '\n' + end.comment
else coll.comment = end.comment
}
offset = end.offset
coll.range = [fc.offset, cePos, end.offset]
} else {
coll.range = [fc.offset, cePos, cePos]
}

coll.range = [fc.offset, offset]
return coll as YAMLMap.Parsed | YAMLSeq.Parsed
}
7 changes: 4 additions & 3 deletions src/compose/resolve-flow-scalar.ts
@@ -1,3 +1,4 @@
import { Range } from '../nodes/Node.js'
import { Scalar } from '../nodes/Scalar.js'
import type { FlowScalar } from '../parse/cst.js'
import type { ComposeErrorHandler } from './composer.js'
Expand All @@ -11,7 +12,7 @@ export function resolveFlowScalar(
value: string
type: Scalar.PLAIN | Scalar.QUOTE_DOUBLE | Scalar.QUOTE_SINGLE | null
comment: string
length: number
range: Range
} {
let _type: Scalar.PLAIN | Scalar.QUOTE_DOUBLE | Scalar.QUOTE_SINGLE
let value: string
Expand Down Expand Up @@ -44,7 +45,7 @@ export function resolveFlowScalar(
value: '',
type: null,
comment: '',
length: source.length
range: [offset, offset + source.length, offset + source.length]
}
}

Expand All @@ -53,7 +54,7 @@ export function resolveFlowScalar(
value,
type: _type,
comment: re.comment,
length: source.length + re.offset
range: [offset, offset + source.length, offset + source.length + re.offset]
}
}

Expand Down
13 changes: 9 additions & 4 deletions src/doc/Document.ts
Expand Up @@ -7,7 +7,8 @@ import {
isScalar,
Node,
NODE_TYPE,
ParsedNode
ParsedNode,
Range
} from '../nodes/Node.js'
import { Pair } from '../nodes/Pair.js'
import type { Scalar } from '../nodes/Scalar.js'
Expand Down Expand Up @@ -35,9 +36,7 @@ export type Replacer = any[] | ((key: any, value: any) => unknown)

export declare namespace Document {
interface Parsed<T extends ParsedNode = ParsedNode> extends Document<T> {
range: [number, number]
/** The schema used with the document. */
schema: Schema
range: Range
}
}

Expand Down Expand Up @@ -65,6 +64,12 @@ export class Document<T = unknown> {
>
>

/**
* The [start, value-end, node-end] character offsets for the part of the
* source parsed into this document (undefined if not parsed).
*/
declare range?: Range

// TS can't figure out that setSchema() will set this, or throw
/** The schema used with the document. Use `setSchema()` to change. */
declare schema: Schema
Expand Down
12 changes: 10 additions & 2 deletions src/nodes/Alias.ts
Expand Up @@ -2,15 +2,23 @@ import { anchorIsValid } from '../doc/anchors'
import type { Document } from '../doc/Document'
import type { StringifyContext } from '../stringify/stringify.js'
import { visit } from '../visit'
import { ALIAS, isAlias, isCollection, isPair, Node, NodeBase } from './Node.js'
import {
ALIAS,
isAlias,
isCollection,
isPair,
Node,
NodeBase,
Range
} from './Node.js'
import type { Scalar } from './Scalar'
import type { ToJSContext } from './toJS.js'
import type { YAMLMap } from './YAMLMap'
import type { YAMLSeq } from './YAMLSeq'

export declare namespace Alias {
interface Parsed extends Alias {
range: [number, number]
range: Range
}
}

Expand Down
8 changes: 5 additions & 3 deletions src/nodes/Node.ts
Expand Up @@ -14,6 +14,8 @@ export type ParsedNode =
| YAMLMap.Parsed
| YAMLSeq.Parsed

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')
Expand Down Expand Up @@ -75,10 +77,10 @@ export abstract class NodeBase {
declare commentBefore?: string | null

/**
* The [start, end] range of characters of the source parsed
* into this node (undefined for pairs or if not parsed)
* The [start, value-end, node-end] character offsets for the part of the
* source parsed into this node (undefined if not parsed).
*/
declare range?: [number, number] | null
declare range?: Range | null

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

0 comments on commit 16d01cc

Please sign in to comment.