Skip to content

Commit 79e9937

Browse files
committed
Stricter typing for assertLess, assertLessOrEqual, asserGreater, assertGreaterOrEqual
- disallow undefined actual values because any comparison with undefined returns false - disallow null expected values - narrow the returned type to exclude undefined
1 parent 73e6595 commit 79e9937

8 files changed

+144
-14
lines changed

assert/greater.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,13 @@ import { AssertionError } from "./assertion_error.ts";
2121
* @param expected The expected value to compare.
2222
* @param msg The optional message to display if the assertion fails.
2323
*/
24-
export function assertGreater<T>(actual: T, expected: T, msg?: string) {
25-
if (actual > expected) return;
24+
export function assertGreater<T>(
25+
actual: Exclude<T, undefined> | null,
26+
expected: NonNullable<T>,
27+
msg?: string,
28+
): asserts actual is Exclude<T, undefined> | null {
29+
// Coerce null to 0 to avoid "Object is possibly null"
30+
if ((actual ?? 0) > expected) return;
2631

2732
const actualString = format(actual);
2833
const expectedString = format(expected);

assert/greater_or_equal.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ import { AssertionError } from "./assertion_error.ts";
2222
* @param msg The optional message to display if the assertion fails.
2323
*/
2424
export function assertGreaterOrEqual<T>(
25-
actual: T,
26-
expected: T,
25+
actual: Exclude<T, undefined> | null,
26+
expected: NonNullable<T>,
2727
msg?: string,
28-
) {
29-
if (actual >= expected) return;
28+
): asserts actual is Exclude<T, undefined> | null {
29+
// Coerce null to 0 to avoid "Object is possibly null"
30+
if ((actual ?? 0) >= expected) return;
3031

3132
const actualString = format(actual);
3233
const expectedString = format(expected);

assert/greater_or_equal_test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,41 @@
11
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
22
import { assertGreaterOrEqual, assertThrows } from "./mod.ts";
3+
import { assertType, type IsExact } from "../testing/types.ts";
34

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

912
Deno.test("assertGreaterOrEqual() throws when actual value is smaller than expected value", () => {
1013
assertThrows(() => assertGreaterOrEqual(1, 2));
14+
assertThrows(() => assertGreaterOrEqual(null, 1));
15+
16+
// Compile-time errors
17+
// assertThrows(() => assertGreater(undefined, 1));
18+
// assertThrows(() => assertGreater(0, null));
19+
});
20+
21+
Deno.test("assertGreaterOrEqual() on strings", () => {
22+
// Strings
23+
assertGreaterOrEqual("", "");
24+
assertThrows(() => assertGreaterOrEqual("", "a"));
25+
assertThrows(() => assertGreaterOrEqual(null, "a"));
26+
});
27+
28+
Deno.test("assertGreater type narrowing", () => {
29+
const n = 0 as number | undefined;
30+
// @ts-expect-error -- `undefined` not allowed for n; disable to see compile-time error below
31+
assertGreaterOrEqual(n, 0); // `undefined` narrowed out
32+
assertType<IsExact<typeof n, number>>(true);
33+
const s = "" as string | undefined;
34+
// @ts-expect-error -- `undefined` not allowed for s
35+
assertGreaterOrEqual(s, ""); // `undefined` narrowed out
36+
assertType<IsExact<typeof s, string>>(true);
37+
const b = false as boolean | undefined;
38+
// @ts-expect-error -- `undefined` not allowed for b
39+
assertGreaterOrEqual(b, false); // `undefined` narrowed out
40+
assertType<IsExact<typeof b, boolean>>(true);
1141
});

assert/greater_test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,41 @@
11
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
22
import { assertGreater, assertThrows } from "./mod.ts";
3+
import { assertType, type IsExact } from "../testing/types.ts";
34

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

1012
Deno.test("assertGreaterOrEqual() throws when actual value is smaller or equal than expected value", () => {
1113
assertThrows(() => assertGreater(1, 2));
14+
assertThrows(() => assertGreater(null, 0));
15+
16+
// Compile-time errors
17+
// assertThrows(() => assertGreater(undefined, 1));
18+
// assertThrows(() => assertGreater(0, null));
19+
});
20+
21+
Deno.test("assertGreater() on strings", () => {
22+
// Strings
23+
assertGreater("b", "a");
24+
assertThrows(() => assertGreater("", "a"));
25+
assertThrows(() => assertGreater(null, "a"));
26+
});
27+
28+
Deno.test("assertGreater type narrowing", () => {
29+
const n = 0 as number | undefined;
30+
// @ts-expect-error -- `undefined` not allowed for n; disable to see compile-time error below
31+
assertGreater(n, -1); // `undefined` narrowed out
32+
assertType<IsExact<typeof n, number>>(true);
33+
const s = "a" as string | undefined;
34+
// @ts-expect-error -- `undefined` not allowed for s
35+
assertGreater(s, ""); // `undefined` narrowed out
36+
assertType<IsExact<typeof s, string>>(true);
37+
const b = true as boolean | undefined;
38+
// @ts-expect-error -- `undefined` not allowed for b
39+
assertGreater(b, false); // `undefined` narrowed out
40+
assertType<IsExact<typeof b, boolean>>(true);
1241
});

assert/less.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,13 @@ import { AssertionError } from "./assertion_error.ts";
2020
* @param expected The expected value to compare.
2121
* @param msg The optional message to display if the assertion fails.
2222
*/
23-
export function assertLess<T>(actual: T, expected: T, msg?: string) {
24-
if (actual < expected) return;
23+
export function assertLess<T>(
24+
actual: Exclude<T, undefined> | null,
25+
expected: NonNullable<T>,
26+
msg?: string,
27+
): asserts actual is Exclude<T, undefined> | null {
28+
// Coerce null to 0 to avoid "Object is possibly null"
29+
if ((actual ?? 0) < expected) return;
2530

2631
const actualString = format(actual);
2732
const expectedString = format(expected);

assert/less_or_equal.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ import { AssertionError } from "./assertion_error.ts";
2222
* @param msg The optional message to display if the assertion fails.
2323
*/
2424
export function assertLessOrEqual<T>(
25-
actual: T,
26-
expected: T,
25+
actual: Exclude<T, undefined> | null,
26+
expected: NonNullable<T>,
2727
msg?: string,
28-
) {
29-
if (actual <= expected) return;
28+
): asserts actual is Exclude<T, undefined> | null {
29+
// Coerce null to 0 to avoid "Object is possibly null"
30+
if ((actual ?? 0) <= expected) return;
3031

3132
const actualString = format(actual);
3233
const expectedString = format(expected);

assert/less_or_equal_test.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,39 @@
11
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
22
import { assertLessOrEqual, assertThrows } from "./mod.ts";
3+
import { assertType, type IsExact } from "../testing/types.ts";
34

4-
Deno.test("assertLessOrEqual", () => {
5+
Deno.test("assertLessOrEqualOrEqual", () => {
6+
// Numbers
57
assertLessOrEqual(1, 2);
6-
assertLessOrEqual(1n, 1n);
8+
assertLessOrEqual(1n, 2n);
9+
assertLessOrEqual(1, 1.1);
10+
assertLessOrEqual(null, 1); // coerced to 0
711

12+
// Failures
813
assertThrows(() => assertLessOrEqual(2, 1));
14+
assertThrows(() => assertLessOrEqual(null, -1));
15+
16+
// Compile-time errors
17+
// assertThrows(() => assertLessOrEqual(undefined, 1));
18+
// assertThrows(() => assertLessOrEqual(0, null));
19+
20+
// Strings
21+
assertLessOrEqual("a", "a");
22+
assertThrows(() => assertLessOrEqual("a", ""));
23+
assertThrows(() => assertLessOrEqual(null, "a"));
24+
});
25+
26+
Deno.test("assertLessOrEqualOrEqual() type narrowing", () => {
27+
const n = 0 as number | undefined;
28+
// @ts-expect-error -- `undefined` not allowed for n
29+
assertLessOrEqual(n, 0); // `undefined` narrowed out
30+
assertType<IsExact<typeof n, number>>(true);
31+
const s = "" as string | undefined;
32+
// @ts-expect-error -- `undefined` not allowed for s
33+
assertLessOrEqual(s, ""); // `undefined` narrowed out
34+
assertType<IsExact<typeof s, string>>(true);
35+
const b = false as boolean | undefined;
36+
// @ts-expect-error -- `undefined` not allowed for b
37+
assertLessOrEqual(b, false); // `undefined` narrowed out
38+
assertType<IsExact<typeof b, boolean>>(true);
939
});

assert/less_test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,39 @@
11
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
22
import { assertLess, assertThrows } from "./mod.ts";
3+
import { assertType, type IsExact } from "../testing/types.ts";
34

45
Deno.test("assertLess", () => {
6+
// Numbers
57
assertLess(1, 2);
68
assertLess(1n, 2n);
79
assertLess(1, 1.1);
10+
assertLess(null, 1); // coerced to 0
811

12+
// Failures
913
assertThrows(() => assertLess(2, 1));
14+
assertThrows(() => assertLess(null, -1));
15+
16+
// Compile-time errors
17+
// assertThrows(() => assertLess(undefined, 1));
18+
// assertThrows(() => assertLess(-1, null));
19+
20+
// Strings
21+
assertLess("a", "b");
22+
assertThrows(() => assertLess("a", ""));
23+
assertThrows(() => assertLess(null, "a"));
24+
});
25+
26+
Deno.test("assertLess() type narrowing", () => {
27+
const n = 0 as number | undefined;
28+
// @ts-expect-error -- `undefined` not allowed for n; disable to see compile-time error below
29+
assertLess(n, 1); // `undefined` narrowed out
30+
assertType<IsExact<typeof n, number>>(true);
31+
const s = "" as string | undefined;
32+
// @ts-expect-error -- `undefined` not allowed for s
33+
assertLess(s, "a"); // `undefined` narrowed out
34+
assertType<IsExact<typeof s, string>>(true);
35+
const b = false as boolean | undefined;
36+
// @ts-expect-error -- `undefined` not allowed for b
37+
assertLess(b, true); // `undefined` narrowed out
38+
assertType<IsExact<typeof b, boolean>>(true);
1039
});

0 commit comments

Comments
 (0)