From 62fcd1139c579f7bfb22585b6f1e639470e43152 Mon Sep 17 00:00:00 2001
From: Josh Goldberg
Date: Thu, 11 Nov 2021 14:57:19 -0500
Subject: [PATCH 1/7] docs: fleshed out dedicated Architecture section
---
README.md | 14 +-
docs/{getting-started => }/README.md | 2 +-
docs/development/CUSTOM_RULES.md | 205 ++++++++++++++++++
docs/development/architecture/ASTS.md | 51 +++++
docs/development/architecture/PACKAGES.md | 77 +++++++
docs/getting-started/linting/ARCHITECTURE.md | 100 ---------
.../plugin-development/README.md | 16 --
.../{getting-started => }/linting/MONOREPO.md | 0
docs/{getting-started => }/linting/README.md | 0
.../linting/TROUBLESHOOTING.md | 0
docs/{getting-started => }/linting/TSLINT.md | 0
.../linting/TYPED_LINTING.md | 0
packages/website/sidebars/sidebar.base.js | 55 +++--
13 files changed, 366 insertions(+), 154 deletions(-)
rename docs/{getting-started => }/README.md (78%)
create mode 100644 docs/development/CUSTOM_RULES.md
create mode 100644 docs/development/architecture/ASTS.md
create mode 100644 docs/development/architecture/PACKAGES.md
delete mode 100644 docs/getting-started/linting/ARCHITECTURE.md
delete mode 100644 docs/getting-started/plugin-development/README.md
rename docs/{getting-started => }/linting/MONOREPO.md (100%)
rename docs/{getting-started => }/linting/README.md (100%)
rename docs/{getting-started => }/linting/TROUBLESHOOTING.md (100%)
rename docs/{getting-started => }/linting/TSLINT.md (100%)
rename docs/{getting-started => }/linting/TYPED_LINTING.md (100%)
diff --git a/README.md b/README.md
index c507cd8460a7..f59590e18be7 100644
--- a/README.md
+++ b/README.md
@@ -22,19 +22,7 @@
---
-## Packages included in this project
-
-- [`@typescript-eslint/eslint-plugin`](./packages/eslint-plugin/) - An ESLint-plugin with many lint rules for TypeScript features, as well as type-aware lint rules.
-
-- [`@typescript-eslint/parser`](./packages/parser/) - A ESLint-parser which enables ESLint to be able to understand TypeScript syntax. It's powered by our `typescript-estree` package.
-
-- [`@typescript-eslint/eslint-plugin-tslint`](./packages/eslint-plugin-tslint) - An ESLint-plugin that allows you to bridge ESLint and TSLint to help you migrate from TSLint to ESLint.
-
-- [`@typescript-eslint/experimental-utils`](./packages/experimental-utils) - Public utilities for working ESLint and for writing ESLint-plugins in TypeScript.
-
-- [`@typescript-eslint/typescript-estree`](./packages/typescript-estree/) - A parser which takes TypeScript source code and produces an [ESTree](https://github.com/estree/estree)-compatible AST.
-
-- [`@typescript-eslint/scope-manager`](./packages/scope-manager) - An [`eslint-scope`](https://github.com/eslint/eslint-scope)-compatible scope analyser that can understand TypeScript language features such as types, enums and namespaces.
+See https://typescript-eslint.io/docs/development/architecture/packages for details on packages included in this monorepo.
## Versioning
diff --git a/docs/getting-started/README.md b/docs/README.md
similarity index 78%
rename from docs/getting-started/README.md
rename to docs/README.md
index 55d6c068521c..e839134000ef 100644
--- a/docs/getting-started/README.md
+++ b/docs/README.md
@@ -10,4 +10,4 @@ The docs are broken down into the following categories:
- [I want to lint my TypeScript codebase.](./linting/README.md)
-- [(TODO) I want to write an ESLint plugin in TypeScript.](./plugin-development/README.md)
+- [I want to development an ESLint plugin in TypeScript.](./plugins/README.md)
diff --git a/docs/development/CUSTOM_RULES.md b/docs/development/CUSTOM_RULES.md
new file mode 100644
index 000000000000..deeac2fad569
--- /dev/null
+++ b/docs/development/CUSTOM_RULES.md
@@ -0,0 +1,205 @@
+---
+id: custom-rules
+sidebar_label: Custom Rules
+title: Custom Rules
+---
+
+:::important
+You should be familiar with [ESLint's developer guide](https://eslint.org/docs/developer-guide) and [Development > Architecture](./architecture/asts) before writing custom rules.
+:::
+
+As long as you are using `@typescript-eslint/parser` as the `parser` in your ESLint configuration, custom ESLint rules generally work the same way for JavaScript and TypeScript code.
+The main two changes to custom rules writing are:
+
+- [AST Extensions](#ast-extensions): targeting TypeScript-specific syntax in your rule selectors
+- [Typed Rules](#typed-rules): using the TypeScript type checker to inform rule logic
+
+## AST Extensions
+
+`@typescript-eslint/estree` creates AST nodes for TypeScript syntax with names that begin with `TS`, such as `TSInterfaceDeclaration` and `TSTypeAnnotation`.
+These nodes are treated just like any other AST node.
+You can query for them in your rule selectors.
+
+This rule written in JavaScript bans interfaces that start with a lower-case letter:
+
+```js
+export const rule = {
+ create(context) {
+ return {
+ TSInterfaceDeclaration(node) {
+ if (/\p{Lu}/.test(node.id.name[0])) {
+ context.report({
+ messageId: 'uppercase',
+ node: node.id,
+ });
+ }
+ },
+ };
+ },
+ meta: {
+ docs: {
+ category: 'Best Practices',
+ description: 'Interface names should start with an upper-case letter.',
+ },
+ messages: {
+ uppercase: 'Start this name with an upper-case letter.',
+ },
+ type: 'suggestion',
+ schema: [],
+ },
+};
+```
+
+### Node Types
+
+TypeScript types for nodes exist in a `TSESTree` namespace exported by `@typescript-eslint/experimental-utils`.
+The above rule body could be better written in TypeScript with a type annotation on the `node`:
+
+```ts
+import { TSESTree } from '@typescript-eslint/experimental-utils';
+import * as eslint from 'eslint';
+
+export const rule: eslint.Rule.RuleModule = {
+ create(context) {
+ return {
+ TSInterfaceDeclaration(node: TSESTree.TSInterfaceDeclaration) {
+ // ...
+ },
+ };
+ },
+ meta: {
+ // ...
+ },
+};
+```
+
+An `AST_NODE_TYPES` enum is exported as well to hold the values for AST node `type` properties.
+`TSESTree.Node` is available as union type that uses its `type` member as a discriminant.
+
+For example, checking `node.type` can narrow down the type of the `node`:
+
+```ts
+import {
+ AST_NODE_TYPES,
+ TSESTree,
+} from '@typescript-eslint/experimental-utils';
+
+export function describeNode(node: TSESTree.Node): string {
+ switch (node.type) {
+ case AST_NODE_TYPES.ArrayExpression:
+ return `Array containing ${node.elements.map(describeNode).join(', ')}`;
+
+ case AST_NODE_TYPES.Literal:
+ return `Literal value ${node.raw}`;
+
+ default:
+ return '🤷';
+ }
+}
+```
+
+## Type Checking
+
+:::tip
+Read TypeScript's [Compiler APIs > Using the Type Checker](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API#using-the-type-checker) section for how to use a program's type checker.
+:::
+
+The biggest addition typescript-eslint brings to ESLint rules is the ability to use TypeScript's type checker APIs.
+
+`@typescript-eslint/experimental-utils` exports an `ESLintUtils` namespace containing a `getParserServices` function that takes in an ESLint context and returns a `parserServices` object.
+That `parserServices` object contains:
+
+`program`: A full TypeScript `ts.Program` object
+`esTreeNodeToTSNodeMap`: Map of `@typescript-eslint/estree` `TSESTree.Node` nodes to their TypeScript `ts.Node` equivalents
+`tsNodeToESTreeNodeMap`: Map of TypeScript `ts.Node` nodes to their `@typescript-eslint/estree` `TSESTree.Node` equivalents
+
+By mapping from ESTree nodes to TypeScript nodes and retrieving the TypeScript program from the parser services, rules are able to ask TypeScript for full type information on those nodes.
+
+This rule bans for-of looping over an enum by using the type-checker via typescript-eslint and TypeScript APIs:
+
+```ts
+import { ESLintUtils } from '@typescript-eslint/experimental-utils';
+import * as ts from 'typescript';
+import * as tsutils from 'tsutils';
+
+export const rule: eslint.Rule.RuleModule = {
+ create(context) {
+ return {
+ ForOfStatement(node) {
+ // 1. Grab the TypeScript program from parser services
+ const parserServices = ESLintUtils.getParserServices(context);
+ const checker = parserServices.program.getTypeChecker();
+
+ // 2. Find the backing TS node for the ES node, then that TS type
+ const originalNode = parserServices.esTreeNodeToTSNodeMap.get(
+ node.right,
+ );
+ const nodeType = checker.getTypeAtLocation(node);
+
+ // 3. Check the TS node type using the TypeScript APIs
+ if (tsutils.isTypeFlagSet(nodeType, ts.TypeFlags.EnumLike)) {
+ context.report({
+ messageId: 'loopOverEnum',
+ node: node.right,
+ });
+ }
+ },
+ };
+ },
+ meta: {
+ docs: {
+ category: 'Best Practices',
+ description: 'Avoid looping over enums.',
+ },
+ messages: {
+ loopOverEnum: 'Do not loop over enums.',
+ },
+ type: 'suggestion',
+ schema: [],
+ },
+};
+```
+
+## Testing
+
+`@typescript-eslint/experimental-utils` exports its own `RuleTester` with a similar API to the built-in [ESLint `RuleTester`](https://eslint.org/docs/developer-guide/nodejs-api#ruletester).
+It should be provided with the same `parser` and `parserOptions` you would use in your ESLint configuration.
+
+For rules that don't need type information, passing just the `parser` will do:
+
+```ts
+import { ESLintUtils } from '@typescript-eslint/experimental-utils';
+import rule from './my-rule';
+
+const ruleTester = new ESLintUtils.RuleTester({
+ parser: '@typescript-eslint/parser',
+});
+
+ruleTester.run('my-rule', rule {
+ valid: [/* ... */],
+ invalid: [/* ... */],
+})
+```
+
+### Testing Typed Rules
+
+For rules that do need type information, `parserOptions` must be passed in as well.
+Tests must have at least an absolute `tsconfigRootDir` path provided as well as a relative `project` path from that directory:
+
+```ts
+import { ESLintUtils } from '@typescript-eslint/experimental-utils';
+import rule from './my-typed-rule';
+
+const ruleTester = new ESLintUtils.RuleTester({
+ parser: '@typescript-eslint/parser',
+ parserOptions: {
+ project: './tsconfig.json',
+ tsconfigRootDir: __dirname,
+ }
+});
+
+ruleTester.run('my-typed-rule', rule {
+ valid: [/* ... */],
+ invalid: [/* ... */],
+})
+```
diff --git a/docs/development/architecture/ASTS.md b/docs/development/architecture/ASTS.md
new file mode 100644
index 000000000000..055ffa713d8b
--- /dev/null
+++ b/docs/development/architecture/ASTS.md
@@ -0,0 +1,51 @@
+---
+id: asts
+title: ASTs
+sidebar_label: ASTs
+---
+
+## Abstract Syntax Trees (AST)s
+
+Parsers such as those in ESLint and TypeScript read in the text of source code and parse it 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.
+
+For example, given this line of code:
+
+```js
+1 + 2;
+```
+
+ESLint would natively understand it as an object like:
+
+```json
+{
+ "type": "ExpressionStatement",
+ "expression": {
+ "type": "BinaryExpression",
+ "left": {
+ "type": "Literal",
+ "value": 1,
+ "raw": "1"
+ },
+ "operator": "+",
+ "right": {
+ "type": "Literal",
+ "value": 2,
+ "raw": "2"
+ }
+ }
+}
+```
+
+ESLint uses an AST format known as **[`estree`]**.
+
+ESTree is more broadly used than just for ESLint -- it is the de facto community standard.
+ESLint's built-in parser that outputs an `estree`-shaped AST is also a separate package, called **[`espree`]**.
+
+:::note
+You can play more with various ASTs such as ESTree on [astexplorer.net] and read more details on their [Wikipedia article](https://en.wikipedia.org/wiki/Abstract_syntax_tree).
+:::
+
+[astexplorer.net]: https://astexplorer.net
+[`espree`]: https://github.com/eslint/espree
+[`estree`]: https://github.com/estree/estree
diff --git a/docs/development/architecture/PACKAGES.md b/docs/development/architecture/PACKAGES.md
new file mode 100644
index 000000000000..26fb4fd5ad3d
--- /dev/null
+++ b/docs/development/architecture/PACKAGES.md
@@ -0,0 +1,77 @@
+---
+id: packages
+title: Packages
+sidebar_label: Packages
+---
+
+This page describes the top-level packages exported by the [typescript-eslint monorepo](https://github.com/typescript-eslint/typescript-eslint).
+Each of these are published as npm packages under the `@typescript-eslint` organization.
+
+## `@typescript-eslint/eslint-plugin`
+
+[`@typescript-eslint/eslint-plugin`] is the core [ESLint plugin](https://eslint.org/docs/user-guide/configuring/plugins) used by consumers to load in custom rules and rule configurations lists from typescript-eslint.
+Those rules rely on ESLint using the `@typescript-eslint/parser` package described below, and are generally built using the other packages on this page.
+
+## `@typescript-eslint/parser`
+
+[`@typescript-eslint/parser`] takes in ESLint configuration settings, reads in TypeScript source text, and produces an ESTree AST.
+This is necessary because TypeScript produces a different, incompatible AST format to the one that ESLint requires to work.
+
+For example, this is not valid JavaScript code because it contains the `: number` type annotation:
+
+```ts
+let x: number = 1;
+```
+
+ESLint's native Espree parser would raise an error attempting to parse it.
+
+Additionally, because TypeScript is developed separately from ESLint, ESTree, and Espree, its AST also represents nodes differently in many cases.
+Many nodes have different names or different member structures.
+
+See more on configuring custom parsers with ESLint on [ESLint's User Guide > Configuring > Plugins](https://eslint.org/docs/user-guide/configuring/plugins#specifying-parser).
+
+:::tip
+You can select `@typescript-eslint/parser` on [astexplorer.net](https://astexplorer.net)'s top-middle âš™ dropdown that defaults to Acorn.
+:::
+
+## `@typescript-eslint/typescript-estree`
+
+[`@typescript-eslint/typescript-estree`] is used by `@typescript-eslint/parser` to take TypeScript source code and produce the equivalent ESTree AST.
+It works by:
+
+1. Invoking the TypeScript compiler on the given source code in order to
+ produce a TypeScript AST
+2. Converting that TypeScript AST into an ESTree AST
+
+> Because [`@typescript-eslint/typescript-estree`] has a very specific purpose, it is reusable for tools with similar
+> requirements to ESLint.
+> It is therefore also used to power the amazing opinionated code formatter [Prettier](https://prettier.io)'s TypeScript support.
+
+## `@typescript-eslint/scope-manager`
+
+[`@typescript-eslint/scope-manager`] is a fork of [`eslint-scope`](https://github.com/eslint/eslint-scope), enhanced to support TypeScript functionality.
+
+A "scope analyser" traverses an AST and builds a model of how variables (and in our case, types) are defined and consumed by the source code.
+This form of static analysis allows you to understand and trace variables throughout the program, allowing you to access powerful information about a program without needing to drop into the much, much heavier type information.
+
+## `@typescript-eslint/experimental-utils`
+
+[`@typescript-eslint/experimental-utils`] contains public utilities for writing custom rules and plugins in TypeScript.
+Rules declared in `@typescript-eslint/eslint-plugin` are created using its utility functions.
+Any custom rules you write generally will be as well.
+
+## `@typescript-eslint/eslint-plugin-tslint`
+
+[`@typescript-eslint/eslint-plugin-tslint`] is a separate ESLint plugin that allows running TSLint rules within ESLint to help you migrate from TSLint to ESLint.
+
+:::caution
+**TSLint is deprecated.** It is in your best interest to migrate off it entirely. See [Linting > TSLint](../../linting/TSLINT.md).
+:::
+
+[`@typescript-eslint/eslint-plugin-tslint`]: https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin-tslint
+[`@typescript-eslint/eslint-plugin`]: https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin
+[`@typescript-eslint/experimental-utils`]: https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/experimental-utils
+[`@typescript-eslint/parser`]: https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser
+[`@typescript-eslint/scope-manager`]: https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/scope-manager
+[`@typescript-eslint/typescript-estree`]: https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/typescript-estree
+[`@typescript-eslint/typescript-estree`]: https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/typescript-estree
diff --git a/docs/getting-started/linting/ARCHITECTURE.md b/docs/getting-started/linting/ARCHITECTURE.md
deleted file mode 100644
index fb21504303c4..000000000000
--- a/docs/getting-started/linting/ARCHITECTURE.md
+++ /dev/null
@@ -1,100 +0,0 @@
----
-id: architecture
-title: Architecture
-sidebar_label: Architecture
----
-
-## Abstract Syntax Trees (AST)s
-
-Parsers such as those in ESLint and TypeScript read in the text of source code and parse it into a standard format they can reason about.
-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.
-
-For example, given this line of code:
-
-```js
-1 + 2;
-```
-
-ESLint would natively understand it as an object like:
-
-```json
-{
- "type": "ExpressionStatement",
- "expression": {
- "type": "BinaryExpression",
- "left": {
- "type": "Literal",
- "value": 1,
- "raw": "1"
- },
- "operator": "+",
- "right": {
- "type": "Literal",
- "value": 2,
- "raw": "2"
- }
- }
-}
-```
-
-ESLint uses an AST format known as **[`estree`]**.
-
-ESTree is more broadly used than just for ESLint -- it is the de facto community standard.
-ESLint' built-in parser that outputs an `estree`-shaped AST is also a separate package, called **[`espree`]**.
-
-:::note
-You can play more with various ASTs such as ESTree on [astexplorer.net] and read more details on their [Wikipedia article](https://en.wikipedia.org/wiki/Abstract_syntax_tree).
-:::
-
-## `@typescript-eslint/parser`
-
-TypeScript produces a different AST format to the one that ESLint requires to work.
-This means that by default, the TypeScript AST is not compatible with ESLint.
-
-For example:
-
-```ts
-let x: number = 1;
-```
-
-That is not valid JavaScript code because it contains the `: number` type annotation.
-ESLint's native Espree parser would raise an error attempting to parse it.
-
-Additionally, because TypeScript is developed separately from ESLint, ESTree, and Espree, its AST also represents nodes differently in many cases.
-Many nodes have different names or different member structures.
-
-[`@typescript-eslint/parser`] is a parser that takes in ESLint configuration settings, reads in TypeScript source text, and produces an ESTree AST.
-
-ESLint allows specifying custom parsers such as `@typescript-eslint/parser`.
-See more on https://eslint.org/docs/user-guide/configuring/plugins#specifying-parser.
-
-:::note
-You can select the `@typescript-eslint/parser` on the top-middle âš™ dropdown in [astexplorer.net] that defaults to Acorn.
-:::
-
-### `@typescript-eslint/typescript-estree`
-
-[`@typescript-eslint/typescript-estree`] is the utility package used by `@typescript-eslint/parser` to take TypeScript source code and produce the equivalent ESTree AST.
-It works by:
-
-1. Invoking the TypeScript compiler on the given source code in order to
- produce a TypeScript AST
-2. Converting that TypeScript AST into an ESTree AST
-
-> Because [`@typescript-eslint/typescript-estree`] has a very specific purpose, it is reusable for tools with similar
-> requirements to ESLint.
-> It is therefore also used to power the amazing opinionated code formatter [Prettier](https://prettier.io)'s TypeScript support.
-
-### `@typescript-eslint/scope-manager`
-
-`@typescript-eslint/scope-manager` is a fork of [`eslint-scope`](https://github.com/eslint/eslint-scope), enhanced to support TypeScript functionality.
-
-A "scope analyser" traverses an AST and builds a model of how variables (and in our case, types) are defined and consumed by the source code.
-This form of static analysis allows you to understand and trace variables throughout the program, allowing you to access powerful information about a program without needing to drop into the much, much heavier type information.
-
-[`@typescript-eslint/parser`]: https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser
-[`@typescript-eslint/scope-manager`]: https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/scope-manager
-[`@typescript-eslint/typescript-estree`]: https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/typescript-estree
-[astexplorer.net]: https://astexplorer.net
-[`espree`]: https://github.com/eslint/espree
-[`estree`]: https://github.com/estree/estree
diff --git a/docs/getting-started/plugin-development/README.md b/docs/getting-started/plugin-development/README.md
deleted file mode 100644
index 7484b2f26a71..000000000000
--- a/docs/getting-started/plugin-development/README.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-id: plugins
-title: Writing an ESLint Plugin in TypeScript
-sidebar_label: Writing an ESLint Plugin in TypeScript
----
-
-TODO:
-
-- talk about how to setup the folder structure
-- talk about how to consume `experimental-utils` to create an empty rule
-- talk about https://eslint.org/docs/developer-guide/selectors and how to use the strict types
-- talk about how to write tests
-
-TODO: (advanced)
-
-- talk about how to use type information
diff --git a/docs/getting-started/linting/MONOREPO.md b/docs/linting/MONOREPO.md
similarity index 100%
rename from docs/getting-started/linting/MONOREPO.md
rename to docs/linting/MONOREPO.md
diff --git a/docs/getting-started/linting/README.md b/docs/linting/README.md
similarity index 100%
rename from docs/getting-started/linting/README.md
rename to docs/linting/README.md
diff --git a/docs/getting-started/linting/TROUBLESHOOTING.md b/docs/linting/TROUBLESHOOTING.md
similarity index 100%
rename from docs/getting-started/linting/TROUBLESHOOTING.md
rename to docs/linting/TROUBLESHOOTING.md
diff --git a/docs/getting-started/linting/TSLINT.md b/docs/linting/TSLINT.md
similarity index 100%
rename from docs/getting-started/linting/TSLINT.md
rename to docs/linting/TSLINT.md
diff --git a/docs/getting-started/linting/TYPED_LINTING.md b/docs/linting/TYPED_LINTING.md
similarity index 100%
rename from docs/getting-started/linting/TYPED_LINTING.md
rename to docs/linting/TYPED_LINTING.md
diff --git a/packages/website/sidebars/sidebar.base.js b/packages/website/sidebars/sidebar.base.js
index c68cc0193835..cfaeaa4fe2f8 100644
--- a/packages/website/sidebars/sidebar.base.js
+++ b/packages/website/sidebars/sidebar.base.js
@@ -1,26 +1,33 @@
module.exports = {
- docs: {
- Guides: [
- 'getting-started/README',
- {
- type: 'category',
- label: 'Linting',
- collapsed: false,
- items: [
- 'getting-started/linting/linting',
- 'getting-started/linting/type-linting',
- 'getting-started/linting/monorepo',
- 'getting-started/linting/troubleshooting',
- 'getting-started/linting/architecture',
- 'getting-started/linting/tslint',
- ],
- },
- {
- type: 'category',
- label: 'Plugins',
- collapsed: false,
- items: ['getting-started/plugin-development/plugins'],
- },
- ],
- },
+ docs: [
+ 'README',
+ {
+ type: 'category',
+ label: 'Linting',
+ collapsed: false,
+ items: [
+ 'linting/linting',
+ 'linting/type-linting',
+ 'linting/monorepo',
+ 'linting/troubleshooting',
+ 'linting/tslint',
+ ],
+ },
+ {
+ type: 'category',
+ label: 'Development',
+ items: [
+ {
+ collapsed: false,
+ label: 'Architecture',
+ type: 'category',
+ items: [
+ 'development/architecture/asts',
+ 'development/architecture/packages',
+ ],
+ },
+ 'development/custom-rules',
+ ],
+ },
+ ],
};
From e5affe6bf2b602ca1d7fd620116b26d8f3905212 Mon Sep 17 00:00:00 2001
From: Josh Goldberg
Date: Thu, 11 Nov 2021 16:01:48 -0500
Subject: [PATCH 2/7] Update docs/README.md
---
docs/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/README.md b/docs/README.md
index e839134000ef..5a5a4474068d 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -10,4 +10,4 @@ The docs are broken down into the following categories:
- [I want to lint my TypeScript codebase.](./linting/README.md)
-- [I want to development an ESLint plugin in TypeScript.](./plugins/README.md)
+- [I want to develop an ESLint plugin in TypeScript.](./plugins/README.md)
From eb618f59c5b754794f98ca53359c575b7bc79eb3 Mon Sep 17 00:00:00 2001
From: Josh Goldberg
Date: Thu, 11 Nov 2021 16:02:57 -0500
Subject: [PATCH 3/7] Update docs/README.md
---
docs/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/README.md b/docs/README.md
index 5a5a4474068d..aa14517ebd62 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -10,4 +10,4 @@ The docs are broken down into the following categories:
- [I want to lint my TypeScript codebase.](./linting/README.md)
-- [I want to develop an ESLint plugin in TypeScript.](./plugins/README.md)
+- [I want to develop an ESLint plugin in TypeScript.](./development/CUSTOM_RULES.md)
From 374e83284a22703d80320e9c1755f0fa7dc3e2ad Mon Sep 17 00:00:00 2001
From: Josh Goldberg
Date: Thu, 11 Nov 2021 17:22:07 -0500
Subject: [PATCH 4/7] Update docs/development/CUSTOM_RULES.md
Co-authored-by: Armano
---
docs/development/CUSTOM_RULES.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/development/CUSTOM_RULES.md b/docs/development/CUSTOM_RULES.md
index deeac2fad569..5ccee976c961 100644
--- a/docs/development/CUSTOM_RULES.md
+++ b/docs/development/CUSTOM_RULES.md
@@ -107,11 +107,11 @@ Read TypeScript's [Compiler APIs > Using the Type Checker](https://github.com/mi
The biggest addition typescript-eslint brings to ESLint rules is the ability to use TypeScript's type checker APIs.
`@typescript-eslint/experimental-utils` exports an `ESLintUtils` namespace containing a `getParserServices` function that takes in an ESLint context and returns a `parserServices` object.
-That `parserServices` object contains:
-`program`: A full TypeScript `ts.Program` object
-`esTreeNodeToTSNodeMap`: Map of `@typescript-eslint/estree` `TSESTree.Node` nodes to their TypeScript `ts.Node` equivalents
-`tsNodeToESTreeNodeMap`: Map of TypeScript `ts.Node` nodes to their `@typescript-eslint/estree` `TSESTree.Node` equivalents
+That `parserServices` object contains:
+- `program`: A full TypeScript `ts.Program` object
+- `esTreeNodeToTSNodeMap`: Map of `@typescript-eslint/estree` `TSESTree.Node` nodes to their TypeScript `ts.Node` equivalents
+- `tsNodeToESTreeNodeMap`: Map of TypeScript `ts.Node` nodes to their `@typescript-eslint/estree` `TSESTree.Node` equivalents
By mapping from ESTree nodes to TypeScript nodes and retrieving the TypeScript program from the parser services, rules are able to ask TypeScript for full type information on those nodes.
From 1a1e9002deeb92d01d61d090211b4122c877fe17 Mon Sep 17 00:00:00 2001
From: Josh Goldberg
Date: Thu, 11 Nov 2021 17:24:37 -0500
Subject: [PATCH 5/7] chore: format, and expand root Development sidebar
section
---
docs/development/CUSTOM_RULES.md | 1 +
packages/website/sidebars/sidebar.base.js | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/docs/development/CUSTOM_RULES.md b/docs/development/CUSTOM_RULES.md
index 5ccee976c961..93be1197e26e 100644
--- a/docs/development/CUSTOM_RULES.md
+++ b/docs/development/CUSTOM_RULES.md
@@ -109,6 +109,7 @@ The biggest addition typescript-eslint brings to ESLint rules is the ability to
`@typescript-eslint/experimental-utils` exports an `ESLintUtils` namespace containing a `getParserServices` function that takes in an ESLint context and returns a `parserServices` object.
That `parserServices` object contains:
+
- `program`: A full TypeScript `ts.Program` object
- `esTreeNodeToTSNodeMap`: Map of `@typescript-eslint/estree` `TSESTree.Node` nodes to their TypeScript `ts.Node` equivalents
- `tsNodeToESTreeNodeMap`: Map of TypeScript `ts.Node` nodes to their `@typescript-eslint/estree` `TSESTree.Node` equivalents
diff --git a/packages/website/sidebars/sidebar.base.js b/packages/website/sidebars/sidebar.base.js
index cfaeaa4fe2f8..5d50b266eef5 100644
--- a/packages/website/sidebars/sidebar.base.js
+++ b/packages/website/sidebars/sidebar.base.js
@@ -16,9 +16,9 @@ module.exports = {
{
type: 'category',
label: 'Development',
+ collapsed: false,
items: [
{
- collapsed: false,
label: 'Architecture',
type: 'category',
items: [
From 74a37670f6b0b5760c50ccf48ac16d5c5bf3731d Mon Sep 17 00:00:00 2001
From: Josh Goldberg
Date: Fri, 12 Nov 2021 17:22:48 -0500
Subject: [PATCH 6/7] chore: review feedback
---
README.md | 11 ++-
docs/development/CUSTOM_RULES.md | 86 +++++++++++++++++++----
docs/development/architecture/PACKAGES.md | 5 +-
packages/website/docusaurus.config.js | 4 ++
4 files changed, 90 insertions(+), 16 deletions(-)
diff --git a/README.md b/README.md
index f59590e18be7..df0c05c590ef 100644
--- a/README.md
+++ b/README.md
@@ -20,9 +20,16 @@
👆
----
+## Packages included in this project
-See https://typescript-eslint.io/docs/development/architecture/packages for details on packages included in this monorepo.
+See https://typescript-eslint.io/docs/development/architecture/packages for more details.
+
+- [`@typescript-eslint/eslint-plugin`](./packages/eslint-plugin)
+- [`@typescript-eslint/parser`](./packages/parser)
+- [`@typescript-eslint/eslint-plugin-tslint`](./packages/eslint-plugin-tslint)
+- [`@typescript-eslint/experimental-utils`](./packages/experimental-utils)
+- [`@typescript-eslint/typescript-estree`](./packages/typescript-estree)
+- [`@typescript-eslint/scope-manager`](./packages/scope-manager)
## Versioning
diff --git a/docs/development/CUSTOM_RULES.md b/docs/development/CUSTOM_RULES.md
index 93be1197e26e..2c235725f5c7 100644
--- a/docs/development/CUSTOM_RULES.md
+++ b/docs/development/CUSTOM_RULES.md
@@ -27,7 +27,7 @@ export const rule = {
create(context) {
return {
TSInterfaceDeclaration(node) {
- if (/\p{Lu}/.test(node.id.name[0])) {
+ if (/[a-z]/.test(node.id.name[0])) {
context.report({
messageId: 'uppercase',
node: node.id,
@@ -50,16 +50,68 @@ export const rule = {
};
```
-### Node Types
+### Writing Rules in TypeScript
+
+The `@typescript-eslint/experimental-utils` package acts as a replacement package for `eslint` that exports all the same objects and types, but with typescript-eslint support.
+
+:::caution
+`@types/eslint` types are based on `@types/estree` and do not recognize typescript-eslint nodes and properties.
+You should generally not need to import from `eslint` when writing custom typescript-eslint rules in TypeScript.
+:::
+
+#### Rule Types
+
+`@typescript-eslint/experimental-utils` exports a `RuleModule` interface that allows specifying generics for:
+
+- `MessageIds`: a union of string literal message IDs that may be reported
+- `Options`: what options users may configure for the rule
+
+```ts
+import { TSESLint } from '@typescript-eslint/experimental-utils';
+
+export const rule: TSESLint.RuleModule<'uppercase', []> = {
+ create(context /* : Readonly> */) {
+ // ...
+ },
+};
+```
+
+For groups of rules that share a common documentation URL, a `RuleCreator` function is exported.
+It takes in a function that transforms a rule name into its documentation URL, then returns a function that takes in a rule module object.
+The returned function is able to infer message IDs from `meta.messages`.
+
+```ts
+import { ESLintUtils } from '@typescript-eslint/experimental-utils';
+
+const createRule = ESLintUtils.RuleCreator(
+ name => `https://example.com/rule/${name}`,
+);
+
+// Type: const rule: RuleModule<"uppercase", ...>
+export const rule = createRule({
+ create(context) {
+ // ...
+ },
+ meta: {
+ messages: {
+ uppercase: 'Start this name with an upper-case letter.',
+ },
+ // ...
+ },
+});
+```
+
+#### Node Types
TypeScript types for nodes exist in a `TSESTree` namespace exported by `@typescript-eslint/experimental-utils`.
The above rule body could be better written in TypeScript with a type annotation on the `node`:
```ts
-import { TSESTree } from '@typescript-eslint/experimental-utils';
-import * as eslint from 'eslint';
+import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';
-export const rule: eslint.Rule.RuleModule = {
+// ...
+
+export const rule = createRule({
create(context) {
return {
TSInterfaceDeclaration(node: TSESTree.TSInterfaceDeclaration) {
@@ -67,10 +119,8 @@ export const rule: eslint.Rule.RuleModule = {
},
};
},
- meta: {
- // ...
- },
-};
+ // ...
+});
```
An `AST_NODE_TYPES` enum is exported as well to hold the values for AST node `type` properties.
@@ -163,9 +213,11 @@ export const rule: eslint.Rule.RuleModule = {
## Testing
-`@typescript-eslint/experimental-utils` exports its own `RuleTester` with a similar API to the built-in [ESLint `RuleTester`](https://eslint.org/docs/developer-guide/nodejs-api#ruletester).
+`@typescript-eslint/experimental-utils` exports a `RuleTester` with a similar API to the built-in [ESLint `RuleTester`](https://eslint.org/docs/developer-guide/nodejs-api#ruletester).
It should be provided with the same `parser` and `parserOptions` you would use in your ESLint configuration.
+### Testing Untyped Rules
+
For rules that don't need type information, passing just the `parser` will do:
```ts
@@ -179,7 +231,7 @@ const ruleTester = new ESLintUtils.RuleTester({
ruleTester.run('my-rule', rule {
valid: [/* ... */],
invalid: [/* ... */],
-})
+});
```
### Testing Typed Rules
@@ -202,5 +254,15 @@ const ruleTester = new ESLintUtils.RuleTester({
ruleTester.run('my-typed-rule', rule {
valid: [/* ... */],
invalid: [/* ... */],
-})
+});
```
+
+:::note
+For now, `ESLintUtils.RuleTester` requires the following physical files be present on disk for typed rules:
+
+- `tsconfig.json`: tsconfig used as the test "project"
+- One of the following two files:
+ - `file.ts`: blank test file used for normal TS tests
+ - `file.tsx`: blank test file used for tests with `parserOptions: { ecmaFeatures: { jsx: true } }`
+
+:::
diff --git a/docs/development/architecture/PACKAGES.md b/docs/development/architecture/PACKAGES.md
index 26fb4fd5ad3d..7e169187423e 100644
--- a/docs/development/architecture/PACKAGES.md
+++ b/docs/development/architecture/PACKAGES.md
@@ -25,8 +25,9 @@ let x: number = 1;
ESLint's native Espree parser would raise an error attempting to parse it.
-Additionally, because TypeScript is developed separately from ESLint, ESTree, and Espree, its AST also represents nodes differently in many cases.
-Many nodes have different names or different member structures.
+Additionally, 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.
See more on configuring custom parsers with ESLint on [ESLint's User Guide > Configuring > Plugins](https://eslint.org/docs/user-guide/configuring/plugins#specifying-parser).
diff --git a/packages/website/docusaurus.config.js b/packages/website/docusaurus.config.js
index b8d59f357d29..9388a4b85bcb 100644
--- a/packages/website/docusaurus.config.js
+++ b/packages/website/docusaurus.config.js
@@ -105,6 +105,10 @@ const config = {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
},
+ tableOfContents: {
+ maxHeadingLevel: 4,
+ minHeadingLevel: 2,
+ },
}),
};
From b6c22ed3ff86ae6c063a4b52422ab6e6172ee273 Mon Sep 17 00:00:00 2001
From: Brad Zacher
Date: Sat, 13 Nov 2021 17:19:11 -0800
Subject: [PATCH 7/7] Update .cspell.json
---
.cspell.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/.cspell.json b/.cspell.json
index 6b30bf254bdf..0516d1196d70 100644
--- a/.cspell.json
+++ b/.cspell.json
@@ -103,6 +103,7 @@
"typedef",
"typedefs",
"unfixable",
+ "unoptimized",
"unprefixed",
"Zacher"
],