93

Let's say I have a file named "File1.js". In this file, I export an object of objects and I give each object a typedef, like so.

/**
 * My typedef for each object.
 * @typedef {Object} MyObject1
 * @property {String} username Your username
 * @property {String} realname Your real name.
 * @property {boolean} isUnique Are you unique as a person?
 */
module.exports = {
  /**
   * Person One!
   * @type {MyObject1}
   */
  myperson: {
    username: 'TheDragonSlayer',
    realname: 'George',
    isUnique: true
  },
  /**
   * Person Two!
   * @type {MyObject1}
   */
  myperson2: {
    username: 'BobMagee',
    realname: 'Bob',
    isUnique: false
  }
}

Now, in a file named "File2.js", I reference this object in a constructor and set it to a new MyObject1.

const persons = require('./File1.js');

class File2 {
  constructor(options = {}) {
    /**
     * The person for this file.
     * @type {MyObject1}
     */
    this.person = options.person ? persons[options.person] : persons.myperson2;
  }
}

module.exports = File2;

I use Visual Studio Code to develop, so by pressing Ctrl+Space I get IntelliSense. Within file one and while I'm making the person objects, IntelliSense tells me that username is a string, realname is a string, and isUnique is a boolean. But, when I go into file2 and reference the newly made person via this.person, when typing this.person.username it does not come up with the expected result of "Username: String".

Is it possible to use the typedef MyObject1 in File2 in vanilla Node.js, or am I out of luck?

Edit: With some more information, I was able to find answers with @export and @import for TypeScript, as well as a tag of sorts that I tried as well. All of which to no avail. I also tried marking File1.js as a @module, and doing module:mymodule~MyMethod, but every time I did that it'd just mark this.person as a NodeModule instead of the method itself.

Justin Emery
  • 1,644
  • 18
  • 25
FireController1847
  • 1,458
  • 1
  • 11
  • 26
  • Did you mean `@typedef` instead of `@typdef` in File1? – Peter G Apr 14 '18 at 22:42
  • @PeterG Yes, sorry about that! – FireController1847 Apr 14 '18 at 22:47
  • 1
    It may just be a question of how intelligent Intellisense is rather than a JSDoc thing. Using WebStorm IDE, I found this scenario works as expected but I'm often finding limits to JSDoc support - for example it's not working as expected when the @typedef is in a dependency project. – Justin Emery Apr 19 '18 at 08:55
  • There's a comment to this effect on the one answer below, but `import("some-module")` is supported by Typescript but is *not* official JSDoc. – Coderer Jan 21 '21 at 11:22
  • Also using `import "./types.js"` and @typedef in types.js file works for me. – aderchox Sep 15 '22 at 17:08

4 Answers4

170

Import the declared type in your file File2.js using the function import.

const persons = require('./File1.js');

/**
 * @typedef {import('./File1.js').MyObject1} MyObject1
 */

class File2 {
...

It works for me.

enter image description here enter image description here

Lemix
  • 2,415
  • 1
  • 15
  • 16
  • 20
    This is TypeScript-specific syntax. Vote for https://github.com/jsdoc/jsdoc/issues/1645. – glen-84 May 28 '20 at 10:42
  • 7
    Any way to not repeatedly type `import('./File1.js')`? (if I need e.g also `file1.MyObject2` and `file1.MyObjectN`). – Doing `@typedef {import('./File1.js')} file1` and `@typedef {file1.MyObject2} MyObject2` MyObject2 is only aliased and looses definition details, getting marked as `/*unresolved*/ any`. – Kamafeather Aug 29 '22 at 16:00
  • This is not working for me in VS Code. Curiously, it works when I do NOT import the file at all. Seems VS code scans typedefs and imports them automatically. That's not a great solution on their side. – Tomáš Zato Aug 24 '23 at 11:48
23

Another thing I discoverd working is to export and empty model and then use it as a reference to the types:

types.js:

/** 
* @typedef MyType 
* @prop {string} name  
* @prop {string} description 
*/

export const Types = {}

Then in your other files you can import that and have the types from that dummy object:

File1.js

import * as Types from "./types.js"

/** @type {Types.MyType} */
const myVar = { name : 'Roy', description : 'abc123'};

pros:

  • alot "cleaner" - less code to write
  • only have to import once

cons:

  • a bit hackey
  • not idiomatic
  • you might get the error:

This import is never used as a value and must use 'import type' because 'importsNotUsedAsValues' is set to 'error'

If you have that flag on.

To get around that warning you only need disable it with a ts-ignore:

// @ts-ignore  
import * as Types from "./types.js";
Chen Peleg
  • 953
  • 10
  • 16
1

Another solution to explore is to add a typescript declaration file @types/myTypes.d.ts... IDEs know how to deal with these and since they are .d.ts, they are automatically "imported" into each file.

The only downside is that these must be pure typescript, so you can't have anything like arrays in them.

It's also handy to add other @types to a project like @types/chrome for chrome extensions (e.g. yarn add @types/chrome)

You may need to configure your IDE to use them as a library. Here's where to do it in WebStorm: enter image description here

TrophyGeek
  • 5,902
  • 2
  • 36
  • 33
0

Here is the cleanest way I've found. I've verified that it works in VSCode for auto-complete/intellisense and shows the information on hover. TypeScript engines should also be able to infer all type info from this.

First off I create a file in the root called api-type-definitions.js. I'll flatten any nested objects/methods/functions so that each is it's own named type definition. Then they can reference each other as needed. Then I create an actual JavaScript variable and assign it the type and export the variable.

/**
 * OPTIONAL: console.error is called by default if verbose: true.
 *
 * @callback {Function} CUSTOMLOGGER
 * @param    {string}   message       The human readable warning/error message
 * @param    {object}   [error]       Sometimes an error or options object is passed
 * @return   {void}
 */

/**
 * @typedef  {object} WINDOWS
 * @property {string} filePath               The target the shortcut points to.
 * @property {string} [outputPath]           Path where shortcut will be placed. Defaults to user's desktop.
 * @property {string} [windowMode="normal"]  How the window should be displayed by default. Valid inputs: 'normal', 'maximized', 'minimized'. Defaults to 'normal'.
 */

/**
 * @typedef  {object} LINUX
 * @property {string} filePath      The target the shortcut points to.
 * @property {string} [outputPath]  Path where shortcut will be placed. Defaults to user's desktop.
 * @property {string} [comment]     Metadata file "comment" property. Description of what the shortcut would open.
 */

/**
 * @typedef  {object} OSX
 * @property {string} filePath      The target the shortcut points to.
 * @property {string} [outputPath]  Path where shortcut will be placed. Defaults to user's desktop.
 * @property {string} [name]        Name of the shortcut file.
 */

/**
 * @typedef  {object}       OPTIONS
 * @property {CUSTOMLOGGER} [customLogger]  Called (if verbose: true) with helpful warning/error messages from internal validators.
 * @property {WINDOWS}      [windows]       Windows shortcut settings.
 * @property {LINUX}        [linux]         Linux shortcut settings.
 * @property {OSX}          [osx]           OSX shortcut settings.
 */

/**
 * @type {LINUX}
 */
let LINUX;

/**
 * @type {OPTIONS}
 */
let OPTIONS;

module.exports = {
  LINUX,
  OPTIONS
};

Note that I only export the types I'll be reusing. The rest of the definitions are just flattened for easier writing/reading.

Then in any file where I want to use the type, I import the variable and reference it in the JSDoc block as the type.

const { OPTIONS } = require('./api-type-definitions.js');

/**
 * Creates OS based shortcuts for files, folders, urls, and applications.
 *
 * @param  {OPTIONS} options  Options object for each OS, and global options
 * @return {boolean}          True = success, false = failed to create the icon or set its permissions (Linux).
 */
function createDesktopShortcut (options) {
  // the library code
}

module.exports = createDesktopShortcut;

Example repos:

I would also highly recommend eslint-plugin-jsdoc, here are my rules if you want a jumping off point: