From 91010e88258bf47a0438e842c8ddca19e0414b48 Mon Sep 17 00:00:00 2001 From: eyelidlessness Date: Sun, 18 Oct 2020 13:05:32 -0700 Subject: [PATCH] feat(eslint-plugin): [prefer-readonly-parameter-types] add `ignoreInferredTypes` option (#2668) --- .../rules/prefer-readonly-parameter-types.md | 52 +++++++++++++++++++ .../rules/prefer-readonly-parameter-types.ts | 13 ++++- .../prefer-readonly-parameter-types.test.ts | 44 ++++++++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.md b/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.md index 74697f2d3e6..34544456f09 100644 --- a/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.md +++ b/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.md @@ -124,10 +124,12 @@ interface Foo { ```ts interface Options { checkParameterProperties?: boolean; + ignoreInferredTypes?: boolean; } const defaultOptions: Options = { checkParameterProperties: true, + ignoreInferredTypes: false, }; ``` @@ -162,3 +164,53 @@ class Foo { ) {} } ``` + +### `ignoreInferredTypes` + +This option allows you to ignore parameters which don't explicitly specify a type. This may be desirable in cases where an external dependency specifies a callback with mutable parameters, and manually annotating the callback's parameters is undesirable. + +Examples of **incorrect** code for this rule with `{ignoreInferredTypes: true}`: + +```ts +import { acceptsCallback, CallbackOptions } from 'external-dependency'; + +acceceptsCallback((options: CallbackOptions) => {}); +``` + +
+external-dependency.d.ts + +```ts +export interface CallbackOptions { + prop: string; +} +type Callback = (options: CallbackOptions) => void; +type AcceptsCallback = (callback: Callback) => void; + +export const acceptsCallback: AcceptsCallback; +``` + +
+ +Examples of **correct** code for this rule with `{ignoreInferredTypes: true}`: + +```ts +import { acceptsCallback } from 'external-dependency'; + +acceceptsCallback(options => {}); +``` + +
+external-dependency.d.ts + +```ts +export interface CallbackOptions { + prop: string; +} +type Callback = (options: CallbackOptions) => void; +type AcceptsCallback = (callback: Callback) => void; + +export const acceptsCallback: AcceptsCallback; +``` + +
diff --git a/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts b/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts index fdad0220c39..a36020e93d3 100644 --- a/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts +++ b/packages/eslint-plugin/src/rules/prefer-readonly-parameter-types.ts @@ -7,6 +7,7 @@ import * as util from '../util'; type Options = [ { checkParameterProperties?: boolean; + ignoreInferredTypes?: boolean; }, ]; type MessageIds = 'shouldBeReadonly'; @@ -30,6 +31,9 @@ export default util.createRule({ checkParameterProperties: { type: 'boolean', }, + ignoreInferredTypes: { + type: 'boolean', + }, }, }, ], @@ -40,9 +44,11 @@ export default util.createRule({ defaultOptions: [ { checkParameterProperties: true, + ignoreInferredTypes: false, }, ], - create(context, [{ checkParameterProperties }]) { + create(context, options) { + const [{ checkParameterProperties, ignoreInferredTypes }] = options; const { esTreeNodeToTSNodeMap, program } = util.getParserServices(context); const checker = program.getTypeChecker(); @@ -81,6 +87,11 @@ export default util.createRule({ param.type === AST_NODE_TYPES.TSParameterProperty ? param.parameter : param; + + if (ignoreInferredTypes && actualParam.typeAnnotation == null) { + continue; + } + const tsNode = esTreeNodeToTSNodeMap.get(actualParam); const type = checker.getTypeAtLocation(tsNode); const isReadOnly = util.isTypeReadonly(checker, type); diff --git a/packages/eslint-plugin/tests/rules/prefer-readonly-parameter-types.test.ts b/packages/eslint-plugin/tests/rules/prefer-readonly-parameter-types.test.ts index d5408a5d859..9278420a764 100644 --- a/packages/eslint-plugin/tests/rules/prefer-readonly-parameter-types.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-readonly-parameter-types.test.ts @@ -247,6 +247,24 @@ ruleTester.run('prefer-readonly-parameter-types', rule, { const willNotCrash = (foo: Readonly) => {}; `, + { + code: ` + type Callback = (options: T) => void; + + declare const acceptsCallback: (callback: Callback) => void; + + interface CallbackOptions { + prop: string; + } + + acceptsCallback(options => {}); + `, + options: [ + { + ignoreInferredTypes: true, + }, + ], + }, ], invalid: [ // arrays @@ -671,5 +689,31 @@ ruleTester.run('prefer-readonly-parameter-types', rule, { }, ], }, + { + code: ` + type Callback = (options: T) => void; + + declare const acceptsCallback: (callback: Callback) => void; + + interface CallbackOptions { + prop: string; + } + + acceptsCallback((options: CallbackOptions) => {}); + `, + options: [ + { + ignoreInferredTypes: true, + }, + ], + errors: [ + { + messageId: 'shouldBeReadonly', + line: 10, + column: 43, + endColumn: 67, + }, + ], + }, ], });