From e30994b97a9af1973b4ec5b5c5d42f52f8000718 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sun, 12 Feb 2023 13:20:53 +0200 Subject: [PATCH 01/20] feat(ts): Add Strict type flag for Document --- src/doc/Document.ts | 61 +++++++++++++++++------------- src/doc/anchors.ts | 7 +++- src/nodes/toJS.ts | 2 +- src/public-api.ts | 38 ++++++++++++++----- src/stringify/stringifyDocument.ts | 4 +- 5 files changed, 72 insertions(+), 40 deletions(-) diff --git a/src/doc/Document.ts b/src/doc/Document.ts index 1ad738a9..2fe1ece0 100644 --- a/src/doc/Document.ts +++ b/src/doc/Document.ts @@ -36,13 +36,19 @@ import { Directives } from './directives.js' export type Replacer = any[] | ((key: any, value: any) => unknown) export declare namespace Document { - interface Parsed extends Document { + interface Parsed< + Contents extends ParsedNode = ParsedNode, + Strict extends boolean = true + > extends Document { directives: Directives range: Range } } -export class Document { +export class Document< + Contents extends Node = Node, + Strict extends boolean = true +> { declare readonly [NODE_TYPE]: symbol /** A comment before this Document */ @@ -52,9 +58,9 @@ export class Document { comment: string | null = null /** The document contents. */ - contents: T | null + contents: Strict extends true ? Contents | null : Contents - directives?: Directives + directives: Strict extends true ? Directives | undefined : Directives /** Errors encountered during parsing. */ errors: YAMLError[] = [] @@ -131,10 +137,9 @@ export class Document { } else this.directives = new Directives({ version }) this.setSchema(version, options) - if (value === undefined) this.contents = null - else { - this.contents = this.createNode(value, _replacer, options) as unknown as T - } + // @ts-expect-error We can't really know that this matches Contents. + this.contents = + value === undefined ? null : this.createNode(value, _replacer, options) } /** @@ -142,8 +147,8 @@ export class Document { * * Custom Node values that inherit from `Object` still refer to their original instances. */ - clone(): Document { - const copy: Document = Object.create(Document.prototype, { + clone(): Document { + const copy: Document = Object.create(Document.prototype, { [NODE_TYPE]: { value: DOC } }) copy.commentBefore = this.commentBefore @@ -153,8 +158,9 @@ export class Document { copy.options = Object.assign({}, this.options) if (this.directives) copy.directives = this.directives.clone() copy.schema = this.schema.clone() + // @ts-expect-error We can't really know that this matches Contents. copy.contents = isNode(this.contents) - ? (this.contents.clone(copy.schema) as unknown as T) + ? this.contents.clone(copy.schema) : this.contents if (this.range) copy.range = this.range.slice() as Document['range'] return copy @@ -179,7 +185,10 @@ export class Document { * `name` will be used as a prefix for a new unique anchor. * If `name` is undefined, the generated anchor will use 'a' as a prefix. */ - createAlias(node: Scalar | YAMLMap | YAMLSeq, name?: string): Alias { + createAlias( + node: Strict extends true ? Scalar | YAMLMap | YAMLSeq : Node, + name?: string + ): Alias { if (!node.anchor) { const prev = anchorNames(this) node.anchor = @@ -276,6 +285,7 @@ export class Document { deleteIn(path: Iterable | null): boolean { if (isEmptyPath(path)) { if (this.contents == null) return false + // @ts-expect-error Presumed impossible if Strict extends false this.contents = null return true } @@ -289,7 +299,7 @@ export class Document { * scalar values from their surrounding node; to disable set `keepScalar` to * `true` (collections are always returned intact). */ - get(key: unknown, keepScalar?: boolean): unknown { + get(key: unknown, keepScalar?: boolean): Strict extends true ? unknown : any { return isCollection(this.contents) ? this.contents.get(key, keepScalar) : undefined @@ -300,7 +310,10 @@ export class Document { * scalar values from their surrounding node; to disable set `keepScalar` to * `true` (collections are always returned intact). */ - getIn(path: Iterable | null, keepScalar?: boolean): unknown { + getIn( + path: Iterable | null, + keepScalar?: boolean + ): Strict extends true ? unknown : any { if (isEmptyPath(path)) return !keepScalar && isScalar(this.contents) ? this.contents.value @@ -331,11 +344,8 @@ export class Document { */ set(key: any, value: unknown): void { if (this.contents == null) { - this.contents = collectionFromPath( - this.schema, - [key], - value - ) as unknown as T + // @ts-expect-error We can't really know that this matches Contents. + this.contents = collectionFromPath(this.schema, [key], value) } else if (assertCollection(this.contents)) { this.contents.set(key, value) } @@ -346,13 +356,12 @@ export class Document { * boolean to add/remove the item from the set. */ setIn(path: Iterable | null, value: unknown): void { - if (isEmptyPath(path)) this.contents = value as T - else if (this.contents == null) { - this.contents = collectionFromPath( - this.schema, - Array.from(path), - value - ) as unknown as T + if (isEmptyPath(path)) { + // @ts-expect-error We can't really know that this matches Contents. + this.contents = value + } else if (this.contents == null) { + // @ts-expect-error We can't really know that this matches Contents. + this.contents = collectionFromPath(this.schema, Array.from(path), value) } else if (assertCollection(this.contents)) { this.contents.setIn(path, value) } diff --git a/src/doc/anchors.ts b/src/doc/anchors.ts index 6537de6c..07cff9ec 100644 --- a/src/doc/anchors.ts +++ b/src/doc/anchors.ts @@ -20,7 +20,7 @@ export function anchorIsValid(anchor: string): true { return true } -export function anchorNames(root: Document | Node) { +export function anchorNames(root: Document | Node) { const anchors = new Set() visit(root, { Value(_key: unknown, node: Scalar | YAMLMap | YAMLSeq) { @@ -38,7 +38,10 @@ export function findNewAnchor(prefix: string, exclude: Set) { } } -export function createNodeAnchors(doc: Document, prefix: string) { +export function createNodeAnchors( + doc: Document, + prefix: string +) { const aliasObjects: unknown[] = [] const sourceObjects: CreateNodeContext['sourceObjects'] = new Map() let prevAnchors: Set | null = null diff --git a/src/nodes/toJS.ts b/src/nodes/toJS.ts index 26cd005c..f743d8bf 100644 --- a/src/nodes/toJS.ts +++ b/src/nodes/toJS.ts @@ -10,7 +10,7 @@ export interface AnchorData { export interface ToJSContext { anchors: Map - doc: Document + doc: Document keep: boolean mapAsMap: boolean mapKeyWarned: boolean diff --git a/src/public-api.ts b/src/public-api.ts index 695f4d2c..86680ea4 100644 --- a/src/public-api.ts +++ b/src/public-api.ts @@ -3,7 +3,7 @@ import type { Reviver } from './doc/applyReviver.js' import { Document, Replacer } from './doc/Document.js' import { prettifyError, YAMLParseError } from './errors.js' import { warn } from './log.js' -import type { ParsedNode } from './nodes/Node.js' +import type { Node, ParsedNode } from './nodes/Node.js' import type { CreateNodeOptions, DocumentOptions, @@ -37,10 +37,19 @@ function parseOptions(options: ParseOptions) { * EmptyStream and contain additional stream information. In * TypeScript, you should use `'empty' in docs` as a type guard for it. */ -export function parseAllDocuments( +export function parseAllDocuments< + Contents extends Node = ParsedNode, + Strict extends boolean = true +>( source: string, options: ParseOptions & DocumentOptions & SchemaOptions = {} -): Document.Parsed[] | EmptyStream { +): + | Array< + Contents extends ParsedNode + ? Document.Parsed + : Document + > + | EmptyStream { const { lineCounter, prettyErrors } = parseOptions(options) const parser = new Parser(lineCounter?.addNewLine) const composer = new Composer(options) @@ -52,31 +61,42 @@ export function parseAllDocuments( doc.warnings.forEach(prettifyError(source, lineCounter)) } - if (docs.length > 0) return docs as Document.Parsed[] + type DocType = Contents extends ParsedNode + ? Document.Parsed + : Document + if (docs.length > 0) return docs as DocType[] return Object.assign< - Document.Parsed[], + DocType[], { empty: true }, ReturnType >([], { empty: true }, composer.streamInfo()) } /** Parse an input string into a single YAML.Document */ -export function parseDocument( +export function parseDocument< + Contents extends Node = ParsedNode, + Strict extends boolean = true +>( source: string, options: ParseOptions & DocumentOptions & SchemaOptions = {} -) { +): Contents extends ParsedNode + ? Document.Parsed + : Document { const { lineCounter, prettyErrors } = parseOptions(options) const parser = new Parser(lineCounter?.addNewLine) const composer = new Composer(options) + type DocType = Contents extends ParsedNode + ? Document.Parsed + : Document // `doc` is always set by compose.end(true) at the very latest - let doc: Document.Parsed = null as any + let doc: DocType = null as any for (const _doc of composer.compose( parser.parse(source), true, source.length )) { - if (!doc) doc = _doc as Document.Parsed + if (!doc) doc = _doc as DocType else if (doc.options.logLevel !== 'silent') { doc.errors.push( new YAMLParseError( diff --git a/src/stringify/stringifyDocument.ts b/src/stringify/stringifyDocument.ts index c2a36192..a55e575f 100644 --- a/src/stringify/stringifyDocument.ts +++ b/src/stringify/stringifyDocument.ts @@ -1,5 +1,5 @@ import { Document } from '../doc/Document.js' -import { isNode } from '../nodes/Node.js' +import { isNode, Node } from '../nodes/Node.js' import { ToStringOptions } from '../options.js' import { createStringifyContext, @@ -9,7 +9,7 @@ import { import { indentComment, lineComment } from './stringifyComment.js' export function stringifyDocument( - doc: Readonly, + doc: Readonly>, options: ToStringOptions ) { const lines: string[] = [] From edd9cdd30e9cd9f5ef493347ba87f5d688713229 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sun, 12 Feb 2023 13:34:15 +0200 Subject: [PATCH 02/20] test: Convert tests/doc/anchors from JS to TS --- tests/doc/{anchors.js => anchors.ts} | 115 +++++++++++++++------------ tests/tsconfig.json | 4 +- 2 files changed, 64 insertions(+), 55 deletions(-) rename tests/doc/{anchors.js => anchors.ts} (77%) diff --git a/tests/doc/anchors.js b/tests/doc/anchors.ts similarity index 77% rename from tests/doc/anchors.js rename to tests/doc/anchors.ts index dc27be2d..9ea4cd06 100644 --- a/tests/doc/anchors.js +++ b/tests/doc/anchors.ts @@ -1,10 +1,10 @@ -import * as YAML from 'yaml' +import { Alias, Document, parse, parseDocument, YAMLMap, YAMLSeq } from 'yaml' test('basic', () => { const src = `- &a 1\n- *a\n` - const doc = YAML.parseDocument(src) + const doc = parseDocument(src) expect(doc.errors).toHaveLength(0) - expect(doc.contents.items).toMatchObject([ + expect(doc.contents?.items).toMatchObject([ { anchor: 'a', value: 1 }, { source: 'a' } ]) @@ -13,9 +13,9 @@ test('basic', () => { test('re-defined anchor', () => { const src = '- &a 1\n- &a 2\n- *a\n' - const doc = YAML.parseDocument(src) + const doc = parseDocument(src) expect(doc.errors).toHaveLength(0) - expect(doc.contents.items).toMatchObject([ + expect(doc.contents?.items).toMatchObject([ { anchor: 'a', value: 1 }, { anchor: 'a', value: 2 }, { source: 'a' } @@ -26,7 +26,7 @@ test('re-defined anchor', () => { test('circular reference', () => { const src = '&a [ 1, *a ]\n' - const doc = YAML.parseDocument(src) + const doc = parseDocument(src) expect(doc.errors).toHaveLength(0) expect(doc.warnings).toHaveLength(0) expect(doc.contents).toMatchObject({ @@ -40,13 +40,13 @@ test('circular reference', () => { describe('anchor on tagged collection', () => { test('!!set', () => { - const res = YAML.parse('- &a !!set { 1, 2 }\n- *a\n') + const res = parse('- &a !!set { 1, 2 }\n- *a\n') expect(Array.from(res[0])).toMatchObject([1, 2]) expect(res[1]).toBe(res[0]) }) test('!!omap', () => { - const res = YAML.parse('- &a !!omap [ 1: 1, 2: 2 ]\n- *a\n') + const res = parse('- &a !!omap [ 1: 1, 2: 2 ]\n- *a\n') expect(Array.from(res[0])).toMatchObject([ [1, 1], [2, 2] @@ -57,7 +57,7 @@ describe('anchor on tagged collection', () => { describe('create', () => { test('node.anchor', () => { - const doc = YAML.parseDocument('[{ a: A }, { b: B }]') + const doc = parseDocument('[{ a: A }, { b: B }]') doc.get(0).anchor = 'AA' doc.getIn([0, 'a'], true).anchor = 'a' doc.getIn([0, 'a'], true).anchor = '' @@ -66,9 +66,9 @@ describe('create', () => { }) test('doc.createAlias', () => { - const doc = YAML.parseDocument('[{ a: A }, { b: B }]') + const doc = parseDocument('[{ a: A }, { b: B }]') const alias = doc.createAlias(doc.get(0), 'AA') - doc.contents.items.push(alias) + doc.contents?.items.push(alias) expect(doc.toJS()).toMatchObject([{ a: 'A' }, { b: 'B' }, { a: 'A' }]) const alias2 = doc.createAlias(doc.get(1), 'AA') expect(doc.get(1).anchor).toBe('AA1') @@ -77,8 +77,8 @@ describe('create', () => { }) test('repeated Array in createNode', () => { - const doc = new YAML.Document() - const ref = [] + const doc = new Document() + const ref: unknown[] = [] const node = doc.createNode({ src: ref, ref }) expect(node).toMatchObject({ items: [ @@ -89,7 +89,7 @@ describe('create', () => { }) test('repeated Date in createNode', () => { - const doc = new YAML.Document() + const doc = new Document() const date = new Date() const value = date.toJSON() const node = doc.createNode({ src: date, ref: date }) @@ -104,7 +104,7 @@ describe('create', () => { describe('errors', () => { test('invalid anchor characters', () => { - const doc = YAML.parseDocument('[{ a: A }, { b: B }]') + const doc = parseDocument('[{ a: A }, { b: B }]') doc.get(0).anchor = 'A A' expect(() => String(doc)).toThrow( 'Anchor must not contain whitespace or control characters: "A A"' @@ -112,7 +112,7 @@ describe('errors', () => { }) test('set tag on alias', () => { - const doc = YAML.parseDocument('[{ a: A }, { b: B }]') + const doc = parseDocument, false>('[{ a: A }, { b: B }]') const node = doc.contents.items[0] const alias = doc.createAlias(node, 'AA') expect(() => { @@ -121,7 +121,9 @@ describe('errors', () => { }) test('alias before anchor', () => { - const doc = YAML.parseDocument('[{ a: A }, { b: B }]') + const doc = parseDocument, false>( + '[{ a: A }, { b: B }]' + ) const alias = doc.createAlias(doc.get(0), 'AA') doc.contents.items.unshift(alias) expect(() => String(doc)).toThrow( @@ -130,30 +132,30 @@ describe('errors', () => { }) test('empty alias', () => { - const doc = YAML.parseDocument('- *\n') + const doc = parseDocument('- *\n') expect(doc.errors).toMatchObject([{ code: 'BAD_ALIAS' }]) }) test('empty anchor on value', () => { - const doc = YAML.parseDocument('- & foo\n') + const doc = parseDocument('- & foo\n') expect(doc.errors).toMatchObject([{ code: 'BAD_ALIAS' }]) }) test('empty anchor at end', () => { - const doc = YAML.parseDocument('- &\n') + const doc = parseDocument('- &\n') expect(doc.errors).toMatchObject([{ code: 'BAD_ALIAS' }]) }) }) describe('warnings', () => { test('anchor ending in colon', () => { - const doc = YAML.parseDocument('- &x:\n') + const doc = parseDocument('- &x:\n') expect(doc.errors).toHaveLength(0) expect(doc.warnings).toMatchObject([{ code: 'BAD_ALIAS' }]) }) test('alias ending in colon', () => { - const doc = YAML.parseDocument('- *x:\n') + const doc = parseDocument('- *x:\n') expect(doc.errors).toHaveLength(0) expect(doc.warnings).toMatchObject([{ code: 'BAD_ALIAS' }]) }) @@ -162,9 +164,9 @@ describe('warnings', () => { describe('__proto__ as anchor name', () => { test('parse', () => { const src = `- &__proto__ 1\n- *__proto__\n` - const doc = YAML.parseDocument(src) + const doc = parseDocument(src) expect(doc.errors).toHaveLength(0) - expect(doc.contents.items).toMatchObject([ + expect(doc.contents?.items).toMatchObject([ { anchor: '__proto__', value: 1 }, { source: '__proto__' } ]) @@ -173,7 +175,9 @@ describe('__proto__ as anchor name', () => { }) test('create/stringify', () => { - const doc = YAML.parseDocument('[{ a: A }, { b: B }]') + const doc = parseDocument, false>( + '[{ a: A }, { b: B }]' + ) const alias = doc.createAlias(doc.contents.items[0], '__proto__') doc.contents.items.push(alias) expect(doc.toJSON()).toMatchObject([{ a: 'A' }, { b: 'B' }, { a: 'A' }]) @@ -213,7 +217,7 @@ describe('merge <<', () => { label: center/big` test('YAML.parse', () => { - const res = YAML.parse(src, { merge: true }) + const res = parse(src, { merge: true }) expect(res).toHaveLength(8) for (let i = 4; i < res.length; ++i) { expect(res[i]).toMatchObject({ x: 1, y: 2, r: 10, label: 'center/big' }) @@ -221,7 +225,7 @@ describe('merge <<', () => { }) test('YAML.parse with merge:false', () => { - const res = YAML.parse(src) + const res = parse(src) expect(res).toHaveLength(8) for (let i = 5; i < res.length; ++i) { expect(res[i]).toHaveProperty('<<') @@ -229,7 +233,7 @@ describe('merge <<', () => { }) test('YAML.parseDocument', () => { - const doc = YAML.parseDocument(src, { merge: true }) + const doc = parseDocument, false>(src, { merge: true }) expect( doc.contents.items.slice(5).map(it => it.items[0].value) ).toMatchObject([ @@ -240,30 +244,27 @@ describe('merge <<', () => { }) test('alias is associated with a sequence', () => { - const res = YAML.parse( - '- &items [{ a: A }, { b: B }]\n- { <<: *items, c: C }', - { - merge: true - } - ) + const res = parse('- &items [{ a: A }, { b: B }]\n- { <<: *items, c: C }', { + merge: true + }) expect(res[1]).toEqual({ a: 'A', b: 'B', c: 'C' }) }) describe('non-alias merges', () => { test('single map', () => { - const res = YAML.parse('{ <<: { a: A }, b: B }', { merge: true }) + const res = parse('{ <<: { a: A }, b: B }', { merge: true }) expect(res).toEqual({ a: 'A', b: 'B' }) }) test('multiple maps', () => { - const res = YAML.parse('{ <<: [{ a: A }, { b: B }], c: C }', { + const res = parse('{ <<: [{ a: A }, { b: B }], c: C }', { merge: true }) expect(res).toEqual({ a: 'A', b: 'B', c: 'C' }) }) test('mixed refs & maps', () => { - const res = YAML.parse('{ <<: [{ a: &a { A: 1 } }, *a], b: B }', { + const res = parse('{ <<: [{ a: &a { A: 1 } }, *a], b: B }', { merge: true }) expect(res).toEqual({ a: { A: 1 }, A: 1, b: 'B' }) @@ -272,7 +273,10 @@ describe('merge <<', () => { describe('create', () => { test('simple case', () => { - const doc = YAML.parseDocument('[{ a: A }, { b: B }]', { merge: true }) + const doc = parseDocument, false>( + '[{ a: A }, { b: B }]', + { merge: true } + ) const [a, b] = doc.contents.items const merge = doc.createPair('<<', doc.createAlias(a)) b.items.push(merge) @@ -281,7 +285,10 @@ describe('merge <<', () => { }) test('merge pair of an alias', () => { - const doc = YAML.parseDocument('[{ a: A }, { b: B }]', { merge: true }) + const doc = parseDocument, false>( + '[{ a: A }, { b: B }]', + { merge: true } + ) const [a, b] = doc.contents.items const alias = doc.createAlias(a, 'AA') const merge = doc.createPair('<<', alias) @@ -291,7 +298,9 @@ describe('merge <<', () => { }) test('require map node', () => { - const doc = YAML.parseDocument('[{ a: A }, { b: B }]', { merge: true }) + const doc = parseDocument('[{ a: A }, { b: B }]', { + merge: true + }) const alias = doc.createAlias(doc.getIn([0, 'a'], true)) doc.addIn([1], doc.createPair('<<', alias)) expect(String(doc)).toBe('[ { a: &a1 A }, { b: B, <<: *a1 } ]\n') @@ -335,7 +344,7 @@ y: y: { k0: 'v0', k1: 'v1', k2: 'v2' } } - const expMap = new Map([ + const expMap = new Map([ [ 'x', [ @@ -360,56 +369,56 @@ y: ]) test('multiple merge keys, masAsMap: false', () => { - const res = YAML.parse(srcKeys, { merge: true }) + const res = parse(srcKeys, { merge: true }) expect(res).toEqual(expObj) }) test('multiple merge keys, masAsMap: true', () => { - const res = YAML.parse(srcKeys, { merge: true, mapAsMap: true }) + const res = parse(srcKeys, { merge: true, mapAsMap: true }) expect(res).toEqual(expMap) }) test('sequence of anchors, masAsMap: false', () => { - const res = YAML.parse(srcSeq, { merge: true }) + const res = parse(srcSeq, { merge: true }) expect(res).toEqual(expObj) }) test('sequence of anchors, masAsMap: true', () => { - const res = YAML.parse(srcSeq, { merge: true, mapAsMap: true }) + const res = parse(srcSeq, { merge: true, mapAsMap: true }) expect(res).toEqual(expMap) }) }) test('do not throw error when key is null', () => { const src = ': 123' - expect(() => YAML.parse(src, { merge: true })).not.toThrow() + expect(() => parse(src, { merge: true })).not.toThrow() }) describe('parse errors', () => { test('non-alias merge value', () => { const src = '{ <<: A, B: b }' - expect(() => YAML.parse(src, { merge: true })).toThrow( + expect(() => parse(src, { merge: true })).toThrow( 'Merge sources must be maps or map aliases' ) }) test('non-map alias', () => { const src = '- &A a\n- { <<: *A, B: b }' - expect(() => YAML.parse(src, { merge: true })).toThrow( + expect(() => parse(src, { merge: true })).toThrow( 'Merge sources must be maps or map aliases' ) }) test('immediate non-map merge value', () => { const src = '{ <<: [ [42] ], a: A }' - expect(() => YAML.parse(src, { merge: true })).toThrow( + expect(() => parse(src, { merge: true })).toThrow( 'Merge sources must be maps or map aliases' ) }) test('circular reference', () => { const src = '&A { <<: *A, B: b }\n' - const doc = YAML.parseDocument(src, { merge: true }) + const doc = parseDocument(src, { merge: true }) expect(doc.errors).toHaveLength(0) expect(doc.warnings).toHaveLength(0) expect(() => doc.toJS()).toThrow('Maximum call stack size exceeded') @@ -417,7 +426,7 @@ y: }) test('missing whitespace', () => { - expect(() => YAML.parse('&a{}')).toThrow( + expect(() => parse('&a{}')).toThrow( 'Tags and anchors must be separated from the next token by white space' ) }) @@ -425,8 +434,8 @@ y: describe('stringify', () => { test('example', () => { - const doc = YAML.parseDocument(src, { merge: true }) - expect(YAML.parse(String(doc), { merge: true })).toMatchObject([ + const doc = parseDocument(src, { merge: true }) + expect(parse(String(doc), { merge: true })).toMatchObject([ { x: 1, y: 2 }, { x: 0, y: 2 }, { r: 10 }, diff --git a/tests/tsconfig.json b/tests/tsconfig.json index 61b9ac77..d82612f2 100644 --- a/tests/tsconfig.json +++ b/tests/tsconfig.json @@ -1,10 +1,10 @@ { "extends": "../tsconfig.json", "compilerOptions": { - "baseUrl": ".", + "baseUrl": "..", "declaration": false, "paths": { - "yaml": ["../src/index.ts"] + "yaml": ["src/index.ts"] }, "rootDir": "..", "types": ["jest", "node"] From 2aced1cbbf1e5ac2baa611c7af09873b24b992d9 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sun, 12 Feb 2023 14:11:21 +0200 Subject: [PATCH 03/20] fix(ts): Add undefined & Date handling to NodeType --- src/nodes/Node.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/nodes/Node.ts b/src/nodes/Node.ts index 1c5cc33a..6e39022e 100644 --- a/src/nodes/Node.ts +++ b/src/nodes/Node.ts @@ -14,8 +14,16 @@ export type Node = | YAMLSeq /** Utility type mapper */ -export type NodeType = T extends string | number | bigint | boolean | null +export type NodeType = T extends + | string + | number + | bigint + | boolean + | null + | undefined ? Scalar + : T extends Date + ? Scalar : T extends Array ? YAMLSeq> : T extends { [key: string]: any } From 837bc32194b4a5e0bdd80ce6ccad2f8f60aec68d Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sun, 12 Feb 2023 14:11:47 +0200 Subject: [PATCH 04/20] test: Convert tests/doc/createNode from JS to TS --- tests/doc/{createNode.js => createNode.ts} | 82 ++++++++++++---------- 1 file changed, 43 insertions(+), 39 deletions(-) rename tests/doc/{createNode.js => createNode.ts} (83%) diff --git a/tests/doc/createNode.js b/tests/doc/createNode.ts similarity index 83% rename from tests/doc/createNode.js rename to tests/doc/createNode.ts index bd8e6104..af586a2f 100644 --- a/tests/doc/createNode.js +++ b/tests/doc/createNode.ts @@ -1,34 +1,29 @@ -import { Document, Scalar, YAMLMap, YAMLSeq } from 'yaml' +import { Alias, Document, Node, Scalar, YAMLMap, YAMLSeq } from 'yaml' import { source } from '../_utils' -let doc -beforeEach(() => { - doc = new Document() -}) - describe('createNode(value)', () => { test('boolean', () => { - const s = doc.createNode(false) + const s = new Document().createNode(false) expect(s).toBeInstanceOf(Scalar) expect(s.value).toBe(false) }) test('null', () => { - const s = doc.createNode(null) + const s = new Document().createNode(null) expect(s).toBeInstanceOf(Scalar) expect(s.value).toBe(null) }) test('undefined', () => { - const s = doc.createNode(undefined) + const s = new Document().createNode(undefined) expect(s).toBeInstanceOf(Scalar) expect(s.value).toBe(null) }) test('number', () => { - const s = doc.createNode(3) + const s = new Document().createNode(3) expect(s).toBeInstanceOf(Scalar) expect(s.value).toBe(3) }) test('string', () => { - const s = doc.createNode('test') + const s = new Document().createNode('test') expect(s).toBeInstanceOf(Scalar) expect(s.value).toBe('test') }) @@ -36,13 +31,13 @@ describe('createNode(value)', () => { describe('explicit tags', () => { test('default tag', () => { - const s = doc.createNode(3, { tag: '!!str' }) + const s = new Document().createNode(3, { tag: '!!str' }) expect(s).toBeInstanceOf(Scalar) expect(s).toMatchObject({ value: 3, tag: 'tag:yaml.org,2002:str' }) }) test('unknown tag', () => { - expect(() => doc.createNode('3', { tag: '!foo' })).toThrow( + expect(() => new Document().createNode('3', { tag: '!foo' })).toThrow( 'Tag !foo not found' ) }) @@ -50,12 +45,13 @@ describe('explicit tags', () => { describe('arrays', () => { test('createNode([])', () => { - const s = doc.createNode([]) + const s = new Document().createNode([]) expect(s).toBeInstanceOf(YAMLSeq) expect(s.items).toHaveLength(0) }) test('createNode([true])', () => { + const doc = new Document() const s = doc.createNode([true]) expect(s).toBeInstanceOf(YAMLSeq) expect(s.items).toMatchObject([{ value: true }]) @@ -64,6 +60,7 @@ describe('arrays', () => { }) test('flow: true', () => { + const doc = new Document() const s = doc.createNode([true], { flow: true }) expect(s).toBeInstanceOf(YAMLSeq) expect(s.items).toMatchObject([{ value: true }]) @@ -74,7 +71,7 @@ describe('arrays', () => { describe('[3, ["four", 5]]', () => { const array = [3, ['four', 5]] test('createNode(value)', () => { - const s = doc.createNode(array) + const s = new Document().createNode(array) as any expect(s).toBeInstanceOf(YAMLSeq) expect(s.items).toHaveLength(2) expect(s.items[0].value).toBe(3) @@ -87,7 +84,7 @@ describe('arrays', () => { const res = '- 3\n- - four\n - 5\n' const doc = new Document(array) expect(String(doc)).toBe(res) - doc.contents = array + doc.contents = array as any expect(String(doc)).toBe(res) doc.contents = doc.createNode(array) expect(String(doc)).toBe(res) @@ -97,12 +94,13 @@ describe('arrays', () => { describe('objects', () => { test('createNode({})', () => { - const s = doc.createNode({}) + const s = new Document().createNode({}) expect(s).toBeInstanceOf(YAMLMap) expect(s.items).toHaveLength(0) }) test('createNode({ x: true })', () => { + const doc = new Document() const s = doc.createNode({ x: true }) expect(s).toBeInstanceOf(YAMLMap) expect(s.items).toMatchObject([ @@ -113,6 +111,7 @@ describe('objects', () => { }) test('flow: true', () => { + const doc = new Document() const s = doc.createNode({ x: true }, { flow: true }) expect(s).toBeInstanceOf(YAMLMap) expect(s.items).toMatchObject([ @@ -123,7 +122,7 @@ describe('objects', () => { }) test('createNode({ x: true, y: undefined })', () => { - const s = doc.createNode({ x: true, y: undefined }) + const s = new Document().createNode({ x: true, y: undefined }) expect(s).toBeInstanceOf(YAMLMap) expect(s.items).toMatchObject([ { key: { value: 'x' }, value: { value: true } } @@ -131,7 +130,10 @@ describe('objects', () => { }) test('createNode({ x: true, y: undefined }, { keepUndefined: true })', () => { - const s = doc.createNode({ x: true, y: undefined }, { keepUndefined: true }) + const s = new Document().createNode( + { x: true, y: undefined }, + { keepUndefined: true } + ) expect(s).toBeInstanceOf(YAMLMap) expect(s.items).toMatchObject([ { key: { value: 'x' }, value: { value: true } }, @@ -142,7 +144,7 @@ describe('objects', () => { describe('{ x: 3, y: [4], z: { w: "five", v: 6 } }', () => { const object = { x: 3, y: [4], z: { w: 'five', v: 6 } } test('createNode(value)', () => { - const s = doc.createNode(object) + const s = new Document().createNode(object) expect(s).toBeInstanceOf(YAMLMap) expect(s.items).toHaveLength(3) expect(s.items).toMatchObject([ @@ -168,7 +170,7 @@ z: v: 6\n` const doc = new Document(object) expect(String(doc)).toBe(res) - doc.contents = object + doc.contents = object as any expect(String(doc)).toBe(res) doc.contents = doc.createNode(object) expect(String(doc)).toBe(res) @@ -178,19 +180,19 @@ z: describe('Set', () => { test('createNode(new Set)', () => { - const s = doc.createNode(new Set()) + const s = new Document().createNode(new Set()) expect(s).toBeInstanceOf(YAMLSeq) expect(s.items).toHaveLength(0) }) test('createNode(new Set([true]))', () => { - const s = doc.createNode(new Set([true])) + const s = new Document().createNode(new Set([true])) expect(s).toBeInstanceOf(YAMLSeq) expect(s.items).toMatchObject([{ value: true }]) }) describe("Set { 3, Set { 'four', 5 } }", () => { const set = new Set([3, new Set(['four', 5])]) test('createNode(set)', () => { - const s = doc.createNode(set) + const s = new Document().createNode(set) as any expect(s).toBeInstanceOf(YAMLSeq) expect(s.items).toHaveLength(2) expect(s.items[0].value).toBe(3) @@ -203,7 +205,7 @@ describe('Set', () => { const res = '- 3\n- - four\n - 5\n' const doc = new Document(set) expect(String(doc)).toBe(res) - doc.contents = set + doc.contents = set as any expect(String(doc)).toBe(res) doc.contents = doc.createNode(set) expect(String(doc)).toBe(res) @@ -219,7 +221,7 @@ describe('Set', () => { }) test('Schema#createNode() - YAML 1.1', () => { const doc = new Document(null, { version: '1.1' }) - const s = doc.createNode(set) + const s = doc.createNode(set) as any expect(s.constructor.tag).toBe('tag:yaml.org,2002:set') expect(s.items).toMatchObject([ { key: { value: 3 } }, @@ -231,23 +233,23 @@ describe('Set', () => { describe('Map', () => { test('createNode(new Map)', () => { - const s = doc.createNode(new Map()) + const s = new Document().createNode(new Map()) expect(s).toBeInstanceOf(YAMLMap) expect(s.items).toHaveLength(0) }) test('createNode(new Map([["x", true]]))', () => { - const s = doc.createNode(new Map([['x', true]])) + const s = new Document().createNode(new Map([['x', true]])) expect(s).toBeInstanceOf(YAMLMap) expect(s.items).toMatchObject([ { key: { value: 'x' }, value: { value: true } } ]) }) describe("Map { 'x' => 3, 'y' => Set { 4 }, Map { 'w' => 'five', 'v' => 6 } => 'z' }", () => { - const map = new Map([ + const map = new Map([ ['x', 3], ['y', new Set([4])], [ - new Map([ + new Map([ ['w', 'five'], ['v', 6] ]), @@ -255,7 +257,7 @@ describe('Map', () => { ] ]) test('createNode(map)', () => { - const s = doc.createNode(map) + const s = new Document().createNode(map) expect(s).toBeInstanceOf(YAMLMap) expect(s.items).toHaveLength(3) expect(s.items).toMatchObject([ @@ -281,7 +283,7 @@ y: : z\n` const doc = new Document(map) expect(String(doc)).toBe(res) - doc.contents = map + doc.contents = map as any expect(String(doc)).toBe(res) doc.contents = doc.createNode(map) expect(String(doc)).toBe(res) @@ -292,7 +294,7 @@ y: describe('toJSON()', () => { test('Date', () => { const date = new Date('2018-12-22T08:02:52Z') - const node = doc.createNode(date) + const node = new Document().createNode(date) expect(node.value).toBe(date.toJSON()) }) }) @@ -300,7 +302,7 @@ describe('toJSON()', () => { describe('strictly equal objects', () => { test('createNode([foo, foo])', () => { const foo = { foo: 'FOO' } - const s = doc.createNode([foo, foo]) + const s = new Document().createNode([foo, foo]) expect(s).toBeInstanceOf(YAMLSeq) expect(s.items).toMatchObject([ { @@ -313,7 +315,9 @@ describe('strictly equal objects', () => { test('createNode([foo, foo], { aliasDuplicateObjects: false })', () => { const foo = { foo: 'FOO' } - const s = doc.createNode([foo, foo], { aliasDuplicateObjects: false }) + const s = new Document().createNode([foo, foo], { + aliasDuplicateObjects: false + }) expect(s).toBeInstanceOf(YAMLSeq) expect(s.items).toMatchObject([ { items: [{ key: { value: 'foo' }, value: { value: 'FOO' } }] }, @@ -324,7 +328,7 @@ describe('strictly equal objects', () => { describe('circular references', () => { test('parent at root', () => { - const map = { foo: 'bar' } + const map: any = { foo: 'bar' } map.map = map const doc = new Document(map) expect(doc.contents).toMatchObject({ @@ -345,7 +349,7 @@ describe('circular references', () => { }) test('ancestor at root', () => { - const baz = {} + const baz: any = {} const map = { foo: { bar: { baz } } } baz.map = map const doc = new Document(map) @@ -391,8 +395,8 @@ describe('circular references', () => { const seq = [{ foo: { bar: { baz } } }, { fe: { fi: { fo: { baz } } } }] const doc = new Document(null) const node = doc.createNode(seq) - const source = node.getIn([0, 'foo', 'bar', 'baz']) - const alias = node.getIn([1, 'fe', 'fi', 'fo', 'baz']) + const source = node.getIn([0, 'foo', 'bar', 'baz']) as Node + const alias = node.getIn([1, 'fe', 'fi', 'fo', 'baz']) as Alias expect(source).toMatchObject({ items: [{ key: { value: 'a' }, value: { value: 1 } }] }) From 90ac31b692b99eb59178446af3919dc3c0be533d Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sun, 12 Feb 2023 14:14:55 +0200 Subject: [PATCH 05/20] test: Convert tests/doc/errors from JS to TS --- tests/doc/{errors.js => errors.ts} | 1 + 1 file changed, 1 insertion(+) rename tests/doc/{errors.js => errors.ts} (99%) diff --git a/tests/doc/errors.js b/tests/doc/errors.ts similarity index 99% rename from tests/doc/errors.js rename to tests/doc/errors.ts index 00fe35ad..3cc97f86 100644 --- a/tests/doc/errors.js +++ b/tests/doc/errors.ts @@ -371,6 +371,7 @@ describe('invalid options', () => { }) test('unknown custom tag', () => { + // @ts-expect-error Deprecated option expect(() => new YAML.Document(undefined, { customTags: ['foo'] })).toThrow( /Unknown custom tag/ ) From 7e6b35c5e980c2642feba0e1244276e57ff64e50 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sun, 12 Feb 2023 14:23:55 +0200 Subject: [PATCH 06/20] fix(ts): Export FoldOptions from yaml/util --- src/util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.ts b/src/util.ts index 954b0f7b..b0e0848e 100644 --- a/src/util.ts +++ b/src/util.ts @@ -4,6 +4,6 @@ export { toJS, ToJSContext } from './nodes/toJS.js' export { map as mapTag } from './schema/common/map.js' export { seq as seqTag } from './schema/common/seq.js' export { string as stringTag } from './schema/common/string.js' -export { foldFlowLines } from './stringify/foldFlowLines' +export { foldFlowLines, FoldOptions } from './stringify/foldFlowLines' export { stringifyNumber } from './stringify/stringifyNumber.js' export { stringifyString } from './stringify/stringifyString.js' From 6e52136235564055d9b58fb098927bf0b0a38f43 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sun, 12 Feb 2023 14:24:09 +0200 Subject: [PATCH 07/20] test: Convert tests/doc/foldFlowLines from JS to TS --- .../{foldFlowLines.js => foldFlowLines.ts} | 20 +++++++++---------- tests/tsconfig.json | 3 ++- 2 files changed, 11 insertions(+), 12 deletions(-) rename tests/doc/{foldFlowLines.js => foldFlowLines.ts} (96%) diff --git a/tests/doc/foldFlowLines.js b/tests/doc/foldFlowLines.ts similarity index 96% rename from tests/doc/foldFlowLines.js rename to tests/doc/foldFlowLines.ts index a70160f6..f9f3b601 100644 --- a/tests/doc/foldFlowLines.js +++ b/tests/doc/foldFlowLines.ts @@ -1,13 +1,13 @@ import * as YAML from 'yaml' -import { foldFlowLines as fold } from 'yaml/util' +import { foldFlowLines as fold, FoldOptions } from 'yaml/util' const FOLD_FLOW = 'flow' const FOLD_QUOTED = 'quoted' describe('plain', () => { const src = 'abc def ghi jkl mno pqr stu vwx yz\n' - let onFold - let options + let onFold: jest.Mock + let options: FoldOptions beforeEach(() => { onFold = jest.fn() options = { indentAtStart: 0, lineWidth: 10, minContentWidth: 0, onFold } @@ -82,16 +82,14 @@ describe('plain', () => { describe('double-quoted', () => { const src = '"abc def ghi jkl mnopqrstuvwxyz\n"' - let onFold - let options + let onFold: jest.Mock + let options: FoldOptions beforeEach(() => { onFold = jest.fn() options = { - indent: '', indentAtStart: 0, lineWidth: 10, minContentWidth: 0, - mode: FOLD_QUOTED, onFold } }) @@ -157,7 +155,7 @@ describe('double-quoted', () => { const x = '{"module":"database","props":{"databaseType":"postgresql"},"extra":{},"foo":"bar\'"}' const str = YAML.stringify({ x }) - const doc = YAML.parseDocument(str) + const doc = YAML.parseDocument(str) expect(doc.errors).toHaveLength(0) expect(doc.contents.items[0].value.value).toBe(x) }) @@ -187,7 +185,7 @@ describe('double-quoted', () => { const value = '>####################################"##########################\'####\\P#' const str = YAML.stringify({ key: [[value]] }) - const doc = YAML.parseDocument(str) + const doc = YAML.parseDocument(str) expect(doc.errors).toHaveLength(0) expect(doc.contents.items[0].value.items[0].items[0].value).toBe(value) }) @@ -267,7 +265,7 @@ folded but is not. Text that is prevented from folding due to being more-indented. Unfolded paragraph.\n` - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument(src) expect(doc.contents.value).toBe( `Text on a line that should get folded with a line width of 20 characters. @@ -293,7 +291,7 @@ Unfolded paragraph.\n` enough length to fold twice - plain with comment # that won't get folded\n` - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument, false>(src) expect(doc.contents.items[0].value).toBe( 'plain value with enough length to fold twice' ) diff --git a/tests/tsconfig.json b/tests/tsconfig.json index d82612f2..808465f2 100644 --- a/tests/tsconfig.json +++ b/tests/tsconfig.json @@ -4,7 +4,8 @@ "baseUrl": "..", "declaration": false, "paths": { - "yaml": ["src/index.ts"] + "yaml": ["src/index.ts"], + "yaml/util": ["src/util.ts"] }, "rootDir": "..", "types": ["jest", "node"] From 81b0f0a38f8ed14e82340a2562cdaf277ee6d712 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sun, 12 Feb 2023 14:38:16 +0200 Subject: [PATCH 08/20] test: Convert tests/doc/YAML-1.2.spec from JS to TS --- .../{YAML-1.2.spec.js => YAML-1.2.spec.ts} | 85 ++++++++++++------- 1 file changed, 52 insertions(+), 33 deletions(-) rename tests/doc/{YAML-1.2.spec.js => YAML-1.2.spec.ts} (96%) diff --git a/tests/doc/YAML-1.2.spec.js b/tests/doc/YAML-1.2.spec.ts similarity index 96% rename from tests/doc/YAML-1.2.spec.js rename to tests/doc/YAML-1.2.spec.ts index 8971dbf8..d6c08748 100644 --- a/tests/doc/YAML-1.2.spec.js +++ b/tests/doc/YAML-1.2.spec.ts @@ -4,7 +4,20 @@ import { YAMLError } from 'yaml' const collectionKeyWarning = /^Keys with collection values will be stringified due to JS Object restrictions/ -const spec = { +const spec: Record< + string, + Record< + string, + { + src: string + tgt: unknown[] + errors?: string[][] + warnings?: string[][] + jsWarnings?: RegExp[] + special?: (src: string) => void + } + > +> = { '2.1. Collections': { 'Example 2.1. Sequence of Scalars': { src: `- Mark McGwire\r @@ -375,15 +388,15 @@ date: 2001-12-14`, date: '2001-12-14' } ], - special: src => { + special(src) { const obj = YAML.parse(src, { schema: 'yaml-1.1' }) expect(Object.keys(obj)).toHaveLength(4) - ;[('canonical', 'iso8601', 'spaced', 'date')].forEach(key => { + for (const key of ['canonical', 'iso8601', 'spaced', 'date']) { const date = obj[key] expect(date).toBeInstanceOf(Date) expect(date.getFullYear()).toBe(2001) expect(date.getMonth()).toBe(11) - }) + } } }, @@ -416,8 +429,8 @@ application specific tag: !something | } ], warnings: [['Unresolved tag: !something']], - special: src => { - const doc = YAML.parseDocument(src, { schema: 'yaml-1.1' }) + special(src) { + const doc = YAML.parseDocument(src, { schema: 'yaml-1.1' }) const data = doc.contents.items[1].value.value expect(data).toBeInstanceOf(Uint8Array) expect(data.byteLength).toBe(65) @@ -664,8 +677,8 @@ mapping: { sky: blue, sea: green }`, alias: *anchor`, tgt: [{ anchored: 'value', alias: 'value' }], warnings: [['Unresolved tag: !local']], - special: src => { - const tag = { tag: '!local', resolve: str => `local:${str}` } + special(src) { + const tag = { tag: '!local', resolve: (str: string) => `local:${str}` } const res = YAML.parse(src, { customTags: [tag] }) expect(res).toMatchObject({ anchored: 'local:value', @@ -700,7 +713,7 @@ double: "text"`, src: `%YAML 1.2 --- text`, tgt: ['text'], - special: src => { + special(src) { const doc = YAML.parseDocument(src) expect(doc.directives.yaml).toMatchObject({ version: '1.2', @@ -925,7 +938,7 @@ Chomping: | "foo"`, tgt: ['foo'], warnings: [['Unsupported YAML version 1.3']], - special: src => { + special(src) { const doc = YAML.parseDocument(src) expect(doc.directives.yaml).toMatchObject({ version: '1.2', @@ -940,7 +953,7 @@ Chomping: | --- foo`, tgt: ['foo'], - special: src => { + special(src) { const doc = YAML.parseDocument(src) expect(doc.directives.yaml).toMatchObject({ version: '1.1', @@ -963,7 +976,7 @@ foo`, --- bar`, tgt: ['bar'], - special: src => { + special(src) { const doc = YAML.parseDocument(src) expect(doc.directives.tags).toMatchObject({ '!!': 'tag:yaml.org,2002:', @@ -985,7 +998,7 @@ bar`, ['Unresolved tag: !foo'], ['Unresolved tag: tag:example.com,2000:app/foo'] ], - special: src => { + special(src) { const customTags = [ { tag: '!foo', @@ -1007,7 +1020,7 @@ bar`, !!int 1 - 3 # Interval, not integer`, tgt: ['1 - 3'], warnings: [['Unresolved tag: tag:example.com,2000:app/int']], - special: src => { + special(src) { const tag = { tag: 'tag:example.com,2000:app/int', resolve: () => 'interval' @@ -1023,10 +1036,10 @@ bar`, !e!foo "bar"`, tgt: ['bar'], warnings: [['Unresolved tag: tag:example.com,2000:app/foo']], - special: src => { + special(src) { const tag = { tag: 'tag:example.com,2000:app/foo', - resolve: str => `foo${str}` + resolve: (str: string) => `foo${str}` } const res = YAML.parse(src, { customTags: [tag] }) expect(res).toBe('foobar') @@ -1043,8 +1056,11 @@ bar`, !m!light green`, tgt: ['fluorescent', 'green'], warnings: [['Unresolved tag: !my-light'], ['Unresolved tag: !my-light']], - special: src => { - const tag = { tag: '!my-light', resolve: str => `light:${str}` } + special(src) { + const tag = { + tag: '!my-light', + resolve: (str: string) => `light:${str}` + } const docs = YAML.parseAllDocuments(src, { customTags: [tag] }) expect(docs.map(d => d.toJS())).toMatchObject([ 'light:fluorescent', @@ -1059,10 +1075,10 @@ bar`, - !e!foo "bar"`, tgt: [['bar']], warnings: [['Unresolved tag: tag:example.com,2000:app/foo']], - special: src => { + special(src) { const tag = { tag: 'tag:example.com,2000:app/foo', - resolve: str => `foo${str}` + resolve: (str: string) => `foo${str}` } const res = YAML.parse(src, { customTags: [tag] }) expect(res).toMatchObject(['foobar']) @@ -1082,8 +1098,8 @@ bar`, ! baz`, tgt: [{ foo: 'baz' }], warnings: [['Unresolved tag: !bar']], - special: src => { - const tag = { tag: '!bar', resolve: str => `bar${str}` } + special(src) { + const tag = { tag: '!bar', resolve: (str: string) => `bar${str}` } const res = YAML.parse(src, { customTags: [tag] }) expect(res).toMatchObject({ foo: 'barbaz' }) } @@ -1110,12 +1126,12 @@ bar`, 'Unresolved tag: tag:example.com,2000:app/tag!' ] ], - special: src => { + special(src) { const customTags = [ - { tag: '!local', resolve: str => `local:${str}` }, + { tag: '!local', resolve: (str: string) => `local:${str}` }, { tag: 'tag:example.com,2000:app/tag!', - resolve: str => `tag!${str}` + resolve: (str: string) => `tag!${str}` } ] const res = YAML.parse(src, { customTags }) @@ -1674,8 +1690,8 @@ folded: value`, tgt: [{ literal: 'value\n', folded: 'value\n' }], warnings: [['Unresolved tag: !foo']], - special: src => { - const tag = { tag: '!foo', resolve: str => `foo${str}` } + special(src) { + const tag = { tag: '!foo', resolve: (str: string) => `foo${str}` } const res = YAML.parse(src, { customTags: [tag] }) expect(res).toMatchObject({ literal: 'value\n', folded: 'foovalue\n' }) } @@ -1694,8 +1710,8 @@ mapping: !!map mapping: { foo: 'bar' } } ], - special: src => { - const doc = YAML.parseDocument(src) + special(src) { + const doc = YAML.parseDocument, false>(src) expect(doc.contents.tag).toBeUndefined() expect(doc.contents.items[0].value.tag).toBe('tag:yaml.org,2002:seq') expect(doc.contents.items[0].value.items[1].tag).toBe( @@ -1720,11 +1736,12 @@ Document`, Document ... # Suffix`, tgt: ['Document'], - special: src => + special(src) { expect(YAML.parseDocument(src).directives.yaml).toMatchObject({ version: '1.2', explicit: true }) + } }, 'Example 9.3. Bare Documents': { @@ -1808,17 +1825,19 @@ for (const section in spec) { expect(json).toMatchObject(tgt) documents.forEach((doc, i) => { expect(doc.errors.map(err => err.message)).toMatchObject( - errors?.[i] || [] + errors?.[i] ?? [] ) expect(doc.warnings.map(err => err.message)).toMatchObject( - warnings?.[i] || [] + warnings?.[i] ?? [] ) for (const err of doc.errors.concat(doc.warnings)) expect(err).toBeInstanceOf(YAMLError) if (!jsWarnings) expect(mockWarn).not.toHaveBeenCalled() else { for (const warning of jsWarnings) - expect(mockWarn.mock.calls.some(call => warning.test(call[0]))) + expect( + mockWarn.mock.calls.some(call => warning.test(String(call[0]))) + ) } }) if (special) special(src) From 90074bb7b5a219661d38113b24ab138476340ab1 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sun, 12 Feb 2023 14:38:53 +0200 Subject: [PATCH 09/20] test: Convert tests/doc/YAML-1.1.spec from JS to TS --- tests/doc/{YAML-1.1.spec.js => YAML-1.1.spec.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/doc/{YAML-1.1.spec.js => YAML-1.1.spec.ts} (95%) diff --git a/tests/doc/YAML-1.1.spec.js b/tests/doc/YAML-1.1.spec.ts similarity index 95% rename from tests/doc/YAML-1.1.spec.js rename to tests/doc/YAML-1.1.spec.ts index 0355b552..06016882 100644 --- a/tests/doc/YAML-1.1.spec.js +++ b/tests/doc/YAML-1.1.spec.ts @@ -18,7 +18,7 @@ test('Use preceding directives if none defined', () => { !bar "Using previous YAML directive" ` const docs = parseAllDocuments(src, { prettyErrors: false, version: '1.1' }) - const warn = tag => ({ message: `Unresolved tag: ${tag}` }) + const warn = (tag: string) => ({ message: `Unresolved tag: ${tag}` }) expect(docs).toMatchObject([ { directives: { yaml: { version: '1.1', explicit: false } }, From d0a1a55902b0964ae451026c11803c2d9771eb97 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sun, 12 Feb 2023 14:49:26 +0200 Subject: [PATCH 10/20] test: Convert tests/doc/comments from JS to TS --- tests/doc/{comments.js => comments.ts} | 85 +++++++++++++++----------- 1 file changed, 51 insertions(+), 34 deletions(-) rename tests/doc/{comments.js => comments.ts} (91%) diff --git a/tests/doc/comments.js b/tests/doc/comments.ts similarity index 91% rename from tests/doc/comments.js rename to tests/doc/comments.ts index 74130f02..6cb19c0f 100644 --- a/tests/doc/comments.js +++ b/tests/doc/comments.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { source } from '../_utils' import * as YAML from 'yaml' @@ -17,7 +18,7 @@ describe('parse comments', () => { #comment string ` - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument(src) expect(doc.contents.commentBefore).toBe('comment\ncomment') expect(String(doc)).toBe(src) }) @@ -30,7 +31,7 @@ describe('parse comments', () => { #comment string ` - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument(src) expect(doc.contents.commentBefore).toBe('comment\n \ncomment') expect(String(doc)).toBe(source` --- @@ -52,7 +53,7 @@ describe('parse comments', () => { describe('top-level scalar comments', () => { test('plain', () => { const src = '#c0\nvalue #c1\n#c2' - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument(src) expect(doc.contents.commentBefore).toBe('c0') expect(doc.contents.comment).toBe('c1') expect(doc.comment).toBe('c2') @@ -62,7 +63,7 @@ describe('parse comments', () => { test('"quoted"', () => { const src = '#c0\n"value" #c1\n#c2' - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument(src) expect(doc.contents.commentBefore).toBe('c0') expect(doc.contents.comment).toBe('c1') expect(doc.comment).toBe('c2') @@ -72,7 +73,7 @@ describe('parse comments', () => { test('block', () => { const src = '#c0\n>- #c1\n value\n#c2\n' - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument(src) expect(doc.contents.commentBefore).toBe('c0') expect(doc.contents.comment).toBe('c1') expect(doc.comment).toBe('c2') @@ -268,7 +269,7 @@ describe('parse comments', () => { describe('flow collection commens', () => { test('line comment after , in seq', () => { - const doc = YAML.parseDocument(source` + const doc = YAML.parseDocument(source` [ a, #c0 b #c1 ]`) @@ -279,7 +280,7 @@ describe('parse comments', () => { }) test('line comment after , in map', () => { - const doc = YAML.parseDocument(source` + const doc = YAML.parseDocument(source` { a, #c0 b: c, #c1 d #c2 @@ -292,7 +293,7 @@ describe('parse comments', () => { }) test('multi-line comments', () => { - const doc = YAML.parseDocument('{ a,\n#c0\n#c1\nb }') + const doc = YAML.parseDocument('{ a,\n#c0\n#c1\nb }') expect(doc.contents.items).toMatchObject([ { key: { value: 'a' } }, { key: { commentBefore: 'c0\nc1', value: 'b' } } @@ -305,21 +306,21 @@ describe('stringify comments', () => { describe('single-line comments', () => { test('plain', () => { const src = 'string' - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument(src) doc.contents.comment = 'comment' expect(String(doc)).toBe('string #comment\n') }) test('"quoted"', () => { const src = '"string\\u0000"' - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument(src) doc.contents.comment = 'comment' expect(String(doc)).toBe('"string\\0" #comment\n') }) test('block', () => { const src = '>\nstring\n' - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument(src) doc.contents.comment = 'comment' expect(String(doc)).toBe('> #comment\nstring\n') }) @@ -328,21 +329,21 @@ describe('stringify comments', () => { describe('multi-line comments', () => { test('plain', () => { const src = 'string' - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument(src) doc.contents.comment = 'comment\nlines' expect(String(doc)).toBe('string\n#comment\n#lines\n') }) test('"quoted"', () => { const src = '"string\\u0000"' - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument(src) doc.contents.comment = 'comment\nlines' expect(String(doc)).toBe('"string\\0"\n#comment\n#lines\n') }) test('block', () => { const src = '>\nstring\n' - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument(src) doc.contents.comment = 'comment\nlines' expect(String(doc)).toBe('> #comment lines\nstring\n') }) @@ -370,7 +371,7 @@ describe('stringify comments', () => { describe('seq comments', () => { test('plain', () => { const src = '- value 1\n- value 2\n' - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument, false>(src) doc.contents.commentBefore = 'c0' doc.contents.items[0].commentBefore = 'c1' doc.contents.items[1].commentBefore = 'c2' @@ -387,7 +388,7 @@ describe('stringify comments', () => { test('multiline', () => { const src = '- value 1\n- value 2\n' - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument, false>(src) doc.contents.items[0].commentBefore = 'c0\nc1' doc.contents.items[1].commentBefore = ' \nc2\n\nc3' doc.contents.comment = 'c4\nc5' @@ -407,10 +408,13 @@ describe('stringify comments', () => { test('seq-in-map', () => { const src = 'map:\n - value 1\n - value 2\n' - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument< + YAML.YAMLMap>, + false + >(src) doc.contents.items[0].key.commentBefore = 'c0' doc.contents.items[0].key.comment = 'c1' - const seq = doc.contents.items[0].value + const seq = doc.contents.items[0].value! seq.commentBefore = 'c2' seq.items[0].commentBefore = 'c3' seq.items[1].commentBefore = 'c4' @@ -428,12 +432,14 @@ describe('stringify comments', () => { }) test('custom stringifier', () => { - const doc = YAML.parseDocument('- a\n- b\n') + const doc = YAML.parseDocument, false>( + '- a\n- b\n' + ) doc.contents.commentBefore = 'c0' doc.contents.items[0].commentBefore = 'c1' doc.contents.items[1].commentBefore = 'c2\nc3' - const commentString = str => str.replace(/^/gm, '// ') - expect(doc.toString({ commentString })).toBe(source` + expect(doc.toString({ commentString: str => str.replace(/^/gm, '// ') })) + .toBe(source` // c0 // c1 - a @@ -447,11 +453,14 @@ describe('stringify comments', () => { describe('map entry comments', () => { test('plain', () => { const src = 'key1: value 1\nkey2: value 2\n' - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument< + YAML.YAMLMap, + false + >(src) doc.contents.items[0].key.commentBefore = 'c0' doc.contents.items[1].key.commentBefore = 'c1' doc.contents.items[1].key.comment = 'c2' - doc.contents.items[1].value.spaceBefore = true + doc.contents.items[1].value!.spaceBefore = true doc.contents.comment = 'c3' expect(String(doc)).toBe(source` #c0 @@ -466,12 +475,15 @@ describe('stringify comments', () => { test('multiline', () => { const src = 'key1: value 1\nkey2: value 2\n' - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument< + YAML.YAMLMap, + false + >(src) doc.contents.items[0].key.commentBefore = 'c0\nc1' doc.contents.items[1].key.commentBefore = ' \nc2\n\nc3' doc.contents.items[1].key.comment = 'c4\nc5' - doc.contents.items[1].value.spaceBefore = true - doc.contents.items[1].value.commentBefore = 'c6' + doc.contents.items[1].value!.spaceBefore = true + doc.contents.items[1].value!.commentBefore = 'c6' doc.contents.comment = 'c7\nc8' expect(String(doc)).toBe(source` #c0 @@ -612,7 +624,7 @@ describe('blank lines', () => { }) test('before first node in document with directives', () => { - const doc = YAML.parseDocument('str\n') + const doc = YAML.parseDocument('str\n') doc.contents.spaceBefore = true expect(doc.toString({ directives: true })).toBe('---\n\nstr\n') }) @@ -658,7 +670,7 @@ describe('blank lines', () => { ] for (const { name, src } of cases) { test(name, () => { - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument(src) expect(String(doc)).toBe(src) let it = doc.contents.items[1] if (it.key) it = it.key @@ -958,7 +970,9 @@ map: describe('newlines as comments', () => { test('seq', () => { - const doc = YAML.parseDocument('- v1\n- v2\n') + const doc = YAML.parseDocument, false>( + '- v1\n- v2\n' + ) const [v1, v2] = doc.contents.items v1.commentBefore = '\n' v1.comment = '\n' @@ -975,14 +989,17 @@ map: }) test('map', () => { - const doc = YAML.parseDocument('k1: v1\nk2: v2') + const doc = YAML.parseDocument< + YAML.YAMLMap, + false + >('k1: v1\nk2: v2') const [p1, p2] = doc.contents.items p1.key.commentBefore = '\n' - p1.value.commentBefore = '\n' - p1.value.comment = '\n' + p1.value!.commentBefore = '\n' + p1.value!.comment = '\n' p2.key.commentBefore = '\n' - p2.value.commentBefore = '\n' - p2.value.comment = '\n' + p2.value!.commentBefore = '\n' + p2.value!.comment = '\n' expect(doc.toString()).toBe(source` k1: From d7ff81b7bb6327c6826090a1878064bad2160b72 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sun, 12 Feb 2023 15:00:03 +0200 Subject: [PATCH 11/20] test: Fix remaining TS bugs & add test type checks to test:types script --- package.json | 2 +- tests/clone.ts | 4 ++-- tests/is-node.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 2315ded0..1839622c 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "test:browsers": "cd playground && npm test", "test:dist": "npm run build:node && jest --config config/jest.config.js", "test:dist:types": "tsc --allowJs --moduleResolution node --noEmit --target es5 dist/index.js", - "test:types": "tsc --noEmit", + "test:types": "tsc --noEmit && tsc --noEmit -p tests/tsconfig.json", "docs:install": "cd docs-slate && bundle install", "docs:deploy": "cd docs-slate && ./deploy.sh", "docs": "cd docs-slate && bundle exec middleman server", diff --git a/tests/clone.ts b/tests/clone.ts index 49d3a257..a10e1c4e 100644 --- a/tests/clone.ts +++ b/tests/clone.ts @@ -1,4 +1,4 @@ -import { isAlias, isScalar, parseDocument, Scalar, visit } from 'yaml' +import { isAlias, isScalar, parseDocument, Scalar, visit, YAMLMap } from 'yaml' import { source } from './_utils' describe('doc.clone()', () => { @@ -37,7 +37,7 @@ describe('doc.clone()', () => { }) test('has separate directives from original', () => { - const doc = parseDocument('foo: bar') + const doc = parseDocument('foo: bar') const copy = doc.clone() copy.directives.yaml.explicit = true expect(copy.toString()).toBe(source` diff --git a/tests/is-node.ts b/tests/is-node.ts index fd903c10..ed26183f 100644 --- a/tests/is-node.ts +++ b/tests/is-node.ts @@ -105,7 +105,7 @@ for (const { fn, exp } of [ pair: false } } -]) { +] as { fn: (x: unknown) => boolean; exp: Record }[]) { describe(fn.name, () => { test('parsed doc', () => { const doc = parseDocument('foo') @@ -154,7 +154,7 @@ for (const { fn, exp } of [ }) test('created alias', () => { - expect(fn(new Alias(new Scalar(42)))).toBe(exp.alias) + expect(fn(new Alias('42'))).toBe(exp.alias) }) test('created pair', () => { From 8f6e75e5eedfbc21c764ce4d96bd8a68a126f99e Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sun, 12 Feb 2023 15:30:01 +0200 Subject: [PATCH 12/20] feat(ts): Add generic types to Composer --- src/compose/compose-doc.ts | 9 +++++++-- src/compose/composer.ts | 18 ++++++++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/compose/compose-doc.ts b/src/compose/compose-doc.ts index ab58c138..e2667988 100644 --- a/src/compose/compose-doc.ts +++ b/src/compose/compose-doc.ts @@ -1,5 +1,6 @@ import type { Directives } from '../doc/directives.js' import { Document } from '../doc/Document.js' +import type { ParsedNode } from '../nodes/Node.js' import type { DocumentOptions, ParseOptions, @@ -15,14 +16,17 @@ import type { ComposeErrorHandler } from './composer.js' import { resolveEnd } from './resolve-end.js' import { resolveProps } from './resolve-props.js' -export function composeDoc( +export function composeDoc< + Contents extends ParsedNode = ParsedNode, + Strict extends boolean = true +>( options: ParseOptions & DocumentOptions & SchemaOptions, directives: Directives, { offset, start, value, end }: CST.Document, onError: ComposeErrorHandler ) { const opts = Object.assign({ _directives: directives }, options) - const doc = new Document(undefined, opts) as Document.Parsed + const doc = new Document(undefined, opts) as Document.Parsed const ctx: ComposeContext = { atRoot: true, directives: doc.directives, @@ -49,6 +53,7 @@ export function composeDoc( 'Block collection cannot start on same line with directives-end marker' ) } + // @ts-expect-error If Contents is set, let's trust the user doc.contents = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, start, null, props, onError) diff --git a/src/compose/composer.ts b/src/compose/composer.ts index 86990c92..b7bb0ffa 100644 --- a/src/compose/composer.ts +++ b/src/compose/composer.ts @@ -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, Range } from '../nodes/Node.js' +import { isCollection, isPair, ParsedNode, Range } from '../nodes/Node.js' import type { DocumentOptions, ParseOptions, @@ -69,9 +69,12 @@ function parsePrelude(prelude: string[]) { * const docs = new Composer().compose(tokens) * ``` */ -export class Composer { +export class Composer< + Contents extends ParsedNode = ParsedNode, + Strict extends boolean = true +> { private directives: Directives - private doc: Document.Parsed | null = null + private doc: Document.Parsed | null = null private options: ParseOptions & DocumentOptions & SchemaOptions private atDirectives = false private prelude: string[] = [] @@ -90,7 +93,7 @@ export class Composer { else this.errors.push(new YAMLParseError(pos, code, message)) } - private decorate(doc: Document.Parsed, afterDoc: boolean) { + private decorate(doc: Document.Parsed, afterDoc: boolean) { const { comment, afterEmptyLine } = parsePrelude(this.prelude) //console.log({ dc: doc.comment, prelude, comment }) if (comment) { @@ -162,7 +165,7 @@ export class Composer { this.atDirectives = true break case 'document': { - const doc = composeDoc( + const doc = composeDoc( this.options, this.directives, token, @@ -247,7 +250,10 @@ export class Composer { this.doc = null } else if (forceDoc) { const opts = Object.assign({ _directives: this.directives }, this.options) - const doc = new Document(undefined, opts) as Document.Parsed + const doc = new Document(undefined, opts) as Document.Parsed< + Contents, + Strict + > if (this.atDirectives) this.onError( endOffset, From 0f1c7f0b1ba05faf8b56ad4854ddf7e4be4bf99b Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sun, 12 Feb 2023 15:30:14 +0200 Subject: [PATCH 13/20] test: Convert tests/doc/parse from JS to TS --- tests/doc/{parse.js => parse.ts} | 60 ++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 26 deletions(-) rename tests/doc/{parse.js => parse.ts} (93%) diff --git a/tests/doc/parse.js b/tests/doc/parse.ts similarity index 93% rename from tests/doc/parse.js rename to tests/doc/parse.ts index 86ea5f7a..6941c43c 100644 --- a/tests/doc/parse.js +++ b/tests/doc/parse.ts @@ -1,5 +1,5 @@ -import fs from 'fs' -import path from 'path' +import { readFileSync } from 'fs' +import { resolve } from 'path' import * as YAML from 'yaml' import { source } from '../_utils' @@ -17,7 +17,7 @@ describe('scalars', () => { test('eemeli/yaml#3', () => { const src = '{ ? : 123 }' - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument(src) expect(doc.errors).toHaveLength(0) expect(doc.contents.items[0].key.value).toBeNull() expect(doc.contents.items[0].value.value).toBe(123) @@ -41,8 +41,8 @@ aliases: }) test('complete file', () => { - const src = fs.readFileSync( - path.resolve(__dirname, '../artifacts/prettier-circleci-config.yml'), + const src = readFileSync( + resolve(__dirname, '../artifacts/prettier-circleci-config.yml'), 'utf8' ) const doc = YAML.parseDocument(src) @@ -322,7 +322,7 @@ describe('indented key with anchor (eemeli/yaml#378)', () => { describe('empty(ish) nodes', () => { test('empty node position', () => { - const doc = YAML.parseDocument('\r\na: # 123\r\n') + const doc = YAML.parseDocument('\r\na: # 123\r\n') const empty = doc.contents.items[0].value expect(empty.range).toEqual([5, 5, 12]) }) @@ -356,7 +356,7 @@ describe('maps with no values', () => { test('flow map', () => { const src = `{\na: null,\n? b\n}` - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument(src) expect(String(doc)).toBe(`{ a: null, b }\n`) doc.contents.items[1].key.comment = 'c' expect(String(doc)).toBe(`{\n a: null,\n b #c\n}\n`) @@ -370,7 +370,7 @@ describe('maps with no values', () => { }) test('implicit scalar key after explicit key with no value', () => { - const doc = YAML.parseDocument('? - 1\nx:\n') + const doc = YAML.parseDocument('? - 1\nx:\n') expect(doc.contents.items).toMatchObject([ { key: { items: [{ value: 1 }] }, value: null }, { key: { value: 'x' }, value: { value: null } } @@ -378,7 +378,7 @@ describe('maps with no values', () => { }) test('implicit flow collection key after explicit key with no value', () => { - const doc = YAML.parseDocument('? - 1\n[x]: y\n') + const doc = YAML.parseDocument('? - 1\n[x]: y\n') expect(doc.contents.items).toMatchObject([ { key: { items: [{ value: 1 }] }, value: null }, { key: { items: [{ value: 'x' }] }, value: { value: 'y' } } @@ -387,13 +387,13 @@ describe('maps with no values', () => { }) describe('Excessive entity expansion attacks', () => { - const root = path.resolve(__dirname, '../artifacts/pr104') - const src1 = fs.readFileSync(path.resolve(root, 'case1.yml'), 'utf8') - const src2 = fs.readFileSync(path.resolve(root, 'case2.yml'), 'utf8') - const srcB = fs.readFileSync(path.resolve(root, 'billion-laughs.yml'), 'utf8') - const srcQ = fs.readFileSync(path.resolve(root, 'quadratic.yml'), 'utf8') + const root = resolve(__dirname, '../artifacts/pr104') + const src1 = readFileSync(resolve(root, 'case1.yml'), 'utf8') + const src2 = readFileSync(resolve(root, 'case2.yml'), 'utf8') + const srcB = readFileSync(resolve(root, 'billion-laughs.yml'), 'utf8') + const srcQ = readFileSync(resolve(root, 'quadratic.yml'), 'utf8') - let origEmitWarning + let origEmitWarning: typeof process.emitWarning beforeAll(() => { origEmitWarning = process.emitWarning }) @@ -521,7 +521,7 @@ describe('duplicate keys', () => { }) describe('handling complex keys', () => { - let origEmitWarning + let origEmitWarning: typeof process.emitWarning beforeAll(() => { origEmitWarning = process.emitWarning }) @@ -530,14 +530,14 @@ describe('handling complex keys', () => { }) test('emit warning when casting key in collection to string as JS Object key', () => { - process.emitWarning = jest.fn() + const spy = (process.emitWarning = jest.fn()) const doc = YAML.parseDocument('[foo]: bar', { prettyErrors: false }) expect(doc.warnings).toHaveLength(0) - expect(process.emitWarning).not.toHaveBeenCalled() + expect(spy).not.toHaveBeenCalled() doc.toJS() - expect(process.emitWarning).toHaveBeenCalledTimes(1) - expect(process.emitWarning.mock.calls[0][0]).toMatch( + expect(spy).toHaveBeenCalledTimes(1) + expect(spy.mock.calls[0][0]).toMatch( /^Keys with collection values will be stringified due to JS Object restrictions/ ) }) @@ -601,14 +601,16 @@ describe('keepSourceTokens', () => { ['{ foo: bar }', 'flow-collection'] ]) { test(`${type}: default false`, () => { - const doc = YAML.parseDocument(src) + const doc = YAML.parseDocument(src) expect(doc.contents).not.toHaveProperty('srcToken') expect(doc.contents.items[0]).not.toHaveProperty('srcToken') expect(doc.get('foo', true)).not.toHaveProperty('srcToken') }) test(`${type}: included when set`, () => { - const doc = YAML.parseDocument(src, { keepSourceTokens: true }) + const doc = YAML.parseDocument(src, { + keepSourceTokens: true + }) expect(doc.contents.srcToken).toMatchObject({ type }) expect(doc.contents.items[0].srcToken).toMatchObject({ key: { type: 'scalar' }, @@ -621,7 +623,9 @@ describe('keepSourceTokens', () => { test('allow for CST modifications (eemeli/yaml#903)', () => { const src = 'foo:\n [ 42 ]' const tokens = Array.from(new YAML.Parser().parse(src)) - const docs = new YAML.Composer({ keepSourceTokens: true }).compose(tokens) + const docs = new YAML.Composer({ + keepSourceTokens: true + }).compose(tokens) const doc = Array.from(docs)[0] const node = doc.get('foo', true) YAML.CST.setScalarValue(node.srcToken, 'eek') @@ -682,6 +686,7 @@ describe('reviver', () => { test('add values to this', () => { const reviver = jest.fn(function (key, value) { expect(key).not.toBe('9') + // @ts-expect-error Let's not try to type this this[9] = 9 return value }) @@ -695,8 +700,9 @@ describe('reviver', () => { }) test('sequence', () => { - const these = [] + const these: unknown[][] = [] const reviver = jest.fn(function (key, value) { + // @ts-expect-error Let's not try to type this these.push(Array.from(key === '' ? this[''] : this)) if (key === '0') return undefined if (key === '3') return 10 @@ -723,8 +729,9 @@ describe('reviver', () => { }) test('!!set', () => { - const these = [] + const these: unknown[][] = [] const reviver = jest.fn(function (key, value) { + // @ts-expect-error Let's not try to type this these.push(Array.from(key === '' ? this[''] : this)) if (key === 2) return undefined if (key === 8) return 10 @@ -751,8 +758,9 @@ describe('reviver', () => { }) test('!!omap', () => { - const these = [] + const these: unknown[][] = [] const reviver = jest.fn(function (key, value) { + // @ts-expect-error Let's not try to type this these.push(Array.from(key === '' ? this[''] : this)) if (key === 2) return undefined if (key === 8) return 10 From d82e9b9664a1e468868322a55dc6d09cce66ec01 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sun, 12 Feb 2023 16:03:40 +0200 Subject: [PATCH 14/20] test: Convert tests/doc/stringify from JS to TS --- tests/doc/{stringify.js => stringify.ts} | 129 +++++++++++++---------- 1 file changed, 74 insertions(+), 55 deletions(-) rename tests/doc/{stringify.js => stringify.ts} (91%) diff --git a/tests/doc/stringify.js b/tests/doc/stringify.ts similarity index 91% rename from tests/doc/stringify.js rename to tests/doc/stringify.ts index b973e96f..4a7ae707 100644 --- a/tests/doc/stringify.js +++ b/tests/doc/stringify.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* global BigInt */ import { source } from '../_utils' @@ -7,7 +8,7 @@ import { Pair, Scalar } from 'yaml' for (const [name, version] of [ ['YAML 1.1', '1.1'], ['YAML 1.2', '1.2'] -]) { +] as const) { describe(name, () => { test('undefined', () => { expect(YAML.stringify(undefined, { version })).toBeUndefined() @@ -47,29 +48,29 @@ for (const [name, version] of [ }) test('float with trailing zeros', () => { - const doc = new YAML.Document(3, { version }) + const doc = new YAML.Document(3, { version }) doc.contents.minFractionDigits = 2 expect(String(doc)).toBe('3.00\n') }) test('scientific float ignores minFractionDigits', () => { - const doc = new YAML.Document(3, { version }) + const doc = new YAML.Document(3, { version }) doc.contents.format = 'EXP' doc.contents.minFractionDigits = 2 expect(String(doc)).toBe('3e+0\n') }) test('integer with HEX format', () => { - const doc = new YAML.Document(42, { version }) + const doc = new YAML.Document(42, { version }) doc.contents.format = 'HEX' expect(String(doc)).toBe('0x2a\n') }) test('float with HEX format', () => { - const doc = new YAML.Document(4.2, { version }) + const doc = new YAML.Document(4.2, { version }) doc.contents.format = 'HEX' expect(String(doc)).toBe('4.2\n') }) test('negative integer with HEX format', () => { - const doc = new YAML.Document(-42, { version }) + const doc = new YAML.Document(-42, { version }) doc.contents.format = 'HEX' const exp = version === '1.2' ? '-42\n' : '-0x2a\n' expect(String(doc)).toBe(exp) @@ -82,18 +83,24 @@ for (const [name, version] of [ expect(YAML.stringify(Object(BigInt('-42')), { version })).toBe('-42\n') }) test('BigInt with HEX format', () => { - const doc = new YAML.Document(BigInt('42'), { version }) + const doc = new YAML.Document(BigInt('42'), { + version + }) doc.contents.format = 'HEX' expect(String(doc)).toBe('0x2a\n') }) test('BigInt with OCT format', () => { - const doc = new YAML.Document(BigInt('42'), { version }) + const doc = new YAML.Document(BigInt('42'), { + version + }) doc.contents.format = 'OCT' const exp = version === '1.2' ? '0o52\n' : '052\n' expect(String(doc)).toBe(exp) }) test('negative BigInt with OCT format', () => { - const doc = new YAML.Document(BigInt('-42'), { version }) + const doc = new YAML.Document(BigInt('-42'), { + version + }) doc.contents.format = 'OCT' const exp = version === '1.2' ? '-42\n' : '-052\n' expect(String(doc)).toBe(exp) @@ -139,9 +146,12 @@ blah blah\n`) test('long line in map', () => { const foo = 'fuzz'.repeat(16) - const doc = new YAML.Document({ foo }, version) + const doc = new YAML.Document< + YAML.YAMLMap, + false + >({ foo }, { version }) for (const node of doc.contents.items) - node.value.type = Scalar.QUOTE_DOUBLE + node.value!.type = Scalar.QUOTE_DOUBLE expect( doc .toString(opt) @@ -152,7 +162,9 @@ blah blah\n`) test('long line in sequence', () => { const foo = 'fuzz'.repeat(16) - const doc = new YAML.Document([foo], version) + const doc = new YAML.Document, false>([foo], { + version + }) for (const node of doc.contents.items) node.type = Scalar.QUOTE_DOUBLE expect( doc @@ -164,8 +176,11 @@ blah blah\n`) test('long line in sequence in map', () => { const foo = 'fuzz'.repeat(16) - const doc = new YAML.Document({ foo: [foo] }, version) - const seq = doc.contents.items[0].value + const doc = new YAML.Document< + YAML.YAMLMap>, + false + >({ foo: [foo] }, { version }) + const seq = doc.contents.items[0].value! for (const node of seq.items) node.type = Scalar.QUOTE_DOUBLE expect( doc @@ -217,7 +232,7 @@ describe('unpaired surrogate pairs of Unicode code points', () => { describe('circular references', () => { test('parent at root', () => { - const map = { foo: 'bar' } + const map: any = { foo: 'bar' } map.map = map expect(YAML.stringify(map)).toBe(`&a1 foo: bar @@ -225,7 +240,7 @@ map: *a1\n`) }) test('ancestor at root', () => { - const baz = {} + const baz: any = {} const map = { foo: { bar: { baz } } } baz.map = map expect(YAML.stringify(map)).toBe(`&a1 @@ -306,8 +321,9 @@ z: }) test('Map with non-Pair item', () => { - const doc = new YAML.Document({ x: 3, y: 4 }) + const doc = new YAML.Document({ x: 3, y: 4 }) expect(String(doc)).toBe('x: 3\ny: 4\n') + // @ts-expect-error This should fail. doc.contents.items.push('TEST') expect(() => String(doc)).toThrow(/^Map items must all be pairs.*TEST/) }) @@ -324,38 +340,43 @@ z: }) describe('No extra whitespace for empty values', () => { + const getDoc = () => + new YAML.Document, false>({ + a: null, + b: null + }) + test('Block map, no comments', () => { - const doc = new YAML.Document({ a: null, b: null }) - expect(doc.toString({ nullStr: '' })).toBe('a:\nb:\n') + expect(getDoc().toString({ nullStr: '' })).toBe('a:\nb:\n') }) test('Block map, with key.comment', () => { - const doc = new YAML.Document({ a: null, b: null }) + const doc = getDoc() doc.contents.items[0].key.comment = 'c' expect(doc.toString({ nullStr: '' })).toBe('a: #c\nb:\n') }) test('Block map, with value.commentBefore', () => { - const doc = new YAML.Document({ a: null, b: null }) + const doc = getDoc() doc.get('a', true).commentBefore = 'c' expect(doc.toString({ nullStr: '' })).toBe('a:\n #c\nb:\n') }) test('Flow map, no comments', () => { - const doc = new YAML.Document({ a: null, b: null }) + const doc = getDoc() doc.contents.flow = true expect(doc.toString({ nullStr: '' })).toBe('{ a:, b: }\n') }) test('Flow map, with key.comment', () => { - const doc = new YAML.Document({ a: null, b: null }) + const doc = getDoc() doc.contents.flow = true doc.contents.items[0].key.comment = 'c' expect(doc.toString({ nullStr: '' })).toBe('{\n a: #c\n ,\n b:\n}\n') }) test('Flow map, with value.commentBefore', () => { - const doc = new YAML.Document({ a: null, b: null }) + const doc = getDoc() doc.contents.flow = true doc.get('a', true).commentBefore = 'c' expect(doc.toString({ nullStr: '' })).toBe( @@ -448,7 +469,7 @@ z: expect(String(doc)).toBe(src) }) test('explicit tag on empty mapping', () => { - const doc = new YAML.Document({ key: {} }) + const doc = new YAML.Document({ key: {} }) doc.get('key').tag = '!tag' expect(String(doc)).toBe(source` key: !tag {} @@ -669,23 +690,20 @@ describe('simple keys', () => { test('key with no value', () => { const doc = YAML.parseDocument('? ~') expect(doc.toString()).toBe('? ~\n') - doc.options.simpleKeys = true expect(doc.toString({ simpleKeys: true })).toBe('~: null\n') }) test('key with block scalar value', () => { - const doc = YAML.parseDocument('foo: bar') + const doc = YAML.parseDocument('foo: bar') doc.contents.items[0].key.type = 'BLOCK_LITERAL' expect(doc.toString()).toBe('? |-\n foo\n: bar\n') - doc.options.simpleKeys = true expect(doc.toString({ simpleKeys: true })).toBe('"foo": bar\n') }) test('key with comment', () => { - const doc = YAML.parseDocument('foo: bar') + const doc = YAML.parseDocument('foo: bar') doc.contents.items[0].key.comment = 'FOO' expect(doc.toString()).toBe('foo: #FOO\n bar\n') - doc.options.simpleKeys = true expect(() => doc.toString({ simpleKeys: true })).toThrow( /With simple keys, key nodes cannot have comments/ ) @@ -694,7 +712,6 @@ describe('simple keys', () => { test('key with collection value', () => { const doc = YAML.parseDocument('[foo]: bar') expect(doc.toString()).toBe('? [ foo ]\n: bar\n') - doc.options.simpleKeys = true expect(() => doc.toString({ simpleKeys: true })).toThrow( /With simple keys, collection cannot be used as a key value/ ) @@ -706,7 +723,6 @@ describe('simple keys', () => { : longkey` const doc = YAML.parseDocument(str) expect(doc.toString()).toBe(`? ${new Array(1026).join('a')}\n: longkey\n`) - doc.options.simpleKeys = true expect(() => doc.toString({ simpleKeys: true })).toThrow( /With simple keys, single line scalar must not span more than 1024 characters/ ) @@ -743,7 +759,7 @@ describe('sortMapEntries', () => { ) }) test('sortMapEntries: function', () => { - const sortMapEntries = (a, b) => + const sortMapEntries = (a: any, b: any) => a.key < b.key ? 1 : a.key > b.key ? -1 : 0 expect(YAML.stringify(obj, { sortMapEntries })).toBe('c: 3\nb: 2\na: 1\n') }) @@ -760,7 +776,7 @@ describe('sortMapEntries', () => { }) describe('custom indent', () => { - let obj + let obj: unknown beforeEach(() => { const doc = new YAML.Document() const seq = doc.createNode(['a']) @@ -806,7 +822,7 @@ describe('custom indent', () => { }) describe('indentSeq: false', () => { - let obj + let obj: unknown beforeEach(() => { const seq = new YAML.Document().createNode(['a']) seq.commentBefore = 'sc' @@ -852,7 +868,7 @@ describe('indentSeq: false', () => { describe('collectionStyle', () => { test('collectionStyle: undefined', () => { - const doc = new YAML.Document({ foo: ['bar'] }) + const doc = new YAML.Document({ foo: ['bar'] }) expect(doc.toString()).toBe('foo:\n - bar\n') doc.contents.flow = false @@ -865,7 +881,7 @@ describe('collectionStyle', () => { }) test("collectionStyle: 'any'", () => { - const doc = new YAML.Document({ foo: ['bar'] }) + const doc = new YAML.Document({ foo: ['bar'] }) expect(doc.toString({ collectionStyle: 'any' })).toBe('foo:\n - bar\n') doc.contents.flow = false @@ -878,7 +894,7 @@ describe('collectionStyle', () => { }) test("collectionStyle: 'block'", () => { - const doc = new YAML.Document({ foo: ['bar'] }) + const doc = new YAML.Document({ foo: ['bar'] }) expect(doc.toString({ collectionStyle: 'block' })).toBe('foo:\n - bar\n') doc.contents.flow = false @@ -891,7 +907,7 @@ describe('collectionStyle', () => { }) test("collectionStyle: 'flow'", () => { - const doc = new YAML.Document({ foo: ['bar'] }) + const doc = new YAML.Document({ foo: ['bar'] }) expect(doc.toString({ collectionStyle: 'flow' })).toBe('{ foo: [ bar ] }\n') doc.get('foo').flow = true @@ -909,7 +925,7 @@ describe('Scalar options', () => { const opt = { defaultStringType: Scalar.PLAIN, defaultKeyType: Scalar.PLAIN - } + } as const expect(YAML.stringify({ foo: 'bar' }, opt)).toBe('foo: bar\n') }) @@ -917,7 +933,7 @@ describe('Scalar options', () => { const opt = { defaultStringType: Scalar.BLOCK_FOLDED, defaultKeyType: Scalar.BLOCK_FOLDED - } + } as const expect(YAML.stringify({ foo: 'bar' }, opt)).toBe('"foo": |-\n bar\n') }) @@ -925,7 +941,7 @@ describe('Scalar options', () => { const opt = { defaultStringType: Scalar.QUOTE_DOUBLE, defaultKeyType: Scalar.PLAIN - } + } as const expect(YAML.stringify({ foo: 'bar' }, opt)).toBe('foo: "bar"\n') }) @@ -933,7 +949,7 @@ describe('Scalar options', () => { const opt = { defaultStringType: Scalar.QUOTE_DOUBLE, defaultKeyType: Scalar.QUOTE_SINGLE - } + } as const expect(YAML.stringify({ foo: 'bar' }, opt)).toBe('\'foo\': "bar"\n') }) @@ -941,7 +957,7 @@ describe('Scalar options', () => { const opt = { defaultStringType: Scalar.QUOTE_DOUBLE, defaultKeyType: null - } + } as const expect(YAML.stringify({ foo: 'bar' }, opt)).toBe('"foo": "bar"\n') }) @@ -949,8 +965,8 @@ describe('Scalar options', () => { const opt = { defaultStringType: Scalar.QUOTE_DOUBLE, defaultKeyType: Scalar.QUOTE_SINGLE - } - const doc = new YAML.Document({ foo: null }) + } as const + const doc = new YAML.Document({ foo: null }) doc.contents.items[0].value = null expect(doc.toString(opt)).toBe('? "foo"\n') }) @@ -1033,7 +1049,7 @@ describe('Scalar options', () => { value: 'foo: >\n bar\n\n fuzz\n', parsed: '>\nfoo\n' } - ]) { + ] as const) { describe(`blockQuote: ${blockQuote}`, () => { test('doc-marker', () => { expect(YAML.stringify('---', { blockQuote })).toBe(marker) @@ -1091,7 +1107,7 @@ describe('Document markers in top-level scalars', () => { }) test("'foo\\n...'", () => { - const doc = new YAML.Document('foo\n...') + const doc = new YAML.Document('foo\n...') doc.contents.type = Scalar.QUOTE_SINGLE const str = String(doc) expect(str).toBe("'foo\n\n ...'\n") @@ -1099,7 +1115,7 @@ describe('Document markers in top-level scalars', () => { }) test('"foo\\n..."', () => { - const doc = new YAML.Document('foo\n...') + const doc = new YAML.Document('foo\n...') doc.contents.type = Scalar.QUOTE_DOUBLE const str = doc.toString({ doubleQuotedMinMultiLineLength: 0 }) expect(str).toBe('"foo\n\n ..."\n') @@ -1201,7 +1217,7 @@ describe('replacer', () => { }) test('Map, array of string', () => { - const map = new Map([ + const map = new Map([ ['a', 1], ['b', 2], [3, 4] @@ -1215,7 +1231,7 @@ describe('replacer', () => { }) test('Map, array of number', () => { - const map = new Map([ + const map = new Map([ ['a', 1], ['3', 2], [3, 4] @@ -1245,23 +1261,26 @@ describe('replacer', () => { test('function as filter of Object entries', () => { const obj = { 1: 1, b: 2, c: [4] } - const fn = (_key, value) => (typeof value === 'number' ? undefined : value) + const fn = (_key: unknown, value: unknown) => + typeof value === 'number' ? undefined : value expect(YAML.stringify(obj, fn)).toBe('c:\n - null\n') }) test('function as filter of Map entries', () => { - const map = new Map([ + const map = new Map([ [1, 1], ['b', 2], ['c', [4]] ]) - const fn = (_key, value) => (typeof value === 'number' ? undefined : value) + const fn = (_key: unknown, value: unknown) => + typeof value === 'number' ? undefined : value expect(YAML.stringify(map, fn)).toBe('c:\n - null\n') }) test('function as transformer', () => { const obj = { a: 1, b: 2, c: [3, 4] } - const fn = (_key, value) => (typeof value === 'number' ? 2 * value : value) + const fn = (_key: unknown, value: unknown) => + typeof value === 'number' ? 2 * value : value expect(YAML.stringify(obj, fn)).toBe('a: 2\nb: 4\nc:\n - 6\n - 8\n') }) From c7cc418f99ec4af0cb58013403c3aa9d3b3670d3 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sun, 12 Feb 2023 16:34:36 +0200 Subject: [PATCH 15/20] test: Convert tests/doc/types from JS to TS --- tests/doc/{types.js => types.ts} | 251 +++++++++++++++++-------------- 1 file changed, 141 insertions(+), 110 deletions(-) rename tests/doc/{types.js => types.ts} (82%) diff --git a/tests/doc/types.js b/tests/doc/types.ts similarity index 82% rename from tests/doc/types.js rename to tests/doc/types.ts index c5e65767..88af6d77 100644 --- a/tests/doc/types.js +++ b/tests/doc/types.ts @@ -1,37 +1,57 @@ -import * as YAML from 'yaml' -import { Scalar, YAMLSeq } from 'yaml' +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { + Document, + DocumentOptions, + Node, + parse, + ParsedNode, + parseDocument as origParseDocument, + ParseOptions, + Scalar, + ScalarTag, + Schema, + SchemaOptions, + stringify, + YAMLMap, + YAMLSeq +} from 'yaml' import { seqTag, stringTag, stringifyString } from 'yaml/util' import { source } from '../_utils' +const parseDocument = ( + source: string, + options?: ParseOptions & DocumentOptions & SchemaOptions +) => origParseDocument(source, options) + describe('tags', () => { describe('implicit tags', () => { test('plain string', () => { - const doc = YAML.parseDocument('foo') + const doc = parseDocument('foo') expect(doc.contents.tag).toBeUndefined() expect(doc.contents.value).toBe('foo') }) test('quoted string', () => { - const doc = YAML.parseDocument('"foo"') + const doc = parseDocument('"foo"') expect(doc.contents.tag).toBeUndefined() expect(doc.contents.value).toBe('foo') }) test('flow map', () => { - const doc = YAML.parseDocument('{ foo }') + const doc = parseDocument('{ foo }') expect(doc.contents.tag).toBeUndefined() expect(doc.contents.toJSON()).toMatchObject({ foo: null }) }) test('flow seq', () => { - const doc = YAML.parseDocument('[ foo ]') + const doc = parseDocument('[ foo ]') expect(doc.contents.tag).toBeUndefined() expect(doc.contents.toJSON()).toMatchObject(['foo']) }) test('block map', () => { - const doc = YAML.parseDocument('foo:\n') + const doc = parseDocument('foo:\n') expect(doc.contents.tag).toBeUndefined() expect(doc.contents.toJSON()).toMatchObject({ foo: null }) }) test('block seq', () => { - const doc = YAML.parseDocument('- foo') + const doc = parseDocument('- foo') expect(doc.contents.tag).toBeUndefined() expect(doc.contents.toJSON()).toMatchObject(['foo']) }) @@ -39,32 +59,32 @@ describe('tags', () => { describe('explicit tags', () => { test('plain string', () => { - const doc = YAML.parseDocument('!!str foo') + const doc = parseDocument('!!str foo') expect(doc.contents.tag).toBe('tag:yaml.org,2002:str') expect(doc.contents.value).toBe('foo') }) test('quoted string', () => { - const doc = YAML.parseDocument('!!str "foo"') + const doc = parseDocument('!!str "foo"') expect(doc.contents.tag).toBe('tag:yaml.org,2002:str') expect(doc.contents.value).toBe('foo') }) test('flow map', () => { - const doc = YAML.parseDocument('!!map { foo }') + const doc = parseDocument('!!map { foo }') expect(doc.contents.tag).toBe('tag:yaml.org,2002:map') expect(doc.contents.toJSON()).toMatchObject({ foo: null }) }) test('flow seq', () => { - const doc = YAML.parseDocument('!!seq [ foo ]') + const doc = parseDocument('!!seq [ foo ]') expect(doc.contents.tag).toBe('tag:yaml.org,2002:seq') expect(doc.contents.toJSON()).toMatchObject(['foo']) }) test('block map', () => { - const doc = YAML.parseDocument('!!map\nfoo:\n') + const doc = parseDocument('!!map\nfoo:\n') expect(doc.contents.tag).toBe('tag:yaml.org,2002:map') expect(doc.contents.toJSON()).toMatchObject({ foo: null }) }) test('block seq', () => { - const doc = YAML.parseDocument('!!seq\n- foo') + const doc = parseDocument('!!seq\n- foo') expect(doc.contents.tag).toBe('tag:yaml.org,2002:seq') expect(doc.contents.toJSON()).toMatchObject(['foo']) }) @@ -81,7 +101,7 @@ describe('tags', () => { '!t"ag x' ]) { test(`invalid tag: ${tag}`, () => { - const doc = YAML.parseDocument(tag) + const doc = parseDocument(tag) expect(doc.errors).not.toHaveLength(0) expect(doc.errors[0].code).toBe('MISSING_CHAR') }) @@ -89,7 +109,7 @@ describe('tags', () => { }) test('eemeli/yaml#97', () => { - const doc = YAML.parseDocument('foo: !!float 3.0') + const doc = parseDocument('foo: !!float 3.0') expect(String(doc)).toBe('foo: !!float 3.0\n') }) }) @@ -108,7 +128,7 @@ describe('number types', () => { - 4.20 - .42 - 00.4` - const doc = YAML.parseDocument(src, { + const doc = parseDocument(src, { intAsBigInt: false, version: '1.1' }) @@ -141,7 +161,7 @@ describe('number types', () => { - 4.20 - .42 - 00.4` - const doc = YAML.parseDocument(src, { + const doc = parseDocument(src, { intAsBigInt: false, version: '1.2' }) @@ -173,7 +193,10 @@ describe('number types', () => { - 3.1e+2 - 5.1_2_3E-1 - 4.02` - const doc = YAML.parseDocument(src, { intAsBigInt: true, version: '1.1' }) + const doc = parseDocument(src, { + intAsBigInt: true, + version: '1.1' + }) expect(doc.contents.items).toMatchObject([ { value: 10n, format: 'BIN' }, { value: 83n, format: 'OCT' }, @@ -196,7 +219,10 @@ describe('number types', () => { - 3.1e+2 - 5.123E-1 - 4.02` - const doc = YAML.parseDocument(src, { intAsBigInt: true, version: '1.2' }) + const doc = parseDocument(src, { + intAsBigInt: true, + version: '1.2' + }) expect(doc.contents.items).toMatchObject([ { value: 83n, format: 'OCT' }, { value: 0n, format: 'OCT' }, @@ -218,7 +244,7 @@ aliases: - docker: - image: circleci/node:8.11.2 - key: repository-{{ .Revision }}\n` - expect(YAML.parse(src)).toMatchObject({ + expect(parse(src)).toMatchObject({ aliases: [ { docker: [{ image: 'circleci/node:8.11.2' }] }, { key: 'repository-{{ .Revision }}' } @@ -233,7 +259,7 @@ describe('json schema', () => { "logical": True "option": TruE` - const doc = YAML.parseDocument(src, { schema: 'json' }) + const doc = parseDocument(src, { schema: 'json' }) expect(doc.toJS()).toMatchObject({ canonical: true, answer: false, @@ -253,7 +279,7 @@ describe('json schema', () => { "negative infinity": -.inf "not a number": .NaN` - const doc = YAML.parseDocument(src, { schema: 'json' }) + const doc = parseDocument>(src, { schema: 'json' }) expect(doc.toJS()).toMatchObject({ canonical: 685230.15, fixed: 685230.15, @@ -262,7 +288,7 @@ describe('json schema', () => { }) expect(doc.errors).toHaveLength(2) doc.errors = [] - doc.contents.items[1].value.tag = 'tag:yaml.org,2002:float' + doc.contents.items[1].value!.tag = 'tag:yaml.org,2002:float' expect(String(doc)).toBe( '"canonical": 685230.15\n"fixed": !!float 685230.15\n"negative infinity": "-.inf"\n"not a number": ".NaN"\n' ) @@ -274,7 +300,7 @@ describe('json schema', () => { "octal": 0o2472256 "hexadecimal": 0x0A74AE` - const doc = YAML.parseDocument(src, { schema: 'json' }) + const doc = parseDocument(src, { schema: 'json' }) expect(doc.toJS()).toMatchObject({ canonical: 685230, decimal: -685230, @@ -295,7 +321,7 @@ describe('json schema', () => { "english": null ~: 'null key'` - const doc = YAML.parseDocument(src, { schema: 'json' }) + const doc = parseDocument(src, { schema: 'json' }) expect(doc.toJS()).toMatchObject({ empty: '', canonical: '~', @@ -318,7 +344,7 @@ answer: FALSE logical: True option: TruE\n` - const doc = YAML.parseDocument(src) + const doc = parseDocument(src) expect(doc.toJS()).toMatchObject({ canonical: true, answer: false, @@ -337,7 +363,7 @@ fixed: 685230.15 negative infinity: -.inf not a number: .NaN` - const doc = YAML.parseDocument(src) + const doc = parseDocument(src) expect(doc.toJS()).toMatchObject({ canonical: 685230.15, fixed: 685230.15, @@ -356,7 +382,7 @@ decimal: +685230 octal: 0o2472256 hexadecimal: 0x0A74AE` - const doc = YAML.parseDocument(src) + const doc = parseDocument(src) expect(doc.toJS()).toMatchObject({ canonical: 685230, decimal: 685230, @@ -375,7 +401,7 @@ canonical: ~ english: null ~: null key` - const doc = YAML.parseDocument(src) + const doc = parseDocument(src) expect(doc.toJS()).toMatchObject({ empty: null, canonical: null, @@ -394,7 +420,7 @@ english: null one: 1 2: two { 3: 4 }: many\n` - const doc = YAML.parseDocument(src, { logLevel: 'error' }) + const doc = parseDocument(src, { logLevel: 'error' }) expect(doc.toJS()).toMatchObject({ one: 1, 2: 'two', @@ -414,9 +440,9 @@ one: 1 one: 1 2: two { 3: 4 }: many\n` - const doc = YAML.parseDocument(src) + const doc = parseDocument(src) expect(doc.toJS({ mapAsMap: true })).toMatchObject( - new Map([ + new Map([ ['one', 1], [2, 'two'], [new Map([[3, 4]]), 'many'] @@ -425,7 +451,7 @@ one: 1 expect(doc.errors).toHaveLength(0) doc.contents.items[2].key = { 3: 4 } expect(doc.toJS({ mapAsMap: true })).toMatchObject( - new Map([ + new Map([ ['one', 1], [2, 'two'], [{ 3: 4 }, 'many'] @@ -450,9 +476,11 @@ generic: !!binary | description: The binary value above is a tiny arrow encoded as a gif image.` - const doc = YAML.parseDocument(src, { schema: 'yaml-1.1' }) - const canonical = doc.contents.items[0].value.value - const generic = doc.contents.items[1].value.value + const doc = parseDocument>>(src, { + schema: 'yaml-1.1' + }) + const canonical = doc.contents.items[0].value!.value + const generic = doc.contents.items[1].value!.value expect(canonical).toBeInstanceOf(Uint8Array) expect(generic).toBeInstanceOf(Uint8Array) expect(canonical).toHaveLength(185) @@ -485,7 +513,7 @@ answer: NO logical: True option: on` - const doc = YAML.parseDocument(src, { version: '1.1' }) + const doc = parseDocument(src, { version: '1.1' }) expect(doc.toJS()).toMatchObject({ canonical: true, answer: false, @@ -508,7 +536,7 @@ sexagesimal: 190:20:30.15 negative infinity: -.inf not a number: .NaN` - const doc = YAML.parseDocument(src) + const doc = parseDocument(src) expect(doc.toJS()).toMatchObject({ canonical: 685230.15, exponential: 685230.15, @@ -537,7 +565,7 @@ hexadecimal: 0x_0A_74_AE binary: 0b1010_0111_0100_1010_1110 sexagesimal: 190:20:30` - const doc = YAML.parseDocument(src) + const doc = parseDocument(src) expect(doc.toJS()).toMatchObject({ canonical: 685230, decimal: 685230, @@ -566,7 +594,7 @@ hexadecimal: 0x_0A_74_AE binary: 0b1010_0111_0100_1010_1110 sexagesimal: 190:20:30` - const doc = YAML.parseDocument(src, { intAsBigInt: true }) + const doc = parseDocument(src, { intAsBigInt: true }) expect(doc.toJS()).toMatchObject({ canonical: 685230n, decimal: 685230n, @@ -593,7 +621,7 @@ canonical: ~ english: null ~: null key` - const doc = YAML.parseDocument(src) + const doc = parseDocument(src) expect(doc.toJS()).toMatchObject({ empty: null, canonical: null, @@ -618,9 +646,9 @@ space separated: 2001-12-14 21:59:43.10 -5 no time zone (Z): 2001-12-15 2:59:43.10 date (00:00:00Z): 2002-12-14` - const doc = YAML.parseDocument(src) + const doc = parseDocument>(src) doc.contents.items.forEach(item => { - expect(item.value.value).toBeInstanceOf(Date) + expect(item.value!.value).toBeInstanceOf(Date) }) expect(doc.toJSON()).toMatchObject({ canonical: '2001-12-15T02:59:43.100Z', @@ -640,9 +668,9 @@ date (00:00:00Z): 2002-12-14\n`) test('stringify', () => { const date = new Date('2018-12-22T08:02:52Z') - const str = YAML.stringify(date) // stringified as !!str + const str = stringify(date) // stringified as !!str expect(str).toBe('2018-12-22T08:02:52.000Z\n') - const str2 = YAML.stringify(date, { version: '1.1' }) + const str2 = stringify(date, { version: '1.1' }) expect(str2).toBe('2018-12-22T08:02:52\n') }) }) @@ -653,7 +681,7 @@ date (00:00:00Z): 2002-12-14\n`) { name: 'parse flow seq', src: `!!pairs [ a: 1, b: 2, a: 3 ]\n` } ]) test(name, () => { - const doc = YAML.parseDocument(src, { version: '1.1' }) + const doc = parseDocument(src, { version: '1.1' }) expect(doc.contents).toBeInstanceOf(YAMLSeq) expect(doc.contents.items).toMatchObject([ { key: { value: 'a' }, value: { value: 1 } }, @@ -666,7 +694,7 @@ date (00:00:00Z): 2002-12-14\n`) }) test('stringify', () => { - const doc = new YAML.Document(null, { version: '1.1' }) + const doc = new Document(null, { version: '1.1' }) doc.contents = doc.createNode( [ ['a', 1], @@ -686,7 +714,7 @@ date (00:00:00Z): 2002-12-14\n`) { name: 'parse flow seq', src: `!!omap [ a: 1, b: 2, c: 3 ]\n` } ]) test(name, () => { - const doc = YAML.parseDocument(src, { version: '1.1' }) + const doc = parseDocument(src, { version: '1.1' }) expect(doc.contents.constructor.tag).toBe('tag:yaml.org,2002:omap') expect(doc.toJS()).toBeInstanceOf(Map) expect(doc.toJS()).toMatchObject( @@ -701,7 +729,7 @@ date (00:00:00Z): 2002-12-14\n`) test('require unique keys', () => { const src = `!!omap\n- a: 1\n- b: 2\n- b: 9\n` - const doc = YAML.parseDocument(src, { + const doc = parseDocument(src, { prettyErrors: false, version: '1.1' }) @@ -719,14 +747,14 @@ date (00:00:00Z): 2002-12-14\n`) ['b', 2], ['c', 3] ]) - const str = YAML.stringify(map, { version: '1.1' }) + const str = stringify(map, { version: '1.1' }) expect(str).toBe(`!!omap\n- a: 1\n- b: 2\n- c: 3\n`) - const str2 = YAML.stringify(map) + const str2 = stringify(map) expect(str2).toBe(`a: 1\nb: 2\nc: 3\n`) }) test('stringify Array', () => { - const doc = new YAML.Document(null, { version: '1.1' }) + const doc = new Document(null, { version: '1.1' }) doc.contents = doc.createNode( [ ['a', 1], @@ -746,7 +774,7 @@ date (00:00:00Z): 2002-12-14\n`) { name: 'parse flow map', src: `!!set { a, b, c }\n` } ]) test(name, () => { - const doc = YAML.parseDocument(src, { version: '1.1' }) + const doc = parseDocument(src, { version: '1.1' }) expect(doc.contents.constructor.tag).toBe('tag:yaml.org,2002:set') expect(doc.toJS()).toBeInstanceOf(Set) expect(doc.toJS()).toMatchObject(new Set(['a', 'b', 'c'])) @@ -755,7 +783,7 @@ date (00:00:00Z): 2002-12-14\n`) test('require null values', () => { const src = `!!set\n? a\n? b\nc: d\n` - const doc = YAML.parseDocument(src, { + const doc = parseDocument(src, { prettyErrors: false, version: '1.1' }) @@ -769,15 +797,15 @@ date (00:00:00Z): 2002-12-14\n`) test('stringify', () => { const set = new Set(['a', 'b', 'c']) - const str = YAML.stringify(set, { version: '1.1' }) + const str = stringify(set, { version: '1.1' }) expect(str).toBe(`!!set\n? a\n? b\n? c\n`) - const str2 = YAML.stringify(set) + const str2 = stringify(set) expect(str2).toBe(`- a\n- b\n- c\n`) }) test('eemeli/yaml#78', () => { const set = new Set(['a', 'b', 'c']) - const str = YAML.stringify({ set }, { version: '1.1' }) + const str = stringify({ set }, { version: '1.1' }) expect(str).toBe(source` set: !!set ? a @@ -790,7 +818,7 @@ date (00:00:00Z): 2002-12-14\n`) describe('!!merge', () => { test('alias', () => { const src = '- &a { a: A, b: B }\n- { <<: *a, b: X }\n' - const doc = YAML.parseDocument(src, { version: '1.1' }) + const doc = parseDocument(src, { version: '1.1' }) expect(doc.toJS()).toMatchObject([ { a: 'A', b: 'B' }, { a: 'A', b: 'X' } @@ -801,7 +829,7 @@ date (00:00:00Z): 2002-12-14\n`) test('alias sequence', () => { const src = '- &a { a: A, b: B, c: C }\n- &b { a: X }\n- { <<: [ *b, *a ], b: X }\n' - const doc = YAML.parseDocument(src, { version: '1.1' }) + const doc = parseDocument(src, { version: '1.1' }) expect(doc.toJS()).toMatchObject([ { a: 'A', b: 'B' }, { a: 'X' }, @@ -812,7 +840,7 @@ date (00:00:00Z): 2002-12-14\n`) test('explicit creation', () => { const src = '- { a: A, b: B }\n- { b: X }\n' - const doc = YAML.parseDocument(src, { version: '1.1' }) + const doc = parseDocument(src, { version: '1.1' }) const alias = doc.createAlias(doc.get(0), 'a') doc.addIn([1], doc.createPair('<<', alias)) expect(doc.toString()).toBe('- &a { a: A, b: B }\n- { b: X, <<: *a }\n') @@ -824,7 +852,7 @@ date (00:00:00Z): 2002-12-14\n`) test('creation by duck typing', () => { const src = '- { a: A, b: B }\n- { b: X }\n' - const doc = YAML.parseDocument(src, { version: '1.1' }) + const doc = parseDocument(src, { version: '1.1' }) const alias = doc.createAlias(doc.get(0), 'a') doc.addIn([1], doc.createPair('<<', alias)) expect(doc.toString()).toBe('- &a { a: A, b: B }\n- { b: X, <<: *a }\n') @@ -849,7 +877,7 @@ describe('custom tags', () => { ` test('parse', () => { - const doc = YAML.parseDocument(src) + const doc = parseDocument>(src) expect(doc.contents).toBeInstanceOf(YAMLSeq) expect(doc.contents.tag).toBe('tag:example.com,2000:test/x') const { items } = doc.contents @@ -861,7 +889,7 @@ describe('custom tags', () => { }) test('stringify', () => { - const doc = YAML.parseDocument(src) + const doc = parseDocument(src) expect(String(doc)).toBe(source` %TAG !e! tag:example.com,2000:test/ --- @@ -874,7 +902,7 @@ describe('custom tags', () => { }) test('modify', () => { - const doc = YAML.parseDocument(src) + const doc = parseDocument>(src) const prefix = 'tag:example.com,2000:other/' doc.directives.tags['!f!'] = prefix expect(doc.directives.tags).toMatchObject({ @@ -887,6 +915,7 @@ describe('custom tags', () => { doc.contents.items[3].comment = 'cc' const s = new Scalar(6) s.tag = '!g' + // @ts-expect-error TS should complain here doc.contents.items.splice(1, 1, s, '7') expect(String(doc)).toBe(source` %TAG !e! tag:example.com,2000:test/ @@ -911,42 +940,42 @@ describe('custom tags', () => { AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs=` test('tag string in tags', () => { - const bin = YAML.parse(src, { customTags: ['binary'] }) + const bin = parse(src, { customTags: ['binary'] }) expect(bin).toBeInstanceOf(Uint8Array) }) test('tag string in tag array', () => { - const bin = YAML.parse(src, { customTags: [['binary']] }) + // @ts-expect-error TS should complain here + const bin = parse(src, { customTags: [['binary']] }) expect(bin).toBeInstanceOf(Uint8Array) }) test('custom tags from function', () => { - const customTags = tags => tags.concat('binary') - const bin = YAML.parse(src, { customTags }) + const bin = parse(src, { customTags: tags => tags.concat('binary') }) expect(bin).toBeInstanceOf(Uint8Array) }) test('no custom tag object', () => { - const bin = YAML.parse(src) + const bin = parse(src) expect(bin).toBeInstanceOf(Uint8Array) }) }) - const regexp = { + const regexp: ScalarTag = { identify: value => value instanceof RegExp, tag: '!re', resolve(str) { - const match = str.match(/^\/([\s\S]+)\/([gimuy]*)$/) + const match = str.match(/^\/([\s\S]+)\/([gimuy]*)$/)! return new RegExp(match[1], match[2]) } } - const sharedSymbol = { - identify: value => value.constructor === Symbol, + const sharedSymbol: ScalarTag = { + identify: (value: any) => value.constructor === Symbol, tag: '!symbol/shared', resolve: str => Symbol.for(str), stringify(item, ctx, onComment, onChompKeep) { - const key = Symbol.keyFor(item.value) + const key = Symbol.keyFor(item.value as symbol) if (key === undefined) throw new Error('Only shared symbols are supported') return stringifyString({ value: key }, ctx, onComment, onChompKeep) @@ -955,26 +984,26 @@ describe('custom tags', () => { describe('RegExp', () => { test('stringify as plain scalar', () => { - const str = YAML.stringify(/re/g, { customTags: [regexp] }) + const str = stringify(/re/g, { customTags: [regexp] }) expect(str).toBe('!re /re/g\n') - const res = YAML.parse(str, { customTags: [regexp] }) + const res = parse(str, { customTags: [regexp] }) expect(res).toBeInstanceOf(RegExp) }) test('stringify as quoted scalar', () => { - const str = YAML.stringify(/re: /g, { customTags: [regexp] }) + const str = stringify(/re: /g, { customTags: [regexp] }) expect(str).toBe('!re "/re: /g"\n') - const res = YAML.parse(str, { customTags: [regexp] }) + const res = parse(str, { customTags: [regexp] }) expect(res).toBeInstanceOf(RegExp) }) test('parse plain string as string', () => { - const res = YAML.parse('/re/g', { customTags: [regexp] }) + const res = parse('/re/g', { customTags: [regexp] }) expect(res).toBe('/re/g') }) test('parse quoted string as string', () => { - const res = YAML.parse('"/re/g"', { customTags: [regexp] }) + const res = parse('"/re/g"', { customTags: [regexp] }) expect(res).toBe('/re/g') }) }) @@ -982,37 +1011,38 @@ describe('custom tags', () => { describe('Symbol', () => { test('stringify as plain scalar', () => { const symbol = Symbol.for('foo') - const str = YAML.stringify(symbol, { customTags: [sharedSymbol] }) + const str = stringify(symbol, { customTags: [sharedSymbol] }) expect(str).toBe('!symbol/shared foo\n') - const res = YAML.parse(str, { customTags: [sharedSymbol] }) + const res = parse(str, { customTags: [sharedSymbol] }) expect(res).toBe(symbol) }) test('stringify as block scalar', () => { const symbol = Symbol.for('foo\nbar') - const str = YAML.stringify(symbol, { customTags: [sharedSymbol] }) + const str = stringify(symbol, { customTags: [sharedSymbol] }) expect(str).toBe('!symbol/shared |-\nfoo\nbar\n') - const res = YAML.parse(str, { customTags: [sharedSymbol] }) + const res = parse(str, { customTags: [sharedSymbol] }) expect(res).toBe(symbol) }) }) test('array within customTags', () => { const obj = { re: /re/g, symbol: Symbol.for('foo') } - const str = YAML.stringify(obj, { customTags: [[regexp, sharedSymbol]] }) + // @ts-expect-error TS should complain here + const str = stringify(obj, { customTags: [[regexp, sharedSymbol]] }) expect(str).toBe('re: !re /re/g\nsymbol: !symbol/shared foo\n') }) describe('schema from custom tags', () => { test('customTags is required', () => { - expect(() => - YAML.parseDocument('foo', { schema: 'custom-test' }) - ).toThrow(/Unknown schema "custom-test"/) + expect(() => parseDocument('foo', { schema: 'custom-test' })).toThrow( + /Unknown schema "custom-test"/ + ) }) test('parse success', () => { const src = '- foo\n- !re /bar/\n' - const doc = YAML.parseDocument(src, { + const doc = parseDocument(src, { customTags: [seqTag, stringTag, regexp], schema: 'custom-test' }) @@ -1025,7 +1055,7 @@ describe('custom tags', () => { test('parse fail', () => { // map, seq & string are always parsed, even if not included const src = '- foo\n- !re /bar/\n' - const doc = YAML.parseDocument(src, { + const doc = parseDocument(src, { customTags: [], schema: 'custom-test' }) @@ -1036,14 +1066,14 @@ describe('custom tags', () => { test('stringify success', () => { let src = '- foo\n' - let doc = YAML.parseDocument(src, { + let doc = parseDocument(src, { customTags: [seqTag, stringTag], schema: 'custom-test' }) expect(doc.toString()).toBe(src) src = '- foo\n- !re /bar/\n' - doc = YAML.parseDocument(src, { + doc = parseDocument(src, { customTags: [seqTag, stringTag, regexp], schema: 'custom-test' }) @@ -1052,13 +1082,13 @@ describe('custom tags', () => { test('stringify fail', () => { const src = '- foo\n' - let doc = YAML.parseDocument(src, { + let doc = parseDocument(src, { customTags: [], schema: 'custom-test' }) expect(() => String(doc)).toThrow(/Tag not resolved for YAMLSeq value/) - doc = YAML.parseDocument(src, { + doc = parseDocument(src, { customTags: [seqTag], schema: 'custom-test' }) @@ -1067,7 +1097,7 @@ describe('custom tags', () => { test('setSchema', () => { const src = '- foo\n' - const doc = YAML.parseDocument(src) + const doc = parseDocument(src) doc.setSchema('1.2', { customTags: [seqTag, stringTag], @@ -1086,7 +1116,7 @@ describe('custom tags', () => { describe('schema changes', () => { test('write as json', () => { - const doc = YAML.parseDocument('foo: bar', { schema: 'core' }) + const doc = parseDocument('foo: bar', { schema: 'core' }) expect(doc.schema.name).toBe('core') doc.setSchema('1.2', { schema: 'json' }) expect(doc.schema.name).toBe('json') @@ -1094,7 +1124,7 @@ describe('schema changes', () => { }) test('fail for missing type', () => { - const doc = YAML.parseDocument('foo: 1971-02-03T12:13:14', { + const doc = parseDocument('foo: 1971-02-03T12:13:14', { version: '1.1' }) expect(doc.directives.yaml).toMatchObject({ @@ -1111,7 +1141,7 @@ describe('schema changes', () => { }) test('set schema + custom tags', () => { - const doc = YAML.parseDocument('foo: 1971-02-03T12:13:14', { + const doc = parseDocument('foo: 1971-02-03T12:13:14', { version: '1.1' }) doc.setSchema('1.1', { customTags: ['timestamp'], schema: 'json' }) @@ -1119,15 +1149,16 @@ describe('schema changes', () => { }) test('set version + custom tags', () => { - const doc = YAML.parseDocument('foo: 1971-02-03T12:13:14', { + const doc = parseDocument('foo: 1971-02-03T12:13:14', { version: '1.1' }) + // @ts-expect-error TS should complain here doc.setSchema(1.2, { customTags: ['timestamp'] }) expect(String(doc)).toBe('foo: 1971-02-03T12:13:14\n') }) test('schema changes on bool', () => { - const doc = YAML.parseDocument('[y, yes, on, n, no, off]', { + const doc = parseDocument('[y, yes, on, n, no, off]', { version: '1.1' }) doc.setSchema('1.1', { schema: 'core' }) @@ -1137,13 +1168,13 @@ describe('schema changes', () => { }) test('set bool + re-stringified', () => { - let doc = YAML.parseDocument('a: True', { + let doc = parseDocument('a: True', { version: '1.2' }) doc.set('a', false) expect(String(doc)).toBe('a: false\n') - doc = YAML.parseDocument('a: on', { + doc = parseDocument('a: on', { version: '1.1' }) doc.set('a', false) @@ -1152,22 +1183,22 @@ describe('schema changes', () => { test('custom schema instance', () => { const src = '[ !!bool yes, &foo no, *foo ]' - const doc = YAML.parseDocument(src, { version: '1.1' }) + const doc = parseDocument(src, { version: '1.1' }) - doc.setSchema('1.2', new YAML.Schema({ schema: 'core' })) + const core = new Schema({ schema: 'core' }) + doc.setSchema('1.2', { schema: core }) expect(String(doc)).toBe('[ !!bool true, &foo false, *foo ]\n') - const yaml11 = new YAML.Schema({ schema: 'yaml-1.1' }) - doc.setSchema('1.1', Object.assign({}, yaml11)) + const yaml11 = new Schema({ schema: 'yaml-1.1' }) + doc.setSchema('1.1', { schema: Object.assign({}, yaml11) }) expect(String(doc)).toBe('[ !!bool yes, &foo no, *foo ]\n') - const schema = new YAML.Schema({ schema: 'core' }) - doc.setSchema(null, { schema }) + doc.setSchema(null, { schema: core }) expect(String(doc)).toBe('[ true, false, false ]\n') }) test('null version requires Schema instance', () => { - const doc = YAML.parseDocument('foo: bar') + const doc = parseDocument('foo: bar') const msg = 'With a null YAML version, the { schema: Schema } option is required' expect(() => doc.setSchema(null)).toThrow(msg) From 82fde880dd9f5997f0a37f205391b25fa84951f8 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sun, 12 Feb 2023 16:39:49 +0200 Subject: [PATCH 16/20] chore: Update ESLint & TS configs for tests --- .eslintrc.yaml | 1 + tests/collection-access.ts | 16 +++++++--------- tests/doc/comments.ts | 1 - tests/doc/stringify.ts | 3 --- tests/doc/types.ts | 1 - tests/tsconfig.json | 2 +- 6 files changed, 9 insertions(+), 15 deletions(-) diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 79896920..24017a94 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -49,6 +49,7 @@ overrides: jest: true rules: camelcase: 0 + '@typescript-eslint/no-non-null-assertion': off '@typescript-eslint/no-unsafe-return': off - files: [tests/doc/**] diff --git a/tests/collection-access.ts b/tests/collection-access.ts index 410d9e2f..ba30e7e1 100644 --- a/tests/collection-access.ts +++ b/tests/collection-access.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ - import { Document, parseDocument, @@ -42,7 +40,7 @@ describe('Map', () => { test('add', () => { map.add({ key: 'c', value: 'x' }) expect(map.get('c')).toBe('x') - // @ts-expect-error + // @ts-expect-error TS should complain here expect(() => map.add('a')).toThrow(/already set/) expect(() => map.add(new Pair('c', 'y'))).toThrow(/already set/) expect(map.items).toHaveLength(3) @@ -88,7 +86,7 @@ describe('Map', () => { expect(map.has('b')).toBe(true) expect(map.has('c')).toBe(false) expect(map.has('')).toBe(false) - // @ts-expect-error + // @ts-expect-error TS should complain here expect(map.has()).toBe(false) }) @@ -96,7 +94,7 @@ describe('Map', () => { expect(map.has(doc.createNode('a'))).toBe(true) expect(map.has(doc.createNode('b'))).toBe(true) expect(map.has(doc.createNode('c'))).toBe(false) - // @ts-expect-error + // @ts-expect-error TS should complain here expect(map.has(doc.createNode())).toBe(false) }) @@ -189,7 +187,7 @@ describe('Seq', () => { expect(seq.has(2)).toBe(false) expect(seq.has('0')).toBe(true) expect(seq.has('')).toBe(false) - // @ts-expect-error + // @ts-expect-error TS should complain here expect(seq.has()).toBe(false) }) @@ -198,7 +196,7 @@ describe('Seq', () => { expect(seq.has(doc.createNode('0'))).toBe(true) expect(seq.has(doc.createNode(2))).toBe(false) expect(seq.has(doc.createNode(''))).toBe(false) - // @ts-expect-error + // @ts-expect-error TS should complain here expect(seq.has(doc.createNode())).toBe(false) }) @@ -300,7 +298,7 @@ describe('OMap', () => { test('add', () => { omap.add({ key: 'c', value: 'x' }) expect(omap.get('c')).toBe('x') - // @ts-expect-error + // @ts-expect-error TS should complain here expect(() => omap.add('a')).toThrow(/already set/) expect(() => omap.add(new Pair('c', 'y'))).toThrow(/already set/) expect(omap.items).toHaveLength(3) @@ -331,7 +329,7 @@ describe('OMap', () => { expect(omap.has('b')).toBe(true) expect(omap.has('c')).toBe(false) expect(omap.has('')).toBe(false) - // @ts-expect-error + // @ts-expect-error TS should complain here expect(omap.has()).toBe(false) }) diff --git a/tests/doc/comments.ts b/tests/doc/comments.ts index 6cb19c0f..503871fb 100644 --- a/tests/doc/comments.ts +++ b/tests/doc/comments.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { source } from '../_utils' import * as YAML from 'yaml' diff --git a/tests/doc/stringify.ts b/tests/doc/stringify.ts index 4a7ae707..5f68dfd4 100644 --- a/tests/doc/stringify.ts +++ b/tests/doc/stringify.ts @@ -1,6 +1,3 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -/* global BigInt */ - import { source } from '../_utils' import * as YAML from 'yaml' import { Pair, Scalar } from 'yaml' diff --git a/tests/doc/types.ts b/tests/doc/types.ts index 88af6d77..b083ebc4 100644 --- a/tests/doc/types.ts +++ b/tests/doc/types.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { Document, DocumentOptions, diff --git a/tests/tsconfig.json b/tests/tsconfig.json index 808465f2..342ab7a2 100644 --- a/tests/tsconfig.json +++ b/tests/tsconfig.json @@ -10,5 +10,5 @@ "rootDir": "..", "types": ["jest", "node"] }, - "include": ["**/*.ts", "**/doc/*.js"] + "include": ["**/*.ts"] } From 52ce17a1cf38538521823f557aa4251ffc652985 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Mon, 13 Feb 2023 14:53:48 +0200 Subject: [PATCH 17/20] chore: Drop support for TS 3.8 & add note to docs --- .github/workflows/typescript.yml | 2 +- README.md | 4 ++++ docs/01_intro.md | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/typescript.yml b/.github/workflows/typescript.yml index 97de9ed8..af66097b 100644 --- a/.github/workflows/typescript.yml +++ b/.github/workflows/typescript.yml @@ -27,6 +27,6 @@ jobs: - run: for d in node_modules/@types/*; do [[ $d == *node ]] || rm -r $d; done - run: npm run test:dist:types - - run: npm install --no-save typescript@3.8 + - run: npm install --no-save typescript@3.9 - run: for d in node_modules/@types/*; do [[ $d == *node ]] || rm -r $d; done - run: npm run test:dist:types diff --git a/README.md b/README.md index 3af1473e..61e1a2ad 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,10 @@ It has no external dependencies and runs on Node.js as well as modern browsers. For the purposes of versioning, any changes that break any of the documented endpoints or APIs will be considered semver-major breaking changes. Undocumented library internals may change between minor versions, and previous APIs may be deprecated (but not removed). +The minimum supported TypeScript version of the included typings is 3.9; +for use in earlier versions you may need to set `skipLibCheck: true` in your config. +This requirement may be updated between minor versions of the library. + For more information, see the project's documentation site: [**eemeli.org/yaml**](https://eemeli.org/yaml/) To install: diff --git a/docs/01_intro.md b/docs/01_intro.md index 622cdd6f..23e52158 100644 --- a/docs/01_intro.md +++ b/docs/01_intro.md @@ -32,6 +32,10 @@ It has no external dependencies and runs on Node.js as well as modern browsers. For the purposes of versioning, any changes that break any of the endpoints or APIs documented here will be considered semver-major breaking changes. Undocumented library internals may change between minor versions, and previous APIs may be deprecated (but not removed). +The minimum supported TypeScript version of the included typings is 3.9; +for use in earlier versions you may need to set `skipLibCheck: true` in your config. +This requirement may be updated between minor versions of the library. + **Note:** These docs are for `yaml@2`. For v1, see the [v1.10.0 tag](https://github.com/eemeli/yaml/tree/v1.10.0) for the source and [eemeli.org/yaml/v1](https://eemeli.org/yaml/v1/) for the documentation. ## API Overview From f0a4afac0fa9d9d813332f7e27eac6dfd53ad0af Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Mon, 13 Feb 2023 14:54:29 +0200 Subject: [PATCH 18/20] ci: Fix types for older TS versions --- .github/workflows/nodejs.yml | 2 +- src/doc/Document.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 0976753f..7ea094db 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -25,8 +25,8 @@ jobs: node-version: ${{ matrix.node-version }} - run: npm ci - run: npm test - - run: npm run test:types - run: npm run test:dist + - run: npm run test:types - run: npm run test:dist:types lint: diff --git a/src/doc/Document.ts b/src/doc/Document.ts index 2fe1ece0..a5f62591 100644 --- a/src/doc/Document.ts +++ b/src/doc/Document.ts @@ -36,6 +36,8 @@ import { Directives } from './directives.js' export type Replacer = any[] | ((key: any, value: any) => unknown) export declare namespace Document { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + /** @ts-ignore The typing of directives fails in TS <= 4.2 */ interface Parsed< Contents extends ParsedNode = ParsedNode, Strict extends boolean = true From 2489810f9d892886d8322869f390de6b2802c829 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Mon, 13 Feb 2023 16:52:36 +0200 Subject: [PATCH 19/20] chore(ts): Use package.json typesVersions to find d.ts files from dist/ --- package.json | 8 +++++++- util.d.ts | 3 --- 2 files changed, 7 insertions(+), 4 deletions(-) delete mode 100644 util.d.ts diff --git a/package.json b/package.json index 1839622c..ddf23b7b 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ "files": [ "browser/", "dist/", - "util.d.ts", "util.js" ], "type": "commonjs", @@ -35,6 +34,13 @@ "default": "./browser/dist/util.js" } }, + "typesVersions": { + "*": { + "*": [ + "dist/*" + ] + } + }, "scripts": { "build": "npm run build:node && npm run build:browser", "build:browser": "rollup -c config/rollup.browser-config.mjs", diff --git a/util.d.ts b/util.d.ts deleted file mode 100644 index c656480d..00000000 --- a/util.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -// Workaround for incomplete exports support in TypeScript -// https://github.com/microsoft/TypeScript/issues/33079 -export * from './dist/util.js' From 84073005b1bf674a976f3b04ea0f45d199b347c1 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Tue, 14 Feb 2023 12:54:38 +0200 Subject: [PATCH 20/20] fix: Avoid polynomial regexp in stringifyString As reported by CodeQL, though unrelated to the rest of the PR --- src/stringify/stringifyString.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/stringify/stringifyString.ts b/src/stringify/stringifyString.ts index 942b2d1a..d600ec66 100644 --- a/src/stringify/stringifyString.ts +++ b/src/stringify/stringifyString.ts @@ -165,6 +165,15 @@ function quotedString(value: string, ctx: StringifyContext) { return qs(value, ctx) } +// The negative lookbehind avoids a polynomial search, +// but isn't supported yet on Safari: https://caniuse.com/js-regexp-lookbehind +let blockEndNewlines: RegExp +try { + blockEndNewlines = new RegExp('(^|(?