Skip to content

Support the JSDoc @enum tag #26021

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

Merged
merged 2 commits into from
Jul 28, 2018
Merged

Support the JSDoc @enum tag #26021

merged 2 commits into from
Jul 28, 2018

Conversation

sandersn
Copy link
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 weswigham and mhegazy July 27, 2018 21:49
@ahocevar
Copy link

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

@sandersn
Copy link
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

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

@eps1lon
Copy link
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

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
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

ghost commented Sep 4, 2018

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

@eps1lon
Copy link
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

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

JSDoc @enum tag support
4 participants