From eba19f072fadea89fc93bf0396154e02d5acf63f Mon Sep 17 00:00:00 2001 From: JounQin Date: Thu, 18 Mar 2021 22:22:12 +0800 Subject: [PATCH 1/4] feat(eslint-plugin): improve report message for `this` call and member-access close #3197 --- .../eslint-plugin/src/rules/no-unsafe-call.ts | 19 +++++++++-- .../src/rules/no-unsafe-member-access.ts | 7 +++- .../fixtures/tsconfig.noImplicitThis.json | 6 ++++ .../tests/rules/no-unsafe-call.test.ts | 22 ++++++++++++- .../rules/no-unsafe-member-access.test.ts | 32 ++++++++++++++++++- 5 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 packages/eslint-plugin/tests/fixtures/tsconfig.noImplicitThis.json diff --git a/packages/eslint-plugin/src/rules/no-unsafe-call.ts b/packages/eslint-plugin/src/rules/no-unsafe-call.ts index 0535bfeab31..f5fa69ed397 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-call.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-call.ts @@ -1,7 +1,14 @@ -import { TSESTree } from '@typescript-eslint/experimental-utils'; +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; -type MessageIds = 'unsafeCall' | 'unsafeNew' | 'unsafeTemplateTag'; +type MessageIds = + | 'unsafeCall' + | 'unsafeCallThis' + | 'unsafeNew' + | 'unsafeTemplateTag'; export default util.createRule<[], MessageIds>({ name: 'no-unsafe-call', @@ -15,6 +22,8 @@ export default util.createRule<[], MessageIds>({ }, messages: { unsafeCall: 'Unsafe call of an any typed value.', + unsafeCallThis: + 'Unsafe call of `this`, you can try to enable the `noImplicitThis` option.', unsafeNew: 'Unsafe construction of an any type value.', unsafeTemplateTag: 'Unsafe any typed template tag.', }, @@ -34,6 +43,12 @@ export default util.createRule<[], MessageIds>({ const type = util.getConstrainedTypeAtLocation(checker, tsNode); if (util.isTypeAnyType(type)) { + if ( + node.type === AST_NODE_TYPES.MemberExpression && + node.object.type === AST_NODE_TYPES.ThisExpression + ) { + messageId = 'unsafeCallThis'; + } context.report({ node: reportingNode, messageId: messageId, diff --git a/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts b/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts index b326c754136..1057cc8da1e 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts @@ -22,6 +22,8 @@ export default util.createRule({ messages: { unsafeMemberExpression: 'Unsafe member access {{property}} on an any value.', + unsafeThisMemberExpression: + 'Unsafe member access {{property}} on `this`, you can try to enable the `noImplicitThis` option.', unsafeComputedMemberAccess: 'Computed name {{property}} resolves to an any value.', }, @@ -60,7 +62,10 @@ export default util.createRule({ const propertyName = sourceCode.getText(node.property); context.report({ node, - messageId: 'unsafeMemberExpression', + messageId: + node.object.type === AST_NODE_TYPES.ThisExpression + ? 'unsafeThisMemberExpression' + : 'unsafeMemberExpression', data: { property: node.computed ? `[${propertyName}]` : `.${propertyName}`, }, diff --git a/packages/eslint-plugin/tests/fixtures/tsconfig.noImplicitThis.json b/packages/eslint-plugin/tests/fixtures/tsconfig.noImplicitThis.json new file mode 100644 index 00000000000..c017e51c6e4 --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/tsconfig.noImplicitThis.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noImplicitThis": false + } +} diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts index 981abb0eadd..31b081c2469 100644 --- a/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts @@ -9,7 +9,7 @@ import { const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', parserOptions: { - project: './tsconfig.json', + project: './tsconfig.noImplicitThis.json', tsconfigRootDir: getFixturesRootDir(), }, }); @@ -148,5 +148,25 @@ function foo(x: { tag: any }) { x.tag\`foo\` } }, ], }), + { + code: noFormat` +const methods = { + methodA() { + return this.methodB() + }, + methodB() { + return true + }, +}; + `, + errors: [ + { + messageId: 'unsafeCallThis', + line: 4, + column: 12, + endColumn: 24, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-member-access.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-member-access.test.ts index a9c21f8712b..d02432080db 100644 --- a/packages/eslint-plugin/tests/rules/no-unsafe-member-access.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unsafe-member-access.test.ts @@ -9,7 +9,7 @@ import { const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', parserOptions: { - project: './tsconfig.json', + project: './tsconfig.noImplicitThis.json', tsconfigRootDir: getFixturesRootDir(), }, }); @@ -202,5 +202,35 @@ function foo(x: string[], y: any) { x[y] } }, ], }), + { + code: noFormat` +const methods = { + methodA() { + return this.methodB() + }, + methodB() { + const getProperty = () => Math.random() > 0.5 ? 'methodB' : 'methodC' + return this[getProperty()]() + }, + methodC() { + return true + }, +}; + `, + errors: [ + { + messageId: 'unsafeThisMemberExpression', + line: 4, + column: 12, + endColumn: 24, + }, + { + messageId: 'unsafeThisMemberExpression', + line: 8, + column: 12, + endColumn: 31, + }, + ], + }, ], }); From 99ced2810ea9cb3e7543715b83dac4c0dfd88030 Mon Sep 17 00:00:00 2001 From: JounQin Date: Sat, 20 Mar 2021 10:11:00 +0800 Subject: [PATCH 2/4] refactor: check noImplicitThis of compilerOptions --- packages/eslint-plugin/src/rules/no-unsafe-call.ts | 7 +++++++ .../eslint-plugin/src/rules/no-unsafe-member-access.ts | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/packages/eslint-plugin/src/rules/no-unsafe-call.ts b/packages/eslint-plugin/src/rules/no-unsafe-call.ts index f5fa69ed397..4b2d7e7a6ce 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-call.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-call.ts @@ -2,6 +2,7 @@ import { AST_NODE_TYPES, TSESTree, } from '@typescript-eslint/experimental-utils'; +import * as tsutils from 'tsutils'; import * as util from '../util'; type MessageIds = @@ -33,6 +34,11 @@ export default util.createRule<[], MessageIds>({ create(context) { const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context); const checker = program.getTypeChecker(); + const compilerOptions = program.getCompilerOptions(); + const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled( + compilerOptions, + 'noImplicitThis', + ); function checkCall( node: TSESTree.Node, @@ -44,6 +50,7 @@ export default util.createRule<[], MessageIds>({ if (util.isTypeAnyType(type)) { if ( + !isNoImplicitThis && node.type === AST_NODE_TYPES.MemberExpression && node.object.type === AST_NODE_TYPES.ThisExpression ) { diff --git a/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts b/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts index 1057cc8da1e..f39bdb0282d 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts @@ -2,6 +2,7 @@ import { TSESTree, AST_NODE_TYPES, } from '@typescript-eslint/experimental-utils'; +import * as tsutils from 'tsutils'; import * as util from '../util'; const enum State { @@ -33,6 +34,11 @@ export default util.createRule({ create(context) { const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context); const checker = program.getTypeChecker(); + const compilerOptions = program.getCompilerOptions(); + const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled( + compilerOptions, + 'noImplicitThis', + ); const sourceCode = context.getSourceCode(); const stateCache = new Map(); @@ -63,6 +69,7 @@ export default util.createRule({ context.report({ node, messageId: + !isNoImplicitThis && node.object.type === AST_NODE_TYPES.ThisExpression ? 'unsafeThisMemberExpression' : 'unsafeMemberExpression', From be72c43e1e203ca9d7685372abc270afa8405f00 Mon Sep 17 00:00:00 2001 From: JounQin Date: Mon, 22 Mar 2021 13:08:00 +0800 Subject: [PATCH 3/4] feat: check `any` type of this expression only check no-unsafe-assignment and no-unsafe-return too --- .../src/rules/no-unsafe-assignment.ts | 39 ++++++++++++++++--- .../eslint-plugin/src/rules/no-unsafe-call.ts | 34 +++++++++------- .../src/rules/no-unsafe-member-access.ts | 36 +++++++++++++---- .../src/rules/no-unsafe-return.ts | 38 +++++++++++++++--- .../src/util/getThisExpression.ts | 26 +++++++++++++ packages/eslint-plugin/src/util/index.ts | 1 + .../tests/rules/no-unsafe-assignment.test.ts | 17 +++++++- .../tests/rules/no-unsafe-call.test.ts | 9 +++++ .../tests/rules/no-unsafe-return.test.ts | 27 ++++++++++++- 9 files changed, 194 insertions(+), 33 deletions(-) create mode 100644 packages/eslint-plugin/src/util/getThisExpression.ts diff --git a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts index 4a8f2a9ea23..ac815208544 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts @@ -2,8 +2,10 @@ import { TSESTree, AST_NODE_TYPES, } from '@typescript-eslint/experimental-utils'; +import * as tsutils from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; +import { getThisExpression } from '../util'; const enum ComparisonType { /** Do no assignment comparison */ @@ -25,13 +27,17 @@ export default util.createRule({ requiresTypeChecking: true, }, messages: { - anyAssignment: 'Unsafe assignment of an any value.', - unsafeArrayPattern: 'Unsafe array destructuring of an any array value.', + anyAssignment: 'Unsafe assignment of an `any` value.', + anyAssignmentThis: [ + 'Unsafe assignment of an `any` value. `this` is typed as `any`.', + 'You can try to fix this by turning on the `noImplicitThis` compiler option, or adding a `this` parameter to the function.', + ].join('\n'), + unsafeArrayPattern: 'Unsafe array destructuring of an `any` array value.', unsafeArrayPatternFromTuple: - 'Unsafe array destructuring of a tuple element with an any value.', + 'Unsafe array destructuring of a tuple element with an `any` value.', unsafeAssignment: 'Unsafe assignment of type {{sender}} to a variable of type {{receiver}}.', - unsafeArraySpread: 'Unsafe spread of an any value in an array.', + unsafeArraySpread: 'Unsafe spread of an `any` value in an array.', }, schema: [], }, @@ -39,6 +45,11 @@ export default util.createRule({ create(context) { const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context); const checker = program.getTypeChecker(); + const compilerOptions = program.getCompilerOptions(); + const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled( + compilerOptions, + 'noImplicitThis', + ); // returns true if the assignment reported function checkArrayDestructureHelper( @@ -243,9 +254,27 @@ export default util.createRule({ return false; } + let messageId: 'anyAssignment' | 'anyAssignmentThis' = 'anyAssignment'; + + if (!isNoImplicitThis) { + // `var foo = this` + const thisExpression = getThisExpression(senderNode); + if ( + thisExpression && + util.isTypeAnyType( + util.getConstrainedTypeAtLocation( + checker, + esTreeNodeToTSNodeMap.get(thisExpression), + ), + ) + ) { + messageId = 'anyAssignmentThis'; + } + } + context.report({ node: reportingNode, - messageId: 'anyAssignment', + messageId, }); return true; } diff --git a/packages/eslint-plugin/src/rules/no-unsafe-call.ts b/packages/eslint-plugin/src/rules/no-unsafe-call.ts index 4b2d7e7a6ce..b08214d36c2 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-call.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-call.ts @@ -1,9 +1,7 @@ -import { - AST_NODE_TYPES, - TSESTree, -} from '@typescript-eslint/experimental-utils'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; import * as tsutils from 'tsutils'; import * as util from '../util'; +import { getThisExpression } from '../util'; type MessageIds = | 'unsafeCall' @@ -22,9 +20,11 @@ export default util.createRule<[], MessageIds>({ requiresTypeChecking: true, }, messages: { - unsafeCall: 'Unsafe call of an any typed value.', - unsafeCallThis: - 'Unsafe call of `this`, you can try to enable the `noImplicitThis` option.', + unsafeCall: 'Unsafe call of an `any` typed value.', + unsafeCallThis: [ + 'Unsafe call of an `any` typed value. `this` is typed as `any`.', + 'You can try to fix this by turning on the `noImplicitThis` compiler option, or adding a `this` parameter to the function.', + ].join('\n'), unsafeNew: 'Unsafe construction of an any type value.', unsafeTemplateTag: 'Unsafe any typed template tag.', }, @@ -49,12 +49,20 @@ export default util.createRule<[], MessageIds>({ const type = util.getConstrainedTypeAtLocation(checker, tsNode); if (util.isTypeAnyType(type)) { - if ( - !isNoImplicitThis && - node.type === AST_NODE_TYPES.MemberExpression && - node.object.type === AST_NODE_TYPES.ThisExpression - ) { - messageId = 'unsafeCallThis'; + if (!isNoImplicitThis) { + // `this()` or `this.foo()` or `this.foo[bar]()` + const thisExpression = getThisExpression(node); + if ( + thisExpression && + util.isTypeAnyType( + util.getConstrainedTypeAtLocation( + checker, + esTreeNodeToTSNodeMap.get(thisExpression), + ), + ) + ) { + messageId = 'unsafeCallThis'; + } } context.report({ node: reportingNode, diff --git a/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts b/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts index f39bdb0282d..13fd7bf0821 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts @@ -4,6 +4,7 @@ import { } from '@typescript-eslint/experimental-utils'; import * as tsutils from 'tsutils'; import * as util from '../util'; +import { getThisExpression } from '../util'; const enum State { Unsafe = 1, @@ -22,9 +23,11 @@ export default util.createRule({ }, messages: { unsafeMemberExpression: - 'Unsafe member access {{property}} on an any value.', - unsafeThisMemberExpression: - 'Unsafe member access {{property}} on `this`, you can try to enable the `noImplicitThis` option.', + 'Unsafe member access {{property}} on an `any` value.', + unsafeThisMemberExpression: [ + 'Unsafe member access {{property}} on an `any` value. `this` is typed as `any`.', + 'You can try to fix this by turning on the `noImplicitThis` compiler option, or adding a `this` parameter to the function.', + ].join('\n'), unsafeComputedMemberAccess: 'Computed name {{property}} resolves to an any value.', }, @@ -66,13 +69,30 @@ export default util.createRule({ if (state === State.Unsafe) { const propertyName = sourceCode.getText(node.property); + + let messageId: 'unsafeMemberExpression' | 'unsafeThisMemberExpression' = + 'unsafeMemberExpression'; + + if (!isNoImplicitThis) { + // `this.foo` or `this.foo[bar]` + const thisExpression = getThisExpression(node); + + if ( + thisExpression && + util.isTypeAnyType( + util.getConstrainedTypeAtLocation( + checker, + esTreeNodeToTSNodeMap.get(thisExpression), + ), + ) + ) { + messageId = 'unsafeThisMemberExpression'; + } + } + context.report({ node, - messageId: - !isNoImplicitThis && - node.object.type === AST_NODE_TYPES.ThisExpression - ? 'unsafeThisMemberExpression' - : 'unsafeMemberExpression', + messageId, data: { property: node.computed ? `[${propertyName}]` : `.${propertyName}`, }, diff --git a/packages/eslint-plugin/src/rules/no-unsafe-return.ts b/packages/eslint-plugin/src/rules/no-unsafe-return.ts index c2366bc96f2..a818be4ef4e 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-return.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-return.ts @@ -2,8 +2,9 @@ import { TSESTree, AST_NODE_TYPES, } from '@typescript-eslint/experimental-utils'; -import { isExpression } from 'tsutils'; +import * as tsutils from 'tsutils'; import * as util from '../util'; +import { getThisExpression } from '../util'; export default util.createRule({ name: 'no-unsafe-return', @@ -16,9 +17,13 @@ export default util.createRule({ requiresTypeChecking: true, }, messages: { - unsafeReturn: 'Unsafe return of an {{type}} typed value', + unsafeReturn: 'Unsafe return of an `{{type}}` typed value.', + unsafeReturnThis: [ + 'Unsafe return of an `{{type}}` typed value. `this` is typed as `any`.', + 'You can try to fix this by turning on the `noImplicitThis` compiler option, or adding a `this` parameter to the function.', + ].join('\n'), unsafeReturnAssignment: - 'Unsafe return of type {{sender}} from function with return type {{receiver}}.', + 'Unsafe return of type `{{sender}}` from function with return type `{{receiver}}`.', }, schema: [], }, @@ -26,6 +31,11 @@ export default util.createRule({ create(context) { const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context); const checker = program.getTypeChecker(); + const compilerOptions = program.getCompilerOptions(); + const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled( + compilerOptions, + 'noImplicitThis', + ); function getParentFunctionNode( node: TSESTree.Node, @@ -74,7 +84,7 @@ export default util.createRule({ // so we have to use the contextual typing in these cases, i.e. // const foo1: () => Set = () => new Set(); // the return type of the arrow function is Set even though the variable is typed as Set - let functionType = isExpression(functionTSNode) + let functionType = tsutils.isExpression(functionTSNode) ? util.getContextualType(checker, functionTSNode) : checker.getTypeAtLocation(functionTSNode); if (!functionType) { @@ -100,10 +110,28 @@ export default util.createRule({ } } + let messageId: 'unsafeReturn' | 'unsafeReturnThis' = 'unsafeReturn'; + + if (!isNoImplicitThis) { + // `return this` + const thisExpression = getThisExpression(returnNode); + if ( + thisExpression && + util.isTypeAnyType( + util.getConstrainedTypeAtLocation( + checker, + esTreeNodeToTSNodeMap.get(thisExpression), + ), + ) + ) { + messageId = 'unsafeReturnThis'; + } + } + // If the function return type was not unknown/unknown[], mark usage as unsafeReturn. return context.report({ node: reportingNode, - messageId: 'unsafeReturn', + messageId, data: { type: anyType === util.AnyType.Any ? 'any' : 'any[]', }, diff --git a/packages/eslint-plugin/src/util/getThisExpression.ts b/packages/eslint-plugin/src/util/getThisExpression.ts new file mode 100644 index 00000000000..1d3dd9c065e --- /dev/null +++ b/packages/eslint-plugin/src/util/getThisExpression.ts @@ -0,0 +1,26 @@ +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; + +export function getThisExpression( + node: TSESTree.Node, +): TSESTree.ThisExpression | undefined { + while (node) { + if (node.type === AST_NODE_TYPES.CallExpression) { + node = node.callee; + } + + if (node.type === AST_NODE_TYPES.ThisExpression) { + return node; + } + + if ('object' in node) { + node = node.object; + } else { + break; + } + } + + return; +} diff --git a/packages/eslint-plugin/src/util/index.ts b/packages/eslint-plugin/src/util/index.ts index e7bb53547fc..79e142b15fd 100644 --- a/packages/eslint-plugin/src/util/index.ts +++ b/packages/eslint-plugin/src/util/index.ts @@ -4,6 +4,7 @@ export * from './astUtils'; export * from './collectUnusedVariables'; export * from './createRule'; export * from './getFunctionHeadLoc'; +export * from './getThisExpression'; export * from './getWrappingFixer'; export * from './isTypeReadonly'; export * from './misc'; diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-assignment.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-assignment.test.ts index 6f3baa3fa1a..b0f8ec61233 100644 --- a/packages/eslint-plugin/tests/rules/no-unsafe-assignment.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unsafe-assignment.test.ts @@ -68,7 +68,7 @@ function assignmentTest( const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', parserOptions: { - project: './tsconfig.json', + project: './tsconfig.noImplicitThis.json', tsconfigRootDir: getFixturesRootDir(), }, }); @@ -347,5 +347,20 @@ declare function Foo(props: Props): never; }, ], }, + { + code: ` +function foo() { + const bar = this; +} + `, + errors: [ + { + messageId: 'anyAssignmentThis', + line: 3, + column: 9, + endColumn: 19, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts index 31b081c2469..1f70c30e248 100644 --- a/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts @@ -157,6 +157,9 @@ const methods = { methodB() { return true }, + methodC() { + return this() + } }; `, errors: [ @@ -166,6 +169,12 @@ const methods = { column: 12, endColumn: 24, }, + { + messageId: 'unsafeCallThis', + line: 10, + column: 12, + endColumn: 16, + }, ], }, ], diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-return.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-return.test.ts index 7d7777e4663..5cfd965ab8e 100644 --- a/packages/eslint-plugin/tests/rules/no-unsafe-return.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unsafe-return.test.ts @@ -9,7 +9,7 @@ import { const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', parserOptions: { - project: './tsconfig.json', + project: './tsconfig.noImplicitThis.json', tsconfigRootDir: getFixturesRootDir(), }, }); @@ -293,5 +293,30 @@ receiver(function test() { }, ], }, + { + code: ` +function foo() { + return this; +} + +function bar() { + return () => this; +} + `, + errors: [ + { + messageId: 'unsafeReturnThis', + line: 3, + column: 3, + endColumn: 15, + }, + { + messageId: 'unsafeReturnThis', + line: 7, + column: 16, + endColumn: 20, + }, + ], + }, ], }); From cee1fb1c373d3f9e3a59a0dbb30ca7b15eea03e6 Mon Sep 17 00:00:00 2001 From: JounQin Date: Mon, 29 Mar 2021 10:04:09 +0800 Subject: [PATCH 4/4] refactor: only ever handle one node per iteration --- packages/eslint-plugin/src/util/getThisExpression.ts | 10 ++++------ .../tests/rules/no-unsafe-member-access.test.ts | 9 +++++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/eslint-plugin/src/util/getThisExpression.ts b/packages/eslint-plugin/src/util/getThisExpression.ts index 1d3dd9c065e..5e2772aebae 100644 --- a/packages/eslint-plugin/src/util/getThisExpression.ts +++ b/packages/eslint-plugin/src/util/getThisExpression.ts @@ -9,14 +9,12 @@ export function getThisExpression( while (node) { if (node.type === AST_NODE_TYPES.CallExpression) { node = node.callee; - } - - if (node.type === AST_NODE_TYPES.ThisExpression) { + } else if (node.type === AST_NODE_TYPES.ThisExpression) { return node; - } - - if ('object' in node) { + } else if (node.type === AST_NODE_TYPES.MemberExpression) { node = node.object; + } else if (node.type === AST_NODE_TYPES.ChainExpression) { + node = node.expression; } else { break; } diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-member-access.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-member-access.test.ts index d02432080db..491d5e97d9f 100644 --- a/packages/eslint-plugin/tests/rules/no-unsafe-member-access.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unsafe-member-access.test.ts @@ -215,6 +215,9 @@ const methods = { methodC() { return true }, + methodD() { + return (this?.methodA)?.() + } }; `, errors: [ @@ -230,6 +233,12 @@ const methods = { column: 12, endColumn: 31, }, + { + messageId: 'unsafeThisMemberExpression', + line: 14, + column: 13, + endColumn: 26, + }, ], }, ],