Search code examples
javascriptvisual-studio-codegoogle-closure-compilerjsdoc

What does a proceeding exclamation mark do for JSDoc @type tag?


VS Code parses the following as if there were no exclamation mark, without any errors:

const el = /** @type {HTMLElement!} */ (document.getElementById("abc"));

What does it do? The official JSDoc documentation says only about preceding marks:

Indicates that the value is of the specified type, but cannot be null.

Not sure what proceeding marks do.


Solution

  • TLDR: !HTMLElement and HTMLElement! are the same annotation, practically speaking.


    const el = /** @type {HTMLElement!} */ (document.getElementById("abc"));
    

    Is a type cast. It says that document.getElementById("abc") is an HTMLElement, and not null. The annotation was likely added because getElementById returns either an HTMLElement or null.

    The author of this line should be 100% confident that an element with an id of abc exists, because after this cast is made, the Closure Compiler will treat this type as an HTMLElement, instead of possibly an HTMLElement or null.

    Types used in casting follow the same rules as types outside of casting, so to demonstrate using these types directly, the examples that follow do not use casting.

    As OP says, in Closure Compiler the exclamation mark means the type must exist, and cannot be null. Additionally, a question mark means it could be the type or it could be null.

    Take for example the following code:

    // ==ClosureCompiler==
    // @compilation_level ADVANCED_OPTIMIZATIONS
    // @output_file_name default.js
    // @formatting pretty_print
    // ==/ClosureCompiler==
    
    const body = document.querySelector('body');
    
    const /** @export {Element!} */ n1 = body;
    const /** @export {!Element} */ n2 = body;
    const /** @export {Element} */ n3 = null;
    const /** @export {?Element} */ n4 = null;
    const /** @export {Element?} */ n5 = null;
    
    console.log({
      n1,
      n2,
      n3,
      n4,
    })
    

    And run it through the Closure Compiler, here are the warnings the compiler generates:

    JSC_TYPE_MISMATCH: initializing variable
    found   : (Element|null)
    required: Element at line 3 character 37
    const /** @export {Element!} */ n1 = body;
                                         ^
    JSC_TYPE_MISMATCH: initializing variable
    found   : (Element|null)
    required: Element at line 4 character 37
    const /** @export {!Element} */ n2 = body;
                                         ^
    

    Note that body is returned by document.querySelector with a type of {?Element} , which we've said the compiler understands can be either an Element or null, a.k.a. Element|null. This example shows that the notation can also be expressed as ?Element or Element?.