From e2dc2a7fdfcf544e86c1e77df9bb03c405eeb0cb Mon Sep 17 00:00:00 2001 From: Vitaly Puzrin Date: Mon, 3 Aug 2026 04:26:48 +0300 Subject: [PATCH] doc: fill descriptions from readme and internal comments --- README.md | 4 +- src/ast/from_js.ts | 9 ++-- src/ast/nodes.ts | 33 +++++++++----- src/ast/presenter.ts | 82 ++++++++++++++++++++++++++++++++++- src/ast/visit.ts | 31 +++++++++---- src/common/exception.ts | 1 + src/dump.ts | 36 +++++++++++++++ src/load.ts | 47 +++++++++++++++++++- src/parser/constructor.ts | 27 ++++++++++++ src/parser/parser.ts | 11 +++++ src/schema.ts | 46 +++++++++++++++----- src/tag.ts | 47 ++++++++++++++------ src/tag/mapping/legacy_map.ts | 8 +++- src/tag/mapping/map.ts | 22 +++++++++- src/tag/mapping/real_map.ts | 28 ++++++++++-- typedoc.json | 14 +++++- 16 files changed, 386 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 198266ac..b1415f68 100644 --- a/README.md +++ b/README.md @@ -54,8 +54,8 @@ an exception on those. options: -- `filename` _(default: null)_ - string to be used as a file path in - error/warning messages. +- `filename` _(default: null)_ - string to be used as a file path in error + messages. - `schema` _(default: `CORE_SCHEMA`)_ - specifies a schema to use. - `FAILSAFE_SCHEMA` - only strings, arrays and plain objects. - `JSON_SCHEMA` - all JSON-supported types. diff --git a/src/ast/from_js.ts b/src/ast/from_js.ts index eb776ccc..fb44ce83 100644 --- a/src/ast/from_js.ts +++ b/src/ast/from_js.ts @@ -150,9 +150,12 @@ function build (state: FromJsState, object: unknown): Node | typeof INVALID { return node } -// A JS value is one YAML document. An unrepresentable root becomes an empty -// document, which the presenter renders as an empty string. -/** @category AST */ +/** + * A JS value is one YAML document. An unrepresentable root becomes an empty + * document, which the presenter renders as an empty string. + * + * @category AST + */ function jsToAst (input: unknown, schema: Schema, options: FromJsOptions = {}): Document[] { const state: FromJsState = { representTypes: buildRepresentTypes(schema), diff --git a/src/ast/nodes.ts b/src/ast/nodes.ts index f0d558cb..b6f9569f 100644 --- a/src/ast/nodes.ts +++ b/src/ast/nodes.ts @@ -16,13 +16,15 @@ class Style { /** @category Nodes */ interface NodeBase { - // YAML tag. Untagged nodes carry the semantic resolved tag; tagged nodes carry - // the printable/verbatim tag spelling. + /** + * YAML tag. Untagged nodes carry the semantic resolved tag; tagged nodes carry + * the printable/verbatim tag spelling. + */ tag: string style: Style anchor?: string - // Reserved for the formatting layer; not populated by the dumper yet. + /** Reserved for the formatting layer; not populated by the dumper yet. */ commentBefore?: string comment?: string commentAfter?: string @@ -50,21 +52,30 @@ interface MappingNode extends NodeBase { /** @category Nodes */ interface AliasNode extends NodeBase { kind: 'alias' - // The anchor name this alias points at (`*name`). + /** The anchor name this alias points at (`*name`). */ anchor: string } /** @category Nodes */ type Node = ScalarNode | SequenceNode | MappingNode | AliasNode -// The layer above `Node`: each document wraps one content node plus its own -// markers/directives. Not a member of `Node` — the fields differ. Document -// directives are ordered presentation data. -/** @category Nodes */ +/** + * The layer above {@link Node}: each document wraps one content node plus its + * own markers/directives. Not a member of {@link Node} — the fields differ. + * Document directives are ordered presentation data. + * + * @category Nodes + */ interface Document { - contents: Node | null // null = empty document - explicitStart?: boolean // print '---' - explicitEnd?: boolean // print '...' + /** null = empty document */ + contents: Node | null + + /** print '---' */ + explicitStart?: boolean + + /** print '...' */ + explicitEnd?: boolean + directives: DocumentDirective[] } diff --git a/src/ast/presenter.ts b/src/ast/presenter.ts index 4d6c8622..a8a0d566 100644 --- a/src/ast/presenter.ts +++ b/src/ast/presenter.ts @@ -60,17 +60,92 @@ ESCAPE_SEQUENCES[0x2029] = '\\P' /** @category AST */ interface PresenterOptions { schema: Schema + + /** + * Indentation width in spaces. + * + * @defaultValue `2` + */ indent?: number + + /** + * Does not add an indentation level to array elements when enabled. + * + * @defaultValue `false` + */ seqNoIndent?: boolean + + /** + * Allows a nested collection to start on the same line after `-`. + * + * @defaultValue `true` + */ seqInlineFirst?: boolean + + /** + * Sorts mapping keys when `true`. A function can be provided to define the + * sort order. + * + * @defaultValue `false` + */ sortKeys?: boolean | ((a: any, b: any) => number) + + /** + * Maximum line width. Set to `-1` for unlimited width. + * + * @defaultValue `80` + */ lineWidth?: number + + /** + * Adds spaces inside flow collection brackets: `{a: 1}` becomes `{ a: 1 }`. + * + * @defaultValue `false` + */ flowBracketPadding?: boolean + + /** + * Omits the space after commas in flow collections: `[1, 2]` becomes + * `[1,2]`. + * + * @defaultValue `false` + */ flowSkipCommaSpace?: boolean + + /** + * Omits the space after `:` in flow mappings: `{a: 1}` becomes `{a:1}`. + * + * @defaultValue `false` + */ flowSkipColonSpace?: boolean + + /** + * Quotes flow mapping keys: `{a: 1}` becomes `{"a": 1}`. + * + * @defaultValue `false` + */ quoteFlowKeys?: boolean + + /** + * Quoting style to use when a string needs quotes. + * + * @defaultValue `'single'` + */ quoteStyle?: 'single' | 'double' + + /** + * Quotes all non-key strings using {@link quoteStyle}. + * + * @defaultValue `false` + */ forceQuotes?: boolean + + /** + * Prints an explicit tag before an anchor: `&ref_0 !!set` becomes + * `!!set &ref_0`. + * + * @defaultValue `false` + */ tagBeforeAnchor?: boolean } @@ -976,8 +1051,11 @@ function writeDocumentDirectives (doc: Document) { return result } -// Documents → text, including the trailing newline. -/** @category AST */ +/** + * Documents → text, including the trailing newline. + * + * @category AST + */ function present (documents: Document[], options: PresenterOptions): string { const state = createPresenterState(options) let result = '' diff --git a/src/ast/visit.ts b/src/ast/visit.ts index af3030bc..24aaa7a6 100644 --- a/src/ast/visit.ts +++ b/src/ast/visit.ts @@ -16,14 +16,23 @@ const VISIT_SKIP = Symbol('visit:skip') // don't descend into this node's chil type VisitControl = typeof VISIT_BREAK | typeof VISIT_SKIP | undefined | void -// Traversal-derived position of the current node. Kept off the node itself: a -// node may sit in several places (alias/dedup reuse), so depth/role belong to -// the walk, not the node. `parent.kind` + `isKey` pin the exact slot. -/** @category AST */ +/** + * Traversal-derived position of the current node. Kept off the node itself: a + * node may sit in several places (alias/dedup reuse), so depth/role belong to + * the walk, not the node. {@link VisitContext.parent} `kind` + + * {@link VisitContext.isKey} pin the exact slot. + * + * @category AST + */ interface VisitContext { - depth: number // 0 = document content root - parent: Node | null // enclosing sequence/mapping, null at the root - isKey: boolean // node sits in a mapping key position + /** 0 = document content root */ + depth: number + + /** Enclosing sequence/mapping, null at the root */ + parent: Node | null + + /** Node sits in a mapping key position */ + isKey: boolean } /** @category AST */ @@ -54,8 +63,12 @@ function visitNode (node: Node, visitor: Visitor, ctx: VisitContext): boolean { return false } -// Walk every node in the documents, calling `visitor` once per node (pre-order). -/** @category AST */ +/** + * Walk every node in the documents, calling {@link Visitor} once per + * node (pre-order). + * + * @category AST + */ function visit (documents: Document[], visitor: Visitor): void { for (const doc of documents) { if (doc.contents && visitNode(doc.contents, visitor, { depth: 0, parent: null, isKey: false })) return diff --git a/src/common/exception.ts b/src/common/exception.ts index e875c95f..9cfb409c 100644 --- a/src/common/exception.ts +++ b/src/common/exception.ts @@ -20,6 +20,7 @@ function formatError (exception: YAMLException, compact?: boolean) { return `${exception.reason} ${where}` } +/** @category Main */ class YAMLException extends Error { reason: string mark?: SnippetMark diff --git a/src/dump.ts b/src/dump.ts index 93afc0e9..0932bf78 100644 --- a/src/dump.ts +++ b/src/dump.ts @@ -14,11 +14,39 @@ import { intYaml11Tag } from './tag/scalar/int_yaml11.ts' import { floatCoreTag } from './tag/scalar/float_core.ts' import { floatYaml11Tag } from './tag/scalar/float_yaml11.ts' +/** @category Main */ interface DumpOptions extends Omit { + /** + * Schema to use. + * + * @defaultValue A {@link YAML11_SCHEMA}-based schema. + */ schema?: Schema + + /** + * Skips invalid types instead of throwing. Invalid mapping pairs and sequence + * items are skipped; `undefined` sequence items are serialized as `null`. + * + * @defaultValue `false` + */ skipInvalid?: boolean + + /** + * Inlines duplicate objects instead of converting them into references. + * + * @defaultValue `false` + */ noRefs?: boolean + + /** + * Nesting level at which collections switch from block to flow style. Set to + * `-1` to never switch automatically. + * + * @defaultValue `-1` + */ flowLevel?: number + + /** Mutates the generated AST before it is rendered. */ transform?: (documents: Document[]) => void } @@ -52,6 +80,14 @@ const DEFAULT_DUMP_OPTIONS: Required = { // Options that need the JS value (tags, format, dedup) go to `jsToAst`; purely // presentational ones go to `present`. +/** + * Serializes `object` as a YAML document. By default it can dump every + * supported YAML type, so it throws an exception if you try to dump regexps or + * functions. However, you can disable exceptions by setting the + * {@link DumpOptions.skipInvalid} option to `true`. + * + * @category Main + */ function dump (input: any, options: DumpOptions = {}) { const opts = { ...DEFAULT_DUMP_OPTIONS, ...options } diff --git a/src/load.ts b/src/load.ts index 544ec672..642d73a5 100644 --- a/src/load.ts +++ b/src/load.ts @@ -12,6 +12,7 @@ import { } from './parser/parser.ts' // `source` is supplied by `loadDocuments` itself, not by the public caller. +/** @category Main */ interface LoadOptions extends ParserOptions, Omit {} type LoadAllIterator = (document: unknown) => void @@ -34,9 +35,22 @@ function loadDocuments (input: string, options: LoadOptions = {}) { return constructFromEvents(events, { ...pick(opts, CONSTRUCTOR_OPT_KEYS), source }) } -// Signatures with iterator are deprecated. Will be removed in the next versions. +/** + * Same as {@link load}, but understands multi-document sources. + * Returns an array of documents. + * + * @category Main + */ function loadAll (input: string, options?: LoadOptions): unknown[] + +/** + * @deprecated Iterator is not supported. + */ function loadAll (input: string, iterator: null, options?: LoadOptions): unknown[] + +/** + * @deprecated Iterator is not supported. + */ function loadAll (input: string, iterator: LoadAllIterator, options?: LoadOptions): void function loadAll ( input: string, @@ -57,6 +71,37 @@ function loadAll ( for (const document of documents) iterator(document) } +/** + * Parses `string` as a single YAML document. Throws {@link YAMLException} on + * error. This function does not understand multi-document or empty sources; it + * throws an exception on those. + * + * > [!WARNING] + * > When processing untrusted input, see the + * > [security considerations](../docs/safety.md). + * + * > [!NOTE] + * > The default {@link CORE_SCHEMA} comes without the `!!merge` tag. You can + * > easily enable it if needed. + * + * > [!WARNING] + * > The default {@link mapTag} is `{}`-object based and does not allow complex + * > keys (objects, arrays and so on). That's an intentional choice for + * > convenience. Also, non-string scalar keys, such as `null`, numbers or + * > booleans, are converted to strings. For non-string keys use + * > {@link realMapTag} instead (it uses native JS `Map`). + * + * @example + * Enable {@link mergeTag} and {@link realMapTag}: + * + * ```javascript + * import { load, CORE_SCHEMA, mergeTag, realMapTag } from 'js-yaml' + * + * load(data, { schema: CORE_SCHEMA.withTags(mergeTag, realMapTag) }) + * ``` + * + * @category Main + */ function load (input: string, options?: LoadOptions) { const documents = loadDocuments(input, options) diff --git a/src/parser/constructor.ts b/src/parser/constructor.ts index f4e3cc60..2d8f4369 100644 --- a/src/parser/constructor.ts +++ b/src/parser/constructor.ts @@ -79,9 +79,36 @@ interface Anchor { interface ConstructorOptions { source: string filename?: string + + /** + * Schema to use. + * + * @defaultValue {@link CORE_SCHEMA} + */ schema?: Schema + + /** + * Enables compatibility with `JSON.parse` behavior. Duplicate keys in a + * mapping override values instead of throwing an error. + * + * @defaultValue `false` + */ json?: boolean + + /** + * Maximum total number of keys processed by merge (`<<`) across one load + * call. Set to `-1` to disable the limit. + * + * @defaultValue `10000` + */ maxTotalMergeKeys?: number + + /** + * Maximum number of alias nodes (`*ref`) per document. Set to `0` to reject + * all aliases, or to `-1` for no limit. + * + * @defaultValue `-1` + */ maxAliases?: number } diff --git a/src/parser/parser.ts b/src/parser/parser.ts index d56e557f..5de7c11b 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -73,7 +73,18 @@ interface ParserSnapshot { /** @category Events */ interface ParserOptions { + /** + * File path used in error messages. + * + * @defaultValue `null` + */ filename?: string + + /** + * Maximum nesting depth for collections. Aliases are not taken into account. + * + * @defaultValue `100` + */ maxDepth?: number } diff --git a/src/schema.ts b/src/schema.ts index a3170031..969ecec9 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -81,18 +81,30 @@ function compileTags (tags: readonly TagDefinition[]) { class Schema { readonly tags: readonly TagDefinition[] readonly implicitScalarTags: readonly ScalarTagDefinition[] - // Dispatch implicit scalar resolvers by `source.charAt(0)`. Each bucket holds the - // resolvers that may match that key, in schema order; a key absent from the map - // uses `implicitScalarAnyFirstChar` (resolvers that declared no first-char - // constraint, so they apply to any first character). + + /** + * Dispatch implicit scalar resolvers by `source.charAt(0)`. Each bucket holds + * the resolvers that may match that key, in schema order; a key absent from + * the map uses + * {@link Schema.implicitScalarAnyFirstChar} + * (resolvers that declared no first-char constraint, so they apply to any + * first character). + */ readonly implicitScalarByFirstChar: ReadonlyMap readonly implicitScalarAnyFirstChar: readonly ScalarTagDefinition[] - // The default scalar tag (`!!str`), resolved once so the composer's fallback for - // unresolved plain scalars avoids a keyed lookup per scalar. + + /** + * The default scalar tag (`!!str`), resolved once so the composer's fallback + * for unresolved plain scalars avoids a keyed lookup per scalar. + */ readonly defaultScalarTag: ScalarTagDefinition - // The default container tags (`!!seq` / `!!map`), used by the dumper: when a - // value is identified by its default tag, the tag is implicit and not printed. - // Undefined if the schema does not define them (then such values can't be dumped). + + /** + * The default container tags (`!!seq` / `!!map`), used by the dumper: when a + * value is identified by its default tag, the tag is implicit and not + * printed. Undefined if the schema does not define them (then such values + * can't be dumped). + */ readonly defaultSequenceTag: SequenceTagDefinition | undefined readonly defaultMappingTag: MappingTagDefinition | undefined readonly exact: TagDefinitionMap @@ -182,7 +194,21 @@ const JSON_SCHEMA = new Schema([ floatJsonTag ]) -/** @category Schema */ +/** + * The default schema for the loaders. Note, {@link CORE_SCHEMA} comes + * without the `!!merge` tag. You can easily enable it if needed. + * + * @example + * Enable {@link mergeTag}: + * + * ```javascript + * import { load, CORE_SCHEMA, mergeTag } from 'js-yaml' + * + * load(data, { schema: CORE_SCHEMA.withTags(mergeTag) }) + * ``` + * + * @category Schema + */ const CORE_SCHEMA = new Schema([ ...FAILSAFE_SCHEMA.tags, nullCoreTag, diff --git a/src/tag.ts b/src/tag.ts index 90d0484d..ca95e20b 100644 --- a/src/tag.ts +++ b/src/tag.ts @@ -16,17 +16,28 @@ interface ScalarTagDefinition { nodeKind: 'scalar' implicit: boolean matchByTagPrefix: boolean - // Set of `source.charAt(0)` keys for which `resolve` may succeed (a superset of - // what it really matches). A key is either a single character or '' (empty - // source). `null` means "no constraint, always try". Used by the composer to - // dispatch implicit scalars by first character without running every resolver. + + /** + * Set of `source.charAt(0)` keys for which + * {@link ScalarTagDefinition.resolve} may succeed (a superset of + * what it really matches). A key is either a single character or '' (empty + * source). `null` means "no constraint, always try". Used by the composer to + * dispatch implicit scalars by first character without running every resolver. + */ implicitFirstChars: readonly string[] | null - // `isExplicit` is true for an explicit tag (`!!tag`), false for implicit plain - // scalar resolution. + + /** + * `isExplicit` is true for an explicit tag (`!!tag`), false for implicit plain + * scalar resolution. + */ resolve: (source: string, isExplicit: boolean, tagName: string) => Result | typeof NOT_RESOLVED identify: IdentifyFn | null - // A scalar's printed form is text, so `represent` always yields a string. The - // factory supplies a `String(data)` default when a tag omits it. + + /** + * A scalar's printed form is text, so + * {@link ScalarTagDefinition.represent} always yields a string. + * The factory supplies a `String(data)` default when a tag omits it. + */ represent: ScalarRepresent representTagName: RepresentTagNameFn | null } @@ -53,13 +64,21 @@ interface MappingTagDefinition { implicit: false matchByTagPrefix: boolean create: (tagName: string) => Carrier - // Writes a pair. Returns '' on success, a non-empty error message otherwise - // (key does not fit the representation, value rejected, ...). Always a string - // so the hot path never allocates an exception wrapper. + + /** + * Writes a pair. Returns '' on success, a non-empty error message otherwise + * (key does not fit the representation, value rejected, ...). Always a string + * so the hot path never allocates an exception wrapper. + */ addPair: (carrier: Carrier, key: unknown, value: unknown) => string - // Read side, mirrors `Map` — defining a representation means defining how to - // read it back. `has` is the hot dedup probe (membership without fetching the - // value); `keys`/`get` are used only on the cold merge path (`<<`). + + /** + * Read side, mirrors `Map` — defining a representation means defining how to + * read it back. {@link MappingTagDefinition.has} is the hot dedup probe + * (membership without fetching the value); + * {@link MappingTagDefinition.keys}/{@link MappingTagDefinition.get} + * are used only on the cold merge path (`<<`). + */ has: (carrier: Carrier, key: unknown) => boolean keys: (result: Result) => Iterable get: (result: Result, key: unknown) => unknown diff --git a/src/tag/mapping/legacy_map.ts b/src/tag/mapping/legacy_map.ts index ecd9dc84..251fb629 100644 --- a/src/tag/mapping/legacy_map.ts +++ b/src/tag/mapping/legacy_map.ts @@ -31,7 +31,13 @@ function normalizeKey (key: unknown): string | null { return String(key) } -/** @category Tags */ +/** + * This implementation exists solely to reproduce v4 behavior exactly. Its use + * is strongly discouraged. If complex or non-string keys are needed, use + * {@link realMapTag} instead. + * + * @category Tags + */ const legacyMapTag = defineMappingTag('tag:yaml.org,2002:map', { create: (): StringMapping => ({}), identify: isPlainObject, diff --git a/src/tag/mapping/map.ts b/src/tag/mapping/map.ts index 0168e1ac..50cfbec3 100644 --- a/src/tag/mapping/map.ts +++ b/src/tag/mapping/map.ts @@ -3,7 +3,27 @@ import { isPlainObject } from '../../common/object.ts' type StringMapping = Record -/** @category Tags */ +/** + * This is the default mapping implementation. It uses `{}` objects and has only + * partial functionality due to language limitations. This choice was made + * because users expect to get JavaScript objects, and it was left unchanged to + * avoid too many breaking changes in the v5 release. + * + * Side effects: + * + * - `Object.hasOwn()` checks or `for...of` loops are required for safe use (to + * avoid falling through to prototypes). + * - Only scalar string keys are supported properly. + * - Other scalar keys, such as `null` and numbers, are converted to strings. + * This is historical behaviour, and it can cause side effects such as + * problems with `!!merge`. + * + * Note that non-string scalar keys may be deprecated in future versions. + * + * Ideally, use {@link realMapTag} instead. + * + * @category Tags + */ const mapTag = defineMappingTag('tag:yaml.org,2002:map', { create: (): StringMapping => ({}), identify: isPlainObject, diff --git a/src/tag/mapping/real_map.ts b/src/tag/mapping/real_map.ts index 7ae67abb..32048ba9 100644 --- a/src/tag/mapping/real_map.ts +++ b/src/tag/mapping/real_map.ts @@ -3,10 +3,30 @@ import { isPlainObject } from '../../common/object.ts' type RealMapping = Map -// A mapping represented as a real `Map`: keys keep their constructed type, -// nothing is stringified. Drop-in replacement for the default `!!map` tag -// (same tag name) — `CORE_SCHEMA.withTags(realMapTag)`. -/** @category Tags */ +/** + * Recommended when non-string keys are actually needed. It uses native + * JavaScript `Map` objects, so keys keep their constructed types instead of + * being converted to strings. + * + * It is not the default to avoid widespread breaking changes in existing + * projects. `Map` has a different access API and does not pass deep equality + * checks against `{}`-based fixtures. Alongside the other changes in v5, + * making it the default was considered too disruptive. + * + * If these differences are acceptable for your project, we recommend using + * {@link realMapTag} to guarantee the absence of problems and side effects. + * + * @example + * Enable {@link realMapTag}: + * + * ```javascript + * import { load, CORE_SCHEMA, realMapTag } from 'js-yaml' + * + * load(data, { schema: CORE_SCHEMA.withTags(realMapTag) }) + * ``` + * + * @category Tags + */ const realMapTag = defineMappingTag('tag:yaml.org,2002:map', { create: () => new Map(), addPair: (container: RealMapping, key, value) => { diff --git a/typedoc.json b/typedoc.json index 67fb508e..b6e7964e 100644 --- a/typedoc.json +++ b/typedoc.json @@ -9,8 +9,18 @@ "navigationLinks": { "GitHub": "https://github.com/nodeca/js-yaml" }, - "defaultCategory": "js-yaml", - "categoryOrder": ["js-yaml", "*"], + "defaultCategory": "missed (default)", + "categoryOrder": [ + "Main", + "Schema", + "Tags", + "Events", + "Nodes", + "AST", + "*", + "other", + "missed (default)" + ], "sort": ["source-order"], "navigation": { "includeCategories": true