Is there any function's result value must be used specifier In Typescript? - eslint

I want to specify that the function's return value must be used using typescript.
There is an attribute [[nodiscard]] in c++. Is there any similar attribute in Typescript?
Example:
function setSomeFields(someThing: MyClass) {
const other = new MyClass();
other.value = someThing.value;
//...
return other;
}
//...
// wanted: error or warning
setSomeFields(myClass);
// correct
const newMyClass = setSomeFields(myClass);

There was a discussion on eslint to for a no-unused-return-value rule that would give you the warning you want but a rule like that was never included. For now, the SonarJS plugin for eslint does seem to have such a rule.

Related

TypeScript shared configuration object with typehinting

I am developing an API library and I am curious about how should endpoint configuration problem should be approached in Node.js with TypeScript. I want all endpoint configuration to be contained within one entity.
I currently have this approach in place:
ApiConstants.ts
------------------------
const BASE_DOMAIN = 'https://api.example.com';
export default Object.freeze({
BASE_DOMAIN: {
V1: `${BASE_DOMAIN}/v0.1`,
V2: `${BASE_DOMAIN}/v0.2`,
V3: `${BASE_DOMAIN}/v0.3`,
},
PATH: {
CATS: '/animals/cats',
},
});
It does the job, I can use it in any class by importing it and accessing the values. The problem is that I want to restrict functions to only accept values declared within this object. When request constructing function should display invalid type intellisense when value is passed which is not a part of this object.
Desired type would look something like this. Path must be declared within ApiConstants.PATH object.
function makeRequest(path: ApiConstants.PATH) {
...
}
How can such behavior be achieved?
function makeRequest(path: keyof typeof ApiConstants.PATH) {
// ...
}
See in Playground
In general, you probably want to do something like
const ApiConstants = Object.freeze({
BASE_DOMAIN: {
V1: `${BASE_DOMAIN}/v0.1`,
V2: `${BASE_DOMAIN}/v0.2`,
V3: `${BASE_DOMAIN}/v0.3`,
},
PATH: {
CATS: '/animals/cats',
},
});
type ApiConstants = typeof ApiConstants;
export default ApiConstants;
to avoid having to use typeof ApiConstants all the time. Note that this means ApiConstants is both a value and a type—Typescript is OK with this, and will know from context what you’re doing, but some programmers find it confusing. A common naming convention is to use an initial lowercase letter for the value, and an initial uppercase for the type, as in const apiConstants = /*…*/; and type ApiConstants = typeof apiConstants;.
On the other hand, it’s kind of convenient that your default export is both the value and the type.
You might also want to add
export type ApiPaths = keyof ApiConstants['PATH'];
We use ['PATH'] because we’re using the type ApiConstants here, not the value. We could still use the value but we’d have to add typeof to it again, as in keyof typeof ApiConstants.PATH.

TS: Cannot invoke an expression whose type lacks a call signature when defined dynamically, but it works

I'm still quite new to typescript, so please be gentle with me if I'm doing something with no sense for this technology!
The problem that I'm trying to solve is having a dynamic way to define how my application errors should be structured, but leaving to the users the faculty to enrich the messages.
So I tried to create this logic in a module that could be extended easily from the application, but I'm currently facing the problem:
Error:(35, 18) TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'ErrorMessage' has no compatible call signatures.
What I thought it was a good idea (but please tell me if I'm wrong), was to use a register and a map to have the possibility to extend this mapping every time I want. So I created my ErrorMessage interface to be like the following:
export interface ErrorMessage {
actionMessage: string;
actionSubject: string;
originalErrorMessage?: string;
toString: () => string;
}
and a register for these, called ErrorResponseRegister, as it follows:
export enum defaultErrors {
ExceptionA = 'ExceptionA',
ExceptionB = 'ExceptionB',
}
export class ErrorResponseRegister {
private mapping: Map<string, ErrorMessage>;
constructor() {
this.mapping = new Map()
.set(defaultErrors.ExceptionA, exceptionAErrorMessage)
.set(defaultErrors.ExceptionB, exceptionBErrorMessage);
}
}
So at the end, every ErrorMessage function should look like:
export function exceptionAErrorMessage(originalErrorMessage?: string): ErrorMessage {
return {
enrichment1: "Something happened",
enrichment2: "in the application core",
originalErrorMessage: originalErrorMessage,
toString(): string {
return `${this.enrichment1} ${this.enrichment2}. Original error message: ${originalErrorMessage}`;
},
};
}
Please note I haven't used classes for this ones, as it doesn't really need to be instantiated
and I can have a bunch of them where the toString() method can vary. I just want to enforce the errors should have an enrichment1 and enrichment2 that highlight the problem in a better way for not-technical people.
So, now, back to code. When I'm trying to use the exceptionAErrorMessage statically, I can't see any problem:
console.log(exceptionAErrorMessage(originalErrorMessage).toString())
But when I try dynamically, using the map defined in the ErrorResponseRegister, something weird happens:
// In ErrorResponseRegister
public buildFor(errorType: string, originalErrorMessage?: string): Error {
const errorMessageBuilder = this.mapping.get(errorType);
if (errorMessageBuilder) {
return errorMessageBuilder(originalErrorMessage).toString();
}
return "undefined - do something else";
}
The code works as expected, the error returned is in the right format, so the toString function is executed correctly.
BUT, the following error appears in the IDE:
Error:(32, 18) TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'ErrorMessage' has no compatible call signatures.
The line that causes the problem is
errorMessageBuilder(originalPosErrorMessage).toString()
Can someone help me to understand what I'm doing wrong?
It looks like your problem is you've mistyped mapping... it doesn't hold ErrorMessage values; it holds (x?: string)=>ErrorMessage values:
private mapping: Map<string, (x?: string) => ErrorMessage>;
What's unfortunate is that you initialize this variable via new Map().set(...) instead of the using an iterable constructor argument.
The former returns a Map<any, any> which is trivially assignable to mapping despite the mistyping. That is, you ran smack into this known issue where the standard library's typings for the no-argument Map constructor signature produces Map<any, any> which suppresses all kinds of otherwise useful error messages. Perhaps that will be fixed one day, but for now I'd suggest instead that you use the iterable constructor argument, whose type signature declaration will infer reasonable types for the keys/values:
constructor() {
this.mapping = new Map([
[defaultErrors.ExceptionA, exceptionAErrorMessage],
[defaultErrors.ExceptionB, exceptionBErrorMessage]
]); // inferred as Map<defaultErrors, (orig?: string)=>ErrorMessage>
}
If you had done so, it would have flagged the assignment as an error with your original typing for mapping (e.g., Type 'Map<defaultErrors, (originalErrorMessage?: string | undefined) => ErrorMessage>' is not assignable to type 'Map<string, ErrorMessage>'.) Oh well!
Once you make those changes, things should behave more reasonably for you. Hope that helps; good luck!
Link to code

Type hints of flow not stripped by babel

I have a React.JS project which uses a custom 'theme' with UI components.
This theme also provides build scripts (webpack config, babel configs, etc.).
I want to start using Flow in this project.
I installed the needed npm packages and added flow to babel's presets, then I added props = {mytestprop: string} to one of my React` classes.
Webpack compiled my code successfully, but the type hints were not stripped! Of course, the browser was not able to execute this code - when I try to run it, it raisesReferenceError: string is not defined.
The current list of presets from .babelrc is: ["es2015", "react", "stage-2", "flow"]. I'm sure that this is the actual list used by babel because if I delete any of the first 3 presets, compilation fails.
Do you have any ideas on what could lead to this behavior when stripping Flow types?
It's not that type annotations are not being stripped. It's that { mytestprop: string } is not a valid type annotation on the right-hand side of an assignment because it clashes with the syntax for defining an object.
Specifically, when Flow's parser sees the statement { mytestprop: string } it will interpret this as an attempt to create an object with a field named mytestprop with its value set to the value of the variable string, so it will leave the statement alone as it is, and you'll get the error you've seen in the browser.
The correct way to type object declarations is to type the left-hand side of the declaration.
For instance,
let myProps: { myTestProp: string } = { myTestProp: "testProp" };
if you aren't declaring your props separately, you could declare a custom type:
type myPropType = { myTestProp: string }
// ...
const myComponent = (props: myPropType) => //render your component
Since the type statement is exclusive to Flow and not a valid JavaScript statement, it will be stripped correctly.

How to properly call multiple environment variables within a variable definition in Node

I have two environment variables that I have saved in my local local.env.js file. I'd like to use them in place of the username and password in the following code:
var admins = {
'frank': { password: 'mypassword' },
};
When I use the code below, I get an error (Unexpected token .):
var admins = {
process.env.BASIC_AUTH_USER: {password: process.env.BASIC_AUTH_PASSWORD},
};
Any suggestions on how to do this correctly?
In ES5 and earlier, a static object declaration cannot use a "computed" value for a property name - it must be a string literal. So, you have to use an actual line of code to assign the property using the obj[computedPropName] = value; syntax. In your specific case, that would look like this:
var admins = {};
admins[process.env.BASIC_AUTH_USER] = {password: process.env.BASIC_AUTH_PASSWORD};
In ES6, you can use a computed value in a static declaration if you enclose it in the array syntax like this:
var admins = {[process.env.BASIC_AUTH_USER]: {password: process.env.BASIC_AUTH_PASSWORD}};
See this description of new ES6 features and this MDN reference for some further description of this feature.
Portions of ES6 are available in some of the latest version of browsers, in runtime environments like node.js and, of course, you can use transpilers to code in ES6, but transpile to ES5 compatible code for many features. We are, of course, a ways away from being able to rely on native ES6 support for general cross browser use (thus the interest in transpilers now).
You can use array-like-notation, like so:
var admins = {};
admins[process.env.BASIC_AUTH_USER] = {
password: process.env.BASIC_AUTH_PASSWORD
};
If BASIC_AUTH_USER is "Frank" and BASIC_AUTH_PASSWORD is "potatosalad" then you will end up with an object like this:
admins: {
Frank: {
password: 'potatosalad'
}
}
Try this
admins = {};
admins[process.env.BASIC_AUTH_USER] = {password: process.env.BASIC_AUTH_PASSWORD};

customized error reporting in v4

Another question on migrating code from v3 to v4:
For v3, I had a customized error reporting, using code like this (in the grammar file):
#members {
public void displayRecognitionError(String[] tokenNames,
RecognitionException e) {
String hdr = getErrorHeader(e);
String msg = getErrorMessage(e, tokenNames);
System.out.println("ERR:"+hdr+":"+msg);
errCount += 1;
}
}
In v4, when compiling the generated java files, I am getting the error:
MyParser.java:163: cannot find symbol
symbol : method getErrorMessage(org.antlr.v4.runtime.RecognitionException,java.lang.String[])
location: class MyParser
String msg = getErrorMessage(e, tokenNames);
^
Is this function replaced by some other function in v4? (I saw some questions and answers on ANTLRErrorListener, but I could not get how to use it for my situation.)
The displayRecognitionError method was removed in ANTLR 4, so even if you correct the body of that method it will not do anything. You need to remove the method from your grammar entirely, and implement ANTLRErrorListener instead. The documentation includes a list of classes that implement the interface, so you can reference those and/or extend one of them to produce the desired functionality.
Once you have an instance of an ANTLRErrorListener, you can use the following code to attach it to a Parser instance.
// remove the default error listener
parser.removeErrorListeners();
// add your custom error listener
parser.addErrorListener(listener);

Resources