Skip to main content

Comment Directives

· 20 min read

Once in a while in TypeScript code, you may come across a comment that looks something like this:

ts
// @ts-expect-error
let value: number = "oops!";
ts
// @ts-expect-error
let value: number = "oops!";

You may also know that // @ts-expect-error suppresses type checking for a single line. Wanting to ignore type errors might seem counterintuitive at first. If a project already decided to use TypeScript, why disable type checking?

Rare cases do exist when TypeScript's type checking needs to be suppressed in a small area of code. TypeScript includes several "comment directives" that can change type checking behavior for a file or on a specific line. A comment directive is a comment that directs (changes) how a tool operates within a file.

This article will explain the kinds of comment directives supported by TypeScript: why they exist, what they do, and how to use them.

TypeScript's Comment Directives

TypeScript's type checker is not always correct. There are rare edge cases when its understanding of types in code doesn't perfectly match reality. For example:

  • Type definitions outside of your control might not be correct
  • Writing the correct types for some area of code might be too time consuming at the moment
  • There might be a transient bug or missing feature in TypeScript itself that prevents it from fully understanding code as written

In rare cases such as those, it can be necessary as a last-ditch resort to ignore a type error reported by TypeScript. TypeScript provides two comment directives that can ignore a type error on a line: // @ts-expect-error and // @ts-ignore.

@ts-expect-error

// @ts-expect-error is a comment directive that tells TypeScript to ignore any type errors on the next line of code. It's made to be used when TypeScript would report a type error even though code is written correctly.

Suppose you know a global process function should take in a string parameter, but its types incorrectly describe it as taking in number. If you were to call process with a value of type string, TypeScript would report a type error:

ts
declare function process(data: number): void;
 
process("abc");
Argument of type 'string' is not assignable to parameter of type 'number'.2345Argument of type 'string' is not assignable to parameter of type 'number'.
ts
declare function process(data: number): void;
 
process("abc");
Argument of type 'string' is not assignable to parameter of type 'number'.2345Argument of type 'string' is not assignable to parameter of type 'number'.

In this case, you know that TypeScript is wrong to report a type error on process("abc"). It's the types that are incorrect -- not the runtime code. You need a way to direct TypeScript to ignore that line.

Adding a // @ts-expect-error comment directive before the offending line would remove the type error:

ts
declare function process(data: number): void;
 
// @ts-expect-error
process("abc"); // No type error ✅
ts
declare function process(data: number): void;
 
// @ts-expect-error
process("abc"); // No type error ✅
caution

It is almost always better to correct type definitions rather than use comment directives. Avoiding Comment Directives covers several preferable strategies.

// @ts-expect-error is only meant to be used before lines that have a TypeScript type error. If the line after the comment does not have a type error, then TypeScript will report a new type error on the unnecessary // @ts-expect-error.

If, say, the process function's types were to be corrected to indicate data is type string, then process("abc") would no longer contain a type error. TypeScript would then report that any preceding // @ts-expect-error is unnecessary. Once a // @ts-expect-error comment directive is unnecessary, you can delete it.

ts
declare function process(data: string): void;
 
// @ts-expect-error
Unused '@ts-expect-error' directive.2578Unused '@ts-expect-error' directive.
process("abc"); // No type error ✅
ts
declare function process(data: string): void;
 
// @ts-expect-error
Unused '@ts-expect-error' directive.2578Unused '@ts-expect-error' directive.
process("abc"); // No type error ✅

@ts-ignore

// @ts-ignore is the same as // @ts-expect-error, but it is allowed to exist even if it doesn't suppress an existing type error. A // @ts-ignore above a line with no type errors will not trigger TypeScript to report a type error.

The following example places a // @ts-ignore above a process("abc") line containing no type errors. TypeScript would not produce a new type error for the unnecessary comment directive:

ts
declare function process(data: string): void;
 
// @ts-ignore
process("abc");
ts
declare function process(data: string): void;
 
// @ts-ignore
process("abc");

There are very few times when // @ts-ignore should be used instead of // @ts-expect-error. Using // @ts-expect-error instead of // @ts-ignore ensures TypeScript will report when a comment directive is no longer needed. Unnecessary comment directives are misleading and take up space.

tip

Choosing the Right Comment Directive covers the rare cases when // @ts-ignore is preferable over // @ts-expect-error.

@ts-nocheck

Many TypeScript projects are written completely or mostly with TypeScript syntax and are fully type checked. However, some TypeScript projects contain JavaScript files that haven't been structured in ways that are type-safe. Attempting to run type checking on files in those projects can sometimes result in many erroneous type errors.

Suppose a utilities.js file defines and exports a weaklyTyped object whose types are inferred incorrectly by TypeScript. Each usage of that weaklyTyped object in another file, index.ts, might incur a type error:

ts
import { weaklyTyped } from "./utilities.js";
Property 'log' does not exist on type '{}'.2339Property 'log' does not exist on type '{}'.
 
weaklyTyped.log("Hello");
weaklyTyped.log("world");
ts
import { weaklyTyped } from "./utilities.js";
Property 'log' does not exist on type '{}'.2339Property 'log' does not exist on type '{}'.
 
weaklyTyped.log("Hello");
weaklyTyped.log("world");

TypeScript provides a // @ts-nocheck comment directive that disables type checking for an entire file. Placing // @ts-nocheck at the top of a file stops TypeScript from reporting any type errors on the file.

Adding a // @ts-check at the top of the index.ts file would prevent TypeScript from reporting type errors from using weaklyTyped:

ts
// @ts-nocheck
import { weaklyTyped } from "./utilities.js";
 
weaklyTyped.log("Hello"); // No type error ✅
weaklyTyped.log("world"); // No type error ✅
ts
// @ts-nocheck
import { weaklyTyped } from "./utilities.js";
 
weaklyTyped.log("Hello"); // No type error ✅
weaklyTyped.log("world"); // No type error ✅
caution

// @ts-nocheck is even more unsafe than // @ts-expect-error and // @ts-ignore. It should only be used as a stopgap until files are refactored to have proper types.

@ts-check

// @ts-check is the opposite of // @ts-ignore: instead of disabling type checking for a file, it enables type checking for the file. This can be useful in projects where TypeScript sees JavaScript files with the allowJs compiler option and doesn't type check them with checkJs compiler option. Placing // @ts-check at the top of a JavaScript file allows easing into type checking for just that file - without enabling type checking on all files.

Take the following index.js file as an example. With TypeScript set to allowJs, it wouldn't have a type error reported for passing a string argument to a function parameter of type number. But with // @ts-check, that type error would be reported:

index.js
ts
/** @param {number} value */
function double(value) {
return value * 2;
}
 
console.log(double(1));
console.log(double("2")); // Should be a type error, but isn't ❌
index.js
ts
/** @param {number} value */
function double(value) {
return value * 2;
}
 
console.log(double(1));
console.log(double("2")); // Should be a type error, but isn't ❌
tip

See JS Projects Utilizing TypeScript in the TypeScript Handbook for using TypeScript on files not using TypeScript's syntax.

Best Practices

Comment directives are an "escape hatch": they allow switching small parts of a project to different type checking behavior. Escape hatches should be used with extreme caution and only when they are absolutely necessary.

Avoiding Comment Directives

If at all possible, don't use comment directives to suppress type errors.

Comment directives are a blunt instrument: they apply to an entire area of code. There is no way to specify a specific type error to target in comment directives.

Suppressing type errors with comment directives is a "bandaid" fix: it doesn't address whatever root issue caused the incorrect type error to begin with. When possible, it's better to improve TypeScript's understanding of code.

Consider trying the following strategies for fixing types before falling back to a comment directive.

Correcting Incorrect Types

If you know the types for code are incorrect, the best outcome is generally to fix those types. That includes declare statements as well as .d.ts files in your project and in dependencies.

In the process example from earlier, a // @ts-expect-error was used because the the declared type for process was incorrect. The // @ts-expect-error was no longer necessary once the type for process was fixed. Good!

If your project uses types from community-authored @types/ packages published by DefinitelyTyped, you may find incorrect definitions within your node_modules/. Those can be fixed at the source by sending a pull request to DefinitelyTyped. Doing so will fix the types for your project -- as well as for any other consumers of that package.

tip

The patch-package package applies changes to node_modules/ packages. You can use it to apply changes locally from in-progress DefinitelyTyped pull requests.

Refactoring for Type Safety

Type Assertions

Choosing The Right Comment Directive

Comment Directive Explanations

Comment directives such as // @ts-expect-error can also include more text. TypeScript developers will often use this to include explanations for the comment directive. Comment directive explanations are generally considered a best practice, so readers of the code understand why the comment directive is necessary.

This expanded comment from before explains the context for why process's types are wrong:

ts
declare function process(data: number): void;
 
// @ts-expect-error -- Pending 'process' being fixed to take in string.
process("abc"); // No type error ✅
ts
declare function process(data: number): void;
 
// @ts-expect-error -- Pending 'process' being fixed to take in string.
process("abc"); // No type error ✅

Including explanations with comment directives is a good practice to adhere to. In addition to explaining the "why" of the comment directive, they hint to future code authors to think on and explain any comment directive they might want to add.

Linting Comment Directives

@typescript-eslint/ban-ts-comment can be used to enforce best practices with TypeScript's comment directives. By default, the rule:

  • Always reports on @ts-ignore and @ts-nocheck comment directives
  • Reports on @ts-expect-error directives that don't have an explanatory comment description
  • Requires explanatory comment descriptions contain at least 3 characters

For example, suppose "@example/package" exports a process function that should take in a string but whose types incorrectly indicate take in a number. A // @ts-expect-error comment could be used to tell TypeScript to ignore the line:

ts
import { process } from "@example/package";
// @ts-ignore
process("New York City");
ts
import { process } from "@example/package";
// @ts-ignore
process("New York City");

Teams using issue trackers such as GitHub Issues or Jira will often prefer to include links to tracking tickets in todo comments. Doing so can help ensure the reason for the comment gets documented more fully, and isn't forgotten about later.

@typescript-eslint/ban-ts-comment's descriptionFormat option can be used to enforce comments align to a certain format. Teams can use that to enforce the explanation for a comment directive includes a link to a tracking issue.

For example, given the following ESLint config, comment directives must include a -- TODO(GH-*) linking to an issue:

ts
export default tseslint.config({
// ...
rules: {
"@typescript-eslint/ban-ts-comment": [
"error",
{
descriptionFormat: "^ -- TODO\\(GH-\\d+\\)",
},
],
},
});
ts
export default tseslint.config({
// ...
rules: {
"@typescript-eslint/ban-ts-comment": [
"error",
{
descriptionFormat: "^ -- TODO\\(GH-\\d+\\)",
},
],
},
});
ts
import { process } from "@example/package";
// @ts-expect-error -- pending updating the process types
process("New York City");
ts
import { process } from "@example/package";
// @ts-expect-error -- pending updating the process types
process("New York City");

See typescript-eslint's Getting Started guide to enable @typescript-eslint/ban-ts-comment and other recommended rules.

Equivalent lint rules in other common linters are:

Comment Directives in Other Tools

TypeScript is not the only static analysis tool that allows inline configuration comments. Formatters, linters, and others often include their own comment directives that operate separately from TypeScript.

Formatter Comment Directives

Popular web formatters such as Biome's, deno fmt, and Prettier all support comment directives that disable formatting within a file. For example, the following line taken from the Prettier documentation shows // prettier-ignore excluding a line from being formatted by Prettier:

js
matrix(1, 0, 0, 0, 1, 0, 0, 0, 1);
// prettier-ignore
matrix(
1, 0, 0,
0, 1, 0,
0, 0, 1
)
js
matrix(1, 0, 0, 0, 1, 0, 0, 0, 1);
// prettier-ignore
matrix(
1, 0, 0,
0, 1, 0,
0, 0, 1
)

Formatter comment directives don't change the behavior of TypeScript's type checker.

Linter Comment Directives

Popular web linters such as Biome's, ESLint, deno lint, and oxlint Linters such as ESLint have their own comment directives that can reconfigure the linter on a per-file or per-line basis.

For example, the following line demonstrates disabling the no-console ESLint rule for a line:

js
// eslint-disable-next-line no-console
console.log("Hello, world!");
js
// eslint-disable-next-line no-console
console.log("Hello, world!");

Linters are not the same tools as type checkers. Linter comment directives don't change the behavior of TypeScript's type checker.

For more information, see:

Closing Thoughts

Comment directives are a "necessary evil" for static analysis tools such as TypeScript. They provide an occasionally-useful escape hatch to write code that the type checker would not otherwise permit. Try to avoid comment directives if possible, and instead write code that works well with the type checker.

If you must use a comment directive, be sure to choose the appropriate one. Consider using a lint rule such as @typescript-eslint/ban-ts-comment to enforce directives be explained.


Got your own TypeScript questions? Ask @learningtypescript.com on Bluesky and the answer might become an article too!