FireController1847
FireController1847

Reputation: 1912

How to "import" a typedef from one file to another in JSDoc using VS Code?

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.

Upvotes: 137

Views: 90349

Answers (8)

Bart Louwers
Bart Louwers

Reputation: 932

TypeScript supports @import since the TypeScript 5.5 Beta.

/** @import { SomeType } from "some-module" */

/**
 * @param {SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}

And for default imports:

/** @import * as someModule from "some-module" */

/**
 * @param {someModule.SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}

https://devblogs.microsoft.com/typescript/announcing-typescript-5-5-beta/#type-imports-in-jsdoc

Upvotes: 1

alexandre-rousseau
alexandre-rousseau

Reputation: 2820

Typescript 5.5 introduced @import syntax. So you can use ESM like syntax in JSdoc like you'ld do In Typescript.

From your example, it becomes:

const persons = require('./File1.js');
/** @import { MyObject1 } from './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;

Upvotes: 23

For those that do not need to import it, but still want the IntelliSense to work, there is another solution, posted here: https://stackoverflow.com/a/55767692/9427691

In short: For VC-Code javascript IntelliSence you can create a jsconfig.json file in the project root folder, that informs which files to include. like so:

{
  "include": [
    "src/**/*.js"
  ]
}

Upvotes: 3

Erick Wendel
Erick Wendel

Reputation: 482

There's another way.

create the types.js

/**
 * @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.
 */

just import them on your file.js

import './types.js'
/** @type {LINUX} */
const Linux = {}

Upvotes: 8

The Jared Wilcurt
The Jared Wilcurt

Reputation: 340

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:

Upvotes: 3

TrophyGeek
TrophyGeek

Reputation: 6099

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

Upvotes: 2

Chen Peleg
Chen Peleg

Reputation: 2034

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

Upvotes: 45

Lemix
Lemix

Reputation: 2920

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

Upvotes: 220

Related Questions