Skip to content

⬅️ Back to Table of Contents

📄 no-unsafe-member-access

📊 Analysis Summary

Metric Count
🔧 Functions 2
📦 Imports 7
📑 Type Aliases 2
🎯 Enums 1

📚 Table of Contents

🛠️ File Location:

📂 packages/eslint-plugin/src/rules/no-unsafe-member-access.ts

📤 Default Export

export default createRule<Options, MessageIds>({ ... })
Property Value
name 'no-unsafe-member-access'
meta.type 'problem'
meta.docs.description 'Disallow member access on a value with type any'
meta.docs.recommended 'recommended'
meta.docs.requiresTypeChecking true
meta.messages.errorComputedMemberAccess 'The type of computed name {{property}} cannot be resolved.'
meta.messages.errorMemberExpression 'Unsafe member access {{property}} on a type that cannot be resolved.'
meta.messages.errorThisMemberExpression [ 'Unsafe member access {{property}}. The type of this cannot be resolved.', 'You can try to fix this by turning on...
meta.messages.unsafeComputedMemberAccess 'Computed name {{property}} resolves to an any value.'
meta.messages.unsafeMemberExpression 'Unsafe member access {{property}} on an any value.'
meta.messages.unsafeThisMemberExpression [ 'Unsafe member access {{property}} on an any value. this is typed as any.', 'You can try to fix this by turni...
meta.schema [ { type: 'object', additionalProperties: false, properties: { allowOptionalChaining: { type: 'boolean', description:...
defaultOptions [ { allowOptionalChaining: false, }, ]

Entry point: create — documented under Functions.


📦 Imports

Name Source
TSESTree @typescript-eslint/utils
AST_NODE_TYPES @typescript-eslint/utils
createRule ../util
getConstrainedTypeAtLocation ../util
getParserServices ../util
getThisExpression ../util
isTypeAnyType ../util

Functions

create(context: any, [{ allowOptionalChaining }]: any): { 'MemberExpression:not(TSClassImplements MemberExpression,…

Parameters:

  • context any
  • [{ allowOptionalChaining }] any

Returns: { 'MemberExpression:not(TSClassImplements MemberExpression, TSInterfaceHeritage MemberExpression)': (node: TSESTree.MemberExpression) => State; 'MemberExpression[computed = true] > *.property'(node: TSESTree.Expression): void; }

Calls:

  • getParserServices (from ../util)
  • services.program.getCompilerOptions
  • tsutils.isStrictCompilerOptionEnabled
  • stateCache.set
  • stateCache.get
  • checkMemberExpression
  • services.getTypeAtLocation
  • isTypeAnyType (from ../util)
  • context.sourceCode.getText
  • getThisExpression (from ../util)
  • getConstrainedTypeAtLocation (from ../util)
  • tsutils.isIntrinsicErrorType
  • context.report

Internal Comments:

// Case notes:
// value?.outer.middle.inner
// The ChainExpression is a child of the root expression, and a parent of all the MemberExpressions.
// But the left-most expression is what we want to report on: the inner-most expressions.
// In fact, this is true even if the chain is on the inside!
// value.outer.middle?.inner;
// It was already true that every `object` (MemberExpression) has optional: boolean
// if the object is unsafe, we know this will be unsafe as well (x4)
// we don't need to report, as we have already reported on the inner member expr (x4)
// `this.foo` or `this.foo[bar]` (x2)
// ignore MemberExpressions with ancestors of type `TSClassImplements` or `TSInterfaceHeritage` (x2)
// x[1] (x4)
// x[1++] x[++x] etc (x3)
// FUN FACT - **all** update expressions return type number, regardless of the argument's type, (x3)
// because JS engines return NaN if there the argument is not a number. (x3)
// perf optimizations - literals can obviously never be `any`

Code
create(context, [{ allowOptionalChaining }]) {
    const services = getParserServices(context);
    const compilerOptions = services.program.getCompilerOptions();
    const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled(
      compilerOptions,
      'noImplicitThis',
    );

    const stateCache = new Map<TSESTree.Node, State>();

    // Case notes:
    // value?.outer.middle.inner
    // The ChainExpression is a child of the root expression, and a parent of all the MemberExpressions.
    // But the left-most expression is what we want to report on: the inner-most expressions.
    // In fact, this is true even if the chain is on the inside!
    // value.outer.middle?.inner;
    // It was already true that every `object` (MemberExpression) has optional: boolean

    function checkMemberExpression(node: TSESTree.MemberExpression): State {
      if (allowOptionalChaining && node.optional) {
        stateCache.set(node, State.Chained);
        return State.Chained;
      }

      const cachedState = stateCache.get(node);
      if (cachedState) {
        return cachedState;
      }

      if (node.object.type === AST_NODE_TYPES.MemberExpression) {
        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 type = services.getTypeAtLocation(node.object);
      const state = isTypeAnyType(type) ? State.Unsafe : State.Safe;
      stateCache.set(node, state);

      if (state === State.Unsafe) {
        const propertyName = context.sourceCode.getText(node.property);

        let messageId: MessageIds | undefined;

        if (!isNoImplicitThis) {
          // `this.foo` or `this.foo[bar]`
          const thisExpression = getThisExpression(node);
          if (thisExpression) {
            const thisType = getConstrainedTypeAtLocation(
              services,
              thisExpression,
            );
            if (isTypeAnyType(thisType)) {
              messageId = tsutils.isIntrinsicErrorType(thisType)
                ? 'errorThisMemberExpression'
                : 'unsafeThisMemberExpression';
            }
          }
        }

        if (!messageId) {
          messageId = tsutils.isIntrinsicErrorType(type)
            ? 'errorMemberExpression'
            : 'unsafeMemberExpression';
        }

        context.report({
          node: node.property,
          messageId,
          data: {
            property: node.computed ? `[${propertyName}]` : `.${propertyName}`,
          },
        });
      }

      return state;
    }

    return {
      // ignore MemberExpressions with ancestors of type `TSClassImplements` or `TSInterfaceHeritage`
      'MemberExpression:not(TSClassImplements MemberExpression, TSInterfaceHeritage MemberExpression)':
        checkMemberExpression,
      'MemberExpression[computed = true] > *.property'(
        node: TSESTree.Expression,
      ): void {
        if (
          allowOptionalChaining &&
          (node.parent as TSESTree.MemberExpression).optional
        ) {
          return;
        }

        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 type = services.getTypeAtLocation(node);

        if (isTypeAnyType(type)) {
          const propertyName = context.sourceCode.getText(node);
          context.report({
            node,
            messageId: tsutils.isIntrinsicErrorType(type)
              ? 'errorComputedMemberAccess'
              : 'unsafeComputedMemberAccess',
            data: {
              property: `[${propertyName}]`,
            },
          });
        }
      },
    };
  }

Internal helpers

Declared inside another function in this file.

checkMemberExpression(node: TSESTree.MemberExpression): State

Parameters:

  • node TSESTree.MemberExpression

Returns: State

Calls:

  • stateCache.set
  • stateCache.get
  • checkMemberExpression
  • services.getTypeAtLocation
  • isTypeAnyType (from ../util)
  • context.sourceCode.getText
  • getThisExpression (from ../util)
  • getConstrainedTypeAtLocation (from ../util)
  • tsutils.isIntrinsicErrorType
  • context.report

Internal Comments:

// if the object is unsafe, we know this will be unsafe as well (x4)
// we don't need to report, as we have already reported on the inner member expr (x4)
// `this.foo` or `this.foo[bar]` (x2)

Code
function checkMemberExpression(node: TSESTree.MemberExpression): State {
      if (allowOptionalChaining && node.optional) {
        stateCache.set(node, State.Chained);
        return State.Chained;
      }

      const cachedState = stateCache.get(node);
      if (cachedState) {
        return cachedState;
      }

      if (node.object.type === AST_NODE_TYPES.MemberExpression) {
        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 type = services.getTypeAtLocation(node.object);
      const state = isTypeAnyType(type) ? State.Unsafe : State.Safe;
      stateCache.set(node, state);

      if (state === State.Unsafe) {
        const propertyName = context.sourceCode.getText(node.property);

        let messageId: MessageIds | undefined;

        if (!isNoImplicitThis) {
          // `this.foo` or `this.foo[bar]`
          const thisExpression = getThisExpression(node);
          if (thisExpression) {
            const thisType = getConstrainedTypeAtLocation(
              services,
              thisExpression,
            );
            if (isTypeAnyType(thisType)) {
              messageId = tsutils.isIntrinsicErrorType(thisType)
                ? 'errorThisMemberExpression'
                : 'unsafeThisMemberExpression';
            }
          }
        }

        if (!messageId) {
          messageId = tsutils.isIntrinsicErrorType(type)
            ? 'errorMemberExpression'
            : 'unsafeMemberExpression';
        }

        context.report({
          node: node.property,
          messageId,
          data: {
            property: node.computed ? `[${propertyName}]` : `.${propertyName}`,
          },
        });
      }

      return state;
    }

Type Aliases

Options

type Options = [
  {
    allowOptionalChaining?: boolean;
  },
];

MessageIds

type MessageIds = | 'errorComputedMemberAccess'
  | 'errorMemberExpression'
  | 'errorThisMemberExpression'
  | 'unsafeComputedMemberAccess'
  | 'unsafeMemberExpression'
  | 'unsafeThisMemberExpression';

Enums

const enum State

Enum Code
const enum State {
  Unsafe = 1,
  Safe = 2,
  Chained = 3,
}

Members

Name Value Description
Unsafe 1 not shown
Safe 2 not shown
Chained 3 not shown

Generated by Syntax Scribe