Typescript: [Salsa] Provide a JSDoc equivalent of `import { ..} from "mod"`

Created on 1 Mar 2017  ·  52Comments  ·  Source: microsoft/TypeScript

Given an implementation of a class:

// file.js

export default class File {
   ...
}

How can you use the type without importing the module:

// another
export default Util {
   /** @param {File} file  */
   getFile(file) {
   }
}

One option is to use @module
e.g.:

   /** @param {module:./file:File} file  */
   getFile(file) {
   }
In Discussion Suggestion VS Code Tracked

Most helpful comment

Maybe?

/**
 * @import {File} from './file'
 */

/**
 * @param {File} file
 */
getFile(file) {
}

All 52 comments

I hit the same issue today, the idea is to not relying on transpiling on server side, for example, I want to define my own object type like this, below does not work:

/**
 * @typedef {Object} Watcher
 * @property {string} dir
 * @property {fs.FSWatcher} watcher
 */

The editor complains that it does not understand fs

We will proceed with #14056 for now, try to special case classes.

Such a workflow would also be brilliant for Angular1 apps where you may have a main file that injects all your dependencies and a separate file that specifies your controller. The controller file exports a function/class. It receives its dependencies but does not inject them directly, being able to tag them using JSdoc as a type that was defined in a different file in your workspace would be invaluable there.

@mhegazy did you ever get anything working along these lines? Trying to reference a js class from a typedef, sounds like the same problem.

Any progress here?

Maybe?

/**
 * @import {File} from './file'
 */

/**
 * @param {File} file
 */
getFile(file) {
}

Helo, was there any update on this? It would be great to have this.

Having a straightforward way to do this is a must in any decent code design where one needs to inject a dependency. Any updates, please?

Any update on this?

I suggest we use the same syntax as Closure does:

/** @param {./foo.Foo} Foo */
function(Foo) {}

https://github.com/google/closure-compiler/wiki/JS-Modules#type-references

You can also write the syntax as /** @type {./path/to/module.propName} */

Sadly, the conversation has not gone far to implement this feature:( Very much looking forward to having this added.

I suggest the use of the same syntax and behind-the ts mechanism as es6 does.
Starting with a triple / ambient slashes as in app.js

/// import Vue from "vue";

/**
 * @type {Vue}
 */
var app = new Vue({
...
)}

We're going to hold off on any new JSDoc syntax, and wait for more feedback here. As per #22445, our current recommendation is to wait on #14844 and then use

/**
 * @typedef {import("express")} express
 */

or

/**
 * @typedef {import("vue").default} Vue
 */

closing in favor of #14844

@DanielRosenwasser

Just remember that Salsa also works with ES2016/ES6 class imports, not just TypeScript. That syntax looks rather foreign to me since I don't use TS.

The syntax in the proposal is not a TS syntax either. we have surveyed github code bases and did not find a common syntax to use instead. use of JSDoc /* @module */ or /* @exports */ was very sparse, if non-existent. We picked import since it looks like ES6 module imports.

Is this covered somewhere after #14844 has been closed? What has been picked? Please plan forward to document it at [https://github.com/Microsoft/TypeScript/wiki/JsDoc-support-in-JavaScript] or [https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html]

The issue has been closed in favor of https://github.com/Microsoft/TypeScript/issues/14844, which is fixed by https://github.com/Microsoft/TypeScript/pull/22592. The change should be included in the next release TypeScript 2.9.
with that you should be able to write:

/** @type {import("./externs").Bar} */
var a;

or

/** @type {typeof import("./externs").Bar}
var b;

More docs should be available as we approach the release date for TS 2.9

no work

image

version 1.22.2 (1.22.2)

Should be working correctly in latest drop of TypeSript (@next). VSCode ships with an older version of TypeScript. Please see Using Newer TypeScript Versions documentation for more details on updating your VSCode to use a different version of TypeScript.

@mhegazy I just tried it in the latest next and it doesn't look like /** @typedef {import("http")} http */ works as the equivalent of an unused const http = require('http');. Extracting individual properties does though:

/**
 * The following works using latest typescript@next (2.9.0-dev.20180506):
 *
 * @typedef {import('http').IncomingMessage} IncomingMessage
 * @typedef {import('http').ServerResponse} ServerResponse
 *
 * But this fails:
 *
 * > Module 'http' does not refer to a type, but is used as a type here.
 *
 * @typedef {import('http')} http
 */

/**
 * @param {IncomingMessage} req
 * @param {ServerResponse} res
 */
function working(req, res) {
  /* [...] */
}

/**
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 */
function notWorking(req, res) {
  /* [...] */
}

This starts to become annoying when importing more than 1 or 2 types from a module.

use typeof instead:

/** @typedef {typeof import('http')} http */

That was one of the alternatives I tried. Unfortunately it doesn't work either.

/**
 * @typedef {typeof import('http')} http
 *
 * The line below fails with:
 *
 * > Cannot access 'http.IncomingMessage' because 'http' is a type, but not a namespace.
 * > Did you mean to retrieve the type of the property 'IncomingMessage' in 'http' with 'http["IncomingMessage"]'?
 *
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 */
function handler(req, res) {
}

If I do change it to http["IncomingMessage"] as suggested by the error message, then using the function fails instead:

/**
 * @typedef {typeof import('http')} http
 *
 * @param {http["IncomingMessage"]} req
 * @param {http["ServerResponse"]} res
 */
function handler(req, res) {}

// > Argument of type '(req: typeof IncomingMessage, res: typeof ServerResponse) => void'
// > is not assignable to parameter of type '(request: IncomingMessage, response: ServerResponse) => void'.
// > Types of parameters 'req' and 'request' are incompatible.
// > Type 'IncomingMessage' is not assignable to type 'typeof IncomingMessage'.
// > Property 'prototype' is missing in type 'IncomingMessage'.
require('http').createServer(handler);

The short version is - what is the suggested way to add JSDoc to handler so that it checks correctly without modifying the code (we're assuming that createServer is in another module and it wouldn't make sense to require http in the file that implements handler):

/**
 * ?
 */
function handler(req, res) {}

require('http').createServer(handler);

Maybe I'm missing something fundamental about how JSDoc types in TypeScript work..?

Since #14844 doesn't mention JSDoc at all, I'm asking here -- how does this new functionality impact eslint? If I get this working in tsc and/or VS Code, will I still be able to run "vanilla" eslint and have it understand the updated syntax? If not, is "this breaks eslint" a good enough reason to reopen this ticket or file a new one?

looks like a question/suggestion for the eslint issue tracker.

I mentioned it on an eslint issue and it sounds like it's fine -- I was just worried that maybe TS was making a vendor-specific JSDoc extension / syntax that wouldn't be compatible with other tools.

I was following this issue assuming that the change would allow IntelliSense suggestions/parameter info to work inside a .js file without doing an explicit require for the module, even for files that don't have typings. I've installed typescript@next and pointed to it using typescript.tsdk, but I can't get suggestions to work doing something like this:

/** @typedef {import('./myService')} myService */

/**
 * @param {myService} myService
 */
function myFunction(myService) {
  myService. // Doesn't show the same suggestions that require('./myService') shows
}

Is there a way to get this to work, or is it separate from this issue?

what does myService.js exports look like?

Thanks for the quick reply! Just some functions with doc blocks:

/**
 * @param {string} param1
 * @param {string} param2
 */
function moduleFunc(param1, param2) {

}
module.exports = {
  moduleFunc,
};

@yulianovdey, filed https://github.com/Microsoft/TypeScript/issues/24064 to track this issue.

@mhegazy Thanks! I appreciate it.

Is/Can the following case be covered/coverable?

type of webpack.Configuration defines devServer as any. So would like to apply type of webpack-dev-server.Configuration to the devServer property.

In the following code devServer is still identified as (property) webpack.Configuration.devServer?: any.

/**
 * @type {import ("webpack").Configuration}
 */
var webpackConfig =
  {
    /**
     * @type {import("webpack-dev-server").Configuration}
     */
    devServer: {
      port: 8080,
      watchContentBase: false,
      hot: true,
      stats: "errors-only",
      host: "0.0.0.0",
    },

You could put in a PR against DefinitelyTyped to fix @types/webpack, then you wouldn't have to worry about it. In fact, the typings file I'm looking at even has "// TODO: Type this" on devServer :D

Thanks for the hint;
Still I think that my more nearer/specific typing may/should be applied. On the general case that artifacts are meant to be not coupled.

When extracted, the following case displays (property) devServer: WebpackDevServer.Configuration for webpackConfig.devServer.

/**
 * @type {import("webpack-dev-server").Configuration}
 */
const devServer = {
  port: 8080,
  watchContentBase: false,
  hot: true,
  stats: "errors-only",
  host: "0.0.0.0",
}

/**
 * @type {import ("webpack").Configuration}
 */
var webpackConfig = {
    devServer
}

Unfortunately, this code breaks my jsdoc generation:

/**
 * @typedef {import("./foo").Bar} Bar
 */

Results into:

ERROR: Unable to parse a tag's type expression for source file in line 1 with tag title "typedef" and text "{import("./foo").Bar} Bar": Invalid type expression "import("./foo").Bar": Expected "!", "=", "?", "[]", "|" or end of input but "(" found.

when I execute jsdoc. Could you give any hints on how to keep my jsdoc working?

looks like a request for your jsdoc generation tool.

@mhegazy Does not seem like that work

image

Currently now only this below work

image

you have declared a type alias called firebase whose type is typeof import('firebase'), but that has nothing to do with the variable firebase that is not defined.

consider adding a .d.ts file in your project with a declaration for firebase:

// globals.d.ts

declare var firebase: typeof import("firebase");

also /* global ... */ is not supported at the moment, https://github.com/Microsoft/TypeScript/issues/15626 tracks adding that.

@mhegazy It is still the same that it need to have d.ts separate from js while I would like to declare in js purely (or don't even need to declare anything if possible)

ps. The /* global */ there is not related. I just put it to satisfied eslint

@mhegazy Also if I put the declare var in some d.ts into the project. I need to actively open it in the tab. Else it was not counted in the engine

It work fine here

image

Until I close the file

image

edit: Seem like this was cause from putting that d.ts in subfolder of the project. Bring out to root folder would not happen

Also if I put the declare var in some d.ts into the project. I need to actively open it in the tab. Else it was not counted in the engine

Make sure you add a jsconfig.json at the root of your project.

Unfortunately, this code breaks my jsdoc generation:

/**
 * @typedef {import("./foo").Bar} Bar
 */

This issue is encompassed in https://github.com/jsdoc3/jsdoc/issues/1537.

JSDoc is not a very active project so I think it's unlikely to add support for this functionality. Unfortunately, this makes it very difficult to migrate vanilla JavaScript to TypeScript type checking while still appeasing JSDoc stakeholders wary of change since the new syntax breaks JSDoc execution. I've experimented with /// <reference path="foo.js" /> syntax but haven't been able to find a substitute that will allow TypeScript to pull in the JSDoc typing.

If it were supported, the /// import {Foo} from './bar' syntax proposed by @joma74 or even /** @import {Foo} from './bar' */ by @sergeysova would work for most folks since the default JSDoc config allows unknown tags such as @import.

@joma74 You could actually do the following:

/**
 * @type {import('webpack').Configuration & { devServer: import('webpack-dev-server').Configuration }}
 */
var webpackConfig = {
  devServer: {
    port: 8080,
    watchContentBase: false,
    hot: true,
    stats: 'errors-only',
    host: '0.0.0.0',
  },
  // ...
};

@mhegazy

declare var firebase: typeof import("firebase");

I made this file in project and it was add to .d.ts and normal intellisense work correctly

image

However the JSDoc does not register the class in namespace loaded this way

image

Can you share a project i can look at?

@mhegazy Thank you

I was make it compact so might need to run npm install first

TTTT.zip

I'm surprised that this was closed as "fixed", when the intent was to "provide a JSDoc equivalent of ...".

/** @typedef {import('foo').Bar} Bar */ is not a "JSDoc equivalent", since it's invalid JSDoc...!

As already pointed out by https://github.com/Microsoft/TypeScript/issues/14377#issuecomment-400464783, it would be great to have a real equivalent that could would _simultaneously_ let Typescript-in-Javascript know about the Bar type _and_ not have JSDoc complain about not knowing Bar in regular @type {Bar}/@param {Bar} bar usage throughout the file.

❤️

Sadly, we still have nothing good to use? :(

It would be great if this issue could be reopened to continue discussion and not let it get lost.

Do even JSDoc have an official syntax for type import?

Is there an way to reference an class as an argument without importing it?

EDIT: For now only this worked for me

/** @typedef {import('../my-class').default} MyClass */

I did a little patch in JSDoc to does not breaks and accepts {import('./file').Foo} -> https://github.com/ricardohbin/jsdoc/pull/1

Was this page helpful?
0 / 5 - 0 ratings