Search code examples
javascripttypescriptgoogle-closure-compilerjsdoc

What is the correct jsdoc type annotation for a try..catch identifier?


When annotating JavaScript sources with JSDoc comments, how do you annotate the type of the try..catch identifier?

try {
  throw new Error();
} catch (whatIsMyType) {
  console.error(whatIsMyType.message);
}

I'm specifically asking for the TypeScript JSDoc dialect, but an answer for the Closure Compiler dialect or even JSDoc itself would be insightful.

I tried (for TypeScript):

try { throw new Error(); } catch (/** @type {Error} */ e) {/*...*/}
try { throw new Error(); } /** @param {Error} e*/ catch (e) {/*...*/}
/** @param {Error} e*/ try { throw new Error(); } catch (e) {/*...*/}

But without success. e has always type any. What would work is

try { throw new Error(); } catch (_e) {
  /** @type {Error} */
  var e = _e;
  // ...
}

And the additional variable would be optimized away by closure compilers advanced mode, but I find that cumbersome and not optimal from a performance perspective (in a dev build) and hope there is a better way (i.e. one where I don't have to create an artificial variable just to annotate the right type).


Solution

  • try {throw new Error();} catch (/** @type {Error}*/whatIsMyType) {
      console.error(whatIsMyType.message);
    }
    

    Is valid according to Closure Compiler.

    I don't write much typescript, but I've poked around at a few sources and mostly don't see annotations at the catch level. The few I did see looked like this;

    try { throw new Error(); } catch (e: Error) {}
    

    Update:

    I think TypeScript has it right in refusing to guarantee a type for the check block. You can throw anything (intentionally or otherwise) within a try block so the compiler can't assure the catch is caught with the right type. In a related answer the solution is that you should check the type in the catch:

    try {
      throw new CustomError();
    }
    catch (err) {
      console.log('bing');
      if (err instanceof CustomError) {
        console.log(err.aPropThatIndeedExistsInCustomError); //works
        console.log(err.aPropThatDoesNotExistInCustomError); //error as expected
      } else {
        console.log(err); // this could still happen
      }
    }
    

    credit for ☝🏽

    And I (and Robert Martin) encourage this style of type checking even with strong types.

    Update 2

    I've re-written the example from above into Closure Compiler syntax, and I believe the point remains valid. Even if Closure lets you do the definition from the first example, you probably don't want to (or at least, not only that):

    class CustomError extends Error {
      constructor() {
        super();
        /** @type {string} */
        this.aPropThatIndeedExistsInCustomError = '';
        throw new Error('Not so fast!');  // The evil part is here
      }
    }
    
    
    try {
      throw new CustomError();
    }
    catch (/**  @type {CustomError} */err) {
      console.log('bing');
      if (err instanceof CustomError) {
        console.log(err.aPropThatIndeedExistsInCustomError); //works
        console.log(err.aPropThatDoesNotExistInCustomError); //error as expected
      } else {
        console.log(err); // this could still happen
      }
    }
    

    on closure-compiler.appspot