Skip to main content

· 12 min read
Josh Goldberg

typescript-eslint is the tooling that enables standard JavaScript tools such as ESLint and Prettier to support TypeScript code. We've been working on a set of breaking changes and general features that we're excited to get in front of users soon. And now, after over two years of development, we're excited to say that typescript-eslint v6 is ready for public beta testing! 🎉

Our plan for typescript-eslint v6 is to:

  1. Have users try out betas starting in early March of 2023
  2. Respond to user feedback for the next 1-3 months
  3. Release a stable version summer of 2023

Nothing mentioned in this blog post is set in stone. If you feel passionately about any of the choices we've made here -positively or negatively- then do let us know on the typescript-eslint Discord's v6 channel!

Trying Out v6

Please do try out the typescript-eslint v6 beta! Its documentation site is hosted on a preview deploy link: v6--typescript-eslint.netlify.app.

As A New User

If you don't yet use typescript-eslint, you can go through our configuration steps on the v6 Getting Started docs. It'll walk you through setting up typescript-eslint in a project.

As An Existing User

If you already use typescript-eslint, you'll need to first replace your package's previous versions of @typescript-eslint/eslint-plugin and @typescript-eslint/parser with @rc-v6 versions:

npm i @typescript-eslint/eslint-plugin@rc-v6 @typescript-eslint/parser@rc-v6 --save-dev

We highly recommend then basing your ESLint configuration on the reworked typescript-eslint recommended configurations mentioned later in this post — especially if it's been a while since you've reworked your linter config.

User-Facing Breaking Changes

These are the changes that users of typescript-eslint -generally, any developer running ESLint on TypeScript code- should pay attention to when upgrading typescript-eslint from v5 to v6.

⏳ indicates a change that has been scheduled for v6 but not yet released. We'll update this blog post as the corresponding pull requests land.

Reworked Configuration Names

The biggest configuration change in typescript-eslint v6 is that we've reworked the names of our provided user configuration files. typescript-eslint v5 provided three recommended configurations:

Those configurations worked well for most projects. However, some users correctly noted two flaws in that approach:

  • Strict rules that didn't require type checking were lumped in with those that did.
  • Stylistic best practices were lumped in with rules that actually find bugs.

As a result, we've reworked the configurations provided by typescript-eslint into these two groups:

  • Functional rule configurations, for best best practices and code correctness:
    • plugin:@typescript-eslint/recommended: Recommended rules that you can drop in without additional configuration.
    • plugin:@typescript-eslint/recommended-type-checked: Additional recommended rules that require type information.
    • plugin:@typescript-eslint/strict: Additional strict rules that can also catch bugs but are more opinionated than recommended rules (without type information).
    • plugin:@typescript-eslint/strict-type-checked: Additional strict rules that do require type information.
  • Stylistic rule configurations, for consistent and predictable syntax usage:
    • plugin:@typescript-eslint/stylistic: Stylistic rules you can drop in without additional configuration.
    • plugin:@typescript-eslint/stylistic-type-checked: Additional stylistic rules that require type information.

plugin:@typescript-eslint/recommended-requiring-type-checking is now an alias for plugin:@typescript-eslint/recommended-type-checked. The alias will be removed in a future major version.

As of v6, we recommend that projects enable two configs from the above:

  • If you are not using typed linting, enable stylistic and either recommended or strict, depending on how intensely you'd like your lint rules to scrutinize your code.
  • If you are using typed linting, enable stylistic-type-checked and either recommended-type-checked or strict-type-checked, depending on how intensely you'd like your lint rules to scrutinize your code.

For example, a typical project that enables typed linting might have an ESLint configuration file like:

.eslintrc.cjs
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended-type-checked',
'plugin:@typescript-eslint/stylistic-type-checked',
],
plugins: ['@typescript-eslint'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: true,
tsconfigRootDir: __dirname,
},
root: true,
};

See Configurations on the v6 docs site preview for the updated documentation on configurations provided by typescript-eslint.

For more information on these changes, see:

Updated Configuration Rules

Every new major version of typescript-eslint comes with changes to which rules are enabled in the preset configurations - and with which options. Because this release also includes a reworking of the configurations themselves, the list of changes is too large to put in this blog post. Instead see the table in Changes to configurations for 6.0.0 for a full list of the changes.

Please do try out the new rule configurations presets and let us know in that discussion!

tip

If your ESLint configuration contains many rules configurations, we suggest the following strategy to start anew:

  1. Remove all your rules configurations
  2. Extend from the preset configs that make sense for you
  3. Run ESLint on your project
  4. In your ESLint configuration, turn off any rules creating errors that don't make sense for your project - with comments explaining why
  5. In your ESLint configuration and/or with inline eslint-disable comments, turn off any rules creating too many errors for you to fix - with "TODO" comments linking to tracking issues/tickets to re-enable them

Miscellaneous changes to all shared configurations include:

Rule Breaking Changes

Several rules were changed in significant enough ways to be considered breaking changes:

Tooling Breaking Changes

Developer-Facing Changes

typescript-eslint v6 comes with a suite of cleanups and improvements for developers as well. If you author any ESLint plugins or other tools that interact with TypeScript syntax, then we recommend you try out typescript-eslint v6 soon. It includes some breaking changes that you may need to accommodate for.

tip

If you're having trouble working with the changes, please let us know on the typescript-eslint Discord's v6 channel!

Type Checker Wrapper APIs

As described in our ASTs and typescript-eslint post, ESLint rules don't natively work with AST nodes compatible with TypeScript's API. Retrieving type information for an ESLint AST node in a custom rule requires code somewhat like:

custom-rule-with-v5.ts
{
// ...
create() {
const services = util.getParserServices(context);
const checker = services.program.getTypeChecker();
const tsNode = services.esTreeNodeToTSNodeMap.get(esNode);
const type = checker.getTypeAtLocation(node);

// ...
}
// ...
}

How cumbersome, just to call to a single method (getTypeAtLocation) on the TypeScript API!

In typescript-eslint v6, we've added a set of wrapper APIs on the services: ParserServices object that act as shortcuts for commonly used TypeScript APIs including getTypeAtLocation:

custom-rule-with-v6.ts
{
// ...
create() {
const services = util.getParserServices(context);
const type = services.getTypeAtLocation(node);

// ...
}
// ...
}

For now, the available wrapper APIs are:

  • getSymbolAtLocation: passes an ESTree's equivalent TypeScript node to checker.getSymbolAtLocation
  • getTypeAtLocation: passes an ESTree node's equivalent TypeScript node to checker.getTypeAtLocation

We hope these wrapper APIs make it more convenient to write lint rules that rely on the awesome power of TypeScript's type checking. In the future, we may add more wrapper APIs, and may even add internal caching to those APIs to improve performance.

note

Rules can still retrieve their full backing TypeScript type checker with services.program.getTypeChecker(). This can be necessary for TypeScript APIs not wrapped by the parser services.

See Custom Rules on the v6 docs site preview for the updated documentation on creating custom rules with typescript-eslint.

AST Breaking Changes

These PRs changed the AST shapes generated by typescript-eslint when parsing code. If you author any ESLint rules that refer to the syntax mentioned by them, these are relevant to you.

⏳ Errors on Invalid AST Parsing

note

These changes only impact API consumers of typescript-eslint that work at parsing level. If the extent of your API usage is writing custom rules, these changes don't impact you.

The @typescript-eslint/typescript-estree parser is by default very forgiving of invalid ASTs. If it encounters invalid syntax, it will still attempt create an AST if possible: even if required properties of nodes don't exist.

For example, this snippet of TypeScript code creates a ClassDeclaration whose id is null:

export class {}

Invalid parsed ASTs can cause problems for downstream tools expecting AST nodes to adhere to the ESTree spec. ESLint rules in particular tend to crash when given invalid ASTs.

@typescript-eslint/typescript-estree will now throw an error when it encounters a known invalid AST such as the export class {} example. This is generally the correct behavior for most parsing contexts so downstream tools don't have to work with a potentially invalid AST.

For consumers that don't want the updated behavior of throwing on invalid ASTs, a new allowInvalidAST option exists to disable the throwing behavior. Keep in mind that with it enabled, ASTs produced by typescript-eslint might not match their TSESTree type definitions.

For more information, see:

Other Developer-Facing Breaking Changes

Appreciation

We'd like to extend a sincere thank you to everybody who pitched in to make typescript-eslint v6 possible.

See the v6.0.0 milestone for the list of issues and associated merged pull requests.

Supporting typescript-eslint

If you enjoyed this blog post and/or or use typescript-eslint, please consider supporting us on Open Collective. We're a small volunteer team and could use your support to make the ESLint experience on TypeScript great. Thanks! 💖

· 6 min read
Josh Goldberg

import and export statements are core features of the JavaScript language. They were added as part of the ECMAScript Modules (ESM) specification, and now are generally available in most mainstream JavaScript environments, including all evergreen browsers and Node.js.

When writing TypeScript code with ESM, it can sometimes be desirable to import or export a type only in the type system. Code may wish to refer to a type, but not actually import or export a corresponding value.

For that purpose, TypeScript 3.8 added type-only imports and exports to the TypeScript language:

import type { SomeThing } from './some-module.js';
export type { SomeThing };

The key difference with export type and import type is that they do not represent runtime code. Attempting to use a value imported as only a type in runtime code will cause a TypeScript error:

import type { SomeThing } from './some-module.js';

new SomeThing();
// ~~~~~~~~~
// 'SomeThing' cannot be used as a value
// because it was imported using 'import type'.

TypeScript 4.5 also added inline type qualifiers, which allow for indicating that only some specifiers in a statement should be type-system-only:

import { type SomeType, SomeValue } from './some-module.js';

Type-only imports and exports are not emitted as runtime code when code is transpiled to JavaScript. This brings up two questions:

  • Why would you want to use these type-only imports and exports?
  • How can you enforce a project use them whenever necessary?

Let's Dig In!

· 4 min read
Josh Goldberg

Programmers who work with tools like ESLint and Prettier often refer to ASTs. But what is an AST, why is it useful for these kinds of tools, and how does that interact with ESLint and TypeScript tooling? Let's dig in!

What's an AST?

Static analysis tools are those that look at code without running it. They typically parse code, or transform it from a string into a standard format they can reason about known as an Abstract Syntax Tree (AST). ASTs are called such because although they might contain information on the location of constructs within source code, they are an abstract representation that cares more about the semantic structure.

In other words, an AST is a description of your code's syntax.

An Example AST

Take this single line of code:

1 + 2;

ESLint's AST format, ESTree, would describe that line of code as an object like:

{
"type": "ExpressionStatement",
"expression": {
"type": "BinaryExpression",
"left": {
"type": "Literal",
"value": 1,
"raw": "1"
},
"operator": "+",
"right": {
"type": "Literal",
"value": 2,
"raw": "2"
}
}
}

Each piece of code described within an AST description is referred to as a node, or AST node. Each node is given a node type indicating the type of code syntax it represents That code snippet includes four nodes of the following types:

  • ExpressionStatement: 1 + 2;
  • BinaryExpression: 1 + 2
  • Literal: 1
  • Literal: 2

That ESTree object representation of the code is what static analysis tools such as ESLint and Prettier work with.

AST Formats

ESTree is more broadly used than just for ESLint -- it is a popular community standard. ESLint's built-in parser that outputs an ESTree-shaped AST is also a separate package, called Espree.

TypeScript has its own separate AST format, often referred to as the TypeScript AST. Because TypeScript is developed separately and with different goals from ESLint, ESTree, and Espree, its AST also represents nodes differently in many cases.

  • TS's AST is optimized for its use case of parsing incomplete code and typechecking.
  • ESTree is unoptimized and intended for "general purpose" use-cases of traversing the AST.

ESLint rules are by default only given nodes in the ESTree AST format - which has no knowledge of TypeScript-specific syntax such as interfaces. On the other hand, TypeScript's type checking APIs require nodes in the TypeScript AST format.

Enter TSESTree

To resolve the incompatibilities between ESTrees and the TypeScript AST typescript-eslint provides its own @typescript-eslint/parser package which:

  1. First parses TypeScript syntax into a TypeScript AST
  2. Creates an ESTree AST based on that TypeScript AST
  3. Keeps track of equivalent nodes across each AST

By creating both an ESTree AST and a TypeScript AST, the typescript-eslint parser allows ESLint rules to work with TypeScript code. That's why the Getting Started guide for typescript-eslint has you specify parser: '@typescript-eslint/parser' in your ESLint config!

We commonly refer to the ESTree format that also includes TypeScript-specific syntax as TSESTree.

AST Playground

The typescript-eslint playground contains an AST explorer that generates an interactive AST for any code entered into the playground. You can activate it under Options > AST Explorer on its left sidebar by selecting the value of AST Viewer.

Further Resources

You can play more with various other ASTs on astexplorer.net, including those for other languages such as CSS and HTML.

The AST Wikipedia article has a great deal more context and history on ASTs.

Glossary

Putting together all the terms introduces in this article:

  • AST (Abstract Syntax Tree): An object representation of your code's syntax.
  • Espree: ESLint's built-in parser that outputs an ESTree-shaped AST.
  • ESTree: The AST specification used by ESLint and other common JavaScript tools.
  • Node Type: What kind of code syntax an AST node refers to, such as BinaryExpression or Literal.
  • Node: A single range of code syntax in an AST.
  • Parser: A tool that reads in a string and outputs an AST.
  • TSESTree: Our extension to the ESTree AST format that also includes TypeScript-specific syntax.

TypeScript Lint Rules and ASTs

Interested in how these ASTs work with ESLint rules? We collaborated with our friends at Sourcegraph on a Tour de Source on typescript-eslint. Read on to learn how ESLint rules use ASTs to analyze code files and, thanks to @typescript-eslint/parser, call TypeScript's type checking APIs to analyze code.

· 7 min read
Josh Goldberg

The typescript-eslint website at https://typescript-eslint.io is the canonical location for documentation on how to use ESLint on TypeScript code. The site includes a documentation page for each of the over 100 ESLint rules exposed by its ESLint plugin. Each of those rule docs pages includes a description of the rule, any options it allows, links to its source code, and other important information about its usage.

Until recently, keeping descriptions of rules consistent between their source code and docs pages was a cumbersome manual chore. We'd written a suite of Jest tests to verify they matched -- but those tests didn't capture everything, often failed with cryptic messages, and were missed by less experienced contributors.

We're happy to say that now, we've overhauled rule docs pages to automatically generate metadata information from rule source code. That means the pages always display up-to-date information without developers needing to manually rewrite docs on rule changes. Hooray! 🎉

This blog post gives an overview of the chore to generate rule docs options automatically.