Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(eslint-plugin): add
no-implicit-any-catch
rule (#2202)
Co-authored-by: Glen <glen.84@gmail.com> Co-authored-by: Brad Zacher <brad.zacher@gmail.com>
- Loading branch information
1 parent
3764248
commit fde89d4
Showing
6 changed files
with
253 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
76 changes: 76 additions & 0 deletions
76
packages/eslint-plugin/docs/rules/no-implicit-any-catch.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
# Disallow usage of the implicit `any` type in catch clauses (`no-implicit-any-catch`) | ||
|
||
TypeScript 4.0 added support for adding an explicit `any` or `unknown` type annotation on a catch clause variable. | ||
|
||
By default, TypeScript will type a catch clause variable as `any`, so explicitly annotating it as `unknown` can add a lot of safety to your codebase. | ||
|
||
The `noImplicitAny` flag in TypeScript does not cover this for backwards compatibility reasons. | ||
|
||
## Rule Details | ||
|
||
This rule requires an explicit type to be declared on a catch clause variable. | ||
|
||
The following pattern is considered a warning: | ||
|
||
```ts | ||
try { | ||
// ... | ||
} catch (e) { | ||
// ... | ||
} | ||
``` | ||
|
||
The following pattern is **_not_** considered a warning: | ||
|
||
<!-- TODO: prettier currently removes the type annotations, re-enable this once prettier is updated --> | ||
<!-- prettier-ignore-start --> | ||
|
||
```ts | ||
try { | ||
// ... | ||
} catch (e: unknown) { | ||
// ... | ||
} | ||
``` | ||
|
||
<!-- prettier-ignore-end --> | ||
|
||
## Options | ||
|
||
The rule accepts an options object with the following properties: | ||
|
||
```ts | ||
type Options = { | ||
// if false, disallow specifying `: any` as the error type as well. See also `no-explicit-any` | ||
allowExplicitAny: boolean; | ||
}; | ||
|
||
const defaults = { | ||
allowExplicitAny: false, | ||
}; | ||
``` | ||
|
||
### `allowExplicitAny` | ||
|
||
The follow is is **_not_** considered a warning with `{ allowExplicitAny: true }` | ||
|
||
<!-- TODO: prettier currently removes the type annotations, re-enable this once prettier is updated --> | ||
<!-- prettier-ignore-start --> | ||
|
||
```ts | ||
try { | ||
// ... | ||
} catch (e: any) { | ||
// ... | ||
} | ||
``` | ||
|
||
<!-- prettier-ignore-end --> | ||
|
||
## When Not To Use It | ||
|
||
If you are not using TypeScript 4.0 (or greater), then you will not be able to use this rule, annotations on catch clauses is not supported. | ||
|
||
## Further Reading | ||
|
||
- [TypeScript 4.0 Beta Release Notes](https://devblogs.microsoft.com/typescript/announcing-typescript-4-0-beta/#unknown-on-catch) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import * as util from '../util'; | ||
import { | ||
TSESLint, | ||
AST_NODE_TYPES, | ||
} from '@typescript-eslint/experimental-utils'; | ||
|
||
export type Options = [ | ||
{ | ||
allowExplicitAny: boolean; | ||
}, | ||
]; | ||
export type MessageIds = | ||
| 'implicitAnyInCatch' | ||
| 'explicitAnyInCatch' | ||
| 'suggestExplicitUnknown'; | ||
|
||
export default util.createRule<Options, MessageIds>({ | ||
name: 'no-implicit-any-catch', | ||
meta: { | ||
type: 'suggestion', | ||
docs: { | ||
description: 'Disallow usage of the implicit `any` type in catch clauses', | ||
category: 'Best Practices', | ||
recommended: false, | ||
suggestion: true, | ||
}, | ||
fixable: 'code', | ||
messages: { | ||
implicitAnyInCatch: 'Implicit any in catch clause', | ||
explicitAnyInCatch: 'Explicit any in catch clause', | ||
suggestExplicitUnknown: | ||
'Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct.', | ||
}, | ||
schema: [ | ||
{ | ||
type: 'object', | ||
additionalProperties: false, | ||
properties: { | ||
allowExplicitAny: { | ||
type: 'boolean', | ||
}, | ||
}, | ||
}, | ||
], | ||
}, | ||
defaultOptions: [ | ||
{ | ||
allowExplicitAny: false, | ||
}, | ||
], | ||
create(context, [{ allowExplicitAny }]) { | ||
return { | ||
CatchClause(node): void { | ||
if (!node.param) { | ||
return; // ignore catch without variable | ||
} | ||
|
||
if (!node.param.typeAnnotation) { | ||
context.report({ | ||
node, | ||
messageId: 'implicitAnyInCatch', | ||
suggest: [ | ||
{ | ||
messageId: 'suggestExplicitUnknown', | ||
fix(fixer): TSESLint.RuleFix { | ||
return fixer.insertTextAfter(node.param!, ': unknown'); | ||
}, | ||
}, | ||
], | ||
}); | ||
} else if ( | ||
!allowExplicitAny && | ||
node.param.typeAnnotation.typeAnnotation.type === | ||
AST_NODE_TYPES.TSAnyKeyword | ||
) { | ||
context.report({ | ||
node, | ||
messageId: 'explicitAnyInCatch', | ||
suggest: [ | ||
{ | ||
messageId: 'suggestExplicitUnknown', | ||
fix(fixer): TSESLint.RuleFix { | ||
return fixer.replaceText( | ||
node.param!.typeAnnotation!, | ||
': unknown', | ||
); | ||
}, | ||
}, | ||
], | ||
}); | ||
} | ||
}, | ||
}; | ||
}, | ||
}); |
78 changes: 78 additions & 0 deletions
78
packages/eslint-plugin/tests/rules/no-implicit-any-catch.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/* eslint-disable eslint-comments/no-use */ | ||
// TODO - prettier currently removes the type annotations, re-enable this once prettier is updated | ||
/* eslint "@typescript-eslint/internal/plugin-test-formatting": ["error", { formatWithPrettier: false }] */ | ||
/* eslint-enable eslint-comments/no-use */ | ||
|
||
import rule from '../../src/rules/no-implicit-any-catch'; | ||
import { RuleTester } from '../RuleTester'; | ||
|
||
const ruleTester = new RuleTester({ | ||
parser: '@typescript-eslint/parser', | ||
}); | ||
|
||
ruleTester.run('no-implicit-any-catch', rule, { | ||
valid: [ | ||
` | ||
try { | ||
} catch (e1: unknown) {} | ||
`, | ||
{ | ||
code: ` | ||
try { | ||
} catch (e2: any) {} | ||
`, | ||
options: [{ allowExplicitAny: true }], | ||
}, | ||
], | ||
invalid: [ | ||
{ | ||
code: ` | ||
try { | ||
} catch (e3) {} | ||
`.trim(), | ||
errors: [ | ||
{ | ||
line: 2, | ||
column: 3, | ||
messageId: 'implicitAnyInCatch', | ||
endLine: 2, | ||
endColumn: 16, | ||
suggestions: [ | ||
{ | ||
messageId: 'suggestExplicitUnknown', | ||
output: ` | ||
try { | ||
} catch (e3: unknown) {} | ||
`.trim(), | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
{ | ||
code: ` | ||
try { | ||
} catch (e4: any) {} | ||
`.trim(), | ||
options: [{ allowExplicitAny: false }], | ||
errors: [ | ||
{ | ||
line: 2, | ||
column: 3, | ||
messageId: 'explicitAnyInCatch', | ||
endLine: 2, | ||
endColumn: 21, | ||
suggestions: [ | ||
{ | ||
messageId: 'suggestExplicitUnknown', | ||
output: ` | ||
try { | ||
} catch (e4: unknown) {} | ||
`.trim(), | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
], | ||
}); |