JsDoc union type with objects not working

Viewed 2438

I'm at my wits end with this. It seems like it should work, I've been wrestling with it for hours, but I'm not sure what's wrong. This is the smallest example I could come up with. I even have a type guard.

/** @typedef {{ a: string }} TypeA*/
/** @typedef {{ b: string }} TypeB*/
/** @typedef {(TypeA | TypeB) } TypeC */

/** @type {TypeC} */
let typeC;

console.log(typeC.b) // autocompletion for property b doesn't work

I get the error:

Property 'b' does not exist on type '{ a: string; } | { b: string; }'.
  Property 'b' does not exist on type '{ a: string; }'.ts(2339)
3 Answers

I found that a jsdoc style type guard lets me access the properties of TypeC if I am able to duck type it as TypeB.

/** @typedef {{ a: string }} TypeA*/
/** @typedef {{ b: string }} TypeB*/
/** @typedef {(TypeA | TypeB) } TypeC */

/**
 * @param {*} value
 * @returns {value is TypeB}
 */
function typeIsB(value) {
  return true;
}

/** @type {TypeC} */
let typeC;

if (typeIsB(typeC)) {
  console.log(typeC.b) // no error, autocomplete works when typing typeC.b
}

Screenshot of autocomplete working:

enter image description here

I think you are looking for the intersection of A and B, not the union. Here is the typescript code for your issue fixed:

interface A {
  a: string;
}

interface B {
  b: string;
}

type C = A & B;

When you say that a value is of type A or B, the typescript compiler does not know which type of value is refrenced by that variable.

/** @typedef {{ a: string }} TypeA*/
/** @typedef {{ b: string }} TypeB*/
/** @typedef {(TypeA | TypeB) } TypeC */

/** @type {TypeC} */
let typeC;

console.log(typeC.b) // autocompletion for property b doesn't work

You need a condition to determine the actual type of the value referenced by typeC. For example:

if ('a' in typeC) {
    console.log(typeC.a) // autocompletion for property a works       
} else {
    console.log(typeC.b) // autocompletion for property b works       
}
Related