1

I have a multiple variable null check:

const path = window.location.pathname
const regex_list = path.match(/\d+/g) // 100% have at least 1 number
const a = document.querySelector('')
const b = document.querySelector('')

if (![regex_list, a, b].includes(null)) {
  regex_list[0]
}

TS gives error:

'regex_list' is possibly 'null'. (tsserver 18047)

enter image description here

Even here:

if (![regex_list].includes(null)) {
  regex_list[0]
}

Info about regex_list:

const regex_list: RegExpExecArray | null

enter image description here

If it is not null than it must be RegExpExecArray, but TS doesn't think so. Is it because it only supports ==/!=/===/!== operators? If yes, then can I safely ignore this error with // @ts-ignore or regex_list![0]?


Update:

Since there are already answers to the main question, I also found an answer to the sub-question about ignoring the error. Instead, an assertion can be used to narrow the type: (regex_list as RegExpExecArray), but it has to be done for every reference of the regex_list (answer).

Andrew15_5
  • 11
  • 4
  • 2
    I don't think TypeScript is clever enough to infer a null check from `[regex_list].includes(null)`; rather, you would have to check it explicitly with `regex_list !== null` for the compiler to narrow the type from `RegExpExecArray | null` to just `RegExpExecArray`. – Matthew Layton Aug 14 '23 at 19:54
  • The [answer by @MatthieuRiegler](https://stackoverflow.com/a/76901845/2887218) is correct; TS has no idea that `Array.includes(x)` could be used to narrow the type of `x`. See the linked questions/answers for more information. – jcalz Aug 14 '23 at 21:30

2 Answers2

2

Array.include() is defined as following :

includes(searchElement: T, fromIndex?: number): boolean;

As you can see, there is no predicate, so typescript won't narrow down and remove the null from the type.

Matthieu Riegler
  • 31,918
  • 20
  • 95
  • 134
  • From hover info: `(method) Array.includes(searchElement: Element | RegExpExecArray | null, fromIndex?: number | undefined): boolean`. `searchElement` can be one of 3 types, including `null`. If `null` is not included, then it should change to `searchElement: Element | RegExpExecArray`, right? But it doesn't. Maybe I don't understand TS well enough. [Others say](https://stackoverflow.com/questions/76901756/typescript-says-that-variable-is-possibly-null-after-array-prototype-includes#comment135569634_76901756) that TS just not as clever as I want it to be. – Andrew15_5 Aug 14 '23 at 21:16
  • @Andrew15_5 the only way it could do that is if `includes` were defined to be a type guard function, and it's not... so it can't. This was suggested at [ms/TS#36275](https://github.com/microsoft/TypeScript/issues/36275) but declined. – jcalz Aug 14 '23 at 21:29
  • And note, even if it *were*, there would be roadblocks to overcome; an inline array literal like `[regex_list, a, b]` won't maintain a connection with one of its elements like `regex_list`. Indeed it just isn't worth trying to force the compiler to "understand" what you're doing; instead a type assertion or refactoring are recommended. – jcalz Aug 14 '23 at 21:42
1

You can be sure that the array is not null with ![regex_list, a, b].includes(null), but You're not sure if the array is empty, because it can exist like this []. Also, since you need to have the first element of the array, you need to see that the array is not empty.

For that you can check the size of the array with regex list.length > 0 in the conditional or use regex list! [n] (will return undefined if it does not exist).

So, to fix it you can simply add this condition:

const path = window.location.pathname
const regex_list = /\d+$/.exec(path)
const a = document.querySelector('')
const b = document.querySelector('')

if (a && b && regex_list && regex_list.length > 0) {
    regex_list[0]
}

You can also take this into account:

console.log([]) // -> true
Lipez_47
  • 11
  • 1