Skip to content

Commit d3470c9

Browse files
authored
feat(eslint-plugin)!: recommended-requiring-type-checking config (typescript-eslint#846)
BREAKING CHANGE: removed some rules from recommended config
1 parent 90b36dd commit d3470c9

36 files changed

+317
-19
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module.exports = {
1515
'eslint:recommended',
1616
'plugin:@typescript-eslint/eslint-recommended',
1717
'plugin:@typescript-eslint/recommended',
18+
'plugin:@typescript-eslint/recommended-requiring-type-checking',
1819
],
1920
rules: {
2021
//

packages/eslint-plugin/README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ You can also enable all the recommended rules for our plugin. Add `plugin:@types
5151
}
5252
```
5353

54-
You can also use [eslint:recommended](https://eslint.org/docs/rules/) with this plugin. Add both `eslint:recommended` and `plugin:@typescript-eslint/eslint-recommended`:
54+
You can also use [eslint:recommended](https://eslint.org/docs/rules/) (the set of rules which are recommended for all projects by the ESLint Team) with this plugin. As noted in the root README, not all eslint core rules are compatible with TypeScript, so you need to add both `eslint:recommended` and `plugin:@typescript-eslint/eslint-recommended` (which will adjust the one from eslint appropriately for TypeScript) to your config:
5555

5656
```json
5757
{
@@ -63,7 +63,24 @@ You can also use [eslint:recommended](https://eslint.org/docs/rules/) with this
6363
}
6464
```
6565

66-
If you want to use rules which require type information, you will need to specify a path to your tsconfig.json file in the "project" property of "parserOptions".
66+
As of version 2 of this plugin, _by design_, none of the rules in the main `recommended` config require type-checking in order to run. This means that they are more lightweight and faster to run.
67+
68+
Some highly valuable rules simply require type-checking in order to be implemented correctly, however, so we provide an additional config you can extend from called `recommended-requiring-type-checking`. You wou apply this _in addition_ to the recommended configs previously mentioned, e.g.:
69+
70+
```json
71+
{
72+
"extends": [
73+
"eslint:recommended",
74+
"plugin:@typescript-eslint/eslint-recommended",
75+
"plugin:@typescript-eslint/recommended",
76+
"plugin:@typescript-eslint/recommended-requiring-type-checking"
77+
]
78+
}
79+
```
80+
81+
Pro Tip: For larger codebases you may want to consider splitting our linting into two separate stages: 1. fast feedback rules which operate purely based on syntax (no type-checking), 2. rules which are based on semantics (type-checking).
82+
83+
NOTE: If you want to use rules which require type information, you will need to specify a path to your tsconfig.json file in the "project" property of "parserOptions". If you do not do this, you will get a runtime error which explains this.
6784

6885
```json
6986
{

packages/eslint-plugin/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@
3030
"main": "dist/index.js",
3131
"scripts": {
3232
"build": "tsc -p tsconfig.build.json",
33-
"check:docs": "ts-node --files ./tools/validate-docs/index.ts",
34-
"check:configs": "ts-node --files ./tools/validate-configs/index.ts",
33+
"check:docs": "../../node_modules/.bin/ts-node --files ./tools/validate-docs/index.ts",
34+
"check:configs": "../../node_modules/.bin/ts-node --files ./tools/validate-configs/index.ts",
3535
"clean": "rimraf dist/",
3636
"format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore",
37-
"generate:configs": "ts-node --files tools/generate-configs.ts",
37+
"generate:configs": "../../node_modules/.bin/ts-node --files tools/generate-configs.ts",
3838
"prebuild": "npm run clean",
3939
"test": "jest --coverage",
4040
"typecheck": "tsc --noEmit"
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"extends": "./configs/base.json",
3+
"rules": {
4+
"@typescript-eslint/await-thenable": "error",
5+
"@typescript-eslint/no-for-in-array": "error",
6+
"@typescript-eslint/no-misused-promises": "error",
7+
"@typescript-eslint/no-unnecessary-type-assertion": "error",
8+
"@typescript-eslint/prefer-includes": "error",
9+
"@typescript-eslint/prefer-regexp-exec": "error",
10+
"@typescript-eslint/prefer-string-starts-ends-with": "error",
11+
"require-await": "off",
12+
"@typescript-eslint/require-await": "error",
13+
"@typescript-eslint/unbound-method": "error",
14+
"no-var": "error",
15+
"prefer-const": "error",
16+
"prefer-rest-params": "error",
17+
"prefer-spread": "error"
18+
}
19+
}

packages/eslint-plugin/src/configs/recommended.json

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
"extends": "./configs/base.json",
33
"rules": {
44
"@typescript-eslint/adjacent-overload-signatures": "error",
5-
"@typescript-eslint/await-thenable": "error",
65
"@typescript-eslint/ban-ts-ignore": "error",
76
"@typescript-eslint/ban-types": "error",
87
"camelcase": "off",
@@ -18,28 +17,19 @@
1817
"@typescript-eslint/no-empty-function": "error",
1918
"@typescript-eslint/no-empty-interface": "error",
2019
"@typescript-eslint/no-explicit-any": "warn",
21-
"@typescript-eslint/no-for-in-array": "error",
2220
"@typescript-eslint/no-inferrable-types": "error",
2321
"@typescript-eslint/no-misused-new": "error",
24-
"@typescript-eslint/no-misused-promises": "error",
2522
"@typescript-eslint/no-namespace": "error",
2623
"@typescript-eslint/no-non-null-assertion": "warn",
2724
"@typescript-eslint/no-this-alias": "error",
28-
"@typescript-eslint/no-unnecessary-type-assertion": "error",
2925
"no-unused-vars": "off",
3026
"@typescript-eslint/no-unused-vars": "warn",
3127
"no-use-before-define": "off",
3228
"@typescript-eslint/no-use-before-define": "error",
3329
"@typescript-eslint/no-var-requires": "error",
34-
"@typescript-eslint/prefer-includes": "error",
3530
"@typescript-eslint/prefer-namespace-keyword": "error",
36-
"@typescript-eslint/prefer-regexp-exec": "error",
37-
"@typescript-eslint/prefer-string-starts-ends-with": "error",
38-
"require-await": "off",
39-
"@typescript-eslint/require-await": "error",
4031
"@typescript-eslint/triple-slash-reference": "error",
4132
"@typescript-eslint/type-annotation-spacing": "error",
42-
"@typescript-eslint/unbound-method": "error",
4333
"no-var": "error",
4434
"prefer-const": "error",
4535
"prefer-rest-params": "error",

packages/eslint-plugin/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import rules from './rules';
33
import all from './configs/all.json';
44
import base from './configs/base.json';
55
import recommended from './configs/recommended.json';
6+
import recommendedRequiringTypeChecking from './configs/recommended-requiring-type-checking.json';
67
import eslintRecommended from './configs/eslint-recommended';
78

89
export = {
@@ -12,5 +13,6 @@ export = {
1213
base,
1314
recommended,
1415
'eslint-recommended': eslintRecommended,
16+
'recommended-requiring-type-checking': recommendedRequiringTypeChecking,
1517
},
1618
};

packages/eslint-plugin/src/rules/await-thenable.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export default util.createRule({
1010
description: 'Disallows awaiting a value that is not a Thenable',
1111
category: 'Best Practices',
1212
recommended: 'error',
13+
requiresTypeChecking: true,
1314
},
1415
messages: {
1516
await: 'Unexpected `await` of a non-Promise (non-"Thenable") value.',

packages/eslint-plugin/src/rules/no-floating-promises.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export default util.createRule({
1010
description: 'Requires Promise-like values to be handled appropriately.',
1111
category: 'Best Practices',
1212
recommended: false,
13+
requiresTypeChecking: true,
1314
},
1415
messages: {
1516
floating: 'Promises must be handled appropriately',

packages/eslint-plugin/src/rules/no-for-in-array.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export default util.createRule({
88
description: 'Disallow iterating over an array with a for-in loop',
99
category: 'Best Practices',
1010
recommended: 'error',
11+
requiresTypeChecking: true,
1112
},
1213
messages: {
1314
forInViolation:

packages/eslint-plugin/src/rules/no-misused-promises.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export default util.createRule<Options, 'conditional' | 'voidReturn'>({
1818
description: 'Avoid using promises in places not designed to handle them',
1919
category: 'Best Practices',
2020
recommended: 'error',
21+
requiresTypeChecking: true,
2122
},
2223
messages: {
2324
voidReturn:

packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export default util.createRule({
1010
category: 'Best Practices',
1111
description: 'Warns when a namespace qualifier is unnecessary',
1212
recommended: false,
13+
requiresTypeChecking: true,
1314
},
1415
fixable: 'code',
1516
messages: {

packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export default util.createRule<[], MessageIds>({
2929
'Warns if an explicitly specified type argument is the default for that type parameter',
3030
category: 'Best Practices',
3131
recommended: false,
32+
requiresTypeChecking: true,
3233
},
3334
fixable: 'code',
3435
messages: {

packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export default util.createRule<Options, MessageIds>({
2828
'Warns if a type assertion does not change the type of an expression',
2929
category: 'Best Practices',
3030
recommended: 'error',
31+
requiresTypeChecking: true,
3132
},
3233
fixable: 'code',
3334
messages: {

packages/eslint-plugin/src/rules/prefer-includes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export default createRule({
1414
description: 'Enforce `includes` method over `indexOf` method',
1515
category: 'Best Practices',
1616
recommended: 'error',
17+
requiresTypeChecking: true,
1718
},
1819
fixable: 'code',
1920
messages: {

packages/eslint-plugin/src/rules/prefer-readonly.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export default util.createRule<Options, MessageIds>({
3232
"Requires that private members are marked as `readonly` if they're never modified outside of the constructor",
3333
category: 'Best Practices',
3434
recommended: false,
35+
requiresTypeChecking: true,
3536
},
3637
fixable: 'code',
3738
messages: {

packages/eslint-plugin/src/rules/prefer-regexp-exec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export default createRule({
1313
'Prefer RegExp#exec() over String#match() if no global flag is provided',
1414
category: 'Best Practices',
1515
recommended: 'error',
16+
requiresTypeChecking: true,
1617
},
1718
messages: {
1819
regExpExecOverStringMatch: 'Use the `RegExp#exec()` method instead.',

packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export default createRule({
2121
'Enforce the use of `String#startsWith` and `String#endsWith` instead of other equivalent methods of checking substrings',
2222
category: 'Best Practices',
2323
recommended: 'error',
24+
requiresTypeChecking: true,
2425
},
2526
messages: {
2627
preferStartsWith: "Use 'String#startsWith' method instead.",

packages/eslint-plugin/src/rules/promise-function-async.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export default util.createRule<Options, MessageIds>({
2222
'Requires any function or method that returns a Promise to be marked async',
2323
category: 'Best Practices',
2424
recommended: false,
25+
requiresTypeChecking: true,
2526
},
2627
messages: {
2728
missingAsync: 'Functions that return promises must be async.',

packages/eslint-plugin/src/rules/require-array-sort-compare.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export default util.createRule({
1212
description: 'Enforce giving `compare` argument to `Array#sort`',
1313
category: 'Best Practices',
1414
recommended: false,
15+
requiresTypeChecking: true,
1516
},
1617
messages: {
1718
requireCompare: "Require 'compare' argument.",

packages/eslint-plugin/src/rules/require-await.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export default util.createRule<Options, MessageIds>({
2424
description: 'Disallow async functions which have no `await` expression',
2525
category: 'Best Practices',
2626
recommended: 'error',
27+
requiresTypeChecking: true,
2728
},
2829
schema: baseRule.meta.schema,
2930
messages: baseRule.meta.messages,

packages/eslint-plugin/src/rules/restrict-plus-operands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export default util.createRule({
1111
'When adding two variables, operands must both be of type number or of type string',
1212
category: 'Best Practices',
1313
recommended: false,
14+
requiresTypeChecking: true,
1415
},
1516
messages: {
1617
notNumbers:

packages/eslint-plugin/src/rules/strict-boolean-expressions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export default util.createRule<Options, 'strictBooleanExpression'>({
2727
description: 'Restricts the types allowed in boolean expressions',
2828
category: 'Best Practices',
2929
recommended: false,
30+
requiresTypeChecking: true,
3031
},
3132
schema: [
3233
{

packages/eslint-plugin/src/rules/unbound-method.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export default util.createRule<Options, MessageIds>({
2626
description:
2727
'Enforces unbound methods are called with their expected scope',
2828
recommended: 'error',
29+
requiresTypeChecking: true,
2930
},
3031
messages: {
3132
unbound:

packages/eslint-plugin/tools/generate-configs.ts

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ function reducer<TMessageIds extends string>(
5858
settings: {
5959
errorLevel?: 'error' | 'warn';
6060
filterDeprecated: boolean;
61+
filterRequiresTypeChecking?: 'include' | 'exclude';
6162
},
6263
): LinterConfigRules {
6364
const key = entry[0];
@@ -67,6 +68,22 @@ function reducer<TMessageIds extends string>(
6768
return config;
6869
}
6970

71+
// Explicitly exclude rules requiring type-checking
72+
if (
73+
settings.filterRequiresTypeChecking === 'exclude' &&
74+
value.meta.docs.requiresTypeChecking === true
75+
) {
76+
return config;
77+
}
78+
79+
// Explicitly include rules requiring type-checking
80+
if (
81+
settings.filterRequiresTypeChecking === 'include' &&
82+
value.meta.docs.requiresTypeChecking !== true
83+
) {
84+
return config;
85+
}
86+
7087
const ruleName = `${RULE_NAME_PREFIX}${key}`;
7188
const recommendation = value.meta.docs.recommended;
7289
const usedSetting = settings.errorLevel
@@ -119,7 +136,7 @@ writeConfig(baseConfig, path.resolve(__dirname, '../src/configs/base.json'));
119136

120137
console.log();
121138
console.log(
122-
'---------------------------------- all.json ----------------------------------',
139+
'------------------------------------------------ all.json ------------------------------------------------',
123140
);
124141
const allConfig: LinterConfig = {
125142
extends: './configs/base.json',
@@ -133,12 +150,16 @@ writeConfig(allConfig, path.resolve(__dirname, '../src/configs/all.json'));
133150

134151
console.log();
135152
console.log(
136-
'------------------------------ recommended.json ------------------------------',
153+
'------------------------------ recommended.json (should not require program) ------------------------------',
137154
);
138155
const recommendedRules = ruleEntries
139156
.filter(entry => !!entry[1].meta.docs.recommended)
140157
.reduce<LinterConfigRules>(
141-
(config, entry) => reducer(config, entry, { filterDeprecated: false }),
158+
(config, entry) =>
159+
reducer(config, entry, {
160+
filterDeprecated: false,
161+
filterRequiresTypeChecking: 'exclude',
162+
}),
142163
{},
143164
);
144165
BASE_RULES_THAT_ARE_RECOMMENDED.forEach(ruleName => {
@@ -152,3 +173,32 @@ writeConfig(
152173
recommendedConfig,
153174
path.resolve(__dirname, '../src/configs/recommended.json'),
154175
);
176+
177+
console.log();
178+
console.log(
179+
'--------------------------------- recommended-requiring-type-checking.json ---------------------------------',
180+
);
181+
const recommendedRulesRequiringProgram = ruleEntries
182+
.filter(entry => !!entry[1].meta.docs.recommended)
183+
.reduce<LinterConfigRules>(
184+
(config, entry) =>
185+
reducer(config, entry, {
186+
filterDeprecated: false,
187+
filterRequiresTypeChecking: 'include',
188+
}),
189+
{},
190+
);
191+
BASE_RULES_THAT_ARE_RECOMMENDED.forEach(ruleName => {
192+
recommendedRulesRequiringProgram[ruleName] = 'error';
193+
});
194+
const recommendedRequiringTypeCheckingConfig: LinterConfig = {
195+
extends: './configs/base.json',
196+
rules: recommendedRulesRequiringProgram,
197+
};
198+
writeConfig(
199+
recommendedRequiringTypeCheckingConfig,
200+
path.resolve(
201+
__dirname,
202+
'../src/configs/recommended-requiring-type-checking.json',
203+
),
204+
);

packages/eslint-plugin/tools/validate-configs/checkConfigRecommended.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ function checkConfigRecommended(): boolean {
1010
const recommendedNames = new Set(Object.keys(recommended));
1111

1212
return Object.entries(rules).reduce<boolean>((acc, [ruleName, rule]) => {
13-
if (!rule.meta.deprecated && rule.meta.docs.recommended !== false) {
13+
if (
14+
!rule.meta.deprecated &&
15+
rule.meta.docs.recommended !== false &&
16+
rule.meta.docs.requiresTypeChecking !== true
17+
) {
1418
const prefixed = `${prefix}${ruleName}` as keyof typeof recommended;
1519
if (recommendedNames.has(prefixed)) {
1620
if (recommended[prefixed] !== rule.meta.docs.recommended) {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy