Skip to content

Commit

Permalink
feat!: Replace error.offset with error.pos: [number, number] (#260)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: In addition to dropping `error.offset`, the shape
of `error.linePos` is changed to a matching tuple of `{ line, col }`
values.
  • Loading branch information
eemeli authored Apr 18, 2021
1 parent 16d01cc commit 89119ee
Show file tree
Hide file tree
Showing 17 changed files with 249 additions and 192 deletions.
17 changes: 13 additions & 4 deletions src/compose/compose-collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import { isMap, isNode, 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'
import type { BlockMap, BlockSequence, FlowCollection } from '../parse/cst.js'
import type {
BlockMap,
BlockSequence,
FlowCollection,
SourceToken
} from '../parse/cst.js'
import { CollectionTag } from '../schema/types.js'
import type { ComposeContext, ComposeNode } from './compose-node.js'
import type { ComposeErrorHandler } from './composer.js'
Expand All @@ -14,7 +19,7 @@ export function composeCollection(
CN: ComposeNode,
ctx: ComposeContext,
token: BlockMap | BlockSequence | FlowCollection,
tagName: string | null,
tagToken: SourceToken | null,
onError: ComposeErrorHandler
) {
let coll: YAMLMap.Parsed | YAMLSeq.Parsed
Expand All @@ -33,6 +38,10 @@ export function composeCollection(
}
}

if (!tagToken) return coll
const tagName = ctx.directives.tagName(tagToken.source, msg =>
onError(tagToken, 'TAG_RESOLVE_FAILED', msg)
)
if (!tagName) return coll

// Cast needed due to: https://github.com/Microsoft/TypeScript/issues/3841
Expand All @@ -53,7 +62,7 @@ export function composeCollection(
tag = kt
} else {
onError(
coll.range[0],
tagToken,
'TAG_RESOLVE_FAILED',
`Unresolved tag: ${tagName}`,
true
Expand All @@ -65,7 +74,7 @@ export function composeCollection(

const res = tag.resolve(
coll,
msg => onError(coll.range[0], 'TAG_RESOLVE_FAILED', msg),
msg => onError(tagToken, 'TAG_RESOLVE_FAILED', msg),
ctx.options
)
const node = isNode(res)
Expand Down
34 changes: 14 additions & 20 deletions src/compose/compose-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Directives } from '../doc/directives.js'
import { Alias } from '../nodes/Alias.js'
import type { ParsedNode } from '../nodes/Node.js'
import type { ParseOptions } from '../options.js'
import type { FlowScalar, Token } from '../parse/cst.js'
import type { FlowScalar, SourceToken, Token } from '../parse/cst.js'
import type { Schema } from '../schema/Schema.js'
import { composeCollection } from './compose-collection.js'
import { composeScalar } from './compose-scalar.js'
Expand All @@ -16,11 +16,11 @@ export interface ComposeContext {
schema: Readonly<Schema>
}

export interface Props {
interface Props {
spaceBefore: boolean
comment: string
anchor: string
tagName: string
anchor: SourceToken | null
tag: SourceToken | null
}

const CN = { composeNode, composeEmptyNode }
Expand All @@ -32,14 +32,14 @@ export function composeNode(
props: Props,
onError: ComposeErrorHandler
) {
const { spaceBefore, comment, anchor, tagName } = props
const { spaceBefore, comment, anchor, tag } = props
let node: ParsedNode
switch (token.type) {
case 'alias':
node = composeAlias(ctx, token, onError)
if (anchor || tagName)
if (anchor || tag)
onError(
token.offset,
token,
'ALIAS_PROPS',
'An alias node must not specify any properties'
)
Expand All @@ -48,18 +48,14 @@ export function composeNode(
case 'single-quoted-scalar':
case 'double-quoted-scalar':
case 'block-scalar':
node = composeScalar(ctx, token, tagName, onError)
if (anchor) {
node.anchor = anchor
}
node = composeScalar(ctx, token, tag, onError)
if (anchor) node.anchor = anchor.source.substring(1)
break
case 'block-map':
case 'block-seq':
case 'flow-collection':
node = composeCollection(CN, ctx, token, tagName, onError)
if (anchor) {
node.anchor = anchor
}
node = composeCollection(CN, ctx, token, tag, onError)
if (anchor) node.anchor = anchor.source.substring(1)
break
default:
console.log(token)
Expand All @@ -78,7 +74,7 @@ export function composeEmptyNode(
offset: number,
before: Token[] | undefined,
pos: number | null,
{ spaceBefore, comment, anchor, tagName }: Props,
{ spaceBefore, comment, anchor, tag }: Props,
onError: ComposeErrorHandler
) {
const token: FlowScalar = {
Expand All @@ -87,10 +83,8 @@ export function composeEmptyNode(
indent: -1,
source: ''
}
const node = composeScalar(ctx, token, tagName, onError)
if (anchor) {
node.anchor = anchor
}
const node = composeScalar(ctx, token, tag, onError)
if (anchor) node.anchor = anchor.source.substring(1)
if (spaceBefore) node.spaceBefore = true
if (comment) node.comment = comment
return node
Expand Down
23 changes: 15 additions & 8 deletions src/compose/compose-scalar.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isScalar, SCALAR } from '../nodes/Node.js'
import { Scalar } from '../nodes/Scalar.js'
import type { BlockScalar, FlowScalar } from '../parse/cst.js'
import type { BlockScalar, FlowScalar, SourceToken } from '../parse/cst.js'
import type { Schema } from '../schema/Schema.js'
import type { ScalarTag } from '../schema/types.js'
import type { ComposeContext } from './compose-node.js'
Expand All @@ -11,28 +11,34 @@ import { resolveFlowScalar } from './resolve-flow-scalar.js'
export function composeScalar(
ctx: ComposeContext,
token: FlowScalar | BlockScalar,
tagName: string | null,
tagToken: SourceToken | null,
onError: ComposeErrorHandler
) {
const { value, type, comment, range } =
token.type === 'block-scalar'
? resolveBlockScalar(token, ctx.options.strict, onError)
: resolveFlowScalar(token, ctx.options.strict, onError)

const tag = tagName
? findScalarTagByName(ctx.schema, value, tagName, onError)
: findScalarTagByTest(ctx.schema, value, token.type === 'scalar')
const tagName = tagToken
? ctx.directives.tagName(tagToken.source, msg =>
onError(tagToken, 'TAG_RESOLVE_FAILED', msg)
)
: null
const tag =
tagToken && tagName
? findScalarTagByName(ctx.schema, value, tagName, tagToken, onError)
: findScalarTagByTest(ctx.schema, value, token.type === 'scalar')

let scalar: Scalar
try {
const res = tag.resolve(
value,
msg => onError(token.offset, 'TAG_RESOLVE_FAILED', msg),
msg => onError(tagToken || token, 'TAG_RESOLVE_FAILED', msg),
ctx.options
)
scalar = isScalar(res) ? res : new Scalar(res)
} catch (error) {
onError(token.offset, 'TAG_RESOLVE_FAILED', error.message)
onError(tagToken || token, 'TAG_RESOLVE_FAILED', error.message)
scalar = new Scalar(value)
}
scalar.range = range
Expand All @@ -49,6 +55,7 @@ function findScalarTagByName(
schema: Schema,
value: string,
tagName: string,
tagToken: SourceToken,
onError: ComposeErrorHandler
) {
if (tagName === '!') return schema[SCALAR] // non-specific tag
Expand All @@ -68,7 +75,7 @@ function findScalarTagByName(
return kt
}
onError(
0,
tagToken,
'TAG_RESOLVE_FAILED',
`Unresolved tag: ${tagName}`,
tagName !== 'tag:yaml.org,2002:str'
Expand Down
49 changes: 32 additions & 17 deletions src/compose/composer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Directives } from '../doc/directives.js'
import { Document } from '../doc/Document.js'
import { ErrorCode, YAMLParseError, YAMLWarning } from '../errors.js'
import { isCollection, isPair } from '../nodes/Node.js'
import { isCollection, isPair, Range } from '../nodes/Node.js'
import {
defaultOptions,
DocumentOptions,
Expand All @@ -12,13 +12,26 @@ import type { Token } from '../parse/cst.js'
import { composeDoc } from './compose-doc.js'
import { resolveEnd } from './resolve-end.js'

type ErrorSource =
| number
| [number, number]
| Range
| { offset: number; source?: string }

export type ComposeErrorHandler = (
offset: number,
source: ErrorSource,
code: ErrorCode,
message: string,
warning?: boolean
) => void

function getErrorPos(src: ErrorSource): [number, number] {
if (typeof src === 'number') return [src, src + 1]
if (Array.isArray(src)) return src.length === 2 ? src : [src[0], src[1]]
const { offset, source } = src
return [offset, offset + (typeof source === 'string' ? source.length : 1)]
}

function parsePrelude(prelude: string[]) {
let comment = ''
let atComment = false
Expand Down Expand Up @@ -73,14 +86,10 @@ export class Composer {
this.options = options
}

private onError = (
offset: number,
code: ErrorCode,
message: string,
warning?: boolean
) => {
if (warning) this.warnings.push(new YAMLWarning(offset, code, message))
else this.errors.push(new YAMLParseError(offset, code, message))
private onError: ComposeErrorHandler = (source, code, message, warning) => {
const pos = getErrorPos(source)
if (warning) this.warnings.push(new YAMLWarning(pos, code, message))
else this.errors.push(new YAMLParseError(pos, code, message))
}

private decorate(doc: Document.Parsed, afterDoc: boolean) {
Expand Down Expand Up @@ -146,9 +155,11 @@ export class Composer {
if (process.env.LOG_STREAM) console.dir(token, { depth: null })
switch (token.type) {
case 'directive':
this.directives.add(token.source, (offset, message, warning) =>
this.onError(offset, 'BAD_DIRECTIVE', message, warning)
)
this.directives.add(token.source, (offset, message, warning) => {
const pos = getErrorPos(token)
pos[0] += offset
this.onError(pos, 'BAD_DIRECTIVE', message, warning)
})
this.prelude.push(token.source)
this.atDirectives = true
break
Expand All @@ -161,7 +172,7 @@ export class Composer {
)
if (this.atDirectives && !doc.directives.marker)
this.onError(
token.offset,
token,
'MISSING_CHAR',
'Missing directives-end indicator line'
)
Expand All @@ -182,7 +193,11 @@ export class Composer {
const msg = token.source
? `${token.message}: ${JSON.stringify(token.source)}`
: token.message
const error = new YAMLParseError(-1, 'UNEXPECTED_TOKEN', msg)
const error = new YAMLParseError(
getErrorPos(token),
'UNEXPECTED_TOKEN',
msg
)
if (this.atDirectives || !this.doc) this.errors.push(error)
else this.doc.errors.push(error)
break
Expand All @@ -191,7 +206,7 @@ export class Composer {
if (!this.doc) {
const msg = 'Unexpected doc-end without preceding document'
this.errors.push(
new YAMLParseError(token.offset, 'UNEXPECTED_TOKEN', msg)
new YAMLParseError(getErrorPos(token), 'UNEXPECTED_TOKEN', msg)
)
break
}
Expand All @@ -212,7 +227,7 @@ export class Composer {
default:
this.errors.push(
new YAMLParseError(
-1,
getErrorPos(token),
'UNEXPECTED_TOKEN',
`Unsupported token ${token.type}`
)
Expand Down
10 changes: 5 additions & 5 deletions src/compose/resolve-block-map.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Pair } from '../nodes/Pair.js'
import { YAMLMap } from '../nodes/YAMLMap.js'
import type { BlockMap } from '../parse/cst.js'
import type { BlockMap, Token } from '../parse/cst.js'
import type { ComposeContext, ComposeNode } from './compose-node.js'
import type { ComposeErrorHandler } from './composer.js'
import { resolveProps } from './resolve-props.js'
Expand Down Expand Up @@ -38,7 +38,7 @@ export function resolveBlockMap(
else if ('indent' in key && key.indent !== bm.indent)
onError(offset, 'BAD_INDENT', startColMsg)
}
if (!keyProps.anchor && !keyProps.tagName && !sep) {
if (!keyProps.anchor && !keyProps.tag && !sep) {
// TODO: assert being at last item?
if (keyProps.comment) {
if (map.comment) map.comment += '\n' + keyProps.comment
Expand All @@ -50,7 +50,7 @@ export function resolveBlockMap(
onError(offset, 'BAD_INDENT', startColMsg)
if (implicitKey && containsNewline(key))
onError(
keyProps.start,
key as Token, // checked by containsNewline()
'MULTILINE_IMPLICIT_KEY',
'Implicit keys need to be on a single line'
)
Expand Down Expand Up @@ -84,7 +84,7 @@ export function resolveBlockMap(
keyProps.start < valueProps.found.offset - 1024
)
onError(
offset,
keyNode.range,
'KEY_OVER_1024_CHARS',
'The : indicator must be at most 1024 chars after the start of an implicit block mapping key'
)
Expand All @@ -99,7 +99,7 @@ export function resolveBlockMap(
// key with no value
if (implicitKey)
onError(
keyStart,
keyNode.range,
'MISSING_CHAR',
'Implicit map keys need to be followed by map values'
)
Expand Down
8 changes: 4 additions & 4 deletions src/compose/resolve-block-scalar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ function parseBlockScalarHeader(
) {
/* istanbul ignore if should not happen */
if (props[0].type !== 'block-scalar-header') {
onError(offset, 'IMPOSSIBLE', 'Block scalar header not found')
onError(props[0], 'IMPOSSIBLE', 'Block scalar header not found')
return null
}
const { source } = props[0]
Expand Down Expand Up @@ -166,19 +166,19 @@ function parseBlockScalarHeader(
if (strict && !hasSpace) {
const message =
'Comments must be separated from other tokens by white space characters'
onError(offset + length, 'COMMENT_SPACE', message)
onError(token, 'COMMENT_SPACE', message)
}
length += token.source.length
comment = token.source.substring(1)
break
case 'error':
onError(offset + length, 'UNEXPECTED_TOKEN', token.message)
onError(token, 'UNEXPECTED_TOKEN', token.message)
length += token.source.length
break
/* istanbul ignore next should not happen */
default: {
const message = `Unexpected token in block scalar header: ${token.type}`
onError(offset + length, 'UNEXPECTED_TOKEN', message)
onError(token, 'UNEXPECTED_TOKEN', message)
const ts = (token as any).source
if (ts && typeof ts === 'string') length += ts.length
}
Expand Down
Loading

0 comments on commit 89119ee

Please sign in to comment.