diff options
author | Bartek IwaĆczuk <biwanczuk@gmail.com> | 2023-02-14 17:38:45 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-02-14 17:38:45 +0100 |
commit | d47147fb6ad229b1c039aff9d0959b6e281f4df5 (patch) | |
tree | 6e9e790f2b9bc71b5f0c9c7e64b95cae31579d58 /ext/node/polyfills/assert.ts | |
parent | 1d00bbe47e2ca14e2d2151518e02b2324461a065 (diff) |
feat(ext/node): embed std/node into the snapshot (#17724)
This commit moves "deno_std/node" in "ext/node" crate. The code is
transpiled and snapshotted during the build process.
During the first pass a minimal amount of work was done to create the
snapshot, a lot of code in "ext/node" depends on presence of "Deno"
global. This code will be gradually fixed in the follow up PRs to migrate
it to import relevant APIs from "internal:" modules.
Currently the code from snapshot is not used in any way, and all
Node/npm compatibility still uses code from
"https://deno.land/std/node" (or from the location specified by
"DENO_NODE_COMPAT_URL"). This will also be handled in a follow
up PRs.
---------
Co-authored-by: crowlkats <crowlkats@toaxl.com>
Co-authored-by: Divy Srivastava <dj.srivastava23@gmail.com>
Co-authored-by: Yoshiya Hinosawa <stibium121@gmail.com>
Diffstat (limited to 'ext/node/polyfills/assert.ts')
-rw-r--r-- | ext/node/polyfills/assert.ts | 940 |
1 files changed, 940 insertions, 0 deletions
diff --git a/ext/node/polyfills/assert.ts b/ext/node/polyfills/assert.ts new file mode 100644 index 000000000..2d5e86e58 --- /dev/null +++ b/ext/node/polyfills/assert.ts @@ -0,0 +1,940 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// deno-lint-ignore-file ban-types +import { + AssertionError, + AssertionErrorConstructorOptions, +} from "internal:deno_node/polyfills/assertion_error.ts"; +import * as asserts from "internal:deno_node/polyfills/_util/std_asserts.ts"; +import { inspect } from "internal:deno_node/polyfills/util.ts"; +import { + ERR_AMBIGUOUS_ARGUMENT, + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, + ERR_INVALID_RETURN_VALUE, + ERR_MISSING_ARGS, +} from "internal:deno_node/polyfills/internal/errors.ts"; +import { isDeepEqual } from "internal:deno_node/polyfills/internal/util/comparisons.ts"; + +function innerFail(obj: { + actual?: unknown; + expected?: unknown; + message?: string | Error; + operator?: string; +}) { + if (obj.message instanceof Error) { + throw obj.message; + } + + throw new AssertionError({ + actual: obj.actual, + expected: obj.expected, + message: obj.message, + operator: obj.operator, + }); +} + +interface ExtendedAssertionErrorConstructorOptions + extends AssertionErrorConstructorOptions { + generatedMessage?: boolean; +} + +// TODO(uki00a): This function is a workaround for setting the `generatedMessage` property flexibly. +function createAssertionError( + options: ExtendedAssertionErrorConstructorOptions, +): AssertionError { + const error = new AssertionError(options); + if (options.generatedMessage) { + error.generatedMessage = true; + } + return error; +} + +/** Converts the std assertion error to node.js assertion error */ +function toNode( + fn: () => void, + opts?: { + actual: unknown; + expected: unknown; + message?: string | Error; + operator?: string; + }, +) { + const { operator, message, actual, expected } = opts || {}; + try { + fn(); + } catch (e) { + if (e instanceof asserts.AssertionError) { + if (typeof message === "string") { + throw new AssertionError({ + operator, + message, + actual, + expected, + }); + } else if (message instanceof Error) { + throw message; + } else { + throw new AssertionError({ + operator, + message: e.message, + actual, + expected, + }); + } + } + throw e; + } +} + +function assert(actual: unknown, message?: string | Error): asserts actual { + if (arguments.length === 0) { + throw new AssertionError({ + message: "No value argument passed to `assert.ok()`", + }); + } + toNode( + () => asserts.assert(actual), + { message, actual, expected: true }, + ); +} +const ok = assert; + +function throws( + fn: () => void, + error?: RegExp | Function | Error, + message?: string, +) { + // Check arg types + if (typeof fn !== "function") { + throw new ERR_INVALID_ARG_TYPE("fn", "function", fn); + } + if ( + typeof error === "object" && error !== null && + Object.getPrototypeOf(error) === Object.prototype && + Object.keys(error).length === 0 + ) { + // error is an empty object + throw new ERR_INVALID_ARG_VALUE( + "error", + error, + "may not be an empty object", + ); + } + if (typeof message === "string") { + if ( + !(error instanceof RegExp) && typeof error !== "function" && + !(error instanceof Error) && typeof error !== "object" + ) { + throw new ERR_INVALID_ARG_TYPE("error", [ + "Function", + "Error", + "RegExp", + "Object", + ], error); + } + } else { + if ( + typeof error !== "undefined" && typeof error !== "string" && + !(error instanceof RegExp) && typeof error !== "function" && + !(error instanceof Error) && typeof error !== "object" + ) { + throw new ERR_INVALID_ARG_TYPE("error", [ + "Function", + "Error", + "RegExp", + "Object", + ], error); + } + } + + // Checks test function + try { + fn(); + } catch (e) { + if ( + validateThrownError(e, error, message, { + operator: throws, + }) + ) { + return; + } + } + if (message) { + let msg = `Missing expected exception: ${message}`; + if (typeof error === "function" && error?.name) { + msg = `Missing expected exception (${error.name}): ${message}`; + } + throw new AssertionError({ + message: msg, + operator: "throws", + actual: undefined, + expected: error, + }); + } else if (typeof error === "string") { + // Use case of throws(fn, message) + throw new AssertionError({ + message: `Missing expected exception: ${error}`, + operator: "throws", + actual: undefined, + expected: undefined, + }); + } else if (typeof error === "function" && error?.prototype !== undefined) { + throw new AssertionError({ + message: `Missing expected exception (${error.name}).`, + operator: "throws", + actual: undefined, + expected: error, + }); + } else { + throw new AssertionError({ + message: "Missing expected exception.", + operator: "throws", + actual: undefined, + expected: error, + }); + } +} + +function doesNotThrow( + fn: () => void, + message?: string, +): void; +function doesNotThrow( + fn: () => void, + error?: Function, + message?: string | Error, +): void; +function doesNotThrow( + fn: () => void, + error?: RegExp, + message?: string, +): void; +function doesNotThrow( + fn: () => void, + expected?: Function | RegExp | string, + message?: string | Error, +) { + // Check arg type + if (typeof fn !== "function") { + throw new ERR_INVALID_ARG_TYPE("fn", "function", fn); + } else if ( + !(expected instanceof RegExp) && typeof expected !== "function" && + typeof expected !== "string" && typeof expected !== "undefined" + ) { + throw new ERR_INVALID_ARG_TYPE("expected", ["Function", "RegExp"], fn); + } + + // Checks test function + try { + fn(); + } catch (e) { + gotUnwantedException(e, expected, message, doesNotThrow); + } +} + +function equal( + actual: unknown, + expected: unknown, + message?: string | Error, +) { + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("actual", "expected"); + } + + if (actual == expected) { + return; + } + + if (Number.isNaN(actual) && Number.isNaN(expected)) { + return; + } + + if (typeof message === "string") { + throw new AssertionError({ + message, + }); + } else if (message instanceof Error) { + throw message; + } + + toNode( + () => asserts.assertStrictEquals(actual, expected), + { + message: message || `${actual} == ${expected}`, + operator: "==", + actual, + expected, + }, + ); +} +function notEqual( + actual: unknown, + expected: unknown, + message?: string | Error, +) { + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("actual", "expected"); + } + + if (Number.isNaN(actual) && Number.isNaN(expected)) { + throw new AssertionError({ + message: `${actual} != ${expected}`, + operator: "!=", + actual, + expected, + }); + } + if (actual != expected) { + return; + } + + if (typeof message === "string") { + throw new AssertionError({ + message, + }); + } else if (message instanceof Error) { + throw message; + } + + toNode( + () => asserts.assertNotStrictEquals(actual, expected), + { + message: message || `${actual} != ${expected}`, + operator: "!=", + actual, + expected, + }, + ); +} +function strictEqual( + actual: unknown, + expected: unknown, + message?: string | Error, +) { + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("actual", "expected"); + } + + toNode( + () => asserts.assertStrictEquals(actual, expected), + { message, operator: "strictEqual", actual, expected }, + ); +} +function notStrictEqual( + actual: unknown, + expected: unknown, + message?: string | Error, +) { + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("actual", "expected"); + } + + toNode( + () => asserts.assertNotStrictEquals(actual, expected), + { message, actual, expected, operator: "notStrictEqual" }, + ); +} + +function deepEqual( + actual: unknown, + expected: unknown, + message?: string | Error, +) { + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("actual", "expected"); + } + + if (!isDeepEqual(actual, expected)) { + innerFail({ actual, expected, message, operator: "deepEqual" }); + } +} +function notDeepEqual( + actual: unknown, + expected: unknown, + message?: string | Error, +) { + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("actual", "expected"); + } + + if (isDeepEqual(actual, expected)) { + innerFail({ actual, expected, message, operator: "notDeepEqual" }); + } +} +function deepStrictEqual( + actual: unknown, + expected: unknown, + message?: string | Error, +) { + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("actual", "expected"); + } + + toNode( + () => asserts.assertEquals(actual, expected), + { message, actual, expected, operator: "deepStrictEqual" }, + ); +} +function notDeepStrictEqual( + actual: unknown, + expected: unknown, + message?: string | Error, +) { + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("actual", "expected"); + } + + toNode( + () => asserts.assertNotEquals(actual, expected), + { message, actual, expected, operator: "deepNotStrictEqual" }, + ); +} + +function fail(message?: string | Error): never { + if (typeof message === "string" || message == null) { + throw createAssertionError({ + message: message ?? "Failed", + operator: "fail", + generatedMessage: message == null, + }); + } else { + throw message; + } +} +function match(actual: string, regexp: RegExp, message?: string | Error) { + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("actual", "regexp"); + } + if (!(regexp instanceof RegExp)) { + throw new ERR_INVALID_ARG_TYPE("regexp", "RegExp", regexp); + } + + toNode( + () => asserts.assertMatch(actual, regexp), + { message, actual, expected: regexp, operator: "match" }, + ); +} + +function doesNotMatch( + string: string, + regexp: RegExp, + message?: string | Error, +) { + if (arguments.length < 2) { + throw new ERR_MISSING_ARGS("string", "regexp"); + } + if (!(regexp instanceof RegExp)) { + throw new ERR_INVALID_ARG_TYPE("regexp", "RegExp", regexp); + } + if (typeof string !== "string") { + if (message instanceof Error) { + throw message; + } + throw new AssertionError({ + message: message || + `The "string" argument must be of type string. Received type ${typeof string} (${ + inspect(string) + })`, + actual: string, + expected: regexp, + operator: "doesNotMatch", + }); + } + + toNode( + () => asserts.assertNotMatch(string, regexp), + { message, actual: string, expected: regexp, operator: "doesNotMatch" }, + ); +} + +function strict(actual: unknown, message?: string | Error): asserts actual { + if (arguments.length === 0) { + throw new AssertionError({ + message: "No value argument passed to `assert.ok()`", + }); + } + assert(actual, message); +} + +function rejects( + // deno-lint-ignore no-explicit-any + asyncFn: Promise<any> | (() => Promise<any>), + error?: RegExp | Function | Error, +): Promise<void>; + +function rejects( + // deno-lint-ignore no-explicit-any + asyncFn: Promise<any> | (() => Promise<any>), + message?: string, +): Promise<void>; + +// Intentionally avoid using async/await because test-assert-async.js requires it +function rejects( + // deno-lint-ignore no-explicit-any + asyncFn: Promise<any> | (() => Promise<any>), + error?: RegExp | Function | Error | string, + message?: string, +) { + let promise: Promise<void>; + if (typeof asyncFn === "function") { + try { + promise = asyncFn(); + } catch (err) { + // If `asyncFn` throws an error synchronously, this function returns a rejected promise. + return Promise.reject(err); + } + + if (!isValidThenable(promise)) { + return Promise.reject( + new ERR_INVALID_RETURN_VALUE( + "instance of Promise", + "promiseFn", + promise, + ), + ); + } + } else if (!isValidThenable(asyncFn)) { + return Promise.reject( + new ERR_INVALID_ARG_TYPE("promiseFn", ["function", "Promise"], asyncFn), + ); + } else { + promise = asyncFn; + } + + function onFulfilled() { + let message = "Missing expected rejection"; + if (typeof error === "string") { + message += `: ${error}`; + } else if (typeof error === "function" && error.prototype !== undefined) { + message += ` (${error.name}).`; + } else { + message += "."; + } + return Promise.reject(createAssertionError({ + message, + operator: "rejects", + generatedMessage: true, + })); + } + + // deno-lint-ignore camelcase + function rejects_onRejected(e: Error) { // TODO(uki00a): In order to `test-assert-async.js` pass, intentionally adds `rejects_` as a prefix. + if ( + validateThrownError(e, error, message, { + operator: rejects, + validationFunctionName: "validate", + }) + ) { + return; + } + } + + return promise.then(onFulfilled, rejects_onRejected); +} + +function doesNotReject( + // deno-lint-ignore no-explicit-any + asyncFn: Promise<any> | (() => Promise<any>), + error?: RegExp | Function, +): Promise<void>; + +function doesNotReject( + // deno-lint-ignore no-explicit-any + asyncFn: Promise<any> | (() => Promise<any>), + message?: string, +): Promise<void>; + +// Intentionally avoid using async/await because test-assert-async.js requires it +function doesNotReject( + // deno-lint-ignore no-explicit-any + asyncFn: Promise<any> | (() => Promise<any>), + error?: RegExp | Function | string, + message?: string, +) { + // deno-lint-ignore no-explicit-any + let promise: Promise<any>; + if (typeof asyncFn === "function") { + try { + const value = asyncFn(); + if (!isValidThenable(value)) { + return Promise.reject( + new ERR_INVALID_RETURN_VALUE( + "instance of Promise", + "promiseFn", + value, + ), + ); + } + promise = value; + } catch (e) { + // If `asyncFn` throws an error synchronously, this function returns a rejected promise. + return Promise.reject(e); + } + } else if (!isValidThenable(asyncFn)) { + return Promise.reject( + new ERR_INVALID_ARG_TYPE("promiseFn", ["function", "Promise"], asyncFn), + ); + } else { + promise = asyncFn; + } + + return promise.then( + () => {}, + (e) => gotUnwantedException(e, error, message, doesNotReject), + ); +} + +function gotUnwantedException( + // deno-lint-ignore no-explicit-any + e: any, + expected: RegExp | Function | string | null | undefined, + message: string | Error | null | undefined, + operator: Function, +): never { + if (typeof expected === "string") { + // The use case of doesNotThrow(fn, message); + throw new AssertionError({ + message: + `Got unwanted exception: ${expected}\nActual message: "${e.message}"`, + operator: operator.name, + }); + } else if ( + typeof expected === "function" && expected.prototype !== undefined + ) { + // The use case of doesNotThrow(fn, Error, message); + if (e instanceof expected) { + let msg = `Got unwanted exception: ${e.constructor?.name}`; + if (message) { + msg += ` ${String(message)}`; + } + throw new AssertionError({ + message: msg, + operator: operator.name, + }); + } else if (expected.prototype instanceof Error) { + throw e; + } else { + const result = expected(e); + if (result === true) { + let msg = `Got unwanted rejection.\nActual message: "${e.message}"`; + if (message) { + msg += ` ${String(message)}`; + } + throw new AssertionError({ + message: msg, + operator: operator.name, + }); + } + } + throw e; + } else { + if (message) { + throw new AssertionError({ + message: `Got unwanted exception: ${message}\nActual message: "${ + e ? e.message : String(e) + }"`, + operator: operator.name, + }); + } + throw new AssertionError({ + message: `Got unwanted exception.\nActual message: "${ + e ? e.message : String(e) + }"`, + operator: operator.name, + }); + } +} + +/** + * Throws `value` if the value is not `null` or `undefined`. + * + * @param err + */ +// deno-lint-ignore no-explicit-any +function ifError(err: any) { + if (err !== null && err !== undefined) { + let message = "ifError got unwanted exception: "; + + if (typeof err === "object" && typeof err.message === "string") { + if (err.message.length === 0 && err.constructor) { + message += err.constructor.name; + } else { + message += err.message; + } + } else { + message += inspect(err); + } + + const newErr = new AssertionError({ + actual: err, + expected: null, + operator: "ifError", + message, + stackStartFn: ifError, + }); + + // Make sure we actually have a stack trace! + const origStack = err.stack; + + if (typeof origStack === "string") { + // This will remove any duplicated frames from the error frames taken + // from within `ifError` and add the original error frames to the newly + // created ones. + const tmp2 = origStack.split("\n"); + tmp2.shift(); + + // Filter all frames existing in err.stack. + let tmp1 = newErr!.stack?.split("\n"); + + for (const errFrame of tmp2) { + // Find the first occurrence of the frame. + const pos = tmp1?.indexOf(errFrame); + + if (pos !== -1) { + // Only keep new frames. + tmp1 = tmp1?.slice(0, pos); + + break; + } + } + + newErr.stack = `${tmp1?.join("\n")}\n${tmp2.join("\n")}`; + } + + throw newErr; + } +} + +interface ValidateThrownErrorOptions { + operator: Function; + validationFunctionName?: string; +} + +function validateThrownError( + // deno-lint-ignore no-explicit-any + e: any, + error: RegExp | Function | Error | string | null | undefined, + message: string | undefined | null, + options: ValidateThrownErrorOptions, +): boolean { + if (typeof error === "string") { + if (message != null) { + throw new ERR_INVALID_ARG_TYPE( + "error", + ["Object", "Error", "Function", "RegExp"], + error, + ); + } else if (typeof e === "object" && e !== null) { + if (e.message === error) { + throw new ERR_AMBIGUOUS_ARGUMENT( + "error/message", + `The error message "${e.message}" is identical to the message.`, + ); + } + } else if (e === error) { + throw new ERR_AMBIGUOUS_ARGUMENT( + "error/message", + `The error "${e}" is identical to the message.`, + ); + } + message = error; + error = undefined; + } + if ( + error instanceof Function && error.prototype !== undefined && + error.prototype instanceof Error + ) { + // error is a constructor + if (e instanceof error) { + return true; + } + throw createAssertionError({ + message: + `The error is expected to be an instance of "${error.name}". Received "${e?.constructor?.name}"\n\nError message:\n\n${e?.message}`, + actual: e, + expected: error, + operator: options.operator.name, + generatedMessage: true, + }); + } + if (error instanceof Function) { + const received = error(e); + if (received === true) { + return true; + } + throw createAssertionError({ + message: `The ${ + options.validationFunctionName + ? `"${options.validationFunctionName}" validation` + : "validation" + } function is expected to return "true". Received ${ + inspect(received) + }\n\nCaught error:\n\n${e}`, + actual: e, + expected: error, + operator: options.operator.name, + generatedMessage: true, + }); + } + if (error instanceof RegExp) { + if (error.test(String(e))) { + return true; + } + throw createAssertionError({ + message: + `The input did not match the regular expression ${error.toString()}. Input:\n\n'${ + String(e) + }'\n`, + actual: e, + expected: error, + operator: options.operator.name, + generatedMessage: true, + }); + } + if (typeof error === "object" && error !== null) { + const keys = Object.keys(error); + if (error instanceof Error) { + keys.push("name", "message"); + } + for (const k of keys) { + if (e == null) { + throw createAssertionError({ + message: message || "object is expected to thrown, but got null", + actual: e, + expected: error, + operator: options.operator.name, + generatedMessage: message == null, + }); + } + + if (typeof e === "string") { + throw createAssertionError({ + message: message || + `object is expected to thrown, but got string: ${e}`, + actual: e, + expected: error, + operator: options.operator.name, + generatedMessage: message == null, + }); + } + if (typeof e === "number") { + throw createAssertionError({ + message: message || + `object is expected to thrown, but got number: ${e}`, + actual: e, + expected: error, + operator: options.operator.name, + generatedMessage: message == null, + }); + } + if (!(k in e)) { + throw createAssertionError({ + message: message || `A key in the expected object is missing: ${k}`, + actual: e, + expected: error, + operator: options.operator.name, + generatedMessage: message == null, + }); + } + const actual = e[k]; + // deno-lint-ignore no-explicit-any + const expected = (error as any)[k]; + if (typeof actual === "string" && expected instanceof RegExp) { + match(actual, expected); + } else { + deepStrictEqual(actual, expected); + } + } + return true; + } + if (typeof error === "undefined") { + return true; + } + throw createAssertionError({ + message: `Invalid expectation: ${error}`, + operator: options.operator.name, + generatedMessage: true, + }); +} + +// deno-lint-ignore no-explicit-any +function isValidThenable(maybeThennable: any): boolean { + if (!maybeThennable) { + return false; + } + + if (maybeThennable instanceof Promise) { + return true; + } + + const isThenable = typeof maybeThennable.then === "function" && + typeof maybeThennable.catch === "function"; + + return isThenable && typeof maybeThennable !== "function"; +} + +Object.assign(strict, { + AssertionError, + deepEqual: deepStrictEqual, + deepStrictEqual, + doesNotMatch, + doesNotReject, + doesNotThrow, + equal: strictEqual, + fail, + ifError, + match, + notDeepEqual: notDeepStrictEqual, + notDeepStrictEqual, + notEqual: notStrictEqual, + notStrictEqual, + ok, + rejects, + strict, + strictEqual, + throws, +}); + +export default Object.assign(assert, { + AssertionError, + deepEqual, + deepStrictEqual, + doesNotMatch, + doesNotReject, + doesNotThrow, + equal, + fail, + ifError, + match, + notDeepEqual, + notDeepStrictEqual, + notEqual, + notStrictEqual, + ok, + rejects, + strict, + strictEqual, + throws, +}); + +export { + AssertionError, + deepEqual, + deepStrictEqual, + doesNotMatch, + doesNotReject, + doesNotThrow, + equal, + fail, + ifError, + match, + notDeepEqual, + notDeepStrictEqual, + notEqual, + notStrictEqual, + ok, + rejects, + strict, + strictEqual, + throws, +}; |