Skip to content

Add entry forany, as usage in TypeScript Guidelines doc #47

@MajorLift

Description

@MajorLift

Motivation

  • There are currently hundreds of instances of any type usage and as assertions spread throughout metamask-extension and core.
  • For future maintainability, there should be clear guidelines defining the limited cases where any, as usage is intentional and acceptable vs. the majority of cases where any, as should be corrected and removed.

References

Research

Catalogue any, as usage in core, metamask-extension and apply fixes where appropriate.

any

Avoiding any

  • any doesn't provide the widest type, or any type at all. It's a compiler setting to disable type checking for the type it's assigned to, which kneecaps type safety by definition.

  • unknown is the universal supertype i.e. the widest possible type.

    • If what's needed is a type that could be "anything," unknown should be used.
    • any and unknown are interchangeable when typing the assignee (every type is assignable to both).
    • any is assignable to all types, while unknown is only assignable to unknown.
    • If replacing any with unknown doesn't work, the typing likely has underlying issues that shouldn't be silenced.
  • If any is used, and errors are introduced (or altered) by future changes to the code,

    • the new or changed warnings will be suppressed,
    • and the code will fail silently.
    • "dangerous because infects all surrounding and downstream code ..."
  • If no good typing solution can be found, using forced type casting with the as keyword or even as unknown as is much preferable to using any:

    • At least we get working intellisense, autocomplete.
    • We also get an indication of the expected type as intended by the author.
  • Function supertype (assignable to any function):

(...args: any[]) => any // bad
(...args: never[]) => unknown // good

Acceptable usages of any

  • Assigning new properties to an object or class at runtime:
(this as any)[key] = obj[key]
  • Within generic constraints:
messenger extends RestrictedControllerMessenger<N, any, any, string, string>
    • In general, using any in this context is not harmful in the same way that it is in other contexts, as the any types are not directly assigned to any specific variable, and only function as constraints.
    • That said, narrower constraints provide better type safety and intellisense.
  • Catching errors:
    • catch only accepts any and unknown as the error type .
    • Recommended: Use unknown with type guards like isJsonRpcError .
  • In tests, for mocking or to intentionally break features.
    • Recommended: Provide accurate typing through assertions wherever possible.

as

Avoiding as

Consider replacing as casting with the following, in order of preference:

  • Prefer type inference.
    • Inferences are responsive to changes in code, while assertions rely on brittle hard-coding. While TypeScript will throw type errors against some unsafe or structurally unsound type assertion scenarios, it will generally accept the user-supplied type without type-checking. This can cause silent failures where errors are suppressed.
    • In some cases, type assertions can be replaced with type narrowing with type guards and null checks.
  • If a type constraint needs to be imposed, prefer the satisfies keyword (for TypeScript v4.9+), since it allows type inference to a more narrow type.
  • : type declarations also enable type constraints to be imposed, but don't allow narrowing by inference.
  • as assertions intended to exclude an empty/nullable type can be replaced by a nullish coalescing operator converting nullable values into an acceptable empty value that doesn't pollute the variable's type signature.
(arr as string[])
(arr ?? [])

Acceptable usages of as

  • To define user-defined type guards: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates
  • To type inputs/outputs that are defined at runtime, provided that schema validation is performed with type guards and unit tests.
    • e.g. The output of JSON.parse() or await response.json() for a known JSON input.
  • In tests, for mocking objects.
    • as unknown as can be used where the mocked object and expected type are significantly divergent e.g. due to omitting required properties.
{ ...mockObject } as unknown as TargetType

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions