Skip to content

⬅️ Back to Table of Contents

πŸ“„ no-non-null-asserted-nullish-coalescing

πŸ“Š Analysis Summary

Metric Count
πŸ”§ Functions 3
πŸ“¦ Imports 8

πŸ“š Table of Contents

πŸ› οΈ File Location:

πŸ“‚ packages/eslint-plugin/src/rules/no-non-null-asserted-nullish-coalescing.ts

πŸ“€ Default Export

export default createRule({ ... })
Property Value
name 'no-non-null-asserted-nullish-coalescing'
meta.type 'problem'
meta.docs.description 'Disallow non-null assertions in the left operand of a nullish coalescing operator'
meta.docs.recommended 'strict'
meta.hasSuggestions true
meta.messages.noNonNullAssertedNullishCoalescing 'The nullish coalescing operator is designed to handle undefined and null - using a non-null assertion is not needed.'
meta.messages.suggestRemovingNonNull 'Remove the non-null assertion.'
meta.schema []
defaultOptions []

Entry point: create β€” documented under Functions.


πŸ“¦ Imports

Name Source
Definition @typescript-eslint/scope-manager
TSESLint @typescript-eslint/utils
DefinitionType @typescript-eslint/scope-manager
ASTUtils @typescript-eslint/utils
TSESTree @typescript-eslint/utils
createRule ../util
nullThrows ../util
NullThrowsReasons ../util

Functions

create(context: any): { 'LogicalExpression[operator = "??"] > TSNonNullExpression…

Parameters:

  • context any

Returns: { 'LogicalExpression[operator = "??"] > TSNonNullExpression.left'(node: TSESTree.TSNonNullExpression): void; }

Calls:

  • context.sourceCode.getScope
  • ASTUtils.findVariable
  • hasAssignmentBeforeNode
  • context.report
  • nullThrows (from ../util)
  • context.sourceCode.getLastToken
  • NullThrowsReasons.MissingToken
  • fixer.remove

Internal Comments:

/*
          Use a suggestion instead of a fixer, because this can break type checks.
          The resulting type of the nullish coalesce is only influenced by the right operand if the left operand can be `null` or `undefined`.
          After removing the non-null assertion the type of the left operand might contain `null` or `undefined` and then the type of the right operand
          might change the resulting type of the nullish coalesce.
          See the following example:

          function test(x?: string): string {
            const bar = x! ?? false; // type analysis reports `bar` has type `string`
            //          x  ?? false; // type analysis reports `bar` has type `string | false`
            return bar;
          }
          */ (x2)

Code
create(context) {
    return {
      'LogicalExpression[operator = "??"] > TSNonNullExpression.left'(
        node: TSESTree.TSNonNullExpression,
      ): void {
        if (node.expression.type === TSESTree.AST_NODE_TYPES.Identifier) {
          const scope = context.sourceCode.getScope(node);
          const identifier = node.expression;
          const variable = ASTUtils.findVariable(scope, identifier.name);
          if (variable && !hasAssignmentBeforeNode(variable, node)) {
            return;
          }
        }

        context.report({
          node,
          messageId: 'noNonNullAssertedNullishCoalescing',
          /*
          Use a suggestion instead of a fixer, because this can break type checks.
          The resulting type of the nullish coalesce is only influenced by the right operand if the left operand can be `null` or `undefined`.
          After removing the non-null assertion the type of the left operand might contain `null` or `undefined` and then the type of the right operand
          might change the resulting type of the nullish coalesce.
          See the following example:

          function test(x?: string): string {
            const bar = x! ?? false; // type analysis reports `bar` has type `string`
            //          x  ?? false; // type analysis reports `bar` has type `string | false`
            return bar;
          }
          */
          suggest: [
            {
              messageId: 'suggestRemovingNonNull',
              fix(fixer): TSESLint.RuleFix {
                const exclamationMark = nullThrows(
                  context.sourceCode.getLastToken(
                    node,
                    ASTUtils.isNonNullAssertionPunctuator,
                  ),
                  NullThrowsReasons.MissingToken('!', 'Non-null Assertion'),
                );
                return fixer.remove(exclamationMark);
              },
            },
          ],
        });
      },
    };
  }

hasAssignmentBeforeNode(variable: TSESLint.Scope.Variable, node: TSESTree.Node): boolean

Parameters:

  • variable TSESLint.Scope.Variable
  • node TSESTree.Node

Returns: boolean

Calls:

  • variable.references.some
  • ref.isWrite
  • variable.defs.some
  • isDefinitionWithAssignment
Code
function hasAssignmentBeforeNode(
  variable: TSESLint.Scope.Variable,
  node: TSESTree.Node,
): boolean {
  return (
    variable.references.some(
      ref => ref.isWrite() && ref.identifier.range[1] < node.range[1],
    ) ||
    variable.defs.some(
      def =>
        isDefinitionWithAssignment(def) && def.node.range[1] < node.range[1],
    )
  );
}

isDefinitionWithAssignment(definition: Definition): boolean

Parameters:

  • definition Definition

Returns: boolean

Code
function isDefinitionWithAssignment(definition: Definition): boolean {
  if (definition.type !== DefinitionType.Variable) {
    return false;
  }

  const variableDeclarator = definition.node;
  return variableDeclarator.definite || variableDeclarator.init != null;
}

Generated by Syntax Scribe