();
+
+ function checkMemberExpression(
+ node: TSESTree.MemberExpression | TSESTree.OptionalMemberExpression,
+ ): State {
+ const cachedState = stateCache.get(node);
+ if (cachedState) {
+ return cachedState;
+ }
+
+ if (util.isMemberOrOptionalMemberExpression(node.object)) {
+ const objectState = checkMemberExpression(node.object);
+ if (objectState === State.Unsafe) {
+ // if the object is unsafe, we know this will be unsafe as well
+ // we don't need to report, as we have already reported on the inner member expr
+ stateCache.set(node, objectState);
+ return objectState;
+ }
+ }
+
+ const tsNode = esTreeNodeToTSNodeMap.get(node.object);
+ const type = checker.getTypeAtLocation(tsNode);
+ const state = util.isTypeAnyType(type) ? State.Unsafe : State.Safe;
+ stateCache.set(node, state);
+
+ if (state === State.Unsafe) {
+ const propertyName = sourceCode.getText(node.property);
+ context.report({
+ node,
+ messageId: 'unsafeMemberExpression',
+ data: {
+ property: node.computed ? `[${propertyName}]` : `.${propertyName}`,
+ },
+ });
+ }
+
+ return state;
+ }
+
+ return {
+ 'MemberExpression, OptionalMemberExpression': checkMemberExpression,
+ };
+ },
+});
diff --git a/packages/eslint-plugin/src/util/astUtils.ts b/packages/eslint-plugin/src/util/astUtils.ts
index a92f00eaa168..3dffeb90e01f 100644
--- a/packages/eslint-plugin/src/util/astUtils.ts
+++ b/packages/eslint-plugin/src/util/astUtils.ts
@@ -221,6 +221,15 @@ function isAwaitKeyword(
return node?.type === AST_TOKEN_TYPES.Identifier && node.value === 'await';
}
+function isMemberOrOptionalMemberExpression(
+ node: TSESTree.Node,
+): node is TSESTree.MemberExpression | TSESTree.OptionalMemberExpression {
+ return (
+ node.type === AST_NODE_TYPES.MemberExpression ||
+ node.type === AST_NODE_TYPES.OptionalMemberExpression
+ );
+}
+
export {
isAwaitExpression,
isAwaitKeyword,
@@ -231,6 +240,7 @@ export {
isFunctionType,
isIdentifier,
isLogicalOrOperator,
+ isMemberOrOptionalMemberExpression,
isNonNullAssertionPunctuator,
isNotNonNullAssertionPunctuator,
isNotOptionalChainPunctuator,
diff --git a/packages/eslint-plugin/src/util/types.ts b/packages/eslint-plugin/src/util/types.ts
index eae3519ea214..f59f3d7c53e5 100644
--- a/packages/eslint-plugin/src/util/types.ts
+++ b/packages/eslint-plugin/src/util/types.ts
@@ -290,3 +290,10 @@ export function getEqualsKind(operator: string): EqualsKind | undefined {
return undefined;
}
}
+
+/**
+ * @returns true if the type is `any`
+ */
+export function isTypeAnyType(type: ts.Type): boolean {
+ return isTypeFlagSet(type, ts.TypeFlags.Any);
+}
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
new file mode 100644
index 000000000000..641e0492e5ab
--- /dev/null
+++ b/packages/eslint-plugin/tests/rules/no-unsafe-member-access.test.ts
@@ -0,0 +1,85 @@
+import rule from '../../src/rules/no-unsafe-member-access';
+import {
+ RuleTester,
+ batchedSingleLineTests,
+ getFixturesRootDir,
+} from '../RuleTester';
+
+const ruleTester = new RuleTester({
+ parser: '@typescript-eslint/parser',
+ parserOptions: {
+ project: './tsconfig.json',
+ tsconfigRootDir: getFixturesRootDir(),
+ },
+});
+
+ruleTester.run('no-unsafe-member-access', rule, {
+ valid: [
+ 'function foo(x: { a: number }) { x.a }',
+ 'function foo(x?: { a: number }) { x?.a }',
+ ],
+ invalid: [
+ ...batchedSingleLineTests({
+ code: `
+function foo(x: any) { x.a }
+function foo(x: any) { x.a.b.c.d.e.f.g }
+function foo(x: { a: any }) { x.a.b.c.d.e.f.g }
+ `,
+ errors: [
+ {
+ messageId: 'unsafeMemberExpression',
+ data: {
+ property: '.a',
+ },
+ line: 2,
+ column: 24,
+ endColumn: 27,
+ },
+ {
+ messageId: 'unsafeMemberExpression',
+ data: {
+ property: '.a',
+ },
+ line: 3,
+ column: 24,
+ endColumn: 27,
+ },
+ {
+ messageId: 'unsafeMemberExpression',
+ data: {
+ property: '.b',
+ },
+ line: 4,
+ column: 31,
+ endColumn: 36,
+ },
+ ],
+ }),
+ ...batchedSingleLineTests({
+ code: `
+function foo(x: any) { x['a'] }
+function foo(x: any) { x['a']['b']['c'] }
+ `,
+ errors: [
+ {
+ messageId: 'unsafeMemberExpression',
+ data: {
+ property: "['a']",
+ },
+ line: 2,
+ column: 24,
+ endColumn: 30,
+ },
+ {
+ messageId: 'unsafeMemberExpression',
+ data: {
+ property: "['a']",
+ },
+ line: 3,
+ column: 24,
+ endColumn: 30,
+ },
+ ],
+ }),
+ ],
+});
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