Skip to content
Permalink
Browse files

feat: suggestion types, suggestions for no-explicit-any (#1250)

  • Loading branch information
bradzacher committed Nov 24, 2019
1 parent 1d56c82 commit b16a4b6a79637d0ac7c61526da6777a0ac3dddd5
@@ -57,7 +57,7 @@
"@types/node": "^12.12.7",
"all-contributors-cli": "^6.11.0",
"cz-conventional-changelog": "^3.0.2",
"eslint": "^6.6.0",
"eslint": "^6.7.0",
"eslint-plugin-eslint-comments": "^3.1.2",
"eslint-plugin-eslint-plugin": "^2.1.0",
"eslint-plugin-import": "^2.18.2",
@@ -11,7 +11,7 @@ export type Options = [
ignoreRestArgs?: boolean;
},
];
export type MessageIds = 'unexpectedAny';
export type MessageIds = 'unexpectedAny' | 'suggestUnknown' | 'suggestNever';

export default util.createRule<Options, MessageIds>({
name: 'no-explicit-any',
@@ -25,6 +25,10 @@ export default util.createRule<Options, MessageIds>({
fixable: 'code',
messages: {
unexpectedAny: 'Unexpected any. Specify a different type.',
suggestUnknown:
'Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct.',
suggestNever:
"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of.",
},
schema: [
{
@@ -172,17 +176,36 @@ export default util.createRule<Options, MessageIds>({
return;
}

let fix: TSESLint.ReportFixFunction | null = null;
const fixOrSuggest: {
fix: TSESLint.ReportFixFunction | null;
suggest: TSESLint.ReportSuggestionArray<MessageIds> | null;
} = {
fix: null,
suggest: [
{
messageId: 'suggestUnknown',
fix(fixer): TSESLint.RuleFix {
return fixer.replaceText(node, 'unknown');
},
},
{
messageId: 'suggestNever',
fix(fixer): TSESLint.RuleFix {
return fixer.replaceText(node, 'never');
},
},
],
};

if (fixToUnknown) {
fix = (fixer =>
fixOrSuggest.fix = (fixer =>
fixer.replaceText(node, 'unknown')) as TSESLint.ReportFixFunction;
}

context.report({
node,
messageId: 'unexpectedAny',
fix,
...fixOrSuggest,
});
},
};
@@ -3,6 +3,7 @@ import { RuleTester } from '../RuleTester';
import { TSESLint } from '@typescript-eslint/experimental-utils';

type InvalidTestCase = TSESLint.InvalidTestCase<MessageIds, Options>;
type SuggestionOutput = TSESLint.SuggestionOutput<MessageIds>;

const ruleTester = new RuleTester({
parser: '@typescript-eslint/parser',
@@ -306,11 +307,31 @@ type obj = {
messageId: 'unexpectedAny',
line: 1,
column: 31,
suggestions: [
{
messageId: 'suggestUnknown',
output: 'function generic(param: Array<unknown>): Array<any> {}',
},
{
messageId: 'suggestNever',
output: 'function generic(param: Array<never>): Array<any> {}',
},
],
},
{
messageId: 'unexpectedAny',
line: 1,
column: 44,
suggestions: [
{
messageId: 'suggestUnknown',
output: 'function generic(param: Array<any>): Array<unknown> {}',
},
{
messageId: 'suggestNever',
output: 'function generic(param: Array<any>): Array<never> {}',
},
],
},
],
},
@@ -705,11 +726,31 @@ type obj = {
messageId: 'unexpectedAny',
line: 1,
column: 15,
suggestions: [
{
messageId: 'suggestUnknown',
output: `class Foo<t = unknown> extends Bar<any> {}`,
},
{
messageId: 'suggestNever',
output: `class Foo<t = never> extends Bar<any> {}`,
},
],
},
{
messageId: 'unexpectedAny',
line: 1,
column: 32,
suggestions: [
{
messageId: 'suggestUnknown',
output: `class Foo<t = any> extends Bar<unknown> {}`,
},
{
messageId: 'suggestNever',
output: `class Foo<t = any> extends Bar<never> {}`,
},
],
},
],
},
@@ -720,11 +761,31 @@ type obj = {
messageId: 'unexpectedAny',
line: 1,
column: 24,
suggestions: [
{
messageId: 'suggestUnknown',
output: `abstract class Foo<t = unknown> extends Bar<any> {}`,
},
{
messageId: 'suggestNever',
output: `abstract class Foo<t = never> extends Bar<any> {}`,
},
],
},
{
messageId: 'unexpectedAny',
line: 1,
column: 41,
suggestions: [
{
messageId: 'suggestUnknown',
output: `abstract class Foo<t = any> extends Bar<unknown> {}`,
},
{
messageId: 'suggestNever',
output: `abstract class Foo<t = any> extends Bar<never> {}`,
},
],
},
],
},
@@ -735,16 +796,46 @@ type obj = {
messageId: 'unexpectedAny',
line: 1,
column: 24,
suggestions: [
{
messageId: 'suggestUnknown',
output: `abstract class Foo<t = unknown> implements Bar<any>, Baz<any> {}`,
},
{
messageId: 'suggestNever',
output: `abstract class Foo<t = never> implements Bar<any>, Baz<any> {}`,
},
],
},
{
messageId: 'unexpectedAny',
line: 1,
column: 44,
suggestions: [
{
messageId: 'suggestUnknown',
output: `abstract class Foo<t = any> implements Bar<unknown>, Baz<any> {}`,
},
{
messageId: 'suggestNever',
output: `abstract class Foo<t = any> implements Bar<never>, Baz<any> {}`,
},
],
},
{
messageId: 'unexpectedAny',
line: 1,
column: 54,
suggestions: [
{
messageId: 'suggestUnknown',
output: `abstract class Foo<t = any> implements Bar<any>, Baz<unknown> {}`,
},
{
messageId: 'suggestNever',
output: `abstract class Foo<t = any> implements Bar<any>, Baz<never> {}`,
},
],
},
],
},
@@ -771,19 +862,51 @@ type obj = {
{
// https://github.com/typescript-eslint/typescript-eslint/issues/64
code: `
function test<T extends Partial<any>>() {}
const test = <T extends Partial<any>>() => {};
`,
function test<T extends Partial<any>>() {}
const test = <T extends Partial<any>>() => {};
`.trimRight(),
errors: [
{
messageId: 'unexpectedAny',
line: 2,
column: 41,
column: 33,
suggestions: [
{
messageId: 'suggestUnknown',
output: `
function test<T extends Partial<unknown>>() {}
const test = <T extends Partial<any>>() => {};
`.trimRight(),
},
{
messageId: 'suggestNever',
output: `
function test<T extends Partial<never>>() {}
const test = <T extends Partial<any>>() => {};
`.trimRight(),
},
],
},
{
messageId: 'unexpectedAny',
line: 3,
column: 41,
column: 33,
suggestions: [
{
messageId: 'suggestUnknown',
output: `
function test<T extends Partial<any>>() {}
const test = <T extends Partial<unknown>>() => {};
`.trimRight(),
},
{
messageId: 'suggestNever',
output: `
function test<T extends Partial<any>>() {}
const test = <T extends Partial<never>>() => {};
`.trimRight(),
},
],
},
],
},
@@ -869,7 +992,23 @@ type obj = {
],
},
] as InvalidTestCase[]).reduce<InvalidTestCase[]>((acc, testCase) => {
acc.push(testCase);
const suggestions = (code: string): SuggestionOutput[] => [
{
messageId: 'suggestUnknown',
output: code.replace(/any/, 'unknown'),
},
{
messageId: 'suggestNever',
output: code.replace(/any/, 'never'),
},
];
acc.push({
...testCase,
errors: testCase.errors.map(e => ({
...e,
suggestions: e.suggestions ?? suggestions(testCase.code),
})),
});
const options = testCase.options || [];
const code = `// fixToUnknown: true\n${testCase.code}`;
acc.push({
@@ -881,7 +1020,17 @@ type obj = {
return err;
}

return { ...err, line: err.line + 1 };
return {
...err,
line: err.line + 1,
suggestions:
err.suggestions?.map(
(s): SuggestionOutput => ({
...s,
output: `// fixToUnknown: true\n${s.output}`,
}),
) ?? suggestions(code),
};
}),
});

@@ -105,6 +105,9 @@ interface RuleFixer {
type ReportFixFunction = (
fixer: RuleFixer,
) => null | RuleFix | RuleFix[] | IterableIterator<RuleFix>;
type ReportSuggestionArray<TMessageIds extends string> = ReportDescriptorBase<
TMessageIds
>[];

interface ReportDescriptorBase<TMessageIds extends string> {
/**
@@ -119,7 +122,19 @@ interface ReportDescriptorBase<TMessageIds extends string> {
* The messageId which is being reported.
*/
messageId: TMessageIds;
// we disallow this because it's much better to use messageIds for reusable errors that are easily testable
// message?: string;
// suggestions instead have this property that works the same, but again it's much better to use messageIds
// desc?: string;
}
interface ReportDescriptorWithSuggestion<TMessageIds extends string>
extends ReportDescriptorBase<TMessageIds> {
/**
* 6.7's Suggestions API
*/
suggest?: ReportSuggestionArray<TMessageIds> | null;
}

interface ReportDescriptorNodeOptionalLoc {
/**
* The Node or AST Token which the report is being attached to
@@ -136,9 +151,9 @@ interface ReportDescriptorLocOnly {
*/
loc: TSESTree.SourceLocation | TSESTree.LineAndColumnData;
}
type ReportDescriptor<TMessageIds extends string> = ReportDescriptorBase<
TMessageIds
> &
type ReportDescriptor<
TMessageIds extends string
> = ReportDescriptorWithSuggestion<TMessageIds> &
(ReportDescriptorNodeOptionalLoc | ReportDescriptorLocOnly);

interface RuleContext<
@@ -416,6 +431,7 @@ interface RuleModule<
export {
ReportDescriptor,
ReportFixFunction,
ReportSuggestionArray,
RuleContext,
RuleFix,
RuleFixer,

0 comments on commit b16a4b6

Please sign in to comment.
You can’t perform that action at this time.