diff --git a/async/retry.ts b/async/retry.ts index de86e14b176d..689f43d8ca54 100644 --- a/async/retry.ts +++ b/async/retry.ts @@ -63,14 +63,6 @@ export interface RetryOptions { jitter?: number; } -const defaultRetryOptions: Required = { - multiplier: 2, - maxTimeout: 60000, - maxAttempts: 5, - minTimeout: 1000, - jitter: 1, -}; - /** * Calls the given (possibly asynchronous) function up to `maxAttempts` times. * Retries as long as the given function throws. If the attempts are exhausted, @@ -122,31 +114,34 @@ const defaultRetryOptions: Required = { * * @typeParam T The return type of the function to retry and returned promise. * @param fn The function to retry. - * @param opts Additional options. + * @param options Additional options. * @returns The promise that resolves with the value returned by the function to retry. */ export async function retry( fn: (() => Promise) | (() => T), - opts?: RetryOptions, + options?: RetryOptions, ): Promise { - const options: Required = { - ...defaultRetryOptions, - ...opts, - }; + const { + multiplier = 2, + maxTimeout = 60000, + maxAttempts = 5, + minTimeout = 1000, + jitter = 1, + } = options ?? {}; - if (options.maxTimeout <= 0) { + if (maxTimeout <= 0) { throw new TypeError( - `Cannot retry as 'maxTimeout' must be positive: current value is ${options.maxTimeout}`, + `Cannot retry as 'maxTimeout' must be positive: current value is ${maxTimeout}`, ); } - if (options.minTimeout > options.maxTimeout) { + if (minTimeout > maxTimeout) { throw new TypeError( - `Cannot retry as 'minTimeout' must be <= 'maxTimeout': current values 'minTimeout=${options.minTimeout}', 'maxTimeout=${options.maxTimeout}'`, + `Cannot retry as 'minTimeout' must be <= 'maxTimeout': current values 'minTimeout=${minTimeout}', 'maxTimeout=${maxTimeout}'`, ); } - if (options.jitter > 1) { + if (jitter > 1) { throw new TypeError( - `Cannot retry as 'jitter' must be <= 1: current value is ${options.jitter}`, + `Cannot retry as 'jitter' must be <= 1: current value is ${jitter}`, ); } @@ -155,16 +150,16 @@ export async function retry( try { return await fn(); } catch (error) { - if (attempt + 1 >= options.maxAttempts) { - throw new RetryError(error, options.maxAttempts); + if (attempt + 1 >= maxAttempts) { + throw new RetryError(error, maxAttempts); } const timeout = exponentialBackoffWithJitter( - options.maxTimeout, - options.minTimeout, + maxTimeout, + minTimeout, attempt, - options.multiplier, - options.jitter, + multiplier, + jitter, ); await new Promise((r) => setTimeout(r, timeout)); } diff --git a/async/retry_test.ts b/async/retry_test.ts index e90b34f245be..17e4bf8d9137 100644 --- a/async/retry_test.ts +++ b/async/retry_test.ts @@ -23,7 +23,7 @@ Deno.test("retry()", async () => { assertEquals(result, 3); }); -Deno.test("retry() fails after max errors is passed", async () => { +Deno.test("retry() fails after five errors by default", async () => { const fiveErrors = generateErroringFunction(5); await assertRejects(() => retry(fiveErrors, { @@ -32,6 +32,16 @@ Deno.test("retry() fails after max errors is passed", async () => { ); }); +Deno.test("retry() fails after five errors when undefined is passed", async () => { + const fiveErrors = generateErroringFunction(5); + await assertRejects(() => + retry(fiveErrors, { + maxAttempts: undefined, + minTimeout: 100, + }) + ); +}); + Deno.test("retry() waits four times by default", async () => { let callCount = 0; const onlyErrors = function () { diff --git a/csv/_io.ts b/csv/_io.ts index f0541cbfd240..1312800de2e8 100644 --- a/csv/_io.ts +++ b/csv/_io.ts @@ -51,11 +51,6 @@ export interface ReadOptions { fieldsPerRecord?: number; } -export const defaultReadOptions: ReadOptions = { - separator: ",", - trimLeadingSpace: false, -}; - export interface LineReader { readLine(): Promise; isEOF(): boolean; diff --git a/csv/parse_stream.ts b/csv/parse_stream.ts index 88b68083736b..f2e3e8f35144 100644 --- a/csv/parse_stream.ts +++ b/csv/parse_stream.ts @@ -3,7 +3,6 @@ import { convertRowToObject, - defaultReadOptions, type LineReader, parseRecord, type ParseResult, @@ -370,8 +369,9 @@ export class CsvParseStream< */ constructor(options?: T) { this.#options = { - ...defaultReadOptions, ...options, + separator: options?.separator ?? ",", + trimLeadingSpace: options?.trimLeadingSpace ?? false, }; if ( diff --git a/csv/parse_stream_test.ts b/csv/parse_stream_test.ts index 3369ca1527c3..1c894f12db36 100644 --- a/csv/parse_stream_test.ts +++ b/csv/parse_stream_test.ts @@ -82,12 +82,9 @@ Deno.test({ }, { name: "Separator is undefined", - input: "a;b;c\n", + input: "a,b,c\n", + output: [["a", "b", "c"]], separator: undefined, - error: { - klass: TypeError, - msg: "Cannot parse record: separator is required", - }, }, { name: "MultiLine", diff --git a/fmt/duration.ts b/fmt/duration.ts index 1a7e0c815d88..d116340aed06 100644 --- a/fmt/duration.ts +++ b/fmt/duration.ts @@ -118,17 +118,18 @@ export interface FormatOptions { */ export function format( ms: number, - options: FormatOptions = {}, + options?: FormatOptions, ): string { - const opt = Object.assign( - { style: "narrow", ignoreZero: false }, - options, - ); + const { + style = "narrow", + ignoreZero = false, + } = options ?? {}; + const duration = millisecondsToDurationObject(ms); const durationArr = durationArray(duration); - switch (opt.style) { + switch (style) { case "narrow": { - if (opt.ignoreZero) { + if (ignoreZero) { return `${ durationArr.filter((x) => x.value).map((x) => `${x.value}${x.type === "us" ? "µs" : x.type}` @@ -142,7 +143,7 @@ export function format( }`; } case "full": { - if (opt.ignoreZero) { + if (ignoreZero) { return `${ durationArr.filter((x) => x.value).map((x) => `${x.value} ${keyList[x.type]}` @@ -159,7 +160,7 @@ export function format( ? addZero(x.value, 3) : addZero(x.value, 2) ); - if (opt.ignoreZero) { + if (ignoreZero) { let cont = true; while (cont) { if (!Number(arr[arr.length - 1])) arr.pop(); diff --git a/fmt/duration_test.ts b/fmt/duration_test.ts index ac8e85bd6098..63e848991011 100644 --- a/fmt/duration_test.ts +++ b/fmt/duration_test.ts @@ -1,5 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals, assertExists, assertThrows } from "@std/assert"; +import { assertEquals, assertExists } from "@std/assert"; import { format } from "./duration.ts"; Deno.test({ @@ -16,6 +16,16 @@ Deno.test({ }, }); +Deno.test({ + name: "format() handles default style (narrow)", + fn() { + assertEquals( + format(99674, { style: undefined }), + "0d 0h 1m 39s 674ms 0µs 0ns", + ); + }, +}); + Deno.test({ name: "format() handles full duration", fn() { @@ -79,16 +89,3 @@ Deno.test({ assertEquals(format(16.342, { ignoreZero: true }), "16ms 342µs"); }, }); - -Deno.test({ - name: "format() handles default style error", - fn() { - assertThrows( - () => { - format(16.342, { style: undefined }); - }, - TypeError, - `style must be "narrow", "full", or "digital"!`, - ); - }, -}); diff --git a/yaml/_type/bool.ts b/yaml/_type/bool.ts index 0d2d13747565..37e6b1a380c9 100644 --- a/yaml/_type/bool.ts +++ b/yaml/_type/bool.ts @@ -18,8 +18,20 @@ export const bool: Type<"scalar", boolean> = { construct: (data: string): boolean => YAML_TRUE_BOOLEANS.includes(data), resolve: (data: string): boolean => YAML_BOOLEANS.includes(data), represent: { - lowercase: (object: boolean): string => object ? "true" : "false", - uppercase: (object: boolean): string => object ? "TRUE" : "FALSE", - camelcase: (object: boolean): string => object ? "True" : "False", + // deno-lint-ignore ban-types + lowercase: (object: boolean | Boolean): string => { + const value = object instanceof Boolean ? object.valueOf() : object; + return value ? "true" : "false"; + }, + // deno-lint-ignore ban-types + uppercase: (object: boolean | Boolean): string => { + const value = object instanceof Boolean ? object.valueOf() : object; + return value ? "TRUE" : "FALSE"; + }, + // deno-lint-ignore ban-types + camelcase: (object: boolean | Boolean): string => { + const value = object instanceof Boolean ? object.valueOf() : object; + return value ? "True" : "False"; + }, }, }; diff --git a/yaml/_type/float.ts b/yaml/_type/float.ts index b8fe8451ff2e..74f812780e72 100644 --- a/yaml/_type/float.ts +++ b/yaml/_type/float.ts @@ -50,8 +50,13 @@ function constructYamlFloat(data: string): number { const SCIENTIFIC_WITHOUT_DOT = /^[-+]?[0-9]+e/; -function representYamlFloat(object: number, style?: StyleVariant): string { - if (isNaN(object)) { +function representYamlFloat( + // deno-lint-ignore ban-types + object: number | Number, + style?: StyleVariant, +): string { + const value = object instanceof Number ? object.valueOf() : object; + if (isNaN(value)) { switch (style) { case "lowercase": return ".nan"; @@ -60,7 +65,7 @@ function representYamlFloat(object: number, style?: StyleVariant): string { case "camelcase": return ".NaN"; } - } else if (Number.POSITIVE_INFINITY === object) { + } else if (Number.POSITIVE_INFINITY === value) { switch (style) { case "lowercase": return ".inf"; @@ -69,7 +74,7 @@ function representYamlFloat(object: number, style?: StyleVariant): string { case "camelcase": return ".Inf"; } - } else if (Number.NEGATIVE_INFINITY === object) { + } else if (Number.NEGATIVE_INFINITY === value) { switch (style) { case "lowercase": return "-.inf"; @@ -78,11 +83,11 @@ function representYamlFloat(object: number, style?: StyleVariant): string { case "camelcase": return "-.Inf"; } - } else if (isNegativeZero(object)) { + } else if (isNegativeZero(value)) { return "-0.0"; } - const res = object.toString(10); + const res = value.toString(10); // JS stringifier can build scientific format without dots: 5e-100, // while YAML requires dot: 5.e-100. Fix it with simple hack @@ -91,6 +96,7 @@ function representYamlFloat(object: number, style?: StyleVariant): string { } function isFloat(object: unknown): object is number { + if (object instanceof Number) object = object.valueOf(); return typeof object === "number" && (object % 1 !== 0 || isNegativeZero(object)); } diff --git a/yaml/_type/int.ts b/yaml/_type/int.ts index 494cf4622725..f0d2e4b37b79 100644 --- a/yaml/_type/int.ts +++ b/yaml/_type/int.ts @@ -131,6 +131,7 @@ function constructYamlInteger(data: string): number { } function isInteger(object: unknown): object is number { + if (object instanceof Number) object = object.valueOf(); return typeof object === "number" && object % 1 === 0 && !isNegativeZero(object); } @@ -142,21 +143,31 @@ export const int: Type<"scalar", number> = { kind: "scalar", predicate: isInteger, represent: { - binary(obj: number): string { - return obj >= 0 - ? `0b${obj.toString(2)}` - : `-0b${obj.toString(2).slice(1)}`; + // deno-lint-ignore ban-types + binary(object: number | Number): string { + const value = object instanceof Number ? object.valueOf() : object; + return value >= 0 + ? `0b${value.toString(2)}` + : `-0b${value.toString(2).slice(1)}`; }, - octal(obj: number): string { - return obj >= 0 ? `0${obj.toString(8)}` : `-0${obj.toString(8).slice(1)}`; + // deno-lint-ignore ban-types + octal(object: number | Number): string { + const value = object instanceof Number ? object.valueOf() : object; + return value >= 0 + ? `0${value.toString(8)}` + : `-0${value.toString(8).slice(1)}`; }, - decimal(obj: number): string { - return obj.toString(10); + // deno-lint-ignore ban-types + decimal(object: number | Number): string { + const value = object instanceof Number ? object.valueOf() : object; + return value.toString(10); }, - hexadecimal(obj: number): string { - return obj >= 0 - ? `0x${obj.toString(16).toUpperCase()}` - : `-0x${obj.toString(16).toUpperCase().slice(1)}`; + // deno-lint-ignore ban-types + hexadecimal(object: number | Number): string { + const value = object instanceof Number ? object.valueOf() : object; + return value >= 0 + ? `0x${value.toString(16).toUpperCase()}` + : `-0x${value.toString(16).toUpperCase().slice(1)}`; }, }, resolve: resolveYamlInteger, diff --git a/yaml/stringify_test.ts b/yaml/stringify_test.ts index 9aa35958be61..68bc70433934 100644 --- a/yaml/stringify_test.ts +++ b/yaml/stringify_test.ts @@ -77,11 +77,18 @@ Deno.test({ }); Deno.test({ - name: "stringify() serializes integers", + name: "stringify() handles integers", fn() { assertEquals(stringify(42), "42\n"); assertEquals(stringify(-42), "-42\n"); + assertEquals(stringify(new Number(42)), "42\n"); + assertEquals(stringify(new Number(-42)), "-42\n"); + }, +}); +Deno.test({ + name: "stringify() handles integers with styles option", + fn() { // binary, octal, and hexadecimal can be specified in styles options assertEquals( stringify(42, { styles: { "!!int": "binary" } }), @@ -91,6 +98,10 @@ Deno.test({ stringify(-42, { styles: { "!!int": "binary" } }), "-0b101010\n", ); + assertEquals( + stringify(new Number(42), { styles: { "!!int": "binary" } }), + "0b101010\n", + ); assertEquals( stringify(42, { styles: { "!!int": "octal" } }), "052\n", @@ -99,6 +110,10 @@ Deno.test({ stringify(-42, { styles: { "!!int": "octal" } }), "-052\n", ); + assertEquals( + stringify(new Number(42), { styles: { "!!int": "octal" } }), + "052\n", + ); assertEquals( stringify(42, { styles: { "!!int": "hexadecimal" } }), "0x2A\n", @@ -107,26 +122,62 @@ Deno.test({ stringify(-42, { styles: { "!!int": "hexadecimal" } }), "-0x2A\n", ); + assertEquals( + stringify(new Number(42), { styles: { "!!int": "hexadecimal" } }), + "0x2A\n", + ); + assertThrows( + () => stringify(42, { styles: { "!!int": "camelcase" } }), + TypeError, + '! tag resolver accepts not "camelcase" style', + ); }, }); Deno.test({ - name: "stringify() serializes boolean values", + name: "stringify() handles boolean values", fn() { - assertEquals(stringify([true, false]), "- true\n- false\n"); + assertEquals(stringify(true), "true\n"); + assertEquals(stringify(false), "false\n"); - // casing can be controlled with styles options + assertEquals(stringify(new Boolean(true)), "true\n"); + assertEquals(stringify(new Boolean(false)), "false\n"); + }, +}); +Deno.test({ + name: "stringify() handles boolean with styles option", + fn() { assertEquals( - stringify([true, false], { styles: { "!!bool": "camelcase" } }), - "- True\n- False\n", + stringify(true, { styles: { "!!bool": "camelcase" } }), + "True\n", ); assertEquals( - stringify([true, false], { styles: { "!!bool": "uppercase" } }), - "- TRUE\n- FALSE\n", + stringify(false, { styles: { "!!bool": "camelcase" } }), + "False\n", + ); + assertEquals( + stringify(new Boolean(true), { styles: { "!!bool": "camelcase" } }), + "True\n", + ); + assertEquals( + stringify(true, { styles: { "!!bool": "uppercase" } }), + "TRUE\n", + ); + assertEquals( + stringify(false, { styles: { "!!bool": "uppercase" } }), + "FALSE\n", + ); + assertEquals( + stringify(new Boolean(true), { styles: { "!!bool": "uppercase" } }), + "TRUE\n", ); - assertThrows( - () => stringify([true, false], { styles: { "!!bool": "octal" } }), + () => stringify(true, { styles: { "!!bool": "octal" } }), + TypeError, + '! tag resolver accepts not "octal" style', + ); + assertThrows( + () => stringify(false, { styles: { "!!bool": "octal" } }), TypeError, '! tag resolver accepts not "octal" style', ); @@ -226,49 +277,57 @@ Deno.test({ }); Deno.test({ - name: "stringify() handles float types", + name: "stringify() handles float values", fn() { - const floats = [ - 4.1, - -1.473, - 6.82e-5, - 6.82e-12, - 5e-12, - 0, - -0, - ]; - assertEquals( - stringify(floats), - `- 4.1 -- -1.473 -- 0.0000682 -- 6.82e-12 -- 5.e-12 -- 0 -- -0.0 -`, + assertEquals(stringify(4.1), `4.1\n`); + assertEquals(stringify(-1.473), `-1.473\n`); + assertEquals(stringify(6.82e-5), `0.0000682\n`); + assertEquals(stringify(6.82e-12), `6.82e-12\n`); + assertEquals(stringify(5e-12), `5.e-12\n`); + assertEquals(stringify(0.0), `0\n`); + assertEquals(stringify(-0), `-0.0\n`); + assertEquals(stringify(new Number(1.234)), `1.234\n`); + assertEquals(stringify(new Number(-1.234)), `-1.234\n`); + + assertEquals(stringify(Infinity), `.inf\n`); + assertEquals(stringify(-Infinity), `-.inf\n`); + assertEquals(stringify(NaN), `.nan\n`); + }, +}); +Deno.test({ + name: "stringify() handles float values with styles option", + fn() { + assertEquals( + stringify(Infinity, { + styles: { "tag:yaml.org,2002:float": "uppercase" }, + }), + `.INF\n`, ); - const infNaN = [Infinity, -Infinity, NaN]; assertEquals( - stringify(infNaN), - `- .inf -- -.inf -- .nan -`, + stringify(-Infinity, { + styles: { "tag:yaml.org,2002:float": "uppercase" }, + }), + `-.INF\n`, ); assertEquals( - stringify(infNaN, { styles: { "tag:yaml.org,2002:float": "uppercase" } }), - `- .INF -- -.INF -- .NAN -`, + stringify(NaN, { styles: { "tag:yaml.org,2002:float": "uppercase" } }), + `.NAN\n`, ); assertEquals( - stringify(infNaN, { styles: { "tag:yaml.org,2002:float": "camelcase" } }), - `- .Inf -- -.Inf -- .NaN -`, + stringify(Infinity, { + styles: { "tag:yaml.org,2002:float": "camelcase" }, + }), + `.Inf\n`, + ); + assertEquals( + stringify(-Infinity, { + styles: { "tag:yaml.org,2002:float": "camelcase" }, + }), + `-.Inf\n`, + ); + assertEquals( + stringify(NaN, { styles: { "tag:yaml.org,2002:float": "camelcase" } }), + `.NaN\n`, ); }, });