Skip to content

Support the JSDoc @enum tag#26021

Merged
sandersn merged 2 commits into
masterfrom
jsdoc/enum-tag
Jul 28, 2018
Merged

Support the JSDoc @enum tag#26021
sandersn merged 2 commits into
masterfrom
jsdoc/enum-tag

Conversation

@sandersn
Copy link
Copy Markdown
Member

@enum is used on a variable declaration with an object literal
initializer. It does a number of things:

  1. The object literal has a closed set of properties, unlike other object literals in Javascript.
  2. The variable's name is resolvable as a type, but it just has the declared type of the enum tag.
  3. Each property's type must be assignable to the enum tag's declared type, which can be any type.

Notably, this PR does not implement the fourth feature from #18161, which is to treat the enum's name as a new type that is not assignable to and from any other types. I believe this would require a lot more effort to implement, and I expect this tag to only be used in codebases transitioning from Closure. If I'm wrong, it shouldn't be a problem to make the semantics stricter.

For example,

/** @enum {string} */
const Target = {
  START: "START",
  END: "END",
  MISTAKE: 0, // error 'number' is not assignable to 'string' -- see (3)
}

Target.THIS_IS_AN_ERROR; // See (1)
/** @type {Target} See (2) */
var target = Target.START;
target = 'lol whatever'; // See Note (4) -- this is like TS classic enums, not TS string enums

Fixes #18161

sandersn added 2 commits July 27, 2018 14:27
`@enum` is used on a variable declaration with an object literal
initializer. It does a number of things:

1. The object literal has a closed set of properties, unlike other
object literals in Javascript.
2. The variable's name is resolvable as a type, but it just has the
declared type of the enum tag.
3. Each property's type must be assignable to the enum tag's declared type,
which can be any type.

For example,

```js
/** @enum {string} */
const Target = {
  START: "START",
  END: "END",
  MISTAKE: 0, // error 'number' is not assignable to 'string' -- see (3)
}

Target.THIS_IS_AN_ERROR; // See (1)
/** @type {Target} See (2) */
var target = Target.START;
```
@sandersn sandersn requested review from mhegazy and weswigham July 27, 2018 21:49
@ahocevar
Copy link
Copy Markdown

Thanks for adding this @sandersn! Out of curiousity: how would the above @enum Target be expressed in TypeScript?

@sandersn
Copy link
Copy Markdown
Member Author

sandersn commented Aug 17, 2018

(disregarding the MISTAKE member)
For strings, there's no precise equivalent, because string enums are stricter than number enums in Typescript. That's because they don't allow computed values, so the compiler always knows which strings are legal. Here's the enum syntax:

enum Target {
    START = "START",
    END = "END"
}
Target.ERROR // error here
var target: Target = Target.START
target = "lol whatever" // error here!

You can also avoid the enum construct since it's not standard JS. There's more boilerplate in that case.

var Target = {
    "START": "START" as "START",
    "END": "END" as "END"
}
type Target = keyof typeof Target
Target.ERROR
var target: Target = Target.START
target = 'lol whatever' // error here too

But you can emulate @enum's behaviour exactly by declaring target: string:

var target: string = Target.START
target = 'lol whatever' // ok, target is just a string

If you don't care about values, you can also get the loose assignability behaviour with number enums, but they still only allow numbers to be assigned:

enum Target {
    START,
    END
}
var target: Target = Target.START
target = 'lol whatever' // error
target = 111 // ok

Edit: Unlike @enum, Typescript doesn't prevent you from mixing and matching strings and numbers as long as you give the values explicitly. And it only supports strings and numbers. @enum supports arbitrary types for its values.

@ahocevar
Copy link
Copy Markdown

Thanks! The 2nd snippet above (avoiding the enum construct) is my favorite.

@eps1lon
Copy link
Copy Markdown
Contributor

eps1lon commented Aug 25, 2018

@sandersn Is it common practice to change the number values of SyntaxKind? An AST parsed from an earlier version of typescript can't be processed with a newer version because it can't identify the SyntaxKind correctly.

@ghost
Copy link
Copy Markdown

ghost commented Sep 4, 2018

@eps1lon Yes, this is common, and it's the reason we use --preserveConstEnums so that users can access ts.SyntaxKind.Foo instead of using a number literal.

@eps1lon
Copy link
Copy Markdown
Contributor

eps1lon commented Sep 4, 2018

@Andy-MS But the value of kind in a node is still a number. So if I compare ts@3.0.SyntaxKind.Foo with ts@3.1.SyntaxKind.Foo I will get false although they should be the same.

@ghost
Copy link
Copy Markdown

ghost commented Sep 4, 2018

It's in general an error to mix-and-match two different TypeScript installs.

@eps1lon
Copy link
Copy Markdown
Contributor

eps1lon commented Sep 4, 2018

I keep hearing that but I have yet to find libraries that pin typescript to exact versions. This is also not documented anywhere and I feel like this should be the case because npm and yarn assume by default that packages follow semver.

@ghost
Copy link
Copy Markdown

ghost commented Sep 4, 2018

Typically TypeScript would be a peer dependency (example: tslint) of a library that needs to require("typescript") (as opposed to just being built using it).

@microsoft microsoft locked as resolved and limited conversation to collaborators Oct 21, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JSDoc @enum tag support

4 participants