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

Discussion: Support @typedef Tag in JsDoc Comments [Salsa] #7758

Closed
zhengbli opened this issue Apr 1, 2016 · 3 comments
Closed

Discussion: Support @typedef Tag in JsDoc Comments [Salsa] #7758

zhengbli opened this issue Apr 1, 2016 · 3 comments
Assignees
Labels
Fixed A PR has been merged for this issue Suggestion An idea for TypeScript

Comments

@zhengbli
Copy link
Contributor

zhengbli commented Apr 1, 2016

Currently type annotation is only allowed in TypeScript file. Although we do allow .d.ts files to be consumed by JavaScript files, there are already code bases employing JsDoc @typedef tag to define types within JavaScript files (Google Closure, etc). Supporting @typedef tag will enhance the editing experience for Salsa on these existing code bases.

The purpose of @typedef is to define a new type that can be used in other JsDoc comment annotations, similar to interface or type alias declaration in TypeScript. However, because there is no definite standard on the shape of @typedef, its has several popular dialects. The most common cases I found are as follows:

Case 1:

/**
 * @typedef {(string | number)} NumberLike
 */

Case 2:

/**
 * @typedef {Object} People
 * @property {number} id the unique id
 * @property {string} name - your name
 */

Case 3:

/**
 * @typedef People
 * @type {Object}
 * @property {number} id the unique id
 * @property {string} name - your name
 */

Case 4:

/** @typedef {(string | number)} */
var NumberLike;

Case 5:

/** @typedef {(string | number)} */
NumberLike;

Case 6:

/** @typedef {{ count: number }} */
People;

/**
 * @typedef {{
 *     id: number,
 *     name: string
 * }}
 */
People.person;

/**@type {People.person}*/
var p;

There are several difficulties / design choices for the current TypeScript type system to understand the above cases:

  1. Does the code need to be valid JavaScript code if ignoring the comments?
    For example:

      /**@typedef {string}*/
      StringType;

    is totally fine with the Google Closure compiler, while running the code directly will generate errors because the variable StringType is not defined.

  2. How does these types translate to TypeScript types?
    In simple cases, the @typedef functions similarly to type alias in TypeScript. However, the type name defined by @typedef can be a dotted name (as shown in case 6), while in TypeScript type alias name has to be an identifier. And like in case 6, a defined type (People) may be augmented later (added People.person property), which is hard to do for a type alias.

    The other route is to represent all the types as interfaces. This solves some of the the augmentation issue, because interfaces can have multiple declarations and be merged together, however it has flaws too. For example:

      /**@typedef {(string | number)} Id*/
      /**@typedef {string} Id.name*/

    in which the union type is hard to represent using interfaces.

  3. For case 4 or case 5, do we create both a value declaration and a type declaration for the variable? Imagine the following code:

    /**@typedef {(string | number)} */
    var foo;
    ...
    var foo = 10; // <- What is "foo" after this line? Should we give you an error / warning?
    if (foo) { // If I call "Go to declaration" on "foo", where will I go to?
        ...
    }

    In addition, if the variable only has a type declaration, potentially naming conflicts could happen. For exapmle, the following TypeScript code wouldn't have any error:

    type Test = string;
    function Test() { return 10; };
    var result = Test();

    However, we are reporting syntax errors for the corresponding JavaScript code using @typedef:

    /**@typedef {string}*/
    var Test;
    function Test() { return 10; };
    var result = Test();
  4. Types defined by @typedef should have a scope, and it may or may not be related to their parent node (they may not even have a parent node). For example:

    function A () {
        /**@typedef {string} TypeA*/
    
        /**@typedef {string} TypeB*/
        /**@param {string} p1*/
        function B () {
            ...
        }
    }

    Both TypeA and TypeB should live inside the block scope that function A contains.

Feedback is welcome to these questions, as I am not a heavy JsDoc user, so I might have missed some scenarios. I will keep prototyping potential solutions and update with a proposal once a reasonable design was reached.

@zhengbli zhengbli added Discussion Issues which may not have code impact Salsa labels Apr 1, 2016
@DanielRosenwasser
Copy link
Member

Can you give context for what some of these actually mean? For instance, I am not sure what the specific differences are between 4 and 5, and I'm not sure whether they're defining a type or declaring the type of a variable. Can you also clarify the difference between @type and @typedef?

However, the type name defined by @typedef can be a dotted name

Technically this could synthesize a namespace for the left portion of the qualified name.

@mhegazy
Copy link
Contributor

mhegazy commented Apr 1, 2016

I would assume that @typedef maps to a type alias declaration. that applies to scoping rules, and merging. TypeScript allows for a name (Symbol) to have multiple meanings (value, type or namespace), so your var foo example, foo is both a typename, and a variable name. the compile will use its type in type position and use its value in value position. goto declaration will take you to both places.

the syntax of @typedef is not something we can control however, so i would check JS code in github, and see what is the most common patterns and support these.

the namespace.type should be possible as well, we just have to create a namespace symbol for namespace and attach the type type alias declaration to it.

@zhengbli
Copy link
Contributor Author

zhengbli commented Apr 1, 2016

@DanielRosenwasser @typedef is used to define a type, while @type is used to describe the type of the next declaration. For example:

/**@typedef {(string | number) NumberLike*/

defines a union type called NumberLike. This declaration can be written in the other popular format as (with or without the var, basically that's also the only difference between case 4 and case 5 above):

/**@typedef {(string | number)*/
var NumberLike;

These translate to TS code:

type NumberLike = string | number;

And later in other JsDoc comments, you can say

/**@type {NumberLike}*/
var foo;

@mhegazy after browsing how people use @typedef on github I found that case 6 is pretty popular. So if we represent the types using type aliases, does that mean:

  • For People, we do:
type People = { count: number };
  • For People.person, we do:
namespace People {
    type person = {{ id: number, name: string }}; 
}

@mhegazy mhegazy added Suggestion An idea for TypeScript Fixed A PR has been merged for this issue and removed Discussion Issues which may not have code impact labels Jun 17, 2016
@mhegazy mhegazy added this to the TypeScript 2.0 milestone Jun 17, 2016
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Fixed A PR has been merged for this issue Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

3 participants