eslint-plugin-import how to add flow declared modules to exceptions - eslint

I have a file in flow-typed directory with some common type declarations like:
common-types.js
// #flow
declare module 'common-types' {
declare export type RequestState = {
setLoading: () => void,
setFinished: () => void,
setError: (error: AxiosFailure) => void,
reset: () => void,
status: LoadingStatus,
error: AxiosFailure | void,
};
declare export type LoadingStatus = '' | 'loading' | 'finished';
declare export type ErrorObject = { [key: string]: string | string[] | Error };
declare export type AxiosFailure = {
status: number,
data: ErrorObject,
}
}
Now I import it like this in files:
import type { RequestState } from 'common-types';
but I get eslint-plugin-import errors about missing file extension as well as unable to resolve path to module 'common-types'
How do I deal with it?

I found a solution. As #logansmyth suggested in comment
Your types should just pass your code along with your data
The problem I had was with webpack aliases. Flow pointed me errors about modules not being installed. Hovewer I found out that I can use mappers in .flowconfig like:
module.name_mapper='^common' ->'<PROJECT_ROOT>/src/common'
along with webpack aliases, which makes eslint-plugin-import and flow happy as well properly type-checking. Now I import types along with common components, no messing with flow-typed directory.

Related

Sometimes when I update the snapshots I got an Attribute __ngContext__

Sometimes when I update the snapshots I got an Attribute ngContext and for fix this problem I've to clean and install my node_modules to "fix" this issue.
I've to do this every time that I need to update a snapshot. I've already searched on multiple solutions and nothing worked.
snapshotSerializers: \[
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
\],
Can someone help me with this, please?
Here is an image
I've updated the jest versions and also the jest-present-angular too but didn't work.
I just want to have a solution that does not makes me clean install the node_modules every time
This is indeed annoying especially because it tends to change after upgrading angular version. My snapshots are now failing as well because of this difference :-/.
- __ngContext__={[Function LRootView]}
+ __ngContext__="0"
So, having look at the jest configuration, the snapshot serializers are being loaded from 'jest-preset-angular' module.
The relevant plugin here is 'jest-preset-angular/build/serializers/ng-snapshot'. Now, they are two ways what to do to get rid of __ngContext__.
replace the plugin entirely by a modified copy
Create a copy of that file in the same directory and adapt it accordingly (line https://github.com/thymikee/jest-preset-angular/blob/40b769b8eba0b82913827793b6d9fe06d41808d9/src/serializers/ng-snapshot.ts#L69):
const attributes = Object.keys(componentInstance).filter(key => key !== '__ngContext__');
Adapt the configuration:
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'./custom-snapshot-serializer.ts',
'jest-preset-angular/build/serializers/html-comment',
],
The disadvantage of this solution is that you have to maintain the plugin although only one line has been changed.
replace the plugin by a wrapper (preferred solution)
This creates just a wrapper for the original implementation. The idea is to remove __ngContext__ before it moves on down the plugin chain. However, the logic of the original plugin is used for the fixture serialization.
import type { ComponentRef, DebugNode, Type, ɵCssSelectorList } from '#angular/core';
import type { ComponentFixture } from '#angular/core/testing';
import type { Colors } from 'pretty-format';
import { test as origTest, print as origPrint } from 'jest-preset-angular/build/serializers/ng-snapshot';
/**
* The follow interfaces are customized heavily inspired by #angular/core/core.d.ts
*/
interface ComponentDef {
selectors: ɵCssSelectorList;
}
interface IvyComponentType extends Type<unknown> {
ɵcmp: ComponentDef;
}
interface NgComponentRef extends ComponentRef<unknown> {
componentType: IvyComponentType;
_elDef: any; // eslint-disable-line #typescript-eslint/no-explicit-any
_view: any; // eslint-disable-line #typescript-eslint/no-explicit-any
}
interface NgComponentFixture extends ComponentFixture<unknown> {
componentRef: NgComponentRef;
// eslint-disable-next-line #typescript-eslint/no-explicit-any
componentInstance: Record<string, any>;
}
/**
* The following types haven't been exported by jest so temporarily we copy typings from 'pretty-format'
*/
interface PluginOptions {
edgeSpacing: string;
min: boolean;
spacing: string;
}
type Indent = (indentSpaces: string) => string;
type Printer = (elementToSerialize: unknown) => string;
export const print = (fixture: any, print: Printer, indent: Indent, opts: PluginOptions, colors: Colors): any => {
const componentInstance = (fixture as NgComponentFixture).componentInstance;
const instance = { ...componentInstance };
delete instance.__ngContext__;
const modifiedFixture = { ...fixture, componentInstance: { ...instance } };
return origPrint(modifiedFixture, print, indent, opts, colors);
};
// eslint-disable-next-line #typescript-eslint/no-explicit-any, #typescript-eslint/explicit-module-boundary-types
export const test = (val: any): boolean => {
return origTest(val);
};
The configuration is adapted the same way as before.

Typescript extending a generic type

I have the following generic interface in my typescript code:
interface BaseResponse<T> {
status_code: string;
data: T;
}
I thought I would be able to use that base interface, without specifying the base's type parameter, in a generic function like this:
class MyService {
static async post<T extends BaseResponse>(path: string, data: any): Promise<T> {
// implementation here
}
}
But this gives the following error:
Generic type 'BaseResponse<T>' requires 1 type argument(s).(2314)
I can fix this error by updating the code like so:
class MyService {
static async post<T extends BaseResponse<U>, U>(path: string, data: any): Promise<T> {
// implementation here
}
}
But this requires me to pass two type parameters when I call the function as below. I was hoping I could only pass one and it could infer the second, but that gives me the error Expected 2 type arguments, but got 1.(2558). Is there any way to accomplish this?
// What I want to be able to do (Causes error mentioned above):
const response1 = await MyService.post<CustomerResponse>('/customers', postData);
// What I have to do instead (note the two type parameters)
const response2 = await MyService.post<CustomerResponse, CustomerData>('/customers', postData);

How to extend built-in function/add a property to a function?

process.hrtime.bigint() does not exist in the standard lib. I'm trying to add it in.
Here's what I've got:
types/bigint.d.ts
// https://github.com/Microsoft/TypeScript/issues/15096#issuecomment-400186862
type BigInt = number
declare const BigInt: typeof Number;
declare namespace NodeJS {
export interface Process {
hrtime: HighResTime
}
export interface HighResTime {
(time?: [number, number]): [number, number];
bigint(): BigInt
}
}
Which I've added to my typeRoots in tsconfig.json:
"typeRoots": ["./node_modules/#types","./types"]
Using like:
///<reference path="../types/bigint.d.ts"/>
export default class ProgressBar {
private startTime?: BigInt;
start() {
this.startTime = process.hrtime.bigint();
this.render();
}
But I get:
/home/me/Projects/xxx/node_modules/ts-node/src/index.ts:261
return new TSError(diagnosticText, diagnosticCodes)
^
TSError: ⨯ Unable to compile TypeScript:
src/ProgressBar.ts(19,41): error TS2339: Property 'bigint' does not exist on type '(time?: [number, number] | undefined) => [number, number]'.
If I remove the <reference> to bigint.d.ts then it gets even worse, and complains the type BigInt doesn't exist (TS2304) -- which doesn't make much sense to me, i thought that was the point of the typeRoot?
But the bigger issue is that even with the reference, TS still doesn't like process.hrtime.bigint(), so I'm guessing I didn't extend the Process interface properly. How do I do it right?

Extend Express JS router TypeScript definition for “named-routes”

We're using an extension called named-routes with Express which has served us quite well in the past. Now that we’re gradually TypeScript-ifying our codebase, we are facing following issue: The module extends Express’ router object, so that routes can have an identifier:
router.get('/admin/user/:id', 'admin.user.edit', (req, res, next) => …
The Express typings are of course not aware of the this optional identifier and report a compile error. I followed the instructions from “Module Augmentation” and created the following express-named-routes.d.ts:
import { IRouterMatcher } from 'express';
import { PathParams, RequestHandlerParams } from 'express-serve-static-core';
declare module 'express' {
export interface IRouterMatcher<T> {
// copied from existing decl. and added the `name` argument
(path: PathParams, name: string, ...handlers: RequestHandler[]): T;
(path: PathParams, name: string, ...handlers: RequestHandlerParams[]): T;
}
}
And of course imported it in the corresponding file:
import '../types/express-named-routes'
But this still gives me an error TS2345: Argument of type '"my.route.name"' is not assignable to parameter of type 'RequestHandlerParams'.
Try wrapping it inside a module called 'named-routes' like this:
declare module 'named-routes' {
import { IRouterMatcher } from 'express';
import { PathParams, RequestHandler, RequestHandlerParams } from 'express-serve-static-core';
module 'express-serve-static-core' {
export interface IRouterMatcher<T> {
// copied from existing decl. and added the `name` argument
(path: PathParams, name: string, ...handlers: RequestHandler[]): T;
(path: PathParams, name: string, ...handlers: RequestHandlerParams[]): T;
}
}
}
Update: I’ve made the typings now available on DefinitelyTyped via #types/named-routes.

Use commander in typescript

I try to use commander in typescript and I could like to give a proper type to my cli. So I start with this code:
import * as program from "commander";
const cli = program
.version("1.0.0")
.usage("[options]")
.option("-d, --debug", "activate more debug messages. Can be set by env var DEBUG.", false)
.parse(process.argv);
console.log(cli.debug)
But I get this error:
example.ts(9,17): error TS2339: Property 'debug' does not exist on type 'Command'.
So I tried to add an interface, as documented here:
import * as program from "commander";
interface InterfaceCLI extends commander.Command {
debug?: boolean;
}
const cli: InterfaceCLI = program
.version("1.0.0")
.usage("[options]")
.option("-d, --debug", "activate more debug messages. Can be set by env var DEBUG.", false)
.parse(process.argv);
console.log(cli.debug)
and I get this error:
example.ts(3,32): error TS2503: Cannot find namespace 'commander'.
From what I understand, cli is actually a class of type commander.Command So I tried to add a class:
import * as program from "commander";
class Cli extends program.Command {
public debug: boolean;
}
const cli: Cli = program
.version("1.0.0")
.usage("[options]")
.option("-d, --debug", "activate more debug messages. Can be set by env var DEBUG.", false)
.parse(process.argv);
console.log(cli.debug)
Which gives me this error:
example.ts(7,7): error TS2322: Type 'Command' is not assignable to type 'Cli'.
Property 'debug' is missing in type 'Command'.
I don't know how to add a property to the Command class, either in my file or in a new .d.ts file.
With your first code snippet and the following dependencies, I do not get an error:
"dependencies": {
"commander": "^2.11.0"
},
"devDependencies": {
"#types/commander": "^2.9.1",
"typescript": "^2.4.1"
}
Typescript interprets cli.debug as any. I guess the type declarations have been updated. So, if you are fine with any, the problem is solved.
If you really want to tell Typescript the type of debug, declaration merging would in principle be the way to go. It basically works like this:
class C {
public foo: number;
}
interface C {
bar: number;
}
const c = new C();
const fooBar = c.foo + c.bar;
However, there is a problem: program.Command is not a type but a variable. So, you cannot do this:
interface program.Command {
debug: boolean;
}
And while you can do this:
function f1(): typeof program.Command {
return program.Command;
}
type T = typeof program.Command;
function f2(): T {
return program.Command;
}
You can neither do this:
interface typeof program.Command {
}
Nor this:
type T = typeof program.Command;
interface T {
}
I do not know whether this problem could be solved or not.
I think I found the solution, I don't really know if it's a good practice, but nontheless.
import { Command } from 'commander';
const cli = new Command();
interface InterfaceCLI{
debug?: boolean;
}
cli
.version("1.0.0")
.usage("[options]")
.option("-d, --debug", "activate more debug messages. Can be set by env var DEBUG.", false)
.parse(process.argv);
const { debug } : InterfaceCli = <InterfaceCli><unknown>cli;
console.log(debug) // here debug is your cli.debug, I just used object destructuring
In the line before the last one I'm using type casting, I'm first casting to unkown for non-overlapping types, and then, finally, casting to our interface - InterfaceCli.

Resources