Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript type definition for RegExpMatchArray with a strict null check

Tags:

typescript

Problem

I'm having some trouble with an iterable object (if my understanding is correct) in Typescript strict mode (strict null checks). I want to use the returned object of 'String.prototype.match()'.

const matchLetter: RegExpMatchArray | null = points[0].match(/[a-zA-Z]/);

const direction: Tdirection = matchLetter[0];

// Two errors:
// 1. Object is possibly 'null'. ts(2531)
// 2. Type 'string' is not assignable to type 'Tdirection'. ts(2322)

What I'm trying to do:

// don't change RegExpMatchArray, it's from typescript lib.es5.d.ts
interface RegExpMatchArray extends Array<string> { index?: number; input?: string; }

// custom types
type Tlocation = { x: number; y: number };
type Tdirection = "R" | "U" | "L" | "D";

// demo data
const pathToPlot = [["R0", "R1", "R2"],["U0", "U1"],["L0"],["D0"]];

// demo operations
const operations = {
  R: (index: number, lastLocation: Tlocation) => { return { x: lastLocation.x + index, y: lastLocation.y }},
  U: (index: number, lastLocation: Tlocation) => { return { x: lastLocation.x + index, y: lastLocation.y }},
  L: (index: number, lastLocation: Tlocation) => { return { x: lastLocation.x - index, y: lastLocation.y }},
  D: (index: number, lastLocation: Tlocation) => { return { x: lastLocation.x, y: lastLocation.y - index }}
};

pathToPlot.forEach(points => {
  // In JS I did it like this: 
  // const direction = points[0].match(/[a-zA-Z]/)[0];

  // Typescript equivalent?
  const matchLetter: RegExpMatchArray | null = points[0].match(/[a-zA-Z]/);

  // This is giving errors: 
  const direction: Tdirection = matchLetter[0];

  // two errors:
  // 1. Object is possibly 'null'. ts(2531)
  // 2. Type 'string' is not assignable to type 'Tdirection'. ts(2322)

  console.log(operations[direction](1, { x: 0, y: 0 }));
});

Live code example: https://codesandbox.io/s/reverent-thunder-s2wzn


What I did so far:

I've read a couple of articles on this matter.

A suggestion was to define a new type for the RegExpArray (described here). But that seems odd to re-define an existing type. I'd rather use the existing one and perform my own logic around it to make it pass.

I also read the article "How to avoid null check pollution in Javascript: use Optionals" (article). It states that you can use a library which provides a method to check if the value is null. If so it handles it with an error message and then it returns something else.

Isn't there a way without the use of a library?

like image 749
Remi Avatar asked Sep 17 '25 20:09

Remi


1 Answers

You have to check for null first:

  const matchLetter: RegExpMatchArray | null = points[0].match(/[a-zA-Z]/);

  if (matchLetter) {
    const direction: Tdirection = matchLetter[0];
  }

If Typescript does not automatically recognize that the matchLetter value is already checked then make it explicit:

  const matchLetter: RegExpMatchArray | null = points[0].match(/[a-zA-Z]/);

  if (matchLetter) {
    const direction: Tdirection = matchLetter![0] as Tdirection;
  }

The trailing ! is a socalled non-null-assertion-operator which makes it explicit that a nullable variable will contain a value at this point. Typescript should not require that if we have the type guard before the access to matchLetter. But I have seen cases where the Typescript linter still complained.

The error on direction is clear since you are trying to assign a generic string to a string enumeration. I have changed the code above to use an as cast to silence the linter.

Once you changed the direction assignment, you will also need a change in the operations expression at the end:

  var direction: Tdirection | undefined;
  if (matchLetter) {
    direction = matchLetter[0] as Tdirection;
  }

  if (direction) {
    console.log(operations[direction](1, { x: 0, y: 0 }));
  }
like image 189
Mike Lischke Avatar answered Sep 19 '25 10:09

Mike Lischke