diff --git a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts index 4a8f2a9ea237..ac8152085449 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 0535bfeab316..b08214d36c27 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-call.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-call.ts @@ -1,7 +1,13 @@ import { TSESTree } from '@typescript-eslint/experimental-utils'; +import * as tsutils from 'tsutils'; import * as util from '../util'; +import { getThisExpression } from '../util'; -type MessageIds = 'unsafeCall' | 'unsafeNew' | 'unsafeTemplateTag'; +type MessageIds = + | 'unsafeCall' + | 'unsafeCallThis' + | 'unsafeNew' + | 'unsafeTemplateTag'; export default util.createRule<[], MessageIds>({ name: 'no-unsafe-call', @@ -14,7 +20,11 @@ export default util.createRule<[], MessageIds>({ requiresTypeChecking: true, }, messages: { - unsafeCall: 'Unsafe call of an any typed value.', + 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.', }, @@ -24,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, @@ -34,6 +49,21 @@ export default util.createRule<[], MessageIds>({ const type = util.getConstrainedTypeAtLocation(checker, tsNode); if (util.isTypeAnyType(type)) { + 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, 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 b326c754136b..13fd7bf0821b 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts @@ -2,7 +2,9 @@ import { TSESTree, AST_NODE_TYPES, } from '@typescript-eslint/experimental-utils'; +import * as tsutils from 'tsutils'; import * as util from '../util'; +import { getThisExpression } from '../util'; const enum State { Unsafe = 1, @@ -21,7 +23,11 @@ export default util.createRule({ }, messages: { unsafeMemberExpression: - 'Unsafe member access {{property}} on an any value.', + '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.', }, @@ -31,6 +37,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(); @@ -58,9 +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: '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 c2366bc96f2e..a818be4ef4ee 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 000000000000..5e2772aebaec --- /dev/null +++ b/packages/eslint-plugin/src/util/getThisExpression.ts @@ -0,0 +1,24 @@ +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; + } else if (node.type === AST_NODE_TYPES.ThisExpression) { + return 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; + } + } + + return; +} diff --git a/packages/eslint-plugin/src/util/index.ts b/packages/eslint-plugin/src/util/index.ts index e7bb53547fc8..79e142b15fd4 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/fixtures/tsconfig.noImplicitThis.json b/packages/eslint-plugin/tests/fixtures/tsconfig.noImplicitThis.json new file mode 100644 index 000000000000..c017e51c6e4c --- /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-assignment.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-assignment.test.ts index 6f3baa3fa1a1..b0f8ec612333 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 981abb0eadde..1f70c30e248c 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,34 @@ function foo(x: { tag: any }) { x.tag\`foo\` } }, ], }), + { + code: noFormat` +const methods = { + methodA() { + return this.methodB() + }, + methodB() { + return true + }, + methodC() { + return this() + } +}; + `, + errors: [ + { + messageId: 'unsafeCallThis', + line: 4, + column: 12, + endColumn: 24, + }, + { + messageId: 'unsafeCallThis', + line: 10, + column: 12, + endColumn: 16, + }, + ], + }, ], }); 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 a9c21f8712b9..491d5e97d9f9 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,44 @@ 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 + }, + methodD() { + return (this?.methodA)?.() + } +}; + `, + errors: [ + { + messageId: 'unsafeThisMemberExpression', + line: 4, + column: 12, + endColumn: 24, + }, + { + messageId: 'unsafeThisMemberExpression', + line: 8, + column: 12, + endColumn: 31, + }, + { + messageId: 'unsafeThisMemberExpression', + line: 14, + column: 13, + endColumn: 26, + }, + ], + }, ], }); 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 7d7777e46632..5cfd965ab8e1 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, + }, + ], + }, ], }); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy