Reputation: 1912
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
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
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
Reputation: 366
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
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
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
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:
Upvotes: 2
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:
cons:
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
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.
Upvotes: 220