Home > front end >  Can assert a variable to be an instance of a String in Deno
Can assert a variable to be an instance of a String in Deno

Time:02-01

Can't seem to be able to assert instances of string in Deno:

import {
  assertInstanceOf
} from "https://deno.land/[email protected]/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

CodePudding user response:

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.

TS Playground

module.ts:

import { assert } from "https://deno.land/[email protected]/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/[email protected]/testing/asserts.ts";

// ---> Begin inlined imports

// https://deno.land/[email protected]/testing/asserts.ts?source#L19
class AssertionError extends Error {
  name = "AssertionError";
  constructor(message) {
    super(message);
  }
}

// https://deno.land/[email protected]/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");
  }
}

  • Related