From 936e252e30cfcebdaf971cf0f66a3031e236a41c Mon Sep 17 00:00:00 2001 From: islandryu <65934663+islandryu@users.noreply.github.com> Date: Wed, 2 Feb 2022 15:25:02 +0900 Subject: [PATCH] feat(eslint-plugin): [explicit-function-return-type] add allowedNames (#4440) * feat(eslint-plugin): [explicit-function-return-type] add allowedNames option * feat(eslint-plugin): [explicit-function-return-type] afix typecheck * Update packages/eslint-plugin/src/rules/explicit-function-return-type.ts Co-authored-by: Josh Goldberg * feat(eslint-plugin): [explicit-function-return-type] Change to allowedNames to work for object properties and class methods * feat(eslint-plugin): [explicit-function-return-type] Change to allowedNames to work for object properties and class methods * fix(eslint-plugin): [explicit-function-return-type] fix for codecov * fix(eslint-plugin): [explicit-function-return-type] add test * fix(eslint-plugin): [explicit-function-return-type] add test * fix(eslint-plugin): [explicit-function-return-type] fix for codecov * fix(eslint-plugin): [explicit-function-return-type] Change allowedName to not ignore computed property Co-authored-by: Josh Goldberg Co-authored-by: Josh Goldberg --- .../rules/explicit-function-return-type.md | 20 ++ .../rules/explicit-function-return-type.ts | 67 +++++++ .../explicit-function-return-type.test.ts | 188 ++++++++++++++++++ 3 files changed, 275 insertions(+) diff --git a/packages/eslint-plugin/docs/rules/explicit-function-return-type.md b/packages/eslint-plugin/docs/rules/explicit-function-return-type.md index 20aaeadb986..a542f328fb6 100644 --- a/packages/eslint-plugin/docs/rules/explicit-function-return-type.md +++ b/packages/eslint-plugin/docs/rules/explicit-function-return-type.md @@ -77,6 +77,10 @@ type Options = { allowDirectConstAssertionInArrowFunctions?: boolean; // if true, concise arrow functions that start with the void keyword will not be checked allowConciseArrowFunctionExpressionsStartingWithVoid?: boolean; + /** + * An array of function/method names that will not have their arguments or their return values checked. + */ + allowedNames?: string[]; }; const defaults = { @@ -85,6 +89,7 @@ const defaults = { allowHigherOrderFunctions: true, allowDirectConstAssertionInArrowFunctions: true, allowConciseArrowFunctionExpressionsStartingWithVoid: false, + allowedNames: [], }; ``` @@ -262,6 +267,21 @@ const log = (message: string) => { var log = (message: string) => void console.log(message); ``` +### `allowedNames` + +You may pass function/method names you would like this rule to ignore, like so: + +```json +{ + "@typescript-eslint/explicit-function-return-type": [ + "error", + { + "allowedNames": ["ignoredFunctionName", "ignoredMethodName"] + } + ] +} +``` + ## When Not To Use It If you don't wish to prevent calling code from using function return values in unexpected ways, then diff --git a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts index d9302a7775f..1f69834d478 100644 --- a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts +++ b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts @@ -13,6 +13,7 @@ type Options = [ allowHigherOrderFunctions?: boolean; allowDirectConstAssertionInArrowFunctions?: boolean; allowConciseArrowFunctionExpressionsStartingWithVoid?: boolean; + allowedNames?: string[]; }, ]; type MessageIds = 'missingReturnType'; @@ -48,6 +49,12 @@ export default util.createRule({ allowConciseArrowFunctionExpressionsStartingWithVoid: { type: 'boolean', }, + allowedNames: { + type: 'array', + items: { + type: 'string', + }, + }, }, additionalProperties: false, }, @@ -60,11 +67,64 @@ export default util.createRule({ allowHigherOrderFunctions: true, allowDirectConstAssertionInArrowFunctions: true, allowConciseArrowFunctionExpressionsStartingWithVoid: false, + allowedNames: [], }, ], create(context, [options]) { const sourceCode = context.getSourceCode(); + function isAllowedName( + node: + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionExpression + | TSESTree.FunctionDeclaration, + ): boolean { + if (!options.allowedNames || !options.allowedNames.length) { + return false; + } + if ( + node.type === AST_NODE_TYPES.ArrowFunctionExpression || + node.type === AST_NODE_TYPES.FunctionExpression + ) { + const parent = node.parent; + let funcName; + if (node.id?.name) { + funcName = node.id.name; + } else if (parent) { + switch (parent.type) { + case AST_NODE_TYPES.VariableDeclarator: { + if (parent.id.type === AST_NODE_TYPES.Identifier) { + funcName = parent.id.name; + } + break; + } + case AST_NODE_TYPES.MethodDefinition: + case AST_NODE_TYPES.PropertyDefinition: + case AST_NODE_TYPES.Property: { + if ( + parent.key.type === AST_NODE_TYPES.Identifier && + parent.computed === false + ) { + funcName = parent.key.name; + } + break; + } + } + } + if (!!funcName && !!options.allowedNames.includes(funcName)) { + return true; + } + } + if ( + node.type === AST_NODE_TYPES.FunctionDeclaration && + node.id && + node.id.type === AST_NODE_TYPES.Identifier && + !!options.allowedNames.includes(node.id.name) + ) { + return true; + } + return false; + } return { 'ArrowFunctionExpression, FunctionExpression'( node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, @@ -79,6 +139,10 @@ export default util.createRule({ return; } + if (isAllowedName(node)) { + return; + } + if ( options.allowTypedFunctionExpressions && (isValidFunctionExpressionReturnType(node, options) || @@ -96,6 +160,9 @@ export default util.createRule({ ); }, FunctionDeclaration(node): void { + if (isAllowedName(node)) { + return; + } if (options.allowTypedFunctionExpressions && node.returnType) { return; } diff --git a/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts b/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts index e1fae3cd116..40c15a89a2f 100644 --- a/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts +++ b/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts @@ -400,6 +400,99 @@ new Foo(1, () => {}); code: 'const log = (message: string) => void console.log(message);', options: [{ allowConciseArrowFunctionExpressionsStartingWithVoid: true }], }, + { + filename: 'test.ts', + options: [ + { + allowedNames: ['test1', 'test2'], + }, + ], + code: ` +function test1() { + return; +} + +const foo = function test2() { + return; +}; + `, + }, + { + filename: 'test.ts', + options: [ + { + allowedNames: ['test1', 'test2'], + }, + ], + code: ` +const test1 = function () { + return; +}; +const foo = function () { + return function test2() {}; +}; + `, + }, + { + filename: 'test.ts', + options: [ + { + allowedNames: ['test1', 'test2'], + }, + ], + code: ` +const test1 = () => { + return; +}; +export const foo = { + test2() { + return 0; + }, +}; + `, + }, + { + filename: 'test.ts', + code: ` +class Test { + constructor() {} + get prop() { + return 1; + } + set prop() {} + method() { + return; + } + arrow = () => 'arrow'; + private method() { + return; + } +} + `, + options: [ + { + allowedNames: ['prop', 'method', 'arrow'], + }, + ], + }, + { + filename: 'test.ts', + code: ` +const x = { + arrowFn: () => { + return; + }, + fn: function () { + return; + }, +}; + `, + options: [ + { + allowedNames: ['arrowFn', 'fn'], + }, + ], + }, { filename: 'test.ts', code: ` @@ -1226,5 +1319,100 @@ const func = (value: number) => ({ type: 'X', value } as const); }, ], }, + { + filename: 'test.ts', + options: [ + { + allowedNames: ['test', '1'], + }, + ], + code: ` +function hoge() { + return; +} +const foo = () => { + return; +}; +const baz = function () { + return; +}; +let [test, test] = function () { + return; +}; +class X { + [test] = function () { + return; + }; +} +const x = { + 1: function () { + reutrn; + }, +}; + `, + errors: [ + { + messageId: 'missingReturnType', + line: 2, + endLine: 2, + column: 1, + endColumn: 16, + }, + { + messageId: 'missingReturnType', + line: 5, + endLine: 5, + column: 13, + endColumn: 18, + }, + { + messageId: 'missingReturnType', + line: 8, + endLine: 8, + column: 13, + endColumn: 24, + }, + { + messageId: 'missingReturnType', + line: 11, + endLine: 11, + column: 20, + endColumn: 31, + }, + { + line: 15, + column: 12, + messageId: 'missingReturnType', + endLine: 15, + endColumn: 23, + }, + { + messageId: 'missingReturnType', + line: 20, + endLine: 20, + column: 6, + endColumn: 17, + }, + ], + }, + { + filename: 'test.ts', + code: ` +const ignoredName = 'notIgnoredName'; +class Foo { + [ignoredName]() {} +} + `, + options: [{ allowedNames: ['ignoredName'] }], + errors: [ + { + messageId: 'missingReturnType', + line: 4, + endLine: 4, + column: 3, + endColumn: 18, + }, + ], + }, ], });