From 1543117874047726a6bc1b71bd2f68779f266591 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Sun, 8 Mar 2020 18:41:15 -0700 Subject: [PATCH] =?UTF-8?q?feat(eslint-plugin):=20[no-unsafe-member-access?= =?UTF-8?q?]=20report=20any=20typed=E2=80=A6=20(#1683)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docs/rules/no-unsafe-member-access.md | 11 +++ .../src/rules/no-unsafe-member-access.ts | 36 ++++++++- .../rules/no-unsafe-member-access.test.ts | 74 +++++++++++++++++++ 3 files changed, 120 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-member-access.md b/packages/eslint-plugin/docs/rules/no-unsafe-member-access.md index e99b2536c16..29c8ea3dd97 100644 --- a/packages/eslint-plugin/docs/rules/no-unsafe-member-access.md +++ b/packages/eslint-plugin/docs/rules/no-unsafe-member-access.md @@ -23,6 +23,11 @@ nestedAny.prop['a']; const key = 'a'; nestedAny.prop[key]; + +// usng an any to access a member is unsafe +const arr = [1, 2, 3]; +arr[anyVar]; +nestedAny[anyVar]; ``` Examples of **correct** code for this rule: @@ -35,6 +40,12 @@ nestedAny.prop['a']; const key = 'a'; nestedAny.prop[key]; + +const arr = [1, 2, 3]; +arr[1]; +const idx = 1; +arr[idx]; +arr[idx++]; ``` ## Related to 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 298cd97561e..3d3b0c7e723 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-member-access.ts @@ -1,4 +1,7 @@ -import { TSESTree } from '@typescript-eslint/experimental-utils'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; const enum State { @@ -19,6 +22,8 @@ export default util.createRule({ messages: { unsafeMemberExpression: 'Unsafe member access {{property}} on an any value', + unsafeComputedMemberAccess: + 'Computed name {{property}} resolves to an any value', }, schema: [], }, @@ -69,6 +74,35 @@ export default util.createRule({ return { 'MemberExpression, OptionalMemberExpression': checkMemberExpression, + ':matches(MemberExpression, OptionalMemberExpression)[computed = true] > *.property'( + node: TSESTree.Expression, + ): void { + if ( + // x[1] + node.type === AST_NODE_TYPES.Literal || + // x[1++] x[++x] etc + // FUN FACT - **all** update expressions return type number, regardless of the argument's type, + // because JS engines return NaN if there the argument is not a number. + node.type === AST_NODE_TYPES.UpdateExpression + ) { + // perf optimizations - literals can obviously never be `any` + return; + } + + const tsNode = esTreeNodeToTSNodeMap.get(node); + const type = checker.getTypeAtLocation(tsNode); + + if (util.isTypeAnyType(type)) { + const propertyName = sourceCode.getText(node); + context.report({ + node, + messageId: 'unsafeComputedMemberAccess', + data: { + property: `[${propertyName}]`, + }, + }); + } + }, }; }, }); 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 641e0492e5a..484d4690c16 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 @@ -15,8 +15,16 @@ const ruleTester = new RuleTester({ ruleTester.run('no-unsafe-member-access', rule, { valid: [ + 'function foo(x: { a: number }, y: any) { x[y++] }', 'function foo(x: { a: number }) { x.a }', 'function foo(x?: { a: number }) { x?.a }', + 'function foo(x: { a: number }) { x["a"] }', + 'function foo(x?: { a: number }) { x?.["a"] }', + 'function foo(x: { a: number }, y: string) { x[y] }', + 'function foo(x?: { a: number }, y: string) { x?.[y] }', + 'function foo(x: string[]) { x[1] }', + 'function foo(x?: string[]) { x?.[1++] }', + 'function foo(x?: string[]) { x?.[(1 as any)++] }', ], invalid: [ ...batchedSingleLineTests({ @@ -81,5 +89,71 @@ function foo(x: any) { x['a']['b']['c'] } }, ], }), + ...batchedSingleLineTests({ + code: ` +function foo(x: { a: number }, y: any) { x[y] } +function foo(x?: { a: number }, y: any) { x?.[y] } +function foo(x: { a: number }, y: any) { x[y += 1] } +function foo(x: { a: number }, y: any) { x[1 as any] } +function foo(x: { a: number }, y: any) { x[y()] } +function foo(x: string[], y: any) { x[y] } + `, + errors: [ + { + messageId: 'unsafeComputedMemberAccess', + data: { + property: '[y]', + }, + line: 2, + column: 44, + endColumn: 45, + }, + { + messageId: 'unsafeComputedMemberAccess', + data: { + property: '[y]', + }, + line: 3, + column: 47, + endColumn: 48, + }, + { + messageId: 'unsafeComputedMemberAccess', + data: { + property: '[y += 1]', + }, + line: 4, + column: 44, + endColumn: 50, + }, + { + messageId: 'unsafeComputedMemberAccess', + data: { + property: '[1 as any]', + }, + line: 5, + column: 44, + endColumn: 52, + }, + { + messageId: 'unsafeComputedMemberAccess', + data: { + property: '[y()]', + }, + line: 6, + column: 44, + endColumn: 47, + }, + { + messageId: 'unsafeComputedMemberAccess', + data: { + property: '[y]', + }, + line: 7, + column: 39, + endColumn: 40, + }, + ], + }), ], });