Can Typescript Infer That An Argument Has Been Validated?
Solution 1:
You can use a type guard. The type guard asserts the type of a particular variable like this:
exportdefaultclassValidate {
publicstaticvalidateEmail(email?: string) {
const emailRegex = newRegExp(
'^(([^<>()[]\\.,;:s@"]+(.[^<>()[]\\.,;:s@"]+)*)|(".+"))@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}])|(([a-zA-Z-0-9]+.)+[a-zA-Z]{2,}))$'
);
if (!Validate.defined(email)) {
thrownewSyntaxError('The text recieved was not defined.');
}
if (!emailRegex.test(email)) {
thrownewSyntaxError('The email entered is not valid');
}
}
publicstaticdefined(text?: string): text is string {
return !!text;
}
publicstaticnotEmpty(text: string) {
if (text.length < 1) {
thrownewSyntaxError('The text entered is empty.');
}
}
}
The downside is that you still need an if statement and the throw expression is outside of the function.
Or you can do something like this, which avoids those problems, but requires you to reassign email
to the result of the function.
exportdefaultclassValidate {
publicstaticvalidateEmail(email?: string) {
const emailRegex = newRegExp(
'^(([^<>()[]\\.,;:s@"]+(.[^<>()[]\\.,;:s@"]+)*)|(".+"))@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}])|(([a-zA-Z-0-9]+.)+[a-zA-Z]{2,}))$'
);
email = Validate.defined(email);
if (!emailRegex.test(email)) {
thrownewSyntaxError('The email entered is not valid');
}
}
publicstaticdefined(text?: string): string {
if (!text) {
thrownewSyntaxError('The text recieved was not defined.');
}
return text;
}
publicstaticnotEmpty(text: string) {
if (text.length < 1) {
thrownewSyntaxError('The text entered is empty.');
}
}
}
Solution 2:
The compiler can sometimes recognize that a variable is of a type narrower than its annotated or inferred type at certain points in the code, by analyzing the control flow. In the following code block, the compiler understands that email
cannot be undefined
if the flow of control reaches the this.notEmpty()
call, and thus there is no error:
if (!email) {
thrownewSyntaxError("The text received was not defined.");
}
this.notEmpty(email); // okay
As you've discovered, though, simply refactoring the code to make the check happen in a different function does not work. The compiler generally does not follow control flow into functions and methods when performing control flow analysis. This is a tradeoff (see Microsoft/TypeScript#9998 in GitHub for more info): it's not feasible for the compiler to simulate the program running on all possible inputs, by analyzing the possible control flow paths through all possible function calls, so it has to use a heuristic somewhere; in this case, the heuristic is generally "assume function calls have no impact on variable types". Therefore the call to this.defined(email)
has no impact on the type of email
as seen by the compiler, and thus it complains about this.notEmpty(email)
.
Luckily, TypeScript 3.7 introduced "assertion functions"; you can give a function a special return type that tells the compiler that a variable passed to the function will be narrowed by the function, and it will use this as part of its control flow analysis. While the compiler doesn't infer such function signatures itself, at least now you can manually annotate that defined()
asserts something about its argument:
static defined(text?: string): asserts text {
if (!text) {
throw new SyntaxError("The text recieved was not defined.");
}
}
The return type of defined()
is asserts text
, which means that text
will be verified as truthy if defined()
returns. And this fixes your original example:
this.defined(email);
this.notEmpty(email); // okay now!
Looks good. Okay, hope that helps; good luck!
Solution 3:
The reason why your code is not compiling is because notEmpty needs to accept a nullable string. That is because at the call site you are passing a nullable string as an argument.The following code fixes the problems with your code.
exportdefaultclassValidate {
staticvalidateEmail(email?: string) {
// TODO: Test this regex properly.const emailRegex = newRegExp('^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$')
this.defined(email);
this.notEmpty(email);
if (email && !emailRegex.test(email)) {
thrownewSyntaxError("The email entered is not valid");
}
}
staticdefined(text?: string) {
if (!text) {
thrownewSyntaxError("The text recieved was not defined.");
}
}
staticnotEmpty(text?: string) {
if (text && text.length < 1) {
thrownewSyntaxError("The text entered is empty.");
}
}
}
Post a Comment for "Can Typescript Infer That An Argument Has Been Validated?"