Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(assert): type narrowing for assertLess, assertLessOrEqual, asserGreater, assertGreaterOrEqual #6245

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion assert/almost_equals_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Deno.test("assertAlmostEquals() throws values outside higher precision range", (
);
});

Deno.test("assertAlmostEquals() matches infinity with inifinity", () => {
Deno.test("assertAlmostEquals() matches infinity with infinity", () => {
assertAlmostEquals(Infinity, Infinity);
});

Expand Down
2 changes: 1 addition & 1 deletion assert/array_includes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { equal } from "./equal.ts";
import { format } from "@std/internal/format";
import { format } from "../internal/format.ts";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This relative import doesn't work when published to JSR as each package is published independently

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made that change because the @std import led to this error when using the repo with file:/// imports from 3rd party code:

Relative import path "@std/internal/build-message" not prefixed with / or ./ or ../ and not in import map

Is there a syntax that would work in both cases, or should I revert the change from the first commit of this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess an import map in the 3rd party code would solve the problem. Feel free to cherry-pick / ignore the first commit.

import { AssertionError } from "./assertion_error.ts";

/** An array-like object (`Array`, `Uint8Array`, `NodeList`, etc.) that is not a string */
Expand Down
8 changes: 4 additions & 4 deletions assert/equals.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { equal } from "./equal.ts";
import { buildMessage } from "@std/internal/build-message";
import { diff } from "@std/internal/diff";
import { diffStr } from "@std/internal/diff-str";
import { format } from "@std/internal/format";
import { buildMessage } from "../internal/build_message.ts";
import { diff } from "../internal/diff.ts";
import { diffStr } from "../internal/diff_str.ts";
import { format } from "../internal/format.ts";

import { AssertionError } from "./assertion_error.ts";

Expand Down
11 changes: 8 additions & 3 deletions assert/greater.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { format } from "@std/internal/format";
import { format } from "../internal/format.ts";
import { AssertionError } from "./assertion_error.ts";

/**
Expand All @@ -21,8 +21,13 @@ import { AssertionError } from "./assertion_error.ts";
* @param expected The expected value to compare.
* @param msg The optional message to display if the assertion fails.
*/
export function assertGreater<T>(actual: T, expected: T, msg?: string) {
if (actual > expected) return;
export function assertGreater<T>(
actual: Exclude<T, undefined> | null,
expected: NonNullable<T>,
msg?: string,
): asserts actual is Exclude<T, undefined> | null {
// Coerce null to 0 to avoid "Object is possibly null"
if ((actual ?? 0) > expected) return;

const actualString = format(actual);
const expectedString = format(expected);
Expand Down
11 changes: 6 additions & 5 deletions assert/greater_or_equal.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { format } from "@std/internal/format";
import { format } from "../internal/format.ts";
import { AssertionError } from "./assertion_error.ts";

/**
Expand All @@ -22,11 +22,12 @@ import { AssertionError } from "./assertion_error.ts";
* @param msg The optional message to display if the assertion fails.
*/
export function assertGreaterOrEqual<T>(
actual: T,
expected: T,
actual: Exclude<T, undefined> | null,
expected: NonNullable<T>,
msg?: string,
) {
if (actual >= expected) return;
): asserts actual is Exclude<T, undefined> | null {
// Coerce null to 0 to avoid "Object is possibly null"
if ((actual ?? 0) >= expected) return;

const actualString = format(actual);
const expectedString = format(expected);
Expand Down
30 changes: 30 additions & 0 deletions assert/greater_or_equal_test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,41 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { assertGreaterOrEqual, assertThrows } from "./mod.ts";
import { assertType, type IsExact } from "../testing/types.ts";

Deno.test("assertGreaterOrEqual() matches when actual value is greater or equal than expected value", () => {
assertGreaterOrEqual(2, 1);
assertGreaterOrEqual(1n, 1n);
assertGreaterOrEqual(1.1, 1);
assertGreaterOrEqual(null, 0); // coerced to 0
});

Deno.test("assertGreaterOrEqual() throws when actual value is smaller than expected value", () => {
assertThrows(() => assertGreaterOrEqual(1, 2));
assertThrows(() => assertGreaterOrEqual(null, 1));

// Compile-time errors
// assertThrows(() => assertGreater(undefined, 1));
// assertThrows(() => assertGreater(0, null));
});

Deno.test("assertGreaterOrEqual() on strings", () => {
// Strings
assertGreaterOrEqual("", "");
assertThrows(() => assertGreaterOrEqual("", "a"));
assertThrows(() => assertGreaterOrEqual(null, "a"));
});

Deno.test("assertGreater type narrowing", () => {
const n = 0 as number | undefined;
// @ts-expect-error -- `undefined` not allowed for n; disable to see compile-time error below
assertGreaterOrEqual(n, 0); // `undefined` narrowed out
assertType<IsExact<typeof n, number>>(true);
const s = "" as string | undefined;
// @ts-expect-error -- `undefined` not allowed for s
assertGreaterOrEqual(s, ""); // `undefined` narrowed out
assertType<IsExact<typeof s, string>>(true);
const b = false as boolean | undefined;
// @ts-expect-error -- `undefined` not allowed for b
assertGreaterOrEqual(b, false); // `undefined` narrowed out
assertType<IsExact<typeof b, boolean>>(true);
});
29 changes: 29 additions & 0 deletions assert/greater_test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,41 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { assertGreater, assertThrows } from "./mod.ts";
import { assertType, type IsExact } from "../testing/types.ts";

Deno.test("assertGreaterOrEqual() matches when actual value is greater than expected value", () => {
assertGreater(2, 1);
assertGreater(2n, 1n);
assertGreater(1.1, 1);
assertGreater(null, -1); // coerced to 0
});

Deno.test("assertGreaterOrEqual() throws when actual value is smaller or equal than expected value", () => {
assertThrows(() => assertGreater(1, 2));
assertThrows(() => assertGreater(null, 0));

// Compile-time errors
// assertThrows(() => assertGreater(undefined, 1));
// assertThrows(() => assertGreater(0, null));
});

Deno.test("assertGreater() on strings", () => {
// Strings
assertGreater("b", "a");
assertThrows(() => assertGreater("", "a"));
assertThrows(() => assertGreater(null, "a"));
});

Deno.test("assertGreater type narrowing", () => {
const n = 0 as number | undefined;
// @ts-expect-error -- `undefined` not allowed for n; disable to see compile-time error below
assertGreater(n, -1); // `undefined` narrowed out
assertType<IsExact<typeof n, number>>(true);
const s = "a" as string | undefined;
// @ts-expect-error -- `undefined` not allowed for s
assertGreater(s, ""); // `undefined` narrowed out
assertType<IsExact<typeof s, string>>(true);
const b = true as boolean | undefined;
// @ts-expect-error -- `undefined` not allowed for b
assertGreater(b, false); // `undefined` narrowed out
assertType<IsExact<typeof b, boolean>>(true);
});
2 changes: 1 addition & 1 deletion assert/is_error.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { AssertionError } from "./assertion_error.ts";
import { stripAnsiCode } from "@std/internal/styles";
import { stripAnsiCode } from "../internal/styles.ts";

/**
* Make an assertion that `error` is an `Error`.
Expand Down
11 changes: 8 additions & 3 deletions assert/less.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { format } from "@std/internal/format";
import { format } from "../internal/format.ts";
import { AssertionError } from "./assertion_error.ts";

/**
Expand All @@ -20,8 +20,13 @@ import { AssertionError } from "./assertion_error.ts";
* @param expected The expected value to compare.
* @param msg The optional message to display if the assertion fails.
*/
export function assertLess<T>(actual: T, expected: T, msg?: string) {
if (actual < expected) return;
export function assertLess<T>(
actual: Exclude<T, undefined> | null,
expected: NonNullable<T>,
msg?: string,
): asserts actual is Exclude<T, undefined> | null {
// Coerce null to 0 to avoid "Object is possibly null"
if ((actual ?? 0) < expected) return;

const actualString = format(actual);
const expectedString = format(expected);
Expand Down
11 changes: 6 additions & 5 deletions assert/less_or_equal.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { format } from "@std/internal/format";
import { format } from "../internal/format.ts";
import { AssertionError } from "./assertion_error.ts";

/**
Expand All @@ -22,11 +22,12 @@ import { AssertionError } from "./assertion_error.ts";
* @param msg The optional message to display if the assertion fails.
*/
export function assertLessOrEqual<T>(
actual: T,
expected: T,
actual: Exclude<T, undefined> | null,
expected: NonNullable<T>,
msg?: string,
) {
if (actual <= expected) return;
): asserts actual is Exclude<T, undefined> | null {
// Coerce null to 0 to avoid "Object is possibly null"
if ((actual ?? 0) <= expected) return;

const actualString = format(actual);
const expectedString = format(expected);
Expand Down
34 changes: 32 additions & 2 deletions assert/less_or_equal_test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,39 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { assertLessOrEqual, assertThrows } from "./mod.ts";
import { assertType, type IsExact } from "../testing/types.ts";

Deno.test("assertLessOrEqual", () => {
Deno.test("assertLessOrEqualOrEqual", () => {
// Numbers
assertLessOrEqual(1, 2);
assertLessOrEqual(1n, 1n);
assertLessOrEqual(1n, 2n);
assertLessOrEqual(1, 1.1);
assertLessOrEqual(null, 1); // coerced to 0

// Failures
assertThrows(() => assertLessOrEqual(2, 1));
assertThrows(() => assertLessOrEqual(null, -1));

// Compile-time errors
// assertThrows(() => assertLessOrEqual(undefined, 1));
// assertThrows(() => assertLessOrEqual(0, null));

// Strings
assertLessOrEqual("a", "a");
assertThrows(() => assertLessOrEqual("a", ""));
assertThrows(() => assertLessOrEqual(null, "a"));
});

Deno.test("assertLessOrEqualOrEqual() type narrowing", () => {
const n = 0 as number | undefined;
// @ts-expect-error -- `undefined` not allowed for n
assertLessOrEqual(n, 0); // `undefined` narrowed out
assertType<IsExact<typeof n, number>>(true);
const s = "" as string | undefined;
// @ts-expect-error -- `undefined` not allowed for s
assertLessOrEqual(s, ""); // `undefined` narrowed out
assertType<IsExact<typeof s, string>>(true);
const b = false as boolean | undefined;
// @ts-expect-error -- `undefined` not allowed for b
assertLessOrEqual(b, false); // `undefined` narrowed out
assertType<IsExact<typeof b, boolean>>(true);
});
29 changes: 29 additions & 0 deletions assert/less_test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,39 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { assertLess, assertThrows } from "./mod.ts";
import { assertType, type IsExact } from "../testing/types.ts";

Deno.test("assertLess", () => {
// Numbers
assertLess(1, 2);
assertLess(1n, 2n);
assertLess(1, 1.1);
assertLess(null, 1); // coerced to 0

// Failures
assertThrows(() => assertLess(2, 1));
assertThrows(() => assertLess(null, -1));

// Compile-time errors
// assertThrows(() => assertLess(undefined, 1));
// assertThrows(() => assertLess(-1, null));

// Strings
assertLess("a", "b");
assertThrows(() => assertLess("a", ""));
assertThrows(() => assertLess(null, "a"));
});

Deno.test("assertLess() type narrowing", () => {
const n = 0 as number | undefined;
// @ts-expect-error -- `undefined` not allowed for n; disable to see compile-time error below
assertLess(n, 1); // `undefined` narrowed out
assertType<IsExact<typeof n, number>>(true);
const s = "" as string | undefined;
// @ts-expect-error -- `undefined` not allowed for s
assertLess(s, "a"); // `undefined` narrowed out
assertType<IsExact<typeof s, string>>(true);
const b = false as boolean | undefined;
// @ts-expect-error -- `undefined` not allowed for b
assertLess(b, true); // `undefined` narrowed out
assertType<IsExact<typeof b, boolean>>(true);
});
2 changes: 1 addition & 1 deletion assert/not_strict_equals.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { AssertionError } from "./assertion_error.ts";
import { format } from "@std/internal/format";
import { format } from "../internal/format.ts";

/**
* Make an assertion that `actual` and `expected` are not strictly equal, using
Expand Down
10 changes: 5 additions & 5 deletions assert/strict_equals.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { buildMessage } from "@std/internal/build-message";
import { diff } from "@std/internal/diff";
import { diffStr } from "@std/internal/diff-str";
import { format } from "@std/internal/format";
import { red } from "@std/internal/styles";
import { buildMessage } from "../internal/build_message.ts";
import { diff } from "../internal/diff.ts";
import { diffStr } from "../internal/diff_str.ts";
import { format } from "../internal/format.ts";
import { red } from "../internal/styles.ts";
import { AssertionError } from "./assertion_error.ts";

/**
Expand Down
Loading