From e3bb62f1a6daf544e8931caa32a119699599e0ec Mon Sep 17 00:00:00 2001 From: Ika Date: Fri, 21 Sep 2018 18:19:01 +0800 Subject: [PATCH] feat(yaml): initial commit --- types/yaml/index.d.ts | 626 ++++++++++++++++++++++++++++++++ types/yaml/map.d.ts | 3 + types/yaml/pair.d.ts | 3 + types/yaml/parse-cst.d.ts | 3 + types/yaml/scalar.d.ts | 3 + types/yaml/seq.d.ts | 3 + types/yaml/tsconfig.json | 30 ++ types/yaml/tslint.json | 3 + types/yaml/types/binary.d.ts | 3 + types/yaml/types/timestamp.d.ts | 3 + types/yaml/yaml-tests.ts | 80 ++++ 11 files changed, 760 insertions(+) create mode 100644 types/yaml/index.d.ts create mode 100644 types/yaml/map.d.ts create mode 100644 types/yaml/pair.d.ts create mode 100644 types/yaml/parse-cst.d.ts create mode 100644 types/yaml/scalar.d.ts create mode 100644 types/yaml/seq.d.ts create mode 100644 types/yaml/tsconfig.json create mode 100644 types/yaml/tslint.json create mode 100644 types/yaml/types/binary.d.ts create mode 100644 types/yaml/types/timestamp.d.ts create mode 100644 types/yaml/yaml-tests.ts diff --git a/types/yaml/index.d.ts b/types/yaml/index.d.ts new file mode 100644 index 0000000000..61de677b96 --- /dev/null +++ b/types/yaml/index.d.ts @@ -0,0 +1,626 @@ +// Type definitions for yaml 1.0 +// Project: https://github.com/eemeli/yaml +// Definitions by: Ika +// Colin Bradley +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.2 + +export const defaultOptions: ParseOptions; + +/** + * May throw on error, and it may log warnings using `console.warn`. + * It only supports input consisting of a single YAML document; + * for multi-document support you should use `YAML.parseAllDocuments` + * @param str Should be a string with YAML formatting. + * @returns The value will match the type of the root value of the parsed YAML document, + * so Maps become objects, Sequences arrays, and scalars result in nulls, booleans, numbers and strings. + */ +export function parse(str: string, options?: ParseOptions): any; + +/** + * @returns Will always include \n as the last character, as is expected of YAML documents. + */ +export function stringify(value: any, options?: ParseOptions): string; + +/** + * Parses a single YAML.Document from the input str; used internally by YAML.parse. + * Will include an error if str contains more than one document. + */ +export function parseDocument( + str: string, + options?: ParseOptions +): ast.Document; + +/** + * When parsing YAML, the input string str may consist of a stream of documents + * separated from each other by `...` document end marker lines. + * @returns An array of Document objects that allow these documents to be parsed and manipulated with more control. + */ +export function parseAllDocuments( + str: string, + options?: ParseOptions +): ast.Document[]; + +/** + * YAML.createNode recursively turns objects into Map and arrays to Seq collections. + * Its primary use is to enable attaching comments or other metadata to a value, + * or to otherwise exert more fine-grained control over the stringified output. + * + * Wraps plain values in Scalar objects. + */ +export function createNode( + value: any, + wrapScalars?: true +): ast.MapBase | ast.SeqBase | ast.Scalar; + +/** + * YAML.createNode recursively turns objects into Map and arrays to Seq collections. + * Its primary use is to enable attaching comments or other metadata to a value, + * or to otherwise exert more fine-grained control over the stringified output. + * + * Doesn't wrap plain values in Scalar objects. + */ +export function createNode( + value: any, + wrapScalars: false +): ast.MapBase | ast.SeqBase | string | number | boolean | null; + +export function parseCST(str: string): ParsedCST; + +export interface ParsedCST extends Array { + setOrigRanges(): boolean; +} + +export const Document: ast.DocumentConstructor; + +export interface ParseOptions { + /** + * Allow non-JSON JavaScript objects to remain in the `toJSON` output. + * Relevant with the YAML 1.1 `!!timestamp` and `!!binary` tags. By default `true`. + */ + keepBlobsInJSON?: boolean; + /** + * Include references in the AST to each node's corresponding CST node. By default `false`. + */ + keepCstNodes?: boolean; + /** + * Store the original node type when parsing documents. By default `true`. + */ + keepNodeTypes?: boolean; + /** + * Enable support for `<<` merge keys. + */ + merge?: boolean; + /** + * The base schema to use. By default `"core"` for YAML 1.2 and `"yaml-1.1"` for earlier versions. + */ + schema?: "core" | "failsafe" | "json" | "yaml-1.1"; + /** + * Array of additional (custom) tags to include in the schema. + */ + tags?: Tag[] | ((tags: Tag[]) => Tag[]); + /** + * The YAML version used by documents without a `%YAML` directive. By default `"1.2"`. + */ + version?: string; +} + +export interface Tag { + /** + * A JavaScript class that should be matched to this tag, e.g. `Date` for `!!timestamp`. + */ + class?: new () => any; + /** + * If `true`, the tag should not be explicitly included when stringifying. + */ + default?: boolean; + /** + * If a tag has multiple forms that should be parsed and/or stringified differently, use `format` to identify them. + */ + format?: string; + /** + * Used by some tags to configure their stringification, where applicable. + */ + options?: object; + /** + * Should return an instance of a class extending `Node`. + * If test is set, will be called with the resulting match as arguments. + * Otherwise, will be called as `resolve(doc, cstNode)`. + */ + resolve(doc: ast.Document, cstNode: cst.Node): ast.Node; + resolve(...match: string[]): ast.Node; + /** + * @param item the node being stringified. + * @param ctx contains the stringifying context variables. + * @param onComment a function that should be called if the stringifier includes the item's comment in its output. + */ + stringify( + item: ast.Node, + ctx: StringifyContext, + onComment: () => void + ): string; + /** + * The fully qualified name of the tag. + */ + tag: string; + /** + * Used to match string values of scalar nodes; captured parts will be passed as arguments of `resolve()`. + */ + test?: RegExp; +} + +export interface StringifyContext { + [key: string]: any; +} + +export type YAMLError = + | YAMLSyntaxError + | YAMLSemanticError + | YAMLReferenceError; + +export interface YAMLSyntaxError extends SyntaxError { + name: "YAMLSyntaxError"; + source: cst.Node; +} + +export interface YAMLSemanticError extends SyntaxError { + name: "YAMLSemanticError"; + source: cst.Node; +} + +export interface YAMLReferenceError extends ReferenceError { + name: "YAMLReferenceError"; + source: cst.Node; +} + +export interface YAMLWarning extends Error { + name: "YAMLReferenceError"; + source: cst.Node; +} + +export namespace cst { + interface Range { + start: number; + end: number; + origStart?: number; + origEnd?: number; + isEmpty(): boolean; + } + + interface ParseContext { + /** Node starts at beginning of line */ + atLineStart: boolean; + /** true if currently in a collection context */ + inCollection: boolean; + /** true if currently in a flow context */ + inFlow: boolean; + /** Current level of indentation */ + indent: number; + /** Start of the current line */ + lineStart: number; + /** The parent of the node */ + parent: Node; + /** Source of the YAML document */ + src: string; + } + + interface Node { + context: ParseContext | null; + /** if not null, indicates a parser failure */ + error: YAMLSyntaxError | null; + /** span of context.src parsed into this node */ + range: Range | null; + valueRange: Range | null; + /** anchors, tags and comments */ + props: Range[]; + /** specific node type */ + type: string; + /** if non-null, overrides source value */ + value: string | null; + + readonly anchor: string | null; + readonly comment: string | null; + readonly hasComment: boolean; + readonly hasProps: boolean; + readonly jsonLike: boolean; + readonly rawValue: string | null; + readonly tag: + | null + | { verbatim: string } + | { handle: string; suffix: string }; + readonly valueRangeContainsNewline: boolean; + } + + interface Alias extends Node { + type: "ALIAS"; + /** contain the anchor without the * prefix */ + readonly rawValue: string; + } + + type Scalar = BlockValue | PlainValue | QuoteValue; + + interface BlockValue extends Node { + type: "BLOCK_FOLDED" | "BLOCK_LITERAL"; + chomping: "CLIP" | "KEEP" | "STRIP"; + blockIndent: number | null; + header: Range; + readonly strValue: string | null; + } + + interface BlockFolded extends BlockValue { + type: "BLOCK_FOLDED"; + } + + interface BlockLiteral extends BlockValue { + type: "BLOCK_LITERAL"; + } + + interface PlainValue extends Node { + type: "PLAIN"; + readonly strValue: string | null; + } + + interface QuoteValue extends Node { + type: "QUOTE_DOUBLE" | "QUOTE_SINGLE"; + readonly strValue: + | null + | string + | { str: string; errors: YAMLSyntaxError[] }; + } + + interface QuoteDouble extends QuoteValue { + type: "QUOTE_DOUBLE"; + } + + interface QuoteSingle extends QuoteValue { + type: "QUOTE_SINGLE"; + } + + interface Comment extends Node { + type: "COMMENT"; + readonly anchor: null; + readonly comment: string; + readonly rawValue: null; + readonly tag: null; + } + + interface MapItem extends Node { + type: "MAP_KEY" | "MAP_VALUE"; + node: ContentNode | null; + } + + interface MapKey extends MapItem { + type: "MAP_KEY"; + } + + interface MapValue extends MapItem { + type: "MAP_VALUE"; + } + + interface Map extends Node { + type: "MAP"; + /** implicit keys are not wrapped */ + items: Array; + } + + interface SeqItem extends Node { + type: "SEQ_ITEM"; + node: ContentNode | null; + } + + interface Seq extends Node { + type: "SEQ"; + items: Array; + } + + interface FlowChar { + char: "{" | "}" | "[" | "]" | "," | "?" | ":"; + offset: number; + } + + interface FlowCollection extends Node { + type: "FLOW_MAP" | "FLOW_SEQ"; + items: Array; + } + + interface FlowMap extends FlowCollection { + type: "FLOW_MAP"; + } + + interface FlowSeq extends FlowCollection { + type: "FLOW_SEQ"; + } + + type ContentNode = Alias | Scalar | Map | Seq | FlowCollection; + + interface Directive extends Node { + type: "DIRECTIVE"; + name: string; + readonly anchor: null; + readonly parameters: string[]; + readonly tag: null; + } + + interface Document extends Node { + type: "DOCUMENT"; + directives: Array; + contents: Array; + readonly anchor: null; + readonly comment: null; + readonly tag: null; + } +} + +export namespace ast { + type AstNode = ScalarNode | MapNode | SeqNode | Alias; + + type DocumentConstructor = new (options?: ParseOptions) => Document; + interface Document { + type: "DOCUMENT"; + /** + * Anchors associated with the document's nodes; + * also provides alias & merge node creators. + */ + anchors: Anchors; + /** + * A comment at the very beginning of the document. + */ + commentBefore: null | string; + /** + * A comment at the end of the document. + */ + comment: null | string; + /** + * only available when `keepCstNodes` is set to `true` + */ + cstNode?: cst.Document; + /** + * The document contents. + */ + contents: AstNode | null; + /** + * Errors encountered during parsing. + */ + errors: YAMLError[]; + /** + * The schema used with the document. + */ + schema: Schema; + /** + * the [start, end] range of characters of the source parsed + * into this node (undefined if not parsed) + */ + range: null | [number, number]; + /** + * Array of prefixes; each will have a string `handle` that + * starts and ends with `!` and a string `prefix` that the handle will be replaced by. + */ + tagPrefixes: Prefix[]; + /** + * The parsed version of the source document; + * if true-ish, stringified output will include a `%YAML` directive. + */ + version?: string; + /** + * Warnings encountered during parsing. + */ + warnings: YAMLWarning[]; + /** + * List the tags used in the document that are not in the default `tag:yaml.org,2002:` namespace. + */ + listNonDefaultTags(): string[]; + /** + * Parse a CST into this document + */ + parse(cst: cst.Document): this; + /** + * Set `handle` as a shorthand string for the `prefix` tag namespace. + */ + setTagPrefix(handle: string, prefix: string): void; + /** + * A plain JavaScript representation of the document `contents`. + */ + toJSON(): any; + /** + * A YAML representation of the document. + */ + toString(): string; + } + + interface Anchors { + /** + * Create a new `Alias` node, adding the required anchor for `node`. + * If `name` is empty, a new anchor name will be generated. + */ + createAlias(node: Node, name?: string): Alias; + /** + * Create a new `Merge` node with the given source nodes. + * Non-`Alias` sources will be automatically wrapped. + */ + createMergePair(...nodes: Node[]): Merge; + /** + * The anchor name associated with `node`, if set. + */ + getName(node: Node): undefined | string; + /** + * The node associated with the anchor `name`, if set. + */ + getNode(name: string): undefined | Node; + /** + * Find an available anchor name with the given `prefix` and a numerical suffix. + */ + newName(prefix: string): string; + /** + * Associate an anchor with `node`. If `name` is empty, a new name will be generated. + * To remove an anchor, use `setAnchor(null, name)`. + */ + setAnchor(node: Node | null, name?: string): void | string; + } + + interface Schema { + merge: boolean; + name: string; + schema: Tag[]; + } + + interface Prefix { + handle: string; + prefix: string; + } + + interface Node { + /** + * a comment on or immediately after this + */ + comment: null | string; + /** + * a comment before this + */ + commentBefore: null | string; + /** + * only available when `keepCstNodes` is set to `true` + */ + cstNode?: cst.Node; + /** + * the [start, end] range of characters of the source parsed + * into this node (undefined for pairs or if not parsed) + */ + range: null | [number, number]; + /** + * a fully qualified tag, if required + */ + tag: null | string; + /** + * a plain JS representation of this node + */ + toJSON(): any; + } + + type ScalarConstructor = new ( + value: null | boolean | number | string + ) => Scalar; + interface Scalar extends Node { + type: + | "BLOCK_FOLDED" + | "BLOCK_LITERAL" + | "PLAIN" + | "QUOTE_DOUBLE" + | "QUOTE_SINGLE" + | undefined; + /** + * By default (undefined), numbers use decimal notation. + * The YAML 1.2 core schema only supports 'HEX' and 'OCT'. + */ + format: "BIN" | "HEX" | "OCT" | "TIME" | undefined; + value: null | boolean | number | string; + } + + type ScalarNode = + | BlockFolded + | BlockLiteral + | PlainValue + | QuoteDouble + | QuoteSingle; + + interface BlockFolded extends Scalar { + type: "BLOCK_FOLDED"; + cstNode?: cst.BlockFolded; + } + + interface BlockLiteral extends Scalar { + type: "BLOCK_LITERAL"; + cstNode?: cst.BlockLiteral; + } + + interface PlainValue extends Scalar { + type: "PLAIN"; + cstNode?: cst.PlainValue; + } + + interface QuoteDouble extends Scalar { + type: "QUOTE_DOUBLE"; + cstNode?: cst.QuoteDouble; + } + + interface QuoteSingle extends Scalar { + type: "QUOTE_SINGLE"; + cstNode?: cst.QuoteSingle; + } + + type PairConstructor = new ( + key: AstNode | null, + value?: AstNode | null + ) => Pair; + interface Pair extends Node { + type: "PAIR"; + /** + * key is always Node or null when parsed, but can be set to anything. + */ + key: AstNode | null; + /** + * value is always Node or null when parsed, but can be set to anything. + */ + value: AstNode | null; + cstNode?: never; // no corresponding cstNode + } + + type MapConstructor = new () => MapBase; + interface MapBase extends Node { + type: "FLOW_MAP" | "MAP" | undefined; + items: Array; + } + + type MapNode = FlowMap | Map; + + interface FlowMap extends MapBase { + type: "FLOW_MAP"; + cstNode?: cst.FlowMap; + } + + interface Map extends MapBase { + type: "MAP"; + cstNode?: cst.Map; + } + + type SeqConstructor = new () => SeqBase; + interface SeqBase extends Node { + type: "FLOW_SEQ" | "SEQ" | undefined; + /** + * item is always Node or null when parsed, but can be set to anything. + */ + items: Array; + } + + type SeqNode = FlowSeq | Seq; + + interface FlowSeq extends SeqBase { + type: "FLOW_SEQ"; + items: Array; + cstNode?: cst.FlowSeq; + } + + interface Seq extends SeqBase { + type: "SEQ"; + items: Array; + cstNode?: cst.Seq; + } + + interface Alias extends Node { + type: "ALIAS"; + source: AstNode; + cstNode?: cst.Alias; + } + + interface Merge extends Node { + type: "MERGE_PAIR"; + /** + * key is always Scalar('<<'), defined by the type specification + */ + key: PlainValue; + /** + * value is always Seq, stringified as *A if length = 1 + */ + value: SeqBase; + cstNode?: cst.PlainValue; + } +} diff --git a/types/yaml/map.d.ts b/types/yaml/map.d.ts new file mode 100644 index 0000000000..048d2094d1 --- /dev/null +++ b/types/yaml/map.d.ts @@ -0,0 +1,3 @@ +import * as YAML from "./index"; +declare const MapConstructor: YAML.ast.MapConstructor; +export default MapConstructor; diff --git a/types/yaml/pair.d.ts b/types/yaml/pair.d.ts new file mode 100644 index 0000000000..e580d9fd8a --- /dev/null +++ b/types/yaml/pair.d.ts @@ -0,0 +1,3 @@ +import * as YAML from "./index"; +declare const PairConstructor: YAML.ast.PairConstructor; +export default PairConstructor; diff --git a/types/yaml/parse-cst.d.ts b/types/yaml/parse-cst.d.ts new file mode 100644 index 0000000000..e27fb01b2f --- /dev/null +++ b/types/yaml/parse-cst.d.ts @@ -0,0 +1,3 @@ +import * as YAML from "./index"; +declare const parseCST: typeof YAML.parseCST; +export default parseCST; diff --git a/types/yaml/scalar.d.ts b/types/yaml/scalar.d.ts new file mode 100644 index 0000000000..ae26f17eb1 --- /dev/null +++ b/types/yaml/scalar.d.ts @@ -0,0 +1,3 @@ +import * as YAML from "./index"; +declare const ScalarConstructor: YAML.ast.ScalarConstructor; +export default ScalarConstructor; diff --git a/types/yaml/seq.d.ts b/types/yaml/seq.d.ts new file mode 100644 index 0000000000..ef38450380 --- /dev/null +++ b/types/yaml/seq.d.ts @@ -0,0 +1,3 @@ +import * as YAML from "./index"; +declare const SeqConstructor: YAML.ast.SeqConstructor; +export default SeqConstructor; diff --git a/types/yaml/tsconfig.json b/types/yaml/tsconfig.json new file mode 100644 index 0000000000..08e26a4bd0 --- /dev/null +++ b/types/yaml/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "map.d.ts", + "pair.d.ts", + "parse-cst.d.ts", + "scalar.d.ts", + "seq.d.ts", + "types/binary.d.ts", + "types/timestamp.d.ts", + "yaml-tests.ts" + ] +} diff --git a/types/yaml/tslint.json b/types/yaml/tslint.json new file mode 100644 index 0000000000..f93cf8562a --- /dev/null +++ b/types/yaml/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": "dtslint/dt.json" +} diff --git a/types/yaml/types/binary.d.ts b/types/yaml/types/binary.d.ts new file mode 100644 index 0000000000..5edd61c195 --- /dev/null +++ b/types/yaml/types/binary.d.ts @@ -0,0 +1,3 @@ +import * as YAML from "../index"; +declare const binary: YAML.Tag[]; +export default binary; diff --git a/types/yaml/types/timestamp.d.ts b/types/yaml/types/timestamp.d.ts new file mode 100644 index 0000000000..7302ea70f0 --- /dev/null +++ b/types/yaml/types/timestamp.d.ts @@ -0,0 +1,3 @@ +import * as YAML from "../index"; +declare const timestamp: YAML.Tag[]; +export default timestamp; diff --git a/types/yaml/yaml-tests.ts b/types/yaml/yaml-tests.ts new file mode 100644 index 0000000000..2571d85a0f --- /dev/null +++ b/types/yaml/yaml-tests.ts @@ -0,0 +1,80 @@ +/// + +import * as fs from "fs"; +import * as YAML from "yaml"; + +YAML.parse("3.14159"); +// 3.14159 + +YAML.parse("[ true, false, maybe, null ]\n"); +// [ true, false, 'maybe', null ] + +const file = fs.readFileSync("./file.yml", "utf8"); +YAML.parse(file); +// { YAML: +// [ 'A human-readable data serialization language', +// 'https://en.wikipedia.org/wiki/YAML' ], +// yaml: +// [ 'A complete JavaScript implementation', +// 'https://www.npmjs.com/package/yaml' ] } + +YAML.stringify(3.14159); +// '3.14159\n' + +YAML.stringify([true, false, "maybe", null]); +// `- true +// - false +// - maybe +// - null +// ` + +YAML.stringify({ number: 3, plain: "string", block: "two\nlines\n" }); +// `number: 3 +// plain: string +// block: > +// two +// +// lines +// ` + +const src = "[{ a: A }, { b: B }]"; +const doc = YAML.parseDocument(src); +const contents = doc.contents as YAML.ast.FlowSeq; +const { anchors } = doc; +const [a, b] = contents.items as YAML.ast.FlowMap[]; +anchors.setAnchor(a.items[0].value); // 'a1' +anchors.setAnchor(b.items[0].value); // 'a2' +anchors.setAnchor(null, "a1"); // 'a1' +anchors.getName(a); // undefined +anchors.getNode("a2"); +// { value: 'B', range: [ 16, 18 ], type: 'PLAIN' } +String(doc); +// [ { a: A }, { b: &a2 B } ] + +const alias = anchors.createAlias(a, "AA"); +contents.items.push(alias); +doc.toJSON(); +// [ { a: 'A' }, { b: 'B' }, { a: 'A' } ] +String(doc); +// [ &AA { a: A }, { b: &a2 B }, *AA ] + +const merge = anchors.createMergePair(alias); +b.items.push(merge); +doc.toJSON(); +// [ { a: 'A' }, { b: 'B', a: 'A' }, { a: 'A' } ] +String(doc); +// [ &AA { a: A }, { b: &a2 B, <<: *AA }, *AA ] + +// This creates a circular reference +merge.value.items.push(anchors.createAlias(b)); +doc.toJSON(); // [RangeError: Maximum call stack size exceeded] +String(doc); +// [ +// &AA { a: A }, +// &a3 { +// b: &a2 B, +// <<: +// [ *AA, *a3 ] +// }, +// *AA +// ]