From a7bdc007d4e31fa716e62be6d5daf33ce5bb4967 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sat, 3 Oct 2020 08:16:04 +0300 Subject: [PATCH 1/7] Refactor resolveString() arguments --- src/resolve/resolveNode.js | 2 +- src/resolve/resolveString.js | 11 ++++------- src/resolve/resolveTag.js | 2 +- src/tags/failsafe/string.js | 3 ++- src/tags/json.js | 3 ++- src/tags/yaml-1.1/binary.js | 2 +- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/resolve/resolveNode.js b/src/resolve/resolveNode.js index 3b76f372..7249e460 100644 --- a/src/resolve/resolveNode.js +++ b/src/resolve/resolveNode.js @@ -93,7 +93,7 @@ function resolveNodeValue(doc, node) { } try { - const str = resolveString(doc, node) + const str = resolveString(node.strValue, error => doc.errors.push(error)) return resolveScalar(str, schema.tags, schema.tags.scalarFallback) } catch (error) { if (!error.source) error.source = node diff --git a/src/resolve/resolveString.js b/src/resolve/resolveString.js index 900c40e2..ce8bdbcc 100644 --- a/src/resolve/resolveString.js +++ b/src/resolve/resolveString.js @@ -1,11 +1,8 @@ -// on error, will return { str: string, errors: Error[] } -export function resolveString(doc, node) { - const res = node.strValue +// on error, strValue may be { str: string, errors: Error[] } +export function resolveString(strValue, onError) { + const res = strValue if (!res) return '' if (typeof res === 'string') return res - res.errors.forEach(error => { - if (!error.source) error.source = node - doc.errors.push(error) - }) + res.errors.forEach(onError) return res.str } diff --git a/src/resolve/resolveTag.js b/src/resolve/resolveTag.js index 5930d1b1..0b1c4878 100644 --- a/src/resolve/resolveTag.js +++ b/src/resolve/resolveTag.js @@ -18,7 +18,7 @@ function resolveByTagName(doc, node, tagName) { } } - const str = resolveString(doc, node) + const str = resolveString(node.strValue, error => doc.errors.push(error)) if (typeof str === 'string' && matchWithTest.length > 0) return resolveScalar(str, matchWithTest, tags.scalarFallback) diff --git a/src/tags/failsafe/string.js b/src/tags/failsafe/string.js index a6281c1f..add4d97d 100644 --- a/src/tags/failsafe/string.js +++ b/src/tags/failsafe/string.js @@ -6,7 +6,8 @@ export const string = { identify: value => typeof value === 'string', default: true, tag: 'tag:yaml.org,2002:str', - resolve: resolveString, + resolve: (doc, node) => + resolveString(node.strValue, error => doc.errors.push(error)), stringify(item, ctx, onComment, onChompKeep) { ctx = Object.assign({ actualString: true }, ctx) return stringifyString(item, ctx, onComment, onChompKeep) diff --git a/src/tags/json.js b/src/tags/json.js index f4c10990..3b6d3262 100644 --- a/src/tags/json.js +++ b/src/tags/json.js @@ -18,7 +18,8 @@ export const json = [ identify: value => typeof value === 'string', default: true, tag: 'tag:yaml.org,2002:str', - resolve: resolveString, + resolve: (doc, node) => + resolveString(node.strValue, error => doc.errors.push(error)), stringify: stringifyJSON }, { diff --git a/src/tags/yaml-1.1/binary.js b/src/tags/yaml-1.1/binary.js index c0b5e197..8aae5a47 100644 --- a/src/tags/yaml-1.1/binary.js +++ b/src/tags/yaml-1.1/binary.js @@ -19,7 +19,7 @@ export const binary = { * document.querySelector('#photo').src = URL.createObjectURL(blob) */ resolve: (doc, node) => { - const src = resolveString(doc, node) + const src = resolveString(node.strValue, error => doc.errors.push(error)) if (typeof Buffer === 'function') { return Buffer.from(src, 'base64') } else if (typeof atob === 'function') { From 04b2d659c330e07c6da580c53540f0eb3797a51e Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sat, 3 Oct 2020 09:09:33 +0300 Subject: [PATCH 2/7] Support asBigInt option for sexagesimal integer values --- src/tags/yaml-1.1/timestamp.js | 42 ++++++++++++++++++++-------------- tests/doc/types.js | 37 ++++++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 19 deletions(-) diff --git a/src/tags/yaml-1.1/timestamp.js b/src/tags/yaml-1.1/timestamp.js index 59628b2d..1b199be4 100644 --- a/src/tags/yaml-1.1/timestamp.js +++ b/src/tags/yaml-1.1/timestamp.js @@ -1,26 +1,36 @@ +import { intOptions } from '../options.js' import { stringifyNumber } from '../../stringify/stringifyNumber.js' -const parseSexagesimal = (sign, parts) => { - const n = parts.split(':').reduce((n, p) => n * 60 + Number(p), 0) - return sign === '-' ? -n : n +const parseSexagesimal = (str, isInt) => { + const sign = str[0] + const parts = sign === '-' || sign === '+' ? str.substring(1) : str + const num = n => (isInt && intOptions.asBigInt ? BigInt(n) : Number(n)) + const res = parts + .replace(/_/g, '') + .split(':') + .reduce((res, p) => res * num(60) + num(p), num(0)) + return sign === '-' ? num(-1) * res : res } // hhhh:mm:ss.sss const stringifySexagesimal = ({ value }) => { - if (isNaN(value) || !isFinite(value)) return stringifyNumber(value) + let num = n => n + if (typeof value === 'bigint') num = n => BigInt(n) + else if (isNaN(value) || !isFinite(value)) return stringifyNumber(value) let sign = '' if (value < 0) { sign = '-' - value = Math.abs(value) + value *= num(-1) } - const parts = [value % 60] // seconds, including ms + const _60 = num(60) + const parts = [value % _60] // seconds, including ms if (value < 60) { parts.unshift(0) // at least one : is required } else { - value = Math.round((value - parts[0]) / 60) - parts.unshift(value % 60) // minutes + value = (value - parts[0]) / _60 + parts.unshift(value % _60) // minutes if (value >= 60) { - value = Math.round((value - parts[0]) / 60) + value = (value - parts[0]) / _60 parts.unshift(value) // hours } } @@ -34,13 +44,12 @@ const stringifySexagesimal = ({ value }) => { } export const intTime = { - identify: value => typeof value === 'number', + identify: value => typeof value === 'bigint' || Number.isInteger(value), default: true, tag: 'tag:yaml.org,2002:int', format: 'TIME', - test: /^([-+]?)([0-9][0-9_]*(?::[0-5]?[0-9])+)$/, - resolve: (str, sign, parts) => - parseSexagesimal(sign, parts.replace(/_/g, '')), + test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+$/, + resolve: str => parseSexagesimal(str, true), stringify: stringifySexagesimal } @@ -49,9 +58,8 @@ export const floatTime = { default: true, tag: 'tag:yaml.org,2002:float', format: 'TIME', - test: /^([-+]?)([0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*)$/, - resolve: (str, sign, parts) => - parseSexagesimal(sign, parts.replace(/_/g, '')), + test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*$/, + resolve: str => parseSexagesimal(str, false), stringify: stringifySexagesimal } @@ -83,7 +91,7 @@ export const timestamp = { millisec || 0 ) if (tz && tz !== 'Z') { - let d = parseSexagesimal(tz[0], tz.slice(1)) + let d = parseSexagesimal(tz, false) if (Math.abs(d) < 30) d *= 60 date -= 60000 * d } diff --git a/tests/doc/types.js b/tests/doc/types.js index fac17637..4927323b 100644 --- a/tests/doc/types.js +++ b/tests/doc/types.js @@ -104,8 +104,7 @@ describe('json schema', () => { "canonical": null "english": null ? null -: "null key"\n` - ) +: "null key"\n`) }) }) @@ -356,6 +355,40 @@ binary: 0b10100111010010101110 sexagesimal: 190:20:30\n`) }) + test('!!int, asBigInt', () => { + const src = `%YAML 1.1 +--- +canonical: 685230 +decimal: +685_230 +octal: 02472256 +hexadecimal: 0x_0A_74_AE +binary: 0b1010_0111_0100_1010_1110 +sexagesimal: 190:20:30` + + try { + YAML.scalarOptions.int.asBigInt = true + const doc = YAML.parseDocument(src) + expect(doc.toJS()).toMatchObject({ + canonical: 685230n, + decimal: 685230n, + octal: 685230n, + hexadecimal: 685230n, + binary: 685230n, + sexagesimal: 685230n + }) + expect(String(doc)).toBe(`%YAML 1.1 +--- +canonical: 685230 +decimal: 685230 +octal: 02472256 +hexadecimal: 0xa74ae +binary: 0b10100111010010101110 +sexagesimal: 190:20:30\n`) + } finally { + YAML.scalarOptions.int.asBigInt = false + } + }) + test('!!null', () => { const src = `%YAML 1.1 --- From a713b36e16d7f0f3beb2b46801d55d174c6e8cdd Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sat, 3 Oct 2020 09:13:13 +0300 Subject: [PATCH 3/7] Use re.test(str) rather than str.match(re) for default tags --- src/resolve/resolveScalar.js | 13 +++++------ src/tags/core.js | 30 ++++++++++++------------- src/tags/yaml-1.1/index.js | 41 ++++++++++++++++++---------------- src/tags/yaml-1.1/timestamp.js | 14 +++++++----- 4 files changed, 50 insertions(+), 48 deletions(-) diff --git a/src/resolve/resolveScalar.js b/src/resolve/resolveScalar.js index 5512f75f..cfebbce1 100644 --- a/src/resolve/resolveScalar.js +++ b/src/resolve/resolveScalar.js @@ -3,14 +3,11 @@ import { Scalar } from '../ast/Scalar.js' // falls back to string on no match export function resolveScalar(str, tags, scalarFallback) { for (const { format, test, resolve } of tags) { - if (test) { - const match = str.match(test) - if (match) { - let res = resolve.apply(null, match) - if (!(res instanceof Scalar)) res = new Scalar(res) - if (format) res.format = format - return res - } + if (test && test.test(str)) { + let res = resolve(str) + if (!(res instanceof Scalar)) res = new Scalar(res) + if (format) res.format = format + return res } } if (scalarFallback) str = scalarFallback(str) diff --git a/src/tags/core.js b/src/tags/core.js index c5a500ab..82536e86 100644 --- a/src/tags/core.js +++ b/src/tags/core.js @@ -8,8 +8,8 @@ import { boolOptions, intOptions, nullOptions } from './options.js' const intIdentify = value => typeof value === 'bigint' || Number.isInteger(value) -const intResolve = (src, part, radix) => - intOptions.asBigInt ? BigInt(src) : parseInt(part, radix) +const intResolve = (src, offset, radix) => + intOptions.asBigInt ? BigInt(src) : parseInt(src.substring(offset), radix) function intStringify(node, radix, prefix) { const { value } = node @@ -48,8 +48,8 @@ export const octObj = { default: true, tag: 'tag:yaml.org,2002:int', format: 'OCT', - test: /^0o([0-7]+)$/, - resolve: (str, oct) => intResolve(str, oct, 8), + test: /^0o[0-7]+$/, + resolve: str => intResolve(str, 2, 8), options: intOptions, stringify: node => intStringify(node, 8, '0o') } @@ -59,7 +59,7 @@ export const intObj = { default: true, tag: 'tag:yaml.org,2002:int', test: /^[-+]?[0-9]+$/, - resolve: str => intResolve(str, str, 10), + resolve: str => intResolve(str, 0, 10), options: intOptions, stringify: stringifyNumber } @@ -69,8 +69,8 @@ export const hexObj = { default: true, tag: 'tag:yaml.org,2002:int', format: 'HEX', - test: /^0x([0-9a-fA-F]+)$/, - resolve: (str, hex) => intResolve(str, hex, 16), + test: /^0x[0-9a-fA-F]+$/, + resolve: str => intResolve(str, 2, 16), options: intOptions, stringify: node => intStringify(node, 16, '0x') } @@ -79,9 +79,9 @@ export const nanObj = { identify: value => typeof value === 'number', default: true, tag: 'tag:yaml.org,2002:float', - test: /^(?:[-+]?\.inf|(\.nan))$/i, - resolve: (str, nan) => - nan + test: /^(?:[-+]?\.(?:inf|Inf|INF|nan|NaN|NAN))$/, + resolve: str => + str.slice(-3).toLowerCase() === 'nan' ? NaN : str[0] === '-' ? Number.NEGATIVE_INFINITY @@ -103,12 +103,12 @@ export const floatObj = { identify: value => typeof value === 'number', default: true, tag: 'tag:yaml.org,2002:float', - test: /^[-+]?(?:\.([0-9]+)|[0-9]+\.([0-9]*))$/, - resolve(str, frac1, frac2) { - const frac = frac1 || frac2 + test: /^[-+]?(?:\.[0-9]+|[0-9]+\.[0-9]*)$/, + resolve(str) { const node = new Scalar(parseFloat(str)) - if (frac && frac[frac.length - 1] === '0') - node.minFractionDigits = frac.length + const dot = str.indexOf('.') + if (dot !== -1 && str[str.length - 1] === '0') + node.minFractionDigits = str.length - dot - 1 return node }, stringify: stringifyNumber diff --git a/src/tags/yaml-1.1/index.js b/src/tags/yaml-1.1/index.js index 3f8c6528..3ea5b4a5 100644 --- a/src/tags/yaml-1.1/index.js +++ b/src/tags/yaml-1.1/index.js @@ -16,8 +16,10 @@ const boolStringify = ({ value }) => const intIdentify = value => typeof value === 'bigint' || Number.isInteger(value) -function intResolve(sign, src, radix) { - let str = src.replace(/_/g, '') +function intResolve(str, offset, radix) { + let sign = str[0] + if (sign === '-' || sign === '+') offset += 1 + str = str.substring(offset).replace(/_/g, '') if (intOptions.asBigInt) { switch (radix) { case 2: @@ -76,7 +78,7 @@ export const yaml11 = failsafe.concat( identify: value => typeof value === 'boolean', default: true, tag: 'tag:yaml.org,2002:bool', - test: /^(?:N|n|[Nn]o|NO|[Ff]alse|FALSE|[Oo]ff|OFF)$/i, + test: /^(?:N|n|[Nn]o|NO|[Ff]alse|FALSE|[Oo]ff|OFF)$/, resolve: () => false, options: boolOptions, stringify: boolStringify @@ -86,8 +88,8 @@ export const yaml11 = failsafe.concat( default: true, tag: 'tag:yaml.org,2002:int', format: 'BIN', - test: /^([-+]?)0b([0-1_]+)$/, - resolve: (str, sign, bin) => intResolve(sign, bin, 2), + test: /^[-+]?0b[0-1_]+$/, + resolve: str => intResolve(str, 2, 2), stringify: node => intStringify(node, 2, '0b') }, { @@ -95,16 +97,16 @@ export const yaml11 = failsafe.concat( default: true, tag: 'tag:yaml.org,2002:int', format: 'OCT', - test: /^([-+]?)0([0-7_]+)$/, - resolve: (str, sign, oct) => intResolve(sign, oct, 8), + test: /^[-+]?0[0-7_]+$/, + resolve: str => intResolve(str, 1, 8), stringify: node => intStringify(node, 8, '0') }, { identify: intIdentify, default: true, tag: 'tag:yaml.org,2002:int', - test: /^([-+]?)([0-9][0-9_]*)$/, - resolve: (str, sign, abs) => intResolve(sign, abs, 10), + test: /^[-+]?[0-9][0-9_]*$/, + resolve: str => intResolve(str, 0, 10), stringify: stringifyNumber }, { @@ -112,17 +114,17 @@ export const yaml11 = failsafe.concat( default: true, tag: 'tag:yaml.org,2002:int', format: 'HEX', - test: /^([-+]?)0x([0-9a-fA-F_]+)$/, - resolve: (str, sign, hex) => intResolve(sign, hex, 16), + test: /^[-+]?0x[0-9a-fA-F_]+$/, + resolve: str => intResolve(str, 2, 16), stringify: node => intStringify(node, 16, '0x') }, { identify: value => typeof value === 'number', default: true, tag: 'tag:yaml.org,2002:float', - test: /^(?:[-+]?\.inf|(\.nan))$/i, - resolve: (str, nan) => - nan + test: /^[-+]?\.(?:inf|Inf|INF|nan|NaN|NAN)$/, + resolve: str => + str.slice(-3).toLowerCase() === 'nan' ? NaN : str[0] === '-' ? Number.NEGATIVE_INFINITY @@ -134,7 +136,7 @@ export const yaml11 = failsafe.concat( default: true, tag: 'tag:yaml.org,2002:float', format: 'EXP', - test: /^[-+]?([0-9][0-9_]*)?(\.[0-9_]*)?[eE][-+]?[0-9]+$/, + test: /^[-+]?(?:[0-9][0-9_]*)?(?:\.[0-9_]*)?[eE][-+]?[0-9]+$/, resolve: str => parseFloat(str.replace(/_/g, '')), stringify: ({ value }) => Number(value).toExponential() }, @@ -142,11 +144,12 @@ export const yaml11 = failsafe.concat( identify: value => typeof value === 'number', default: true, tag: 'tag:yaml.org,2002:float', - test: /^[-+]?(?:[0-9][0-9_]*)?\.([0-9_]*)$/, - resolve(str, frac) { + test: /^[-+]?(?:[0-9][0-9_]*)?\.[0-9_]*$/, + resolve(str) { const node = new Scalar(parseFloat(str.replace(/_/g, ''))) - if (frac) { - const f = frac.replace(/_/g, '') + const dot = str.indexOf('.') + if (dot !== -1) { + const f = str.substring(dot + 1).replace(/_/g, '') if (f[f.length - 1] === '0') node.minFractionDigits = f.length } return node diff --git a/src/tags/yaml-1.1/timestamp.js b/src/tags/yaml-1.1/timestamp.js index 1b199be4..075e29b6 100644 --- a/src/tags/yaml-1.1/timestamp.js +++ b/src/tags/yaml-1.1/timestamp.js @@ -71,15 +71,17 @@ export const timestamp = { // may be omitted altogether, resulting in a date format. In such a case, the time part is // assumed to be 00:00:00Z (start of day, UTC). test: RegExp( - '^(?:' + - '([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})' + // YYYY-Mm-Dd - '(?:(?:t|T|[ \\t]+)' + // t | T | whitespace + '^([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})' + // YYYY-Mm-Dd + '(?:' + // time is optional + '(?:t|T|[ \\t]+)' + // t | T | whitespace '([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}(\\.[0-9]+)?)' + // Hh:Mm:Ss(.ss)? '(?:[ \\t]*(Z|[-+][012]?[0-9](?::[0-9]{2})?))?' + // Z | +5 | -03:30 - ')?' + - ')$' + ')?$' ), - resolve: (str, year, month, day, hour, minute, second, millisec, tz) => { + resolve(str) { + let [_, year, month, day, hour, minute, second, millisec, tz] = str.match( + timestamp.test + ) if (millisec) millisec = (millisec + '00').substr(1, 3) let date = Date.UTC( year, From 4309d7879f1147a0bdc30541e5724ed8da417e52 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sat, 3 Oct 2020 09:25:46 +0300 Subject: [PATCH 4/7] Refactor away schema.tags.scalarFallback --- src/resolve/resolveNode.js | 2 +- src/resolve/resolveScalar.js | 6 ++---- src/resolve/resolveTag.js | 2 +- src/stringify/stringifyString.js | 2 +- src/tags/json.js | 11 +++++++---- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/resolve/resolveNode.js b/src/resolve/resolveNode.js index 7249e460..e47c650c 100644 --- a/src/resolve/resolveNode.js +++ b/src/resolve/resolveNode.js @@ -94,7 +94,7 @@ function resolveNodeValue(doc, node) { try { const str = resolveString(node.strValue, error => doc.errors.push(error)) - return resolveScalar(str, schema.tags, schema.tags.scalarFallback) + return resolveScalar(str, schema.tags) } catch (error) { if (!error.source) error.source = node errors.push(error) diff --git a/src/resolve/resolveScalar.js b/src/resolve/resolveScalar.js index cfebbce1..d2506976 100644 --- a/src/resolve/resolveScalar.js +++ b/src/resolve/resolveScalar.js @@ -1,7 +1,6 @@ import { Scalar } from '../ast/Scalar.js' -// falls back to string on no match -export function resolveScalar(str, tags, scalarFallback) { +export function resolveScalar(str, tags) { for (const { format, test, resolve } of tags) { if (test && test.test(str)) { let res = resolve(str) @@ -10,6 +9,5 @@ export function resolveScalar(str, tags, scalarFallback) { return res } } - if (scalarFallback) str = scalarFallback(str) - return new Scalar(str) + return new Scalar(str) // fallback to string } diff --git a/src/resolve/resolveTag.js b/src/resolve/resolveTag.js index 0b1c4878..36fb4c8b 100644 --- a/src/resolve/resolveTag.js +++ b/src/resolve/resolveTag.js @@ -20,7 +20,7 @@ function resolveByTagName(doc, node, tagName) { const str = resolveString(node.strValue, error => doc.errors.push(error)) if (typeof str === 'string' && matchWithTest.length > 0) - return resolveScalar(str, matchWithTest, tags.scalarFallback) + return resolveScalar(str, matchWithTest) const kt = knownTags[tagName] if (kt) { diff --git a/src/stringify/stringifyString.js b/src/stringify/stringifyString.js index fc0af359..8d2ad65f 100644 --- a/src/stringify/stringifyString.js +++ b/src/stringify/stringifyString.js @@ -260,7 +260,7 @@ function plainString(item, ctx, onComment, onChompKeep) { // and others in v1.1. if (actualString) { const { tags } = ctx.doc.schema - const resolved = resolveScalar(str, tags, tags.scalarFallback).value + const resolved = resolveScalar(str, tags).value if (typeof resolved !== 'string') return doubleQuotedString(value, ctx) } const body = implicitKey diff --git a/src/tags/json.js b/src/tags/json.js index 3b6d3262..81324dec 100644 --- a/src/tags/json.js +++ b/src/tags/json.js @@ -56,9 +56,12 @@ export const json = [ test: /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][-+]?[0-9]+)?$/, resolve: str => parseFloat(str), stringify: stringifyJSON + }, + { + default: true, + test: /^/, + resolve(str) { + throw new SyntaxError(`Unresolved plain scalar ${JSON.stringify(str)}`) + } } ] - -json.scalarFallback = str => { - throw new SyntaxError(`Unresolved plain scalar ${JSON.stringify(str)}`) -} From 6eecd50851a06af0fc4e48112d22fc535b568132 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sat, 3 Oct 2020 11:46:41 +0300 Subject: [PATCH 5/7] Refactor tag resolve() interface BREAKING CHANGE: All tag resolvers are now called with two arguments: - value: string | YAMLMap | YAMLSeq - onError(message: string): void with the value being determined solely by the node's shape, rather than any explicit tag it may have. --- docs/06_custom_tags.md | 4 +- src/resolve/resolveMap.js | 5 --- src/resolve/resolveNode.js | 7 +++- src/resolve/resolveSeq.js | 5 --- src/resolve/resolveString.js | 8 ---- src/resolve/resolveTag.js | 75 ++++++++++++++++++++++-------------- src/tags/failsafe/map.js | 3 +- src/tags/failsafe/seq.js | 3 +- src/tags/failsafe/string.js | 4 +- src/tags/json.js | 9 ++--- src/tags/yaml-1.1/binary.js | 11 ++---- src/tags/yaml-1.1/omap.js | 7 ++-- src/tags/yaml-1.1/pairs.js | 43 ++++++++++----------- src/tags/yaml-1.1/set.js | 13 +++---- tests/doc/YAML-1.2.spec.js | 35 +++++------------ tests/doc/stringify.js | 6 +-- tests/doc/types.js | 2 +- types.d.ts | 18 ++++----- 18 files changed, 117 insertions(+), 141 deletions(-) delete mode 100644 src/resolve/resolveString.js diff --git a/docs/06_custom_tags.md b/docs/06_custom_tags.md index 1f8d6cf5..3e90e2c0 100644 --- a/docs/06_custom_tags.md +++ b/docs/06_custom_tags.md @@ -110,7 +110,7 @@ If you wish to implement your own custom tags, the [`!!binary`](https://github.c At the lowest level, [`YAML.parseCST()`](#cst-parser) will take care of turning string input into a concrete syntax tree (CST). In the CST all scalar values are available as strings, and maps & sequences as collections of nodes. Each schema includes a set of default data types, which handle converting at least strings, maps and sequences into their AST nodes. These are considered to have _implicit_ tags, and are autodetected. Custom tags, on the other hand, should almost always define an _explicit_ `tag` with which their value will be prefixed. This may be application-specific local `!tag`, a shorthand `!ns!tag`, or a verbatim `!`. -Once identified by matching the `tag`, the `resolve(doc, cstNode): Node | any` function will turn a CST node into an AST node. For scalars, this is relatively simple, as the stringified node value is directly available, and should be converted to its actual value. Collections are trickier, and it's almost certain that it'll make sense to use the `parseMap(doc, cstNode)` and `parseSeq(doc, cstNode)` functions exported from `'yaml/util'` to initially resolve the CST collection into a `YAMLMap` or `YAMLSeq` object, and to work with that instead -- this is for instance what the YAML 1.1 collections do. +Once identified by matching the `tag`, the `resolve(value, onError): Node | any` function will turn a parsed value into an AST node. `value` may be either a `string`, a `YAMLMap` or a `YAMLSeq`, depending on the node's shape. A custom tag should verify that value is of its expected type. Note that during the CST -> AST parsing, the anchors and comments attached to each node are also resolved for each node. This metadata will unfortunately be lost when converting the values to JS objects, so collections should have values that extend one of the existing collection classes. Collections should therefore either fall back to their parent classes' `toJSON()` methods, or define their own in order to allow their contents to be expressed as the appropriate JS object. @@ -145,7 +145,7 @@ To define your own tag, you'll need to define an object comprising of some of th - **`identify(value): boolean`** is used by `doc.createNode()` to detect your data type, e.g. using `typeof` or `instanceof`. Required. - `nodeClass: Node` is the `Node` child class that implements this tag. Required for collections and tags that have overlapping JS representations. - `options: Object` is used by some tags to configure their stringification. -- **`resolve(doc, cstNode): Node | any`** turns a CST node into an AST node; `doc` is the resulting `YAML.Document` instance. If returning a non-`Node` value, the output will be wrapped as a `Scalar`. Required. +- **`resolve(value, onError): Node | any`** turns a parsed value into an AST node; `value` is either a `string`, a `YAMLMap` or a `YAMLSeq`. `onError(msg)` should be called with an error message string when encountering errors, as it'll allow you to still return some value for the node. If returning a non-`Node` value, the output will be wrapped as a `Scalar`. Required. - `stringify(item, ctx, onComment, onChompKeep): string` is an optional function stringifying the `item` AST node in the current context `ctx`. `onComment` and `onChompKeep` are callback functions for a couple of special cases. If your data includes a suitable `.toString()` method, you can probably leave this undefined and use the default stringifier. - **`tag: string`** is the identifier for your data type, with which its stringified form will be prefixed. Should either be a !-prefixed local `!tag`, or a fully qualified `tag:domain,date:foo`. Required. - `test: RegExp` and `default: boolean` allow for values to be stringified without an explicit tag and detected using a regular expression. For most cases, it's unlikely that you'll actually want to use these, even if you first think you do. diff --git a/src/resolve/resolveMap.js b/src/resolve/resolveMap.js index d2f6e4ca..a7d8870d 100644 --- a/src/resolve/resolveMap.js +++ b/src/resolve/resolveMap.js @@ -16,11 +16,6 @@ import { import { resolveNode } from './resolveNode.js' export function resolveMap(doc, cst) { - if (cst.type !== Type.MAP && cst.type !== Type.FLOW_MAP) { - const msg = `A ${cst.type} node cannot be resolved as a mapping` - doc.errors.push(new YAMLSyntaxError(cst, msg)) - return null - } const { comments, items } = cst.type === Type.FLOW_MAP ? resolveFlowMapItems(doc, cst) diff --git a/src/resolve/resolveNode.js b/src/resolve/resolveNode.js index e47c650c..3959eb9d 100644 --- a/src/resolve/resolveNode.js +++ b/src/resolve/resolveNode.js @@ -7,7 +7,6 @@ import { } from '../errors.js' import { resolveScalar } from './resolveScalar.js' -import { resolveString } from './resolveString.js' import { resolveTagName } from './resolveTagName.js' import { resolveTag } from './resolveTag.js' @@ -93,7 +92,11 @@ function resolveNodeValue(doc, node) { } try { - const str = resolveString(node.strValue, error => doc.errors.push(error)) + let str = node.strValue || '' + if (typeof str !== 'string') { + str.errors.forEach(error => doc.errors.push(error)) + str = str.str + } return resolveScalar(str, schema.tags) } catch (error) { if (!error.source) error.source = node diff --git a/src/resolve/resolveSeq.js b/src/resolve/resolveSeq.js index b6870a7e..5b5d1ab1 100644 --- a/src/resolve/resolveSeq.js +++ b/src/resolve/resolveSeq.js @@ -13,11 +13,6 @@ import { import { resolveNode } from './resolveNode.js' export function resolveSeq(doc, cst) { - if (cst.type !== Type.SEQ && cst.type !== Type.FLOW_SEQ) { - const msg = `A ${cst.type} node cannot be resolved as a sequence` - doc.errors.push(new YAMLSyntaxError(cst, msg)) - return null - } const { comments, items } = cst.type === Type.FLOW_SEQ ? resolveFlowSeqItems(doc, cst) diff --git a/src/resolve/resolveString.js b/src/resolve/resolveString.js deleted file mode 100644 index ce8bdbcc..00000000 --- a/src/resolve/resolveString.js +++ /dev/null @@ -1,8 +0,0 @@ -// on error, strValue may be { str: string, errors: Error[] } -export function resolveString(strValue, onError) { - const res = strValue - if (!res) return '' - if (typeof res === 'string') return res - res.errors.forEach(onError) - return res.str -} diff --git a/src/resolve/resolveTag.js b/src/resolve/resolveTag.js index 36fb4c8b..df891269 100644 --- a/src/resolve/resolveTag.js +++ b/src/resolve/resolveTag.js @@ -1,53 +1,73 @@ import { Collection } from '../ast/Collection.js' import { Scalar } from '../ast/Scalar.js' import { Type, defaultTags } from '../constants.js' -import { YAMLReferenceError, YAMLWarning } from '../errors.js' +import { + YAMLReferenceError, + YAMLSemanticError, + YAMLWarning +} from '../errors.js' +import { resolveMap } from './resolveMap.js' import { resolveScalar } from './resolveScalar.js' -import { resolveString } from './resolveString.js' +import { resolveSeq } from './resolveSeq.js' -function resolveByTagName(doc, node, tagName) { - const { knownTags, tags } = doc.schema +function resolveByTagName({ knownTags, tags }, tagName, value, onError) { const matchWithTest = [] for (const tag of tags) { if (tag.tag === tagName) { - if (tag.test) matchWithTest.push(tag) - else { - const res = tag.resolve(doc, node) + if (tag.test) { + if (typeof value === 'string') matchWithTest.push(tag) + else onError(`The tag ${tagName} cannot be applied to a collection`) + } else { + const res = tag.resolve(value, onError) return res instanceof Collection ? res : new Scalar(res) } } } - - const str = resolveString(node.strValue, error => doc.errors.push(error)) - if (typeof str === 'string' && matchWithTest.length > 0) - return resolveScalar(str, matchWithTest) + if (matchWithTest.length > 0) return resolveScalar(value, matchWithTest) const kt = knownTags[tagName] if (kt) { tags.push(Object.assign({}, kt, { default: false, test: undefined })) - const res = kt.resolve(doc, node) + const res = kt.resolve(value, onError) return res instanceof Collection ? res : new Scalar(res) } return null } -function getFallbackTagName({ type }) { - switch (type) { - case Type.FLOW_MAP: - case Type.MAP: - return defaultTags.MAP - case Type.FLOW_SEQ: - case Type.SEQ: - return defaultTags.SEQ - default: - return defaultTags.STR - } -} - export function resolveTag(doc, node, tagName) { + const { MAP, SEQ, STR } = defaultTags + let value, fallback + const onError = message => + doc.errors.push(new YAMLSemanticError(node, message)) try { - const res = resolveByTagName(doc, node, tagName) + switch (node.type) { + case Type.FLOW_MAP: + case Type.MAP: + value = resolveMap(doc, node) + fallback = MAP + if (tagName === SEQ || tagName === STR) + onError(`The tag ${tagName} cannot be applied to a mapping`) + break + case Type.FLOW_SEQ: + case Type.SEQ: + value = resolveSeq(doc, node) + fallback = SEQ + if (tagName === MAP || tagName === STR) + onError(`The tag ${tagName} cannot be applied to a sequence`) + break + default: + value = node.strValue || '' + if (typeof value !== 'string') { + value.errors.forEach(error => doc.errors.push(error)) + value = value.str + } + if (tagName === MAP || tagName === SEQ) + onError(`The tag ${tagName} cannot be applied to a scalar`) + fallback = STR + } + + const res = resolveByTagName(doc.schema, tagName, value, onError) if (res) { if (tagName && node.tag) res.tag = tagName return res @@ -60,11 +80,10 @@ export function resolveTag(doc, node, tagName) { } try { - const fallback = getFallbackTagName(node) if (!fallback) throw new Error(`The tag ${tagName} is unavailable`) const msg = `The tag ${tagName} is unavailable, falling back to ${fallback}` doc.warnings.push(new YAMLWarning(node, msg)) - const res = resolveByTagName(doc, node, fallback) + const res = resolveByTagName(doc.schema, fallback, value, onError) res.tag = tagName return res } catch (error) { diff --git a/src/tags/failsafe/map.js b/src/tags/failsafe/map.js index 7041562c..2109c294 100644 --- a/src/tags/failsafe/map.js +++ b/src/tags/failsafe/map.js @@ -1,6 +1,5 @@ import { createPair } from '../../ast/Pair.js' import { YAMLMap } from '../../ast/YAMLMap.js' -import { resolveMap } from '../../resolve/resolveMap.js' function createMap(schema, obj, ctx) { const { keepUndefined, replacer } = ctx @@ -27,5 +26,5 @@ export const map = { default: true, nodeClass: YAMLMap, tag: 'tag:yaml.org,2002:map', - resolve: resolveMap + resolve: map => map } diff --git a/src/tags/failsafe/seq.js b/src/tags/failsafe/seq.js index cd78efdf..9cb52148 100644 --- a/src/tags/failsafe/seq.js +++ b/src/tags/failsafe/seq.js @@ -1,6 +1,5 @@ import { YAMLSeq } from '../../ast/YAMLSeq.js' import { createNode } from '../../doc/createNode.js' -import { resolveSeq } from '../../resolve/resolveSeq.js' function createSeq(schema, obj, ctx) { const { replacer } = ctx @@ -23,5 +22,5 @@ export const seq = { default: true, nodeClass: YAMLSeq, tag: 'tag:yaml.org,2002:seq', - resolve: resolveSeq + resolve: seq => seq } diff --git a/src/tags/failsafe/string.js b/src/tags/failsafe/string.js index add4d97d..f1cd90d9 100644 --- a/src/tags/failsafe/string.js +++ b/src/tags/failsafe/string.js @@ -1,4 +1,3 @@ -import { resolveString } from '../../resolve/resolveString.js' import { stringifyString } from '../../stringify/stringifyString.js' import { strOptions } from '../options.js' @@ -6,8 +5,7 @@ export const string = { identify: value => typeof value === 'string', default: true, tag: 'tag:yaml.org,2002:str', - resolve: (doc, node) => - resolveString(node.strValue, error => doc.errors.push(error)), + resolve: str => str, stringify(item, ctx, onComment, onChompKeep) { ctx = Object.assign({ actualString: true }, ctx) return stringifyString(item, ctx, onComment, onChompKeep) diff --git a/src/tags/json.js b/src/tags/json.js index 81324dec..abe6f7fe 100644 --- a/src/tags/json.js +++ b/src/tags/json.js @@ -1,7 +1,6 @@ /* global BigInt */ import { Scalar } from '../ast/Scalar.js' -import { resolveString } from '../resolve/resolveString.js' import { map } from './failsafe/map.js' import { seq } from './failsafe/seq.js' import { intOptions } from './options.js' @@ -18,8 +17,7 @@ export const json = [ identify: value => typeof value === 'string', default: true, tag: 'tag:yaml.org,2002:str', - resolve: (doc, node) => - resolveString(node.strValue, error => doc.errors.push(error)), + resolve: str => str, stringify: stringifyJSON }, { @@ -60,8 +58,9 @@ export const json = [ { default: true, test: /^/, - resolve(str) { - throw new SyntaxError(`Unresolved plain scalar ${JSON.stringify(str)}`) + resolve(str, onError) { + onError(`Unresolved plain scalar ${JSON.stringify(str)}`) + return str } } ] diff --git a/src/tags/yaml-1.1/binary.js b/src/tags/yaml-1.1/binary.js index 8aae5a47..fcd255e6 100644 --- a/src/tags/yaml-1.1/binary.js +++ b/src/tags/yaml-1.1/binary.js @@ -1,8 +1,6 @@ /* global atob, btoa, Buffer */ import { Type } from '../../constants.js' -import { YAMLReferenceError } from '../../errors.js' -import { resolveString } from '../../resolve/resolveString.js' import { stringifyString } from '../../stringify/stringifyString.js' import { binaryOptions as options } from '../options.js' @@ -18,8 +16,7 @@ export const binary = { * const blob = new Blob([buffer], { type: 'image/jpeg' }) * document.querySelector('#photo').src = URL.createObjectURL(blob) */ - resolve: (doc, node) => { - const src = resolveString(node.strValue, error => doc.errors.push(error)) + resolve(src, onError) { if (typeof Buffer === 'function') { return Buffer.from(src, 'base64') } else if (typeof atob === 'function') { @@ -29,10 +26,10 @@ export const binary = { for (let i = 0; i < str.length; ++i) buffer[i] = str.charCodeAt(i) return buffer } else { - const msg = + onError( 'This environment does not support reading binary tags; either Buffer or atob is required' - doc.errors.push(new YAMLReferenceError(node, msg)) - return null + ) + return src } }, options, diff --git a/src/tags/yaml-1.1/omap.js b/src/tags/yaml-1.1/omap.js index 97632f60..a42c5a7b 100644 --- a/src/tags/yaml-1.1/omap.js +++ b/src/tags/yaml-1.1/omap.js @@ -39,14 +39,13 @@ export class YAMLOMap extends YAMLSeq { } } -function parseOMap(doc, cst) { - const pairs = parsePairs(doc, cst) +function parseOMap(seq, onError) { + const pairs = parsePairs(seq, onError) const seenKeys = [] for (const { key } of pairs.items) { if (key instanceof Scalar) { if (seenKeys.includes(key.value)) { - const msg = 'Ordered maps must not include duplicate keys' - throw new YAMLSemanticError(cst, msg) + onError(`Ordered maps must not include duplicate keys: ${key.value}`) } else { seenKeys.push(key.value) } diff --git a/src/tags/yaml-1.1/pairs.js b/src/tags/yaml-1.1/pairs.js index 50ae6817..eb4dc132 100644 --- a/src/tags/yaml-1.1/pairs.js +++ b/src/tags/yaml-1.1/pairs.js @@ -1,32 +1,29 @@ -import { YAMLSemanticError } from '../../errors.js' import { createPair, Pair } from '../../ast/Pair.js' import { YAMLMap } from '../../ast/YAMLMap.js' import { YAMLSeq } from '../../ast/YAMLSeq.js' -import { resolveSeq } from '../../resolve/resolveSeq.js' -export function parsePairs(doc, cst) { - const seq = resolveSeq(doc, cst) - for (let i = 0; i < seq.items.length; ++i) { - let item = seq.items[i] - if (item instanceof Pair) continue - else if (item instanceof YAMLMap) { - if (item.items.length > 1) { - const msg = 'Each pair must have its own sequence indicator' - throw new YAMLSemanticError(cst, msg) +export function parsePairs(seq, onError) { + if (seq instanceof YAMLSeq) { + for (let i = 0; i < seq.items.length; ++i) { + let item = seq.items[i] + if (item instanceof Pair) continue + else if (item instanceof YAMLMap) { + if (item.items.length > 1) + onError('Each pair must have its own sequence indicator') + const pair = item.items[0] || new Pair() + if (item.commentBefore) + pair.commentBefore = pair.commentBefore + ? `${item.commentBefore}\n${pair.commentBefore}` + : item.commentBefore + if (item.comment) + pair.comment = pair.comment + ? `${item.comment}\n${pair.comment}` + : item.comment + item = pair } - const pair = item.items[0] || new Pair() - if (item.commentBefore) - pair.commentBefore = pair.commentBefore - ? `${item.commentBefore}\n${pair.commentBefore}` - : item.commentBefore - if (item.comment) - pair.comment = pair.comment - ? `${item.comment}\n${pair.comment}` - : item.comment - item = pair + seq.items[i] = item instanceof Pair ? item : new Pair(item) } - seq.items[i] = item instanceof Pair ? item : new Pair(item) - } + } else onError('Expected a sequence for this tag') return seq } diff --git a/src/tags/yaml-1.1/set.js b/src/tags/yaml-1.1/set.js index 11ba81d4..f421ad34 100644 --- a/src/tags/yaml-1.1/set.js +++ b/src/tags/yaml-1.1/set.js @@ -1,8 +1,6 @@ -import { YAMLSemanticError } from '../../errors.js' import { createPair, Pair } from '../../ast/Pair.js' import { Scalar } from '../../ast/Scalar.js' import { YAMLMap, findPair } from '../../ast/YAMLMap.js' -import { resolveMap } from '../../resolve/resolveMap.js' export class YAMLSet extends YAMLMap { static tag = 'tag:yaml.org,2002:set' @@ -52,11 +50,12 @@ export class YAMLSet extends YAMLMap { } } -function parseSet(doc, cst) { - const map = resolveMap(doc, cst) - if (!map.hasAllNullValues()) - throw new YAMLSemanticError(cst, 'Set items must all have null values') - return Object.assign(new YAMLSet(), map) +function parseSet(map, onError) { + if (map instanceof YAMLMap) { + if (map.hasAllNullValues()) return Object.assign(new YAMLSet(), map) + else onError('Set items must all have null values') + } else onError('Expected a mapping for this tag') + return map } function createSet(schema, iterable, ctx) { diff --git a/tests/doc/YAML-1.2.spec.js b/tests/doc/YAML-1.2.spec.js index e38255df..5fd2ccd3 100644 --- a/tests/doc/YAML-1.2.spec.js +++ b/tests/doc/YAML-1.2.spec.js @@ -515,10 +515,10 @@ application specific tag: !something | ], warnings: [ [ - 'The tag tag:clarkevans.com,2002:shape is unavailable, falling back to tag:yaml.org,2002:seq', 'The tag tag:clarkevans.com,2002:circle is unavailable, falling back to tag:yaml.org,2002:map', 'The tag tag:clarkevans.com,2002:line is unavailable, falling back to tag:yaml.org,2002:map', - 'The tag tag:clarkevans.com,2002:label is unavailable, falling back to tag:yaml.org,2002:map' + 'The tag tag:clarkevans.com,2002:label is unavailable, falling back to tag:yaml.org,2002:map', + 'The tag tag:clarkevans.com,2002:shape is unavailable, falling back to tag:yaml.org,2002:seq' ] ] }, @@ -730,10 +730,7 @@ alias: *anchor`, ['The tag !local is unavailable, falling back to tag:yaml.org,2002:str'] ], special: src => { - const tag = { - tag: '!local', - resolve: (doc, node) => 'local:' + node.strValue - } + const tag = { tag: '!local', resolve: str => `local:${str}` } const res = YAML.parse(src, { customTags: [tag] }) expect(res).toMatchObject({ anchored: 'local:value', @@ -1096,7 +1093,7 @@ bar`, special: src => { const tag = { tag: 'tag:example.com,2000:app/foo', - resolve: (doc, node) => 'foo' + node.strValue + resolve: str => `foo${str}` } const res = YAML.parse(src, { customTags: [tag] }) expect(res).toBe('foobar') @@ -1121,10 +1118,7 @@ bar`, ] ], special: src => { - const tag = { - tag: '!my-light', - resolve: (doc, node) => 'light:' + node.strValue - } + const tag = { tag: '!my-light', resolve: str => `light:${str}` } const docs = YAML.parseAllDocuments(src, { customTags: [tag] }) expect(docs.map(d => d.toJS())).toMatchObject([ 'light:fluorescent', @@ -1146,7 +1140,7 @@ bar`, special: src => { const tag = { tag: 'tag:example.com,2000:app/foo', - resolve: (doc, node) => 'foo' + node.strValue + resolve: str => `foo${str}` } const res = YAML.parse(src, { customTags: [tag] }) expect(res).toMatchObject(['foobar']) @@ -1169,10 +1163,7 @@ bar`, ['The tag !bar is unavailable, falling back to tag:yaml.org,2002:str'] ], special: src => { - const tag = { - tag: '!bar', - resolve: (doc, node) => 'bar' + node.strValue - } + const tag = { tag: '!bar', resolve: str => `bar${str}` } const res = YAML.parse(src, { customTags: [tag] }) expect(res).toMatchObject({ foo: 'barbaz' }) } @@ -1203,13 +1194,10 @@ bar`, ], special: src => { const customTags = [ - { - tag: '!local', - resolve: (doc, node) => 'local:' + node.strValue - }, + { tag: '!local', resolve: str => `local:${str}` }, { tag: 'tag:example.com,2000:app/tag!', - resolve: (doc, node) => 'tag!' + node.strValue + resolve: str => `tag!${str}` } ] const res = YAML.parse(src, { customTags }) @@ -1777,10 +1765,7 @@ folded: ['The tag !foo is unavailable, falling back to tag:yaml.org,2002:str'] ], special: src => { - const tag = { - tag: '!foo', - resolve: (doc, node) => 'foo' + node.strValue - } + const tag = { tag: '!foo', resolve: str => `foo${str}` } const res = YAML.parse(src, { customTags: [tag] }) expect(res).toMatchObject({ literal: 'value\n', folded: 'foovalue\n' }) } diff --git a/tests/doc/stringify.js b/tests/doc/stringify.js index 9e6fd9b0..b5d93626 100644 --- a/tests/doc/stringify.js +++ b/tests/doc/stringify.js @@ -347,8 +347,8 @@ describe('eemeli/yaml#80: custom tags', () => { const regexp = { identify: value => value instanceof RegExp, tag: '!re', - resolve(doc, cst) { - const match = cst.strValue.match(/^\/([\s\S]+)\/([gimuy]*)$/) + resolve(str) { + const match = str.match(/^\/([\s\S]+)\/([gimuy]*)$/) return new RegExp(match[1], match[2]) } } @@ -356,7 +356,7 @@ describe('eemeli/yaml#80: custom tags', () => { const sharedSymbol = { identify: value => value.constructor === Symbol, tag: '!symbol/shared', - resolve: (doc, cst) => Symbol.for(cst.strValue), + resolve: (str) => Symbol.for(str), stringify(item, ctx, onComment, onChompKeep) { const key = Symbol.keyFor(item.value) if (key === undefined) diff --git a/tests/doc/types.js b/tests/doc/types.js index 4927323b..8e81ba79 100644 --- a/tests/doc/types.js +++ b/tests/doc/types.js @@ -509,7 +509,7 @@ date (00:00:00Z): 2002-12-14\n`) expect(doc.errors).toMatchObject([ { name: 'YAMLSemanticError', - message: 'Ordered maps must not include duplicate keys' + message: 'Ordered maps must not include duplicate keys: b' } ]) }) diff --git a/types.d.ts b/types.d.ts index d20baf76..0d7737f3 100644 --- a/types.d.ts +++ b/types.d.ts @@ -118,6 +118,14 @@ export namespace Schema { * Used by some tags to configure their stringification, where applicable. */ options?: object + /** + * Turns a value into an AST node. + * If returning a non-`Node` value, the output will be wrapped as a `Scalar`. + */ + resolve( + value: string | YAMLMap | YAMLSeq, + onError: (message: string) => void + ): Node | any /** * Optional function stringifying the AST node in the current context. If your * data includes a suitable `.toString()` method, you can probably leave this @@ -145,11 +153,7 @@ export namespace Schema { } interface CustomTag extends BaseTag { - /** - * Turns a CST node into an AST node. If returning a non-`Node` value, the - * output will be wrapped as a `Scalar`. - */ - resolve(doc: Document, cstNode: CST.Node): Node | any + default?: false } interface DefaultTag extends BaseTag { @@ -159,10 +163,6 @@ export namespace Schema { * use this, even if you first think you do. */ default: true - /** - * Alternative form used by default tags; called with `test` match results. - */ - resolve(...match: string[]): Node | any /** * Together with `default` allows for values to be stringified without an * explicit tag and detected using a regular expression. For most cases, it's From 79c8f6f2e2f5a0e98d70d4fedc75dcdcffb06f07 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sat, 3 Oct 2020 11:52:37 +0300 Subject: [PATCH 6/7] Drop exports of { parseMap, parseSeq } from 'yaml/util' BREAKING CHANGE: With the change in the tag resolver API, these functions are no longer useful when writing custom tags. --- docs/06_custom_tags.md | 2 -- src/util.js | 2 -- util.d.ts | 8 +------- util.js | 2 -- util.mjs | 3 --- 5 files changed, 1 insertion(+), 16 deletions(-) diff --git a/docs/06_custom_tags.md b/docs/06_custom_tags.md index 3e90e2c0..c198784c 100644 --- a/docs/06_custom_tags.md +++ b/docs/06_custom_tags.md @@ -128,8 +128,6 @@ Finally, `stringify(item, ctx, ...): string` defines how your data should be rep ```js import { findPair, // (items, key) => Pair? -- Given a key, find a matching Pair - parseMap, // (doc, cstNode) => new YAMLMap - parseSeq, // (doc, cstNode) => new YAMLSeq stringifyNumber, // (node) => string stringifyString, // (node, ctx, ...) => string toJS, // (value, arg, ctx) => any -- Recursively convert to plain JS diff --git a/src/util.js b/src/util.js index 4cc92844..71aab825 100644 --- a/src/util.js +++ b/src/util.js @@ -1,6 +1,4 @@ export { findPair, toJS } from './ast/index.js' -export { resolveMap as parseMap } from './resolve/resolveMap.js' -export { resolveSeq as parseSeq } from './resolve/resolveSeq.js' export { stringifyNumber } from './stringify/stringifyNumber.js' export { stringifyString } from './stringify/stringifyString.js' diff --git a/util.d.ts b/util.d.ts index 5364c26f..dace24ec 100644 --- a/util.d.ts +++ b/util.d.ts @@ -1,14 +1,8 @@ -import { Document } from './index' import { CST } from './parse-cst' -import { AST, Pair, Scalar, Schema } from './types' +import { Pair, Scalar, Schema } from './types' export function findPair(items: any[], key: Scalar | any): Pair | undefined -export function parseMap(doc: Document, cst: CST.Map): AST.BlockMap -export function parseMap(doc: Document, cst: CST.FlowMap): AST.FlowMap -export function parseSeq(doc: Document, cst: CST.Seq): AST.BlockSeq -export function parseSeq(doc: Document, cst: CST.FlowSeq): AST.FlowSeq - export function stringifyNumber(item: Scalar): string export function stringifyString( item: Scalar, diff --git a/util.js b/util.js index 06dd2c99..d9472bcb 100644 --- a/util.js +++ b/util.js @@ -2,8 +2,6 @@ const util = require('./dist/util') exports.findPair = util.findPair exports.toJSON = util.toJSON -exports.parseMap = util.parseMap -exports.parseSeq = util.parseSeq exports.stringifyNumber = util.stringifyNumber exports.stringifyString = util.stringifyString diff --git a/util.mjs b/util.mjs index 89e654ab..9df8eaa7 100644 --- a/util.mjs +++ b/util.mjs @@ -3,9 +3,6 @@ import util from './dist/util.js' export const findPair = util.findPair export const toJSON = util.toJSON -export const parseMap = util.parseMap -export const parseSeq = util.parseSeq - export const stringifyNumber = util.stringifyNumber export const stringifyString = util.stringifyString From 22894a45a909e3b000719daebafb0e0204c9c9e1 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Sat, 3 Oct 2020 12:33:19 +0300 Subject: [PATCH 7/7] Satisfy lint --- src/tags/yaml-1.1/index.js | 2 +- src/tags/yaml-1.1/omap.js | 1 - src/tags/yaml-1.1/timestamp.js | 4 +++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tags/yaml-1.1/index.js b/src/tags/yaml-1.1/index.js index 3ea5b4a5..75a6e611 100644 --- a/src/tags/yaml-1.1/index.js +++ b/src/tags/yaml-1.1/index.js @@ -17,7 +17,7 @@ const intIdentify = value => typeof value === 'bigint' || Number.isInteger(value) function intResolve(str, offset, radix) { - let sign = str[0] + const sign = str[0] if (sign === '-' || sign === '+') offset += 1 str = str.substring(offset).replace(/_/g, '') if (intOptions.asBigInt) { diff --git a/src/tags/yaml-1.1/omap.js b/src/tags/yaml-1.1/omap.js index a42c5a7b..cb390569 100644 --- a/src/tags/yaml-1.1/omap.js +++ b/src/tags/yaml-1.1/omap.js @@ -1,4 +1,3 @@ -import { YAMLSemanticError } from '../../errors.js' import { Pair } from '../../ast/Pair.js' import { Scalar } from '../../ast/Scalar.js' import { YAMLMap } from '../../ast/YAMLMap.js' diff --git a/src/tags/yaml-1.1/timestamp.js b/src/tags/yaml-1.1/timestamp.js index 075e29b6..e2c5bfc0 100644 --- a/src/tags/yaml-1.1/timestamp.js +++ b/src/tags/yaml-1.1/timestamp.js @@ -1,3 +1,5 @@ +/* global BigInt */ + import { intOptions } from '../options.js' import { stringifyNumber } from '../../stringify/stringifyNumber.js' @@ -79,7 +81,7 @@ export const timestamp = { ')?$' ), resolve(str) { - let [_, year, month, day, hour, minute, second, millisec, tz] = str.match( + let [, year, month, day, hour, minute, second, millisec, tz] = str.match( timestamp.test ) if (millisec) millisec = (millisec + '00').substr(1, 3)