diff --git a/packages/sourcecred/src/util/combo.js b/packages/sourcecred/src/util/combo.js index 7e715666a..7d1c16539 100644 --- a/packages/sourcecred/src/util/combo.js +++ b/packages/sourcecred/src/util/combo.js @@ -3,6 +3,8 @@ // Simple parser combinator library for structured types rather than // bytestring parsing. +import stringify from "json-stable-stringify"; + export type JsonObject = | string | number @@ -25,9 +27,11 @@ export class Parser<+T> { constructor(f: (JsonObject) => ParseResult) { this._f = f; } + parse(raw: JsonObject): ParseResult { return this._f(raw); } + parseOrThrow(raw: JsonObject): T { const result = this.parse(raw); if (result.ok) { @@ -201,10 +205,23 @@ export function fmap(p: Parser, f: (T) => U): Parser { }); } +// replacer for stringify.JSON, in order to replace empty array with just `[]`. +// +// this replacer function can be extended and universally be used with other +// aspect like empty objects. +function emptyArrayReplacer(key: string, value: any) { + // check if the array is empty. + if (Array.isArray(value) && value.length === 0) { + return []; + } + + return value; +} + // Create a parser that tries each of the given parsers on the same // input, taking the first successful parse or failing if all parsers // fail. In the failure case, the provided `errorFn` will be called with -// the error messages from all the subparsers to form the resulting +// the error messages from all the sub-parsers to form the resulting // error; the default error function includes the full text of all the // error messages, but a user-supplied error function may act with // domain-specific precision. @@ -231,7 +248,10 @@ export function fmap(p: Parser, f: (T) => U): Parser { export function orElse>>( parsers: T, errorFn?: (string[]) => string = (errors) => - `no parse matched: ${JSON.stringify(errors)}` + `no parse matched: ${stringify(errors, { + replacer: emptyArrayReplacer, + spacer: 4, + })}` ): Parser<$ElementType<$TupleMap, number>> { return new Parser((x) => { const errors = []; @@ -284,6 +304,7 @@ export function rename(oldKey: string, parser: Parser): RenameField { class RenameFieldImpl<+T> extends Parser { +oldKey: string; + constructor(oldKey: string, parser: Parser) { super(parser._f); this.oldKey = oldKey;