-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Support a @nonnull/@nonnullable JSDoc assertion comment #23405
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
@sandersn please add |
@RyanCavanaugh Or more appropriately as sugar for |
That's the easier and more correct way to write it, yes 😝 |
I'm not super excited about this suggestion because it doesn't improve the clunkiness of the existing solution. We are comparing /** @type {() => void} */(expr)
/** @nonnull */(expr) 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 Given those two things, the difference between If sugar were free, I'd be happier about this proposal. Edit: Updated with @weswigham's even smaller syntax. |
@sandersn you can shave two characters - |
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 sort of DOM stuff can be pretty annoying without a simple nonnull assertion operator, especially as the DOM d.ts files get tightened from webidl generation (I mean, technically This is wandering way out of the usual jsdoc area, and it may be a terrible idea, but what about |
Bump for awesomness! |
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:
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 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? |
Slightly off topic, but another possibility for dealing with issue #23403 is if Typescript provided an type NonUndefined<T> = T extends undefined ? 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_.
*/
function assertDefined<T>(value: T, valueName?: string): NonUndefined<T>
{
if (value === undefined)
{
throw new Error(`Encountered unexpected undefined value${valueName? ` for '${valueName}'` : ""}`);
}
return (value as NonUndefined<T>);
} Which could then be used like this: while (queue.length) {
assertDefined(queue.pop())();
} Some of the nice things about this approach are:
let test: string | null | undefined = null;
let result: string = assertDefined(test); There could be an
|
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}
*/
export function assertDefined(value, valueName) {
if (value === undefined) {
throw new Error(`Encountered unexpected undefined value${valueName? ` for '${valueName}'` : ""}`);
}
return /** @type {*} */ (value);
} |
thanks for the inspiration @rob-myers 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
*/
function notNull(value) {
// Use `==` to check for both null and undefined
if (value == null) throw new Error(`did not expect value to be null or undefined`)
return value
} |
Here's my solution, I prefer it to
/**
* 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
*/
export function $(value) {
return /** @type {NonNullable<T>} */ (value);
} |
Based on #48650: foo(nullableThing/*:!*/) optional trailing when its the last thing on a line: const foo: NonNullableThing = nullableThing //!
// The same with semi:
const foo: NonNullableThing = nullableThing; //! Maybe with const foo: NonNullableThing = nullableThing //:!
// The same with semi:
const foo: NonNullableThing = nullableThing; //:! Less is more, JSDoc so verbose! |
This doesn't really solve #23403. The expected behavior there is:
not having to add a weird comment assertion (the compiler should know that |
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 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} */
const leftColumn = document.getElementById("leftColumn");
// Current syntax for TS files:
const leftColumn = document.getElementById("leftColumn")!;
// Workaround that currently works for JS files:
const leftColumn = /** @type {HTMLElement} */ (document.getElementById("leftColumn")); (I know this example isn't perfect, since an ad blocker or user-script could remove The type checker should highlight an error if regardless of /** @type {!string} */ Error: Type 'HTMLElement' is not assignable to type 'string'
const leftColumn = document.getElementById("leftColumn"); 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
|
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 /** @type {Map<string, number>} */
const map = new Map();
map.set("a", 1);
if (map.has("a")) {
const a = map.get("a");
const power = a ** 2; // Error: 'a' is possibly 'undefined'.
} It would be desirable if the following would be possible in JavaScript with JSDocs: /** @type {Map<string, number>} */
const map = new Map();
map.set("a", 1);
if (map.has("a")) {
/** @type {!number} */
const a = map.get("a");
const power = a ** 2;
} So long as However, if the type is not satisfied, then it should report the respective error. For example: /** @type {Map<string, number>} */
const map = new Map();
map.set("a", 1);
if (map.has("a")) {
/** @type {!string} */ // Error: Type 'number' is not assignable to type 'string'
const a = map.get("a");
const power = a ** 2;
} In this case, an error is reported because |
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 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 Reference:
On the side, I saw the syntax |
This could be used in place of the non-null assertion operator, and solve #23403.
Related is #23217, which tracks definite assignment assertions.
The text was updated successfully, but these errors were encountered: