Reference a TS interface from jsDoc - javascript

Using VSCode is almost possible to have the benefits from Typescript in plain .js thanks to jsDoc notation and a tsconfig.json file:
{
"compileOnSave": false,
"compilerOptions": {
"noEmit": true,
"allowJs": true,
"checkJs": true,
"target": "es6",
"resolveJsonModule": true,
"moduleResolution": "node",
},
"include": ["index.js", "./src"]
}
/**
* #return {Promise<string>}
*/
const foo = Promise.resolve('hi');
module.exports = foo;
Now, is it possible to reference an interface defined in a d.ts at node_modules? in particular I'm returning a -let's call- "my_dependency.Storage" object but I'm unable to reference it using plain javascript:
/**
* #param {Storage} storage
*/
const goo = storage => ...
will understand that I'm refering to Web Storage API from lib.dom.d.ts
The equivalent typescript would be:
import {Storage} from "my_dependency"
I've tried using triple slash directives unsuccessfully
///<reference path="node_modules/my_dependency/lib/index.d.ts" />
I'm expecting something like (pseudo-code)
/**
* #param {my_module.Storage} storage
*/

As #Akxe mention this is how you can do this for referring a type from a lib:
/**
* #param {import("my_dependency").Storage} storage
*/
const goo = storage => ...
Now, I found myself repeating that statement over and over again so I created an ambient alias as following:
types.d.ts
declare namespace MyProject {
type St = import("my_dependency").Storage; // alias: reference my_dependency.Storage from plain js code
}
index.js
/// <reference path="types.d.ts"/>
...
src/foo.js
/**
* #param {MyProject.St} storage
*/
const goo = storage => ...
==EDIT==
Here is a post at dev.to that expands this topic

Related

Is there a way to find only unused JSDoc type definitions?

For example:
/**
* A number, or a string containing a number.
* #typedef {(number|string)} NumberLike
*
* Dummy type
* #typedef {(string|null)} StringLike
*/
/**
* Set the magic number.
* #param {NumberLike} x - The magic number.
*/
function setMagicNumber(x) {
}
As you can see NumberLike is used, but the StringLike type is not used and I'd like to find all such unused type definitions on our project. It's a Nodejs project with typescript installed.
Here's our tsconfig.json file:
{
"compilerOptions": {
"baseUrl": ".",
"jsx": "react",
"allowJs": true,
"checkJs": true,
"target": "ESNext",
"noEmit": true,
"moduleResolution": "node",
"isolatedModules": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"strictNullChecks": true,
},
}
I'm not presently familiar with AST tools (which I think would be the domain of tools for finding this information). However, here's an example to illustrate an idea:
Let's say that the example JavaScript file in your post is saved at ./module.mjs. Here's another module, ./find-typedefs.mjs:
import {promises as fs} from 'fs';
function getTypedefNames (sourceText) {
const regex = /#typedef\s+{[^}]+}\s+[A-Za-z]+/g;
return [...new Set((sourceText.match(regex) ?? [])
.map(str => str.split(/\s+/).slice(-1)[0]))];
}
async function main () {
const [filePath] = process.argv.slice(2)
const text = await fs.readFile(filePath, {encoding: 'utf8'});
const names = getTypedefNames(text);
console.log(names.join('\n'));
}
main();
Running the file outputs this:
$ node find-typedefs.mjs module.mjs
NumberLike
StringLike
By using the function getTypedefNames as a basis for potentially more abstractions, you could automate searching the files in your project to produce a list of names that you can then search (and, for example, count) in each file. If you are using an editor like VS Code, you can also use regular expressions (like the one in that function) directly in the editor's Find menu.
Hopefully this will save you some manual work of eye-scanning.

Link runtime dependencies, for VSCode autocomplete

I can't get VSCode to link in-code function parameters to their dependencies (vars, functions, modules), and get autocomplete suggestions in code.
Is there a way, with JSDoc, to make VScode recognize what module corresponds to a certain parameter?
// const database = require('./data-access/mongodb-adapter.js')
/**
* Factory for our service
*
* QUESTION: Can JSDoc refer a module in another file?
* (and instruct VSCode to wire it)
*
* #param {module('./data-access/mongodb-adapter.js')} database The adapter to the app database
*/
function makeService(database) {
return {
find: query => database.find(query)
}
}
module.exports = makeService
In PHP, with PHPDoc, I would type-hint the variable $database, adding an annotation /* #var MongodbAdapter $database */ in the same scope.
VSCode makes it clickable (when holding cmd ⌘) and links me to the referenced module/file/function.
Is there a way to specify the same on factory/constructor dependencies? (without transpiling)
in some cases you can if you are you using js files you need to create a jsconfig.json at the root of your project
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"ES5",
"dom.iterable",
"esnext",
"ES2015",
"ES2016",
"ES2017",
"ES2018",
"ES2019",
"ES2020"
],
"allowJs": true,
"checkJs": true,
"module": "commonjs",
"moduleResolution": "node",
"resolveJsonModule": true,
"declaration": true,
"declarationDir": "./typings",
"pretty": true,
"sourceMap": true,
"type":[],
"jsx": "react",
"baseUrl": "./node_modules",
"paths": {
"*": [
"./#types/*/index.d.ts",
"./#types/*/types.d.ts",
"./*/index.d.ts",
"./*/types.d.ts",
"../types/*/index.d.ts"
]
},
"typeRoots": [
"./#types",
"../types",
"../typings"
]
},
"typeAcquisition": {
"enable": true
},
"include": [
"src",
"types"
],
"exclude": [
"node_modules"
]
}
a jsconfig.json is the same as a tsconfig.json so if there are some types from your dependencies that you would like to use you need to install them and add them into the "type" array.
the way of using it is with for example we want to use express first we add
$ npm i -D #types/express
at the "type":[] array from the jsconfig we add express
...
"type":["express"]
...
now on your code
/**
* #description
* your desc
* #type {import("express").RequestHandler} RequestHandler
* #param {import("express").Request} req request
* #param {import("express").Response} res response
* #author Ernesto Jara Olveda
* #copyright (C) 14
*/
export const renderLoginPage = (req, res) => {
res.setHeader("Content-Type", "text/html; charset=uft-8");
res.render("login.hbs", {
canonical: "http://localhost:3000",
theme: "theme-light",
lang: "es",
highlight: "highlight-red",
gradient: "body-default",
token: req.csrfToken(),
});
};
if by any means the code u want to reference is not a package but a file in your src folder you have a problem, it cannot be done that way. you need to create a *.d.ts for example
src
--controller
----user
------user.js
------user.d.ts
let`s imagine we want to reference some from. user.js then we need to create a user.d.ts file and add all them definitions of your user.js
you can use https://www.typescriptlang.org/play?#code to help u out but as you can see there is alot you need to. write down. I recommend you to instead of doing all that create you project in typescript instead
enter image description here

Client-side dynamic import from TypeScript

I'm trying to create a client-side page that reads config settings from JavaScript files. I have it built in JavaScript and dynamically add scripts, but I'm converting over to TypeScript and trying to do it with dynamic import statements. It transpiles and runs fine using a development server (webpack and webpack-dev-server), but I want to run it on the local file system without a server. I just can't seem to find a combination of settings to make it work, assuming it even can. Can someone help?
The final structure I'm looking to create would be:
index.html
index.js
config
--userConfig.js
--vehicles
--Sonata.js
--Civic.js
where I can add new car descriptions into the vehicles folder and then change which one I want to use by changing the setting in userConfig.js
// config/userConfig.js
/**
* #typedef {object} userObj
* #param {string} vehicle
*/
/** #type {userObj} */
export var userCfg = {
vehicle: "Civic"
// config/vehicles/Civic.js
/**
* #typedef {object} carObject
* #param {string} make
* #param {string} model
* #param {number} maxRPM
*/
/** #type {carObject} */
export var car = {
make: "Honda",
model: "Civic",
maxRPM: "6500"
};
In the source config folder, I also have:
// config/vehicles/carConfig.ts
export class carObject {
make: string;
model: string;
maxRPM: number;
}
The html and index.ts files look like this
<!DOCTYPE html>
<html>
<head>
<script src="bundle.js"></script>
</head>
<body>
<div id="app">Web App Placeholder</div>
</body>
</html>
// index.ts
import { userCfg } from "./config/userConfig"
import * as carConfig from "./config/vehicles/carConfig"
var car: carConfig.carObject = new carConfig.carObject;
function showCar() {
document.getElementById("app").innerHTML =
`${car.make} ${car.model} - ${car.maxRPM}`;
}
window.onload = function () {
loadCarConfig(userCfg.vehicle).then(showCar);
}
async function loadCarConfig(path: string) {
let v = await import(`./config/vehicles/${path}.js`);
car = v.car;
}
Lastly, the tsconfig.json and webpack.config.js are:
{
"compilerOptions": {
"target": "ES2018",
"outDir": "./dist",
"rootDir": "./src",
"module": "commonjs",
"allowJs": true
},
"exclude": [
"./webpack.config.js",
"./dist"
]
}
// #ts-ignore
module.exports = {
mode: "development",
entry: "./src/index.ts",
output: { filename: "bundle.js" },
resolve: { extensions: [".ts", ".js" ]},
module: {
rules: [
{ test: /\.ts/, use: "ts-loader", exclude: /node-modules/ }
]
},
devServer: {
contentBase: "./assets",
port: 4500
}
};
Changing the vehicle property in userConfig.js to another vehicle (assuming a corresponding .js file exists) will change which properties are loaded.
Is there a good way to make this work client side without a server? Thanks

Javascript: Unexpected token { - occurs when try to import custom class in typescript file into javascript file

I am writing simple CLI scripts for react-native project. But I am not available to import class from typescript to javascript. It throw me following error.
import { TestResult } from './src/lib/modules/test-result';
SyntaxError: Unexpected token {...
package.json
...
"script": {
...
"formular:convert": "node convertor.js"
...
}
...
convertor.js
import {TestResult} from './src/lib/models/test-result';
...
test-result.ts
import uuid from 'uuid/v4';
import { Exception } from '../../errors';
import { OilTypes, RawMaterialTypes } from '../oils';
import { CalculatedResult } from './calculated-result';
import { DeviceSettings } from './device-settings';
import { DeviceStatus } from './device-status';
import { DisplayedResult } from './displayed-result';
import { Measurement } from './measurement';
import { PhoneInfo } from './phone-info';
import { PrimaryCompound } from './primary-compound';
export class TestResult {
constructor(
public id: string = uuid(),
public createdAt: Date = new Date(),
public updatedAt: Date = new Date(),
public errors: Exception[] = [],
public alias: string = '',
public analyteTemp: number = 75,
public flowerWeight: number = 0,
public hidden: boolean = false,
public note: string = '',
public oilType: OilTypes = OilTypes.OliveOil,
public primaryCompound: PrimaryCompound = PrimaryCompound.UNDEFINED,
public units: string = '',
public user: string = '',
public air310Meas: Measurement = new Measurement(),
public air280Meas: Measurement = new Measurement(),
public analyte310Meas: Measurement = new Measurement(),
public analyte280Meas: Measurement = new Measurement(),
public calculated: CalculatedResult = new CalculatedResult(),
public displayed: DisplayedResult = new DisplayedResult(),
public status: DeviceStatus = new DeviceStatus(),
public settings: DeviceSettings = new DeviceSettings(),
public phoneInfo: PhoneInfo = new PhoneInfo(),
public rawMaterialType: RawMaterialTypes = RawMaterialTypes.Isolate,
) {
}
}
tsconfig.json
{
"compilerOptions": {
/* Basic Options */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
"allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
"jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
/* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
"removeComments": true, /* Do not emit comments to output. */
"noEmit": true, /* Do not emit outputs. */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
/* Additional Checks */
"noUnusedLocals": true, /* Report errors on unused locals. */
"noUnusedParameters": true, /* Report errors on unused parameters. */
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"baseUrl": "./", /* Base directory to resolve non-absolute module names. */
"types": [ /* Type declaration files to be included in compilation. */
"jest",
"mocha",
"detox"
],
"allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
/* Experimental Options */
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
"skipLibCheck": true, /* Skip type checking of declaration files. Requires TypeScript version 2.0 or later. */
"forceConsistentCasingInFileNames": true,/* Disallow inconsistently-cased references to the same file. */
"resolveJsonModule": true /* Include modules imported with '.json' extension. Requires TypeScript version 2.9 or later. */
},
"exclude": [
"node_modules",
"babel.config.js",
"metro.config.js",
"jest.config.js"
]
}
I stuggled to find the solution, and found some answers here and there, but all of them doesn't work.
Thanks for your help.
You are running node convertor.js and convertor.js contains an import statement: import {TestResult} from './src/lib/models/test-result'; This is not valid syntax in the version of node you are running, hence the error.
Fix
You need to transpile the non-transpiled .js files with something like TypeScript (allowJs) or Babel.
I ran into this issue as well while trying to use the react-native-device-info library with react-native 0.59. It turned out I was missing the #babel/core library in my package.json. After adding this module, the error went away and I was able to build the application.
Install using npm
npm i -s #babel/core
Install using yarn
yarn add #babel/core

Transpile typescript into plain Javascript

Something is a bit cloudy in my mind which is the following.
I have a module written in typescript which will be imported later in some html pages.
sdk.ts
export class PM {
header: any;
headerLink: string;
headerDiv: any;
/**
* #todo remove constructor.
*/
constructor(mode: string) {
if (mode == null || mode == undefined) {
this.buildGUI();
}
}
/**
* Build GUI.
* It builds the GUI by wrapping the body in a container, adding the header and sidebar.
*/
buildGUI(): void {
this.initAndCreateComponents();
this.insertScript(this.headerLink);
}
/**
* Insert script.
* It inserts the script's import tag in the head of the document.
* #param {string} scriptLink - script's link to be loaded.
*/
insertScript(scriptLink: string): void {
const script = document.createElement('script');
script.src = scriptLink;
document.body.appendChild(script);
};
/**
* Init and Create Components.
* It initialises the variables values and it creates the components.
*/
initAndCreateComponents(): void {
this.headerLink = '/header/pm-header.js';
this.header = document.createElement("pm-header");
this.headerDiv = document.createElement("div");
this.headerDiv.classList.add('pm-header-wrapper');
this.headerDiv.appendChild(this.header);
document.body.insertBefore(this.headerDiv, document.body.firstChild);
}
}
new PM(null);
and this is my tsconfig.json
{
"compileOnSave": false,
"include": [
"src",
"test"
],
"exclude": [
"dist",
"node_modules"
],
"compilerOptions": {
"sourceMap": false,
"outDir": "./dist",
"declaration": true,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"typeRoots": [
"node_modules/#types"
],
"types": [
"#types/jasmine",
"#types/node"
],
"lib": [
"es2017",
"dom",
"es2015.generator",
"es2015.iterable",
"es2015.promise",
"es2015.symbol",
"es2015.symbol.wellknown",
"esnext.asynciterable"
]
}
}
now when I run tsc I get and sdk.js that looks like this:
define(["require", "exports"], function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var PM = /** #class */ (function () {
/**
* #todo remove constructor.
*/
function PM(mode) {
if (mode == null || mode == undefined) {
this.buildGUI();
}
}
/**
* Build GUI.
* It builds the GUI by wrapping the body in a container, adding the header and sidebar.
*/
PM.prototype.buildGUI = function () {
this.initAndCreateComponents();
this.insertScript(this.headerLink);
};
...
Now this generated file is supposed to be imported in several html pages, and when I did my research I found that it could only be loaded using require like this:
<script data-main="/sdk/sdk.js" src="/sdk/require.js"></script>
What I want is a way to load my script without the use of any library, to be loaded like any regular plain javascript file.
If you don't want to use a module system (although I highly recommend you look into using one) you should remove export from your class (and from any other symbol in your file) , this will make your module be treated as a simple script file.
You should also add "module": "none" to your tsconfig.json to let the compiler know you will not be using a module system. This should trigger errors anywhere your code depends on modules (either because you export something or you use an import)
Note Since you will not be using a module system any class/variable/function you declare in your script file will be in the global scope (as they would be for any js file). You may want to consider using namespaces to organize your code and get out of the global scope.

Categories