Can't seem to be able to assert instances of string
in Deno:
import {
assertInstanceOf
} from "https://deno.land/std@0.174.0/testing/asserts.ts";
assertInstanceOf( "foo", string );
Throws:
error: TS2693 [ERROR]: 'string' only refers to a type, but is being used as a value here.
assertInstanceOf( "foo", string );
~~~~~~
at file:///home/jmerelo/Code/my-stackoverflow-examples/js/string-assert.ts:6:26
Fair enough, let's try this
assertInstanceOf( "foo", String );
Now I'm confused:
Uncaught error from ./string-assert.ts FAILED
ERRORS
./string-assert.ts (uncaught error)
error: AssertionError: Expected object to be an instance of "String" but was "string".
Any idea of what would be the correct type here?
There's clearly a workaround here, to use typeof
. But I would like to know what's the solution to this Catch-22
There's clearly a workaround here, to use
typeof
. But I would like to know what's the solution to this Catch-22
It's not a Catch-22, but a false premise. In JavaScript: while primitives do appear object-like in some aspects, they are not objects (see JavaScript data types and data structures) — therefore they are not useful operands for use with the instanceof
operator because the evaluation will always be false
(see spec):
instanceof
The instanceof operator tests to see if the prototype property of a constructor appears anywhere in the prototype chain of an object.
For strings, this is explained further on MDN's String
article in the section String primitives and String objects.
Below is a code example demonstrating how to discriminate and assert whether a value is a string literal or an object instance of String
using Deno's standard testing library and user-defined type guard functions.
module.ts
:
import { assert } from "https://deno.land/std@0.175.0/testing/asserts.ts";
function isStringLiteral(actual: unknown): actual is string {
return typeof actual === "string";
}
function isStringInstance(actual: unknown): actual is String {
return typeof actual === "object" && actual instanceof String;
}
function isAnyString(actual: unknown): actual is string | String {
return isStringLiteral(actual) || isStringInstance(actual);
}
const testCases: [name: string, value: unknown][] = [
["string literal", "foo"],
["string instance", new String("foo")],
["number literal", 42],
["number instance", new Number(42)],
];
console.log("Testing for string types...");
for (const [name, value] of testCases) {
try {
assert(isAnyString(value));
console.log("✅", name);
} catch {
console.error("❌", name);
continue;
}
try {
assert(isStringLiteral(value));
console.log("type:", "literal");
} catch {
assert(isStringInstance(value));
console.log("type:", "instance");
}
}
Output:
% deno --version
deno 1.30.0 (release, x86_64-apple-darwin)
v8 10.9.194.5
typescript 4.9.4
% deno check module.ts
% echo $?
0
% deno run module.ts
Testing for string types...
✅ string literal
type: literal
✅ string instance
type: instance
❌ number literal
❌ number instance
Compiled JavaScript with imports inlined:
// import { assert } from "https://deno.land/std@0.175.0/testing/asserts.ts";
// ---> Begin inlined imports
// https://deno.land/std@0.175.0/testing/asserts.ts?source#L19
class AssertionError extends Error {
name = "AssertionError";
constructor(message) {
super(message);
}
}
// https://deno.land/std@0.175.0/testing/asserts.ts?source#L138
/** Make an assertion, error will be thrown if `expr` does not have truthy value. */
function assert(expr, msg = "") {
if (!expr) {
throw new AssertionError(msg);
}
}
// <--- End inlined imports
function isStringLiteral(actual) {
return typeof actual === "string";
}
function isStringInstance(actual) {
return typeof actual === "object" && actual instanceof String;
}
function isAnyString(actual) {
return isStringLiteral(actual) || isStringInstance(actual);
}
const testCases = [["string literal", "foo"], ["string instance", new String("foo")], ["number literal", 42], ["number instance", new Number(42)]];
console.log("Testing for string types...");
for (const [name, value] of testCases) {
try {
assert(isAnyString(value));
console.log("✅", name);
} catch {
console.error("❌", name);
continue;
}
try {
assert(isStringLiteral(value));
console.log("type:", "literal");
} catch {
assert(isStringInstance(value));
console.log("type:", "instance");
}
}