Skip to content
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

const assertions in JSDoc #30445

Closed
5 tasks done
ajafff opened this issue Mar 16, 2019 · 15 comments · Fixed by #45464
Closed
5 tasks done

const assertions in JSDoc #30445

ajafff opened this issue Mar 16, 2019 · 15 comments · Fixed by #45464
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@ajafff
Copy link
Contributor

ajafff commented Mar 16, 2019

Search Terms

const jsdoc

Suggestion

v3.4 added support for as const to mark a literal expression as immutable and disable type widening. This feature uses the TypeScript type assertion syntax {} as const or <const>{}.

In JS files you can use JSDoc to do type assertions: /** @type {T} */(v)
Unfortunately that doesn't work with const assertions.

// @ts-check
const foo = /** @type {const} */([1]); // Error: cannot find name 'const'

This proposes adding support for /** @type {const} */([1]).

An alternative could be /** @const */([1]).

Use Cases

The same as for const assertions in TS files.

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@texastoland
Copy link
Contributor

texastoland commented Apr 18, 2019

It's a small difference, but /** @const */ would be much nicer than /** @type {TypeIOnlyDefinedToMakeThisReadable} */. I see the consistency with /** @type {const} */, too.

@MrEfrem
Copy link

MrEfrem commented Dec 17, 2019

When is planning to implement this?

@grrowl
Copy link

grrowl commented Jan 7, 2020

Not sure if it suits typescript's "value as const" pattern but JSDoc already includes the @constant annotation: https://jsdoc.app/tags-constant.html

/** @constant
    @type {string}
    @default
*/
const RED = 'FF0000';

/** @constant {number} */
var ONE = 1;

@pastelmind
Copy link

As a workaround, you can use identity functions to coerce string and number literals to literal types:

/**
 * Identity function. Coerces string/number literals to literal types.
 * @template {string | number} T
 * @param {T} v
 * @return {T}
 */
function c(v) {
  return v;
}

/**
 * Creates a tuple (array) from the given arguments.
 * @template {any[]} T
 * @param {T} v
 * @return {T}
 */
function t(...v) {
  return v;
}

const MyLiteralObject = {
  string_before: "foo bar",          // string
  string_after: c("foo bar"),        // "foo bar"
  number_before: 42,                 // number
  number_after: c(42),               // 42
  array_before: [2, "dog"],          // (string | number)[]
  array_after: t(c(2), c("dog")),    // [2, "dog"]
  empty_array: [],                   // any[]
  empty_tuple: t(),                  // []
};

@intptr-t
Copy link

@pastelmind's idea one solution, but I'm concerned about the overhead.
I think we need a way to prevent c and v from being executed.

As a workaround, you can use identity functions to coerce string and number literals to literal types:

/**
 * Identity function. Coerces string/number literals to literal types.
 * @template {string | number} T
 * @param {T} v
 * @return {T}
 */
function c(v) {
  return v;
}

/**
 * Creates a tuple (array) from the given arguments.
 * @template {any[]} T
 * @param {T} v
 * @return {T}
 */
function t(...v) {
  return v;
}

const MyLiteralObject = {
  string_before: "foo bar",          // string
  string_after: c("foo bar"),        // "foo bar"
  number_before: 42,                 // number
  number_after: c(42),               // 42
  array_before: [2, "dog"],          // (string | number)[]
  array_after: t(c(2), c("dog")),    // [2, "dog"]
  empty_array: [],                   // any[]
  empty_tuple: t(),                  // []
};

I think ajafff's idea is a good one.

I'm sorry I didn't have an idea,
I think we need an approach that can deal with more complex patterns.

// @ts-check
// ---- Substitute cases
const foo = /** @type {[1]} */ ([1]); // Good: foo is type [1]
foo[0] = 2; // <- ERROR: Type checking is working well!!

// ---- Cases that alternative solutions cannot handle
const complex1 = {
    /** @type {1} */
    item: 1, // <- Type check ERROR: 1 is a number
};

/** @constant */
const complex2 = {
    /** @constant {1} */
    item: 2, // <- Type check ERROR: 1 is a number
};
complex2.item = 3; // Type checking is not working.

@AlCalzone
Copy link
Contributor

I want to provide an additional use case, now that #41631 was reopened and the official workaround is to use as const, which is not available in JS.

💻 Use Cases

In iobroker, objects in the database have different types that can be distinguished by how the ID is shaped. The different object types take different properties, so type-checking this would go a long way to reduce errors.

The type declarations already reflect this:
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/81e930ed9983d081271ebf1978551b1e4c8b5b16/types/iobroker/iobroker-tests.ts#L649-L707
but this feature is limited to TypeScript devs who can use as const.

While these tests are hard-coded for string literals, in the real world most of our developers use JS and IDs are often built on the fly, e.g.

const id = `my-tree.0.${someString}`; // inferred as string
setObject(id, { /* I could really use a better type for this object */ });

If id was instead typed as my-tree.0.${string}, the object type in setObject could be more specific to the purpose it actually serves.

@MrEfrem
Copy link

MrEfrem commented Feb 24, 2021

It's more one use case: https://github.com/ThomasAribart/json-schema-to-ts. It doesn't work with JSDoc types without as const.
I use JSON schemas in most of my backends and I have to create JSDoc types for payload manually every time.

@trongthanh
Copy link

I'm also looking for solution to enable auto-completion and code hint for our design system's theme object which is ideally limited to vanilla JS and JSDoc. Essentially, when developer type a theme's key, they should see the actual value of that key so that they don't go back and forth looking up the theme's spec. This feature is the key to it.

Currently, I'm doing a work-around with tuple (for array) and literal string type for object key like so:

/**
 * Tuple with literal type
 * @type {[
  '12px',
  '16px',
  '20px',
  '24px',
]}
 */
const sizesScale = [
  '12px', // 0
  '16px', // 1
  '20px', // 2
  '24px', // 3
];

// The literal types will follow sub-sequence assignment like so:
const sizes = {
  ...sizesScale,
  xs: sizesScale[0],
  sm: sizesScale[1],
  md: sizesScale[2],
  lg: sizesScale[3],
}

/**
 * Literal types in enum-like objects
 * @type {{
  body: 400,
  heading: 700,
  normal: 400,
  bold: 700,
}}
 */
const fontWeights = {
  body: 400,
  heading: 700,
  normal: 400,
  bold: 700,
};

As you can see, we can't avoid the values duplication, but since the @type is the whole object / array, we can quickly sync the JSDoc by copying them from the actual JS literals.

This feature will eliminate the duplication.

@mohd-akram
Copy link

Any update on this?

@thesoftwarephilosopher
Copy link

Fwiw, this is one of the main missing features of JavaScript+JSDoc that is pushing me to migrate to TypeScript. I've run into it several times in the past couple years. Most other problems have at least adequate solutions with JSDoc, but this doesn't.

@AustinGil
Copy link

For anyone coming here and scrolling to the bottom to see if it's fixed, it's fixed!

#45464

You can use /** @type {const} */ inline

// type is number[]
const arr1 = [1, 2, 3]

// type is readonly [1, 2, 3]
const arr2 = /** @type {const} */ ([1, 2, 3])

Note that the type coercion has to be written inline and the parenthesis are necessary.

Hope this helps :)

@gaurav5430
Copy link

actually it seems to work in js files, not sure if it is supposed to work on the playground

@rbuckton
Copy link
Member

rbuckton commented Aug 2, 2022

It does work on the playground if you change the language from TypeScript to JavaScript under the TS Config tab: https://www.typescriptlang.org/play?ts=4.5.5&filetype=js#code/PTAEBcE8AcFNQJYGdQCdYEMAmB7AdgDaSgDaAjADSgBMVAzALoCwAUAMb5LigaqrWgAvKGAAqUaAACUOKADeHPFwC+oUWAAU5KrVCMAlEA

image
image

@ObscurusGrassator
Copy link

Not work for json file require - const is ignored.

let translations = /** @type { const } */ (require('./translations.js'));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.