Skip to content

⬅️ Back to Table of Contents

πŸ“„ no-confusing-non-null-assertion

πŸ“Š Analysis Summary

Metric Count
πŸ”§ Functions 4
πŸ“¦ Imports 7
πŸ“Š Variables & Constants 1
πŸ“‘ Type Aliases 2

πŸ“š Table of Contents

πŸ› οΈ File Location:

πŸ“‚ packages/eslint-plugin/src/rules/no-confusing-non-null-assertion.ts

πŸ“€ Default Export

export default createRule<[], MessageId>({ ... })
Property Value
name 'no-confusing-non-null-assertion'
meta.type 'problem'
meta.docs.description 'Disallow non-null assertion in locations that may be confusing'
meta.docs.recommended 'stylistic'
meta.hasSuggestions true
meta.messages.confusingAssign 'Confusing combination of non-null assertion and assignment like a! = b, which looks very similar to a != b.'
meta.messages.confusingEqual 'Confusing combination of non-null assertion and equality test like a! == b, which looks very similar to a !== b.'
meta.messages.confusingOperator 'Confusing combination of non-null assertion and {{operator}} operator like a! {{operator}} b, which might be mis...
meta.messages.notNeedInAssign 'Remove unnecessary non-null assertion (!) in assignment left-hand side.'
meta.messages.notNeedInEqualTest 'Remove unnecessary non-null assertion (!) in equality test.'
meta.messages.notNeedInOperator 'Remove possibly unnecessary non-null assertion (!) in the left operand of the {{operator}} operator.'
meta.messages.wrapUpLeft 'Wrap the left-hand side in parentheses to avoid confusion with "{{operator}}" operator.'
meta.schema []
defaultOptions []

Entry point: create β€” documented under Functions.


πŸ“¦ Imports

Name Source
TSESLint @typescript-eslint/utils
TSESTree @typescript-eslint/utils
ReportDescriptor @typescript-eslint/utils/ts-eslint
RuleFix @typescript-eslint/utils/ts-eslint
AST_NODE_TYPES @typescript-eslint/utils
AST_TOKEN_TYPES @typescript-eslint/utils
createRule ../util

Variables & Constants

Name Type Kind Value Exported
confusingOperators Set<"=" \| "==" \| "===" \| "in" \| "... const new Set([ '=', '==', '===', 'in', 'instanceof', ] as const) βœ—

Functions

create(context: any): { 'BinaryExpression, AssignmentExpression'(node: TSESTree.A…

Parameters:

  • context any

Returns: { 'BinaryExpression, AssignmentExpression'(node: TSESTree.AssignmentExpression | TSESTree.BinaryExpression): void; }

Calls:

  • isConfusingOperator
  • context.sourceCode.getLastToken
  • context.sourceCode.getTokenAfter
  • fixer.remove
  • wrapUpLeftFixer
  • context.report
  • confusingOperatorToMessageData

Internal Comments:

// istanbul ignore next (x2)
// Look for a non-null assertion as the last token on the left hand side. (x2)
// That way, we catch things like `1 + two! === 3`, even though the left (x2)
// hand side isn't a non-null assertion AST node. (x2)

Code
create(context) {
    function confusingOperatorToMessageData(
      operator: ConfusingOperator,
    ): Pick<ReportDescriptor<MessageId>, 'data' | 'messageId'> {
      switch (operator) {
        case '=':
          return {
            messageId: 'confusingAssign',
          };
        case '==':
        case '===':
          return {
            messageId: 'confusingEqual',
          };
        case 'in':
        case 'instanceof':
          return {
            messageId: 'confusingOperator',
            data: { operator },
          };
        // istanbul ignore next
        default:
          operator satisfies never;
          throw new Error(`Unexpected operator ${operator as string}`);
      }
    }

    return {
      'BinaryExpression, AssignmentExpression'(
        node: TSESTree.AssignmentExpression | TSESTree.BinaryExpression,
      ): void {
        const operator = node.operator;

        if (isConfusingOperator(operator)) {
          // Look for a non-null assertion as the last token on the left hand side.
          // That way, we catch things like `1 + two! === 3`, even though the left
          // hand side isn't a non-null assertion AST node.
          const leftHandFinalToken = context.sourceCode.getLastToken(node.left);
          const tokenAfterLeft = context.sourceCode.getTokenAfter(node.left);
          if (
            leftHandFinalToken?.type === AST_TOKEN_TYPES.Punctuator &&
            leftHandFinalToken.value === '!' &&
            tokenAfterLeft?.value !== ')'
          ) {
            if (node.left.type === AST_NODE_TYPES.TSNonNullExpression) {
              let suggestions: TSESLint.SuggestionReportDescriptor<MessageId>[];
              switch (operator) {
                case '=':
                  suggestions = [
                    {
                      messageId: 'notNeedInAssign',
                      fix: (fixer): RuleFix => fixer.remove(leftHandFinalToken),
                    },
                  ];
                  break;

                case '==':
                case '===':
                  suggestions = [
                    {
                      messageId: 'notNeedInEqualTest',
                      fix: (fixer): RuleFix => fixer.remove(leftHandFinalToken),
                    },
                  ];
                  break;

                case 'in':
                case 'instanceof':
                  suggestions = [
                    {
                      messageId: 'notNeedInOperator',
                      data: { operator },
                      fix: (fixer): RuleFix => fixer.remove(leftHandFinalToken),
                    },
                    {
                      messageId: 'wrapUpLeft',
                      data: { operator },
                      fix: wrapUpLeftFixer(node),
                    },
                  ];
                  break;

                // istanbul ignore next
                default:
                  operator satisfies never;
                  return;
              }
              context.report({
                node,
                ...confusingOperatorToMessageData(operator),
                suggest: suggestions,
              });
            } else {
              context.report({
                node,
                ...confusingOperatorToMessageData(operator),
                suggest: [
                  {
                    messageId: 'wrapUpLeft',
                    data: { operator },
                    fix: wrapUpLeftFixer(node),
                  },
                ],
              });
            }
          }
        }
      },
    };
  }

isConfusingOperator(operator: string): operator is ConfusingOperator

Parameters:

  • operator string

Returns: operator is ConfusingOperator

Calls:

  • confusingOperators.has
Code
function isConfusingOperator(operator: string): operator is ConfusingOperator {
  return confusingOperators.has(operator as ConfusingOperator);
}

wrapUpLeftFixer(node: TSESTree.AssignmentExpression | TSESTre…): TSESLint.ReportFixFunction

Parameters:

  • node TSESTree.AssignmentExpression | TSESTree.BinaryExpression

Returns: TSESLint.ReportFixFunction

Calls:

  • fixer.insertTextBefore
  • fixer.insertTextAfter
Code
function wrapUpLeftFixer(
  node: TSESTree.AssignmentExpression | TSESTree.BinaryExpression,
): TSESLint.ReportFixFunction {
  return (fixer): TSESLint.RuleFix[] => [
    fixer.insertTextBefore(node.left, '('),
    fixer.insertTextAfter(node.left, ')'),
  ];
}

Internal helpers

Declared inside another function in this file.

confusingOperatorToMessageData(operator: ConfusingOperator): Pick<ReportDescriptor<MessageId>, 'data' | 'messageId'>

Parameters:

  • operator ConfusingOperator

Returns: Pick<ReportDescriptor<MessageId>, 'data' | 'messageId'>

Internal Comments:

// istanbul ignore next

Code
function confusingOperatorToMessageData(
      operator: ConfusingOperator,
    ): Pick<ReportDescriptor<MessageId>, 'data' | 'messageId'> {
      switch (operator) {
        case '=':
          return {
            messageId: 'confusingAssign',
          };
        case '==':
        case '===':
          return {
            messageId: 'confusingEqual',
          };
        case 'in':
        case 'instanceof':
          return {
            messageId: 'confusingOperator',
            data: { operator },
          };
        // istanbul ignore next
        default:
          operator satisfies never;
          throw new Error(`Unexpected operator ${operator as string}`);
      }
    }

Type Aliases

MessageId

type MessageId = | 'confusingAssign'
  | 'confusingEqual'
  | 'confusingOperator'
  | 'notNeedInAssign'
  | 'notNeedInEqualTest'
  | 'notNeedInOperator'
  | 'wrapUpLeft';

ConfusingOperator

type ConfusingOperator = typeof confusingOperators extends Set<infer T> ? T : never;

Generated by Syntax Scribe