You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Support a @nonnull/@nonnullable JSDoc assertion comment #23405
Ryan Cavanaugh (@RyanCavanaugh) Or more appropriately as sugar for expr!, which is almost just sugar for (expr as NonNullable<typeof expr>), but also has fallback behavior if NonNullable doesn't exist.
The two obstacles come from the cast syntax, not the contents of the comment: you need (1) a C-style comment that (2) is followed by a parenthesised expression. Note that the original example is wrong. You'd need /** @nonnull */(queue.pop())(). You're in for a bunch of punctuation and some confusing parentheses.
Given those two things, the difference between @nonnull and @type {*} is pretty small (1 character, to wit). Even @type {() => void} isn't that much of an improvement.
If sugar were free, I'd be happier about this proposal.
I think there's an important difference between a cast and a nonnull assertion, from the typechecking perspective, however - a nonnull doesn't destroy type information (ie, for quick info or inference), while an any cast does.
In cases like this where comments do not specifically serve a documentation-for-humans purpose, but a documentation-for-ts-compiler purpose, I believe the rules should not specifically follow JSDoc rules.
Here's why:
JSDoc comments were designed to serve a documentation-for-humans purpose (for example the prose in a comment may end up on a documentation website or something similar).
If all type-oriented comments are limited to following JSDoc format, this interferes with existing JSDoc tooling:
Some JSDoc tools do not take into account any code, and read nothing more than comments in a file.
Encountering an island /** @nonnull */ in the extracted comments would have no semantic meaning, for example.
For JSDoc tools that do inspect code in order to infer more information from the code, having a /** @nonnull */ floating in front of an expression still doesn't make semantic sense.
A non-TypeScript doc tool would have no idea what documentation to generate from this if it didn't crash, and even in a TSDoc environment it isn't worth documentingsuch things; only TSDoc will explicitly know not to document it.
I've seen projects purposefully use JSDoc instead of TSDoc tooling for various reasons, a primary reason being that the developer wishes to be in precise control of the documentation output so that what one writes is exactly what one gets. This is in contrast to TSDoc which often documents too much that isn't semantically important and gives little (or difficult) control over the output.
We can get all the type information we need from an IDE with good intellisense, while otherwise leaving semantic documentation-for-humans to JSDoc comments (as well as JSDoc tools if you prefer, like I do).
Yes, maybe we can tell JSDoc tools to ignore the strange parts they don't understand (like random /** @nonull */ comments floating around, but that complicates the tools in unwated ways just to satisfy TypeScript (I've written my own JSDoc parser and documentation generator, so this is my first-hand sentiment).
I believe we should keep JSDoc comments pure to their purpose, as first-class citizens in documentation for humans, and leave anything else (documentation for the TypeScript compiler) in a different syntax, if possible.
Wdyt?
Reacted by Jan Žák, Willem Garnier, chpio, Hannes Leutloff, Konstantin Kutsyllo and snarbles2
Slightly off topic, but another possibility for dealing with issue #23403 is if Typescript provided an assertDefined() utility method:
typeNonUndefined<T>=Textendsundefined ? never : T;/** * Throws if the supplied value is _undefined_ (_null_ is allowed).\ * Returns (via casting) the supplied value as a T with _undefined_ removed from its type space. * This informs the compiler that the value cannot be _undefined_. */functionassertDefined<T>(value: T,valueName?: string): NonUndefined<T>{if(value===undefined){thrownewError(`Encountered unexpected undefined value${valueName? ` for '${valueName}'` : ""}`);}return(valueasNonUndefined<T>);}
TS design-time (programmer) mistakes - ie. incorrectly "asserting" that a value will never be undefined - are still explicitly caught at JS runtime.
It's more targeted than using NonNullable<T> since it only excludes undefined, not undefined and null, so the compiler will still correctly catch cases like this:
One could argue that the syntax feels more "natural" than using a JSDoc comment, and you can step through it with the debugger.
Depending on tooling, like /**!*/, it's much easier to find uses of assertDefined in your code with a simple search than the non-null assertion operator !.
We can also write the function above using JSDoc types.
/** * JSDoc types lack a non-undefined assertion. * https://github.com/Microsoft/TypeScript/issues/23405#issuecomment-873331031 * * Throws if the supplied value is _undefined_ (_null_ is allowed).\ * Returns (via casting) the supplied value as a T with _undefined_ removed from its type space. * This informs the compiler that the value cannot be _undefined_. * @template T * @param {T} value * @param {string} [valueName] * @returns {T extends undefined ? never : T} */exportfunctionassertDefined(value,valueName){if(value===undefined){thrownewError(`Encountered unexpected undefined value${valueName? ` for '${valueName}'` : ""}`);}return/** @type {*} */(value);}
Reacted by Mathias Gomez, Mekhi, JenieX and Julian Cataldo
made some small changes as some stuff isn't always necessary
/** * JSDoc types lack a non-null assertion. * https://github.com/Microsoft/TypeScript/issues/23405#issuecomment-873331031 * * @template T * @param {T} value */functionnotNull(value){// Use `==` to check for both null and undefinedif(value==null)thrownewError(`did not expect value to be null or undefined`)returnvalue}
Here's my solution, I prefer it to notNull above because:
It can be compiled away, making it have zero cost
It's just one character 😝
/** * Non-null casting like TypeScript's [Non-null Assertion Operator][1]. * * It removes `null` and `undefined` from a type without doing any explicit * checking. It's effectively a type assertion that `value` isn’t `null` or * `undefined`. Just like other type assertions, this doesn’t change the runtime * behavior of your code, so it’s important to only use it when you know that * the value can’t be `null` or `undefined`. * * @example * ```js * function liveDangerously(x?: number | null) { * // No error * console.log($(x).toFixed()); * } * ``` * * [1]: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#non-null-assertion-operator-postfix- * * @template T * @param {T} value * @returns {NonNullable<T>} `value` unchanged */exportfunction$(value){return/** @type {NonNullable<T>} */(value);}
I'd be very keen to see this feature as well, however none of the proposed solutions use features that are documented as part of the JSDoc standard.
JSDoc already has non-nullable types, it would be better for TypeScript to use this rather than create its own syntax. In JSDoc, one can use the {!type} syntax to assert that a type can never be null.
I don't have any qualms with custom comments/directives/syntax for TypeScript when a JSDoc equivalent isn't available, but there is syntax that can be treated as equivalent already.
My proposal is that the following snippets would be treated identically by tsc:
// Desireable syntax for JS files:/** @type {!HTMLElement} */constleftColumn=document.getElementById("leftColumn");// Current syntax for TS files:constleftColumn=document.getElementById("leftColumn")!;// Workaround that currently works for JS files:constleftColumn=/** @type {HTMLElement} */(document.getElementById("leftColumn"));
(I know this example isn't perfect, since an ad blocker or user-script could remove #leftColumn. This is just for demonstrative purposes.)
The type checker should highlight an error if regardless of null, the type does not match. For example:
This is much more intuitive for users familiar with typing with JSDoc already, and takes advantage of existing standards, which I assumed was the goal in supporting JSDoc in the first place.
Source
Type name
Syntax examples
Description
…
…
…
Non-nullable type
A number, but never null. {!number}
Indicates that the value is of the specified type, but cannot be null.
Just adding a more practical example since I was writing about this somewhere else. This would probably be a good test case for when/if this is implemented as well.
In the following code, the line const power = a ** 2; is not satisfied. We humans are aware that map.get("a") should return a number, assuming no type constraints have been ignored:
/** @type {Map<string, number>} */constmap=newMap();map.set("a",1);if(map.has("a")){consta=map.get("a");constpower=a**2;// Error: 'a' is possibly 'undefined'.}
It would be desirable if the following would be possible in JavaScript with JSDocs:
So long as a satisfies NonNullable<number> this should apply a non-null assertion, and treat a as number instead of number | undefined.
However, if the type is not satisfied, then it should report the respective error. For example:
/** @type {Map<string, number>} */constmap=newMap();map.set("a",1);if(map.has("a")){/** @type {!string} */// Error: Type 'number' is not assignable to type 'string'consta=map.get("a");constpower=a**2;}
In this case, an error is reported because a satisfies NonNullable<string> is not satisfied.
Losing inference and having to be explicit about the type is IMO not sufficiently ergonomic.
My proposal does not lose inference. Could you elaborate on what you mean there?
As for ergonomics, that depends on what your primary tooling is. The issue title explicitly references JSDoc, and the most ergonomic thing to do for a JSDoc user is to use JSDoc. They are writing JavaScript, and TypeScript is augmenting the development experience, they are not writing TypeScript.
Ideally, a user who chose to write their project in JavaScript, and uses JSDocs, should not be required to learn TypeScript-specific syntax/directives to take advantage of features that could already be achieved in JSDoc. It should just run in the background like magic, which is what it does most of the time already.
There are some exceptions to this, such as generics, as JSDoc does not support them, hence TypeScript affords the special @template directive.
You might have different priorities, so if you'd prefer a more concise syntax, that's fair. There are other issues where people have requested a more concise syntax similar to Facebook's Flow. However, that has nothing to do with JSDoc anymore, so it's not a satisfying or "ergonomic" solution for a JSDoc user.
So if one was to take Flow's approach, the syntax would be to append /*: ! */ after the statement to assert non-null. However, for me, nothing beats simply allowing me to use the tools my team are already familiar with and using everywhere else. As a library developer, I also don't think it will be pleasant for new contributors to see obscure syntax littered around.
On the side, I saw the syntax //! was also proposed in this thread. In case the team do decide to go that direction, just a heads-up. For most editors, there's at least one extension that treats comments that start with ! as warnings/alerts, such as Better Comments for VSC which has 8,000,000+ installs.
Why not just abuse JSDoc's @type tag a little bit more than TypeScript already does and introduce a /** @type {!} */ (expression) syntax? That would be very much in line with how JSDoc has the {!type} syntax for making types non-nullable.
Reacted by Jayden Seric, EqualMa, Caleb Hearon, Lemmingh, Eli and NemoStein
From time to time I stumble on this issue... For example:
classTest{/** @type {string} */key}
Property 'key' has no initializer and is not definitely assigned in the constructor.
Yes, of course it it, but I'm validating this at initialization, before using anywhere, so I can guarantee that this value will be non-null at runtime.
Works, but is far from elegant and can bring other issues
The ideal would be:
classTest{/** @type {string!} */key}
It is compatible with TS nullable types - {string?} - but a bit strange considering the JSDoc way of doing it: {!string}
Although tsc also supports {?string} I don't think I ever saw that in the wild, but ! could be treated the same (before or after).
Activity
[-]Support a @nonnullable JSDoc comment[/-][+]Support a @nonnull/@nonnullable JSDoc assertion comment[/+]RyanCavanaugh commentedon Apr 17, 2018
Nathan Shively-Sanders (@sandersn) please add
/** @nonnull */expras JS sugar for(expr as NonNullable<typeof expr>)🍻weswigham commentedon Apr 17, 2018
Ryan Cavanaugh (@RyanCavanaugh) Or more appropriately as sugar for
expr!, which is almost just sugar for(expr as NonNullable<typeof expr>), but also has fallback behavior ifNonNullabledoesn't exist.RyanCavanaugh commentedon Apr 17, 2018
That's the easier and more correct way to write it, yes 😝
sandersn commentedon Apr 17, 2018
I'm not super excited about this suggestion because it doesn't improve the clunkiness of the existing solution. We are comparing
The two obstacles come from the cast syntax, not the contents of the comment: you need (1) a C-style comment that (2) is followed by a parenthesised expression. Note that the original example is wrong. You'd need
/** @nonnull */(queue.pop())(). You're in for a bunch of punctuation and some confusing parentheses.Given those two things, the difference between
@nonnulland@type {*}is pretty small (1 character, to wit). Even@type {() => void}isn't that much of an improvement.If sugar were free, I'd be happier about this proposal.
Edit: Updated with Wesley Wigham (@weswigham)'s even smaller syntax.
weswigham commentedon Apr 17, 2018
Nathan Shively-Sanders (@sandersn) you can shave two characters -
@type {*}weswigham commentedon Apr 17, 2018
I think there's an important difference between a cast and a nonnull assertion, from the typechecking perspective, however - a nonnull doesn't destroy type information (ie, for quick info or inference), while an
anycast does.10 remaining items
trusktr commentedon Mar 17, 2020
I like this.
In cases like this where comments do not specifically serve a documentation-for-humans purpose, but a documentation-for-ts-compiler purpose, I believe the rules should not specifically follow JSDoc rules.
Here's why:
JSDoc comments were designed to serve a documentation-for-humans purpose (for example the prose in a comment may end up on a documentation website or something similar).
If all type-oriented comments are limited to following JSDoc format, this interferes with existing JSDoc tooling:
/** @nonnull */in the extracted comments would have no semantic meaning, for example./** @nonnull */floating in front of an expression still doesn't make semantic sense.I've seen projects purposefully use JSDoc instead of TSDoc tooling for various reasons, a primary reason being that the developer wishes to be in precise control of the documentation output so that what one writes is exactly what one gets. This is in contrast to TSDoc which often documents too much that isn't semantically important and gives little (or difficult) control over the output.
We can get all the type information we need from an IDE with good intellisense, while otherwise leaving semantic documentation-for-humans to JSDoc comments (as well as JSDoc tools if you prefer, like I do).
Yes, maybe we can tell JSDoc tools to ignore the strange parts they don't understand (like random
/** @nonull */comments floating around, but that complicates the tools in unwated ways just to satisfy TypeScript (I've written my own JSDoc parser and documentation generator, so this is my first-hand sentiment).I believe we should keep JSDoc comments pure to their purpose, as first-class citizens in documentation for humans, and leave anything else (documentation for the TypeScript compiler) in a different syntax, if possible.
Wdyt?
Rich-MSR commentedon Jul 3, 2021
Slightly off topic, but another possibility for dealing with issue #23403 is if Typescript provided an
assertDefined()utility method:Which could then be used like this:
Some of the nice things about this approach are:
NonNullable<T>since it only excludesundefined, notundefinedandnull, so the compiler will still correctly catch cases like this:There could be an
assertNotNull()too./**!*/, it's much easier to find uses ofassertDefinedin your code with a simple search than the non-null assertion operator!.rob-myers commentedon Dec 12, 2021
We can also write the function above using JSDoc types.
jimmywarting commentedon Sep 16, 2022
thanks for the inspiration rob-myers
made some small changes as some stuff isn't always necessary
ziadkh0 commentedon Sep 22, 2023
Here's my solution, I prefer it to
notNullabove because:trusktr commentedon Sep 23, 2023
Based on #48650:
optional trailing when its the last thing on a line:
Maybe with
:if there's a hazard people are worried about, but I've never seen//!anywhere:Less is more, JSDoc so verbose!
Neme12 commentedon Feb 24, 2024
This doesn't really solve #23403. The expected behavior there is:
not having to add a weird comment assertion (the compiler should know that
popwon't returnundefinedbecauselengthwas checked).SethFalco commentedon May 8, 2025
I'd be very keen to see this feature as well, however none of the proposed solutions use features that are documented as part of the JSDoc standard.
JSDoc already has non-nullable types, it would be better for TypeScript to use this rather than create its own syntax. In JSDoc, one can use the
{!type}syntax to assert that a type can never be null.I don't have any qualms with custom comments/directives/syntax for TypeScript when a JSDoc equivalent isn't available, but there is syntax that can be treated as equivalent already.
My proposal is that the following snippets would be treated identically by tsc:
(I know this example isn't perfect, since an ad blocker or user-script could remove
#leftColumn. This is just for demonstrative purposes.)The type checker should highlight an error if regardless of
null, the type does not match. For example:This is much more intuitive for users familiar with typing with JSDoc already, and takes advantage of existing standards, which I assumed was the goal in supporting JSDoc in the first place.
Source
Related PRs
non-null assertion expr!.SethFalco commentedon May 8, 2025
Just adding a more practical example since I was writing about this somewhere else. This would probably be a good test case for when/if this is implemented as well.
In the following code, the line
const power = a ** 2;is not satisfied. We humans are aware thatmap.get("a")should return a number, assuming no type constraints have been ignored:It would be desirable if the following would be possible in JavaScript with JSDocs:
So long as
a satisfies NonNullable<number>this should apply a non-null assertion, and treataasnumberinstead ofnumber | undefined.However, if the type is not satisfied, then it should report the respective error. For example:
In this case, an error is reported because
a satisfies NonNullable<string>is not satisfied.mhofman commentedon May 8, 2025
Losing inference and having to be explicit about the type is IMO not sufficiently ergonomic.
SethFalco commentedon May 8, 2025
My proposal does not lose inference. Could you elaborate on what you mean there?
As for ergonomics, that depends on what your primary tooling is. The issue title explicitly references JSDoc, and the most ergonomic thing to do for a JSDoc user is to use JSDoc. They are writing JavaScript, and TypeScript is augmenting the development experience, they are not writing TypeScript.
Ideally, a user who chose to write their project in JavaScript, and uses JSDocs, should not be required to learn TypeScript-specific syntax/directives to take advantage of features that could already be achieved in JSDoc. It should just run in the background like magic, which is what it does most of the time already.
There are some exceptions to this, such as generics, as JSDoc does not support them, hence TypeScript affords the special
@templatedirective.You might have different priorities, so if you'd prefer a more concise syntax, that's fair. There are other issues where people have requested a more concise syntax similar to Facebook's Flow. However, that has nothing to do with JSDoc anymore, so it's not a satisfying or "ergonomic" solution for a JSDoc user.
For context:
So if one was to take Flow's approach, the syntax would be to append
/*: ! */after the statement to assert non-null. However, for me, nothing beats simply allowing me to use the tools my team are already familiar with and using everywhere else. As a library developer, I also don't think it will be pleasant for new contributors to see obscure syntax littered around.Reference:
On the side, I saw the syntax
//!was also proposed in this thread. In case the team do decide to go that direction, just a heads-up. For most editors, there's at least one extension that treats comments that start with!as warnings/alerts, such as Better Comments for VSC which has 8,000,000+ installs.Enable project TypeScript config `compilerOptions.noUncheckedIndexedA…
Enable project TypeScript config `compilerOptions.noUncheckedIndexedA…
aweebit commentedon May 31, 2025
Why not just abuse JSDoc's
@typetag a little bit more than TypeScript already does and introduce a/** @type {!} */ (expression)syntax? That would be very much in line with how JSDoc has the{!type}syntax for making types non-nullable.NemoStein commentedon Jun 3, 2026
From time to time I stumble on this issue... For example:
Property 'key' has no initializer and is not definitely assigned in the constructor.Yes, of course it it, but I'm validating this at initialization, before using anywhere, so I can guarantee that this value will be non-null at runtime.
For now, I'm doing this:
Works, but is far from elegant and can bring other issues
The ideal would be:
It is compatible with TS nullable types -
{string?}- but a bit strange considering the JSDoc way of doing it:{!string}Although tsc also supports
{?string}I don't think I ever saw that in the wild, but!could be treated the same (before or after).