From 25f459cdc4f38d44b48554e04cfa1676538ccdfb Mon Sep 17 00:00:00 2001 From: Nikita Stefaniak Date: Sun, 28 Feb 2021 20:06:57 +0100 Subject: [PATCH] feat(eslint-plugin): [prom-func-async] report only function head (#2872) --- .../src/rules/promise-function-async.ts | 3 + .../src/util/explicitReturnTypeUtils.ts | 63 +-------------- .../src/util/getFunctionHeadLoc.ts | 79 +++++++++++++++++++ packages/eslint-plugin/src/util/index.ts | 1 + .../rules/promise-function-async.test.ts | 19 +++++ 5 files changed, 104 insertions(+), 61 deletions(-) create mode 100644 packages/eslint-plugin/src/util/getFunctionHeadLoc.ts diff --git a/packages/eslint-plugin/src/rules/promise-function-async.ts b/packages/eslint-plugin/src/rules/promise-function-async.ts index 00888c8f39f..ce624be643c 100644 --- a/packages/eslint-plugin/src/rules/promise-function-async.ts +++ b/packages/eslint-plugin/src/rules/promise-function-async.ts @@ -91,6 +91,7 @@ export default util.createRule({ ]); const parserServices = util.getParserServices(context); const checker = parserServices.program.getTypeChecker(); + const sourceCode = context.getSourceCode(); function validateNode( node: @@ -140,12 +141,14 @@ export default util.createRule({ return context.report({ messageId: 'missingAsync', node, + loc: util.getFunctionHeadLoc(node, sourceCode), }); } context.report({ messageId: 'missingAsync', node, + loc: util.getFunctionHeadLoc(node, sourceCode), fix: fixer => { if ( node.parent && diff --git a/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts b/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts index d2c3a684c86..cd2f0b11826 100644 --- a/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts +++ b/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts @@ -2,9 +2,9 @@ import { TSESTree, AST_NODE_TYPES, TSESLint, - AST_TOKEN_TYPES, } from '@typescript-eslint/experimental-utils'; import { isTypeAssertion, isConstructor, isSetter } from './astUtils'; +import { getFunctionHeadLoc } from './getFunctionHeadLoc'; import { nullThrows, NullThrowsReasons } from './nullThrows'; type FunctionExpression = @@ -12,65 +12,6 @@ type FunctionExpression = | TSESTree.FunctionExpression; type FunctionNode = FunctionExpression | TSESTree.FunctionDeclaration; -/** - * Creates a report location for the given function. - * The location only encompasses the "start" of the function, and not the body - * - * eg. - * function foo(args) {} - * ^^^^^^^^^^^^^^^^^^ - * - * get y(args) {} - * ^^^^^^^^^^^ - * - * const x = (args) => {} - * ^^^^^^^^^ - */ -function getReporLoc( - node: FunctionNode, - sourceCode: TSESLint.SourceCode, -): TSESTree.SourceLocation { - /** - * Returns start column position - * @param node - */ - function getLocStart(): TSESTree.LineAndColumnData { - /* highlight method name */ - const parent = node.parent; - if ( - parent && - (parent.type === AST_NODE_TYPES.MethodDefinition || - (parent.type === AST_NODE_TYPES.Property && parent.method)) - ) { - return parent.loc.start; - } - - return node.loc.start; - } - - /** - * Returns end column position - * @param node - */ - function getLocEnd(): TSESTree.LineAndColumnData { - /* highlight `=>` */ - if (node.type === AST_NODE_TYPES.ArrowFunctionExpression) { - return sourceCode.getTokenBefore( - node.body, - token => - token.type === AST_TOKEN_TYPES.Punctuator && token.value === '=>', - )!.loc.end; - } - - return sourceCode.getTokenBefore(node.body)!.loc.end; - } - - return { - start: getLocStart(), - end: getLocEnd(), - }; -} - /** * Checks if a node is a variable declarator with a type annotation. * ``` @@ -327,7 +268,7 @@ function checkFunctionReturnType( return; } - report(getReporLoc(node, sourceCode)); + report(getFunctionHeadLoc(node, sourceCode)); } /** diff --git a/packages/eslint-plugin/src/util/getFunctionHeadLoc.ts b/packages/eslint-plugin/src/util/getFunctionHeadLoc.ts new file mode 100644 index 00000000000..dc18a832f94 --- /dev/null +++ b/packages/eslint-plugin/src/util/getFunctionHeadLoc.ts @@ -0,0 +1,79 @@ +import { + AST_NODE_TYPES, + AST_TOKEN_TYPES, + TSESLint, + TSESTree, +} from '@typescript-eslint/experimental-utils'; + +type FunctionNode = + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionExpression + | TSESTree.FunctionDeclaration; + +/** + * Creates a report location for the given function. + * The location only encompasses the "start" of the function, and not the body + * + * eg. + * + * ``` + * function foo(args) {} + * ^^^^^^^^^^^^^^^^^^ + * + * get y(args) {} + * ^^^^^^^^^^^ + * + * const x = (args) => {} + * ^^^^^^^^^ + * ``` + */ +export function getFunctionHeadLoc( + node: FunctionNode, + sourceCode: TSESLint.SourceCode, +): TSESTree.SourceLocation { + function getLocStart(): TSESTree.LineAndColumnData { + if (node.parent && node.parent.type === AST_NODE_TYPES.MethodDefinition) { + // return the start location for class method + + if (node.parent.decorators && node.parent.decorators.length > 0) { + // exclude decorators + return sourceCode.getTokenAfter( + node.parent.decorators[node.parent.decorators.length - 1], + )!.loc.start; + } + + return node.parent.loc.start; + } + + if ( + node.parent && + node.parent.type === AST_NODE_TYPES.Property && + node.parent.method + ) { + // return the start location for object method shorthand + return node.parent.loc.start; + } + + // return the start location for a regular function + return node.loc.start; + } + + function getLocEnd(): TSESTree.LineAndColumnData { + if (node.type === AST_NODE_TYPES.ArrowFunctionExpression) { + // find the end location for arrow function expression + return sourceCode.getTokenBefore( + node.body, + token => + token.type === AST_TOKEN_TYPES.Punctuator && token.value === '=>', + )!.loc.end; + } + + // return the end location for a regular function + return sourceCode.getTokenBefore(node.body)!.loc.end; + } + + return { + start: getLocStart(), + end: getLocEnd(), + }; +} diff --git a/packages/eslint-plugin/src/util/index.ts b/packages/eslint-plugin/src/util/index.ts index 810398a093f..fc5346f7346 100644 --- a/packages/eslint-plugin/src/util/index.ts +++ b/packages/eslint-plugin/src/util/index.ts @@ -3,6 +3,7 @@ import { ESLintUtils } from '@typescript-eslint/experimental-utils'; export * from './astUtils'; export * from './collectUnusedVariables'; export * from './createRule'; +export * from './getFunctionHeadLoc'; export * from './isTypeReadonly'; export * from './misc'; export * from './nullThrows'; diff --git a/packages/eslint-plugin/tests/rules/promise-function-async.test.ts b/packages/eslint-plugin/tests/rules/promise-function-async.test.ts index 49f6e250b6c..961047138c9 100644 --- a/packages/eslint-plugin/tests/rules/promise-function-async.test.ts +++ b/packages/eslint-plugin/tests/rules/promise-function-async.test.ts @@ -611,6 +611,25 @@ async function foo(): Promise | SPromise { return Math.random() > 0.5 ? Promise.resolve('value') : Promise.resolve(false); +} + `, + }, + { + code: ` +class Test { + @decorator + public test() { + return Promise.resolve(123); + } +} + `, + errors: [{ line: 4, column: 3, messageId }], + output: ` +class Test { + @decorator + public async test() { + return Promise.resolve(123); + } } `, },