How to fix no-unsafe-any rule?

Viewed 23013

I'm using TSLint to lint my Angular TypeScript code. I enabled no-unsafe-any rule, as it seems like a good rule to me to never assume anything about properties of type any.

The problem is the rule reports errors on some of my code, which I'm unable to fix in any way other than disabling the rule. Example of a code that's invalid according to that rule below.

public intercept(request: HttpRequest<{}>, next: HttpHandler): Observable<HttpEvent<{}>> {
  return next
    .handle(request)
    .pipe(
      catchError(error => {
        if (error && error.status === httpCodeUnauthorized) {
          // Auto logout if unathorized
          this.authenticationService.logout();
        }

        const errorMessage = (error.error && error.error.message) || error.statusText;

        return throwError(errorMessage);
      }),
    );
}

Linter reports 4 errors on 2 lines:

ERROR: /home/robert/programming/npc/gui/src/app/core/authentication/unauthorized.interceptor.ts[24, 24]: Unsafe use of expression of type 'any'.
ERROR: /home/robert/programming/npc/gui/src/app/core/authentication/unauthorized.interceptor.ts[29, 33]: Unsafe use of expression of type 'any'.
ERROR: /home/robert/programming/npc/gui/src/app/core/authentication/unauthorized.interceptor.ts[29, 48]: Unsafe use of expression of type 'any'.
ERROR: /home/robert/programming/npc/gui/src/app/core/authentication/unauthorized.interceptor.ts[29, 72]: Unsafe use of expression of type 'any'

2 problematic lines are:

  • if (error && error.status === httpCodeUnauthorized) {
  • const errorMessage = (error.error && error.error.message) || error.statusText;

The root of the problem is that error argument of a handler given to catchError (Rxjs library function) has any type. I understand error can be of any type, so it's unsafe to assume it has any properties defined, but I'm first checking for the presence of those properties before actually refering to them, which seems safe to me.

What can/should I do to convince the linter/TypeScript compiler that it's safe and pass the rule?

2 Answers

You have two options, when you know that error always has a specific type, you can just annotate the type. If you can not be sure, you can use a type guard.

Type annotation

With a type annotation, you can simply tell the compiler, that you expect error to be of a certain type. You can avoid the type any completely with this approach:

interface Error {
    status: string,
    statusText: string,
    error: { message: string | undefined } | undefined;
}

catchError((error: Error | undefined) => {
    //...
}

Type guard

You can use a type guard whenever a value could be of a certain type, but doesn't necessarily have to be of that type. The type guard will check for the type and within the following block, the variable will be of that checked type:

function isError(value: any | undefined): value is Error {
    return error && ((error as Error).status !== undefined);
}

catchError(error => {
    if (isError(error)) {
        //Inside this block, error will be of type Error
    }
}

In case of Angular the error should always be in of type HttpErrorResponse

catchError((error: HttpErrorResponse) => {
//...
}

That said, in your code you look into error.error which is defined as any in HttpErrorResponse thus there you should probably use type guard to check and cast it to Error object. Not there is no need to define the Error - it should be defined by typescript base types.

function isError(value: any | undefined): value is Error {
  return error && ((error as Error).message !== undefined);
}

then use it in

const errorMessage = isError(error.error) ? error.error.message : error.statusText;
Related