Skip to content

⬅️ Back to Table of Contents

πŸ“„ no-unnecessary-type-constraint

πŸ“Š Analysis Summary

Metric Count
πŸ”§ Functions 4
πŸ“¦ Imports 6
πŸ“‘ Type Aliases 1

πŸ“š Table of Contents

πŸ› οΈ File Location:

πŸ“‚ packages/eslint-plugin/src/rules/no-unnecessary-type-constraint.ts

πŸ“€ Default Export

export default createRule({ ... })
Property Value
name 'no-unnecessary-type-constraint'
meta.type 'suggestion'
meta.docs.description 'Disallow unnecessary constraints on generic types'
meta.docs.recommended 'recommended'
meta.hasSuggestions true
meta.messages.removeUnnecessaryConstraint 'Remove the unnecessary {{constraint}} constraint.'
meta.messages.unnecessaryConstraint 'Constraining the generic type {{name}} to {{constraint}} does nothing and is unnecessary.'
meta.schema []
defaultOptions []

Entry point: create β€” documented under Functions.


πŸ“¦ Imports

Name Source
TSESLint @typescript-eslint/utils
TSESTree @typescript-eslint/utils
AST_NODE_TYPES @typescript-eslint/utils
extname node:path
MakeRequired ../util
createRule ../util

Functions

create(context: any): { ':not(ArrowFunctionExpression) > TSTypeParameterDeclarati…

Parameters:

  • context any

Returns: { ':not(ArrowFunctionExpression) > TSTypeParameterDeclaration > TSTypeParameter[constraint]'(node: TypeParameterWithConstraint): void; 'ArrowFunctionExpression > TSTypeParameterDeclaration > TSTypeParameter[constraint]'(node: TypeParameterWithConstraint): void; }

Calls:

  • extname(filename).toLocaleLowerCase
  • checkRequiresGenericDeclarationDisambiguation
  • unnecessaryConstraints.get
  • context.sourceCode.getTokensAfter
  • context.report
  • fixer.replaceTextRange
  • shouldAddTrailingComma
  • checkNode

Internal Comments:

// In theory, we could use the type checker for more advanced constraint types... (x2)
// ...but in practice, these types are rare, and likely not worth requiring type info. (x2)
// https://github.com/typescript-eslint/typescript-eslint/pull/2516#discussion_r495731858 (x2)
// Only <T>() => {} would need trailing comma

Code
create(context) {
    // In theory, we could use the type checker for more advanced constraint types...
    // ...but in practice, these types are rare, and likely not worth requiring type info.
    // https://github.com/typescript-eslint/typescript-eslint/pull/2516#discussion_r495731858
    const unnecessaryConstraints = new Map([
      [AST_NODE_TYPES.TSAnyKeyword, 'any'],
      [AST_NODE_TYPES.TSUnknownKeyword, 'unknown'],
    ]);

    function checkRequiresGenericDeclarationDisambiguation(
      filename: string,
    ): boolean {
      const pathExt = extname(filename).toLocaleLowerCase() as ts.Extension;
      switch (pathExt) {
        case ts.Extension.Cts:
        case ts.Extension.Mts:
        case ts.Extension.Tsx:
          return true;

        default:
          return false;
      }
    }

    const requiresGenericDeclarationDisambiguation =
      checkRequiresGenericDeclarationDisambiguation(context.filename);

    const checkNode = (
      node: TypeParameterWithConstraint,
      inArrowFunction: boolean,
    ): void => {
      const constraint = unnecessaryConstraints.get(node.constraint.type);
      function shouldAddTrailingComma(): boolean {
        if (!inArrowFunction || !requiresGenericDeclarationDisambiguation) {
          return false;
        }
        // Only <T>() => {} would need trailing comma
        return (
          (node.parent as TSESTree.TSTypeParameterDeclaration).params.length ===
            1 &&
          context.sourceCode.getTokensAfter(node)[0].value !== ',' &&
          !node.default
        );
      }

      if (constraint) {
        context.report({
          node,
          messageId: 'unnecessaryConstraint',
          data: {
            name: node.name.name,
            constraint,
          },
          suggest: [
            {
              messageId: 'removeUnnecessaryConstraint',
              data: {
                constraint,
              },
              fix(fixer): TSESLint.RuleFix | null {
                return fixer.replaceTextRange(
                  [node.name.range[1], node.constraint.range[1]],
                  shouldAddTrailingComma() ? ',' : '',
                );
              },
            },
          ],
        });
      }
    };

    return {
      ':not(ArrowFunctionExpression) > TSTypeParameterDeclaration > TSTypeParameter[constraint]'(
        node: TypeParameterWithConstraint,
      ): void {
        checkNode(node, false);
      },
      'ArrowFunctionExpression > TSTypeParameterDeclaration > TSTypeParameter[constraint]'(
        node: TypeParameterWithConstraint,
      ): void {
        checkNode(node, true);
      },
    };
  }

Internal helpers

Declared inside another function in this file.

checkRequiresGenericDeclarationDisambiguation(filename: string): boolean

Parameters:

  • filename string

Returns: boolean

Calls:

  • extname(filename).toLocaleLowerCase
Code
function checkRequiresGenericDeclarationDisambiguation(
      filename: string,
    ): boolean {
      const pathExt = extname(filename).toLocaleLowerCase() as ts.Extension;
      switch (pathExt) {
        case ts.Extension.Cts:
        case ts.Extension.Mts:
        case ts.Extension.Tsx:
          return true;

        default:
          return false;
      }
    }

checkNode(node: TypeParameterWithConstraint, inArrowFunction: boolean): void

Parameters:

  • node TypeParameterWithConstraint
  • inArrowFunction boolean

Returns: void

Calls:

  • unnecessaryConstraints.get
  • context.sourceCode.getTokensAfter
  • context.report
  • fixer.replaceTextRange
  • shouldAddTrailingComma

Internal Comments:

// Only <T>() => {} would need trailing comma

Code
(
      node: TypeParameterWithConstraint,
      inArrowFunction: boolean,
    ): void => {
      const constraint = unnecessaryConstraints.get(node.constraint.type);
      function shouldAddTrailingComma(): boolean {
        if (!inArrowFunction || !requiresGenericDeclarationDisambiguation) {
          return false;
        }
        // Only <T>() => {} would need trailing comma
        return (
          (node.parent as TSESTree.TSTypeParameterDeclaration).params.length ===
            1 &&
          context.sourceCode.getTokensAfter(node)[0].value !== ',' &&
          !node.default
        );
      }

      if (constraint) {
        context.report({
          node,
          messageId: 'unnecessaryConstraint',
          data: {
            name: node.name.name,
            constraint,
          },
          suggest: [
            {
              messageId: 'removeUnnecessaryConstraint',
              data: {
                constraint,
              },
              fix(fixer): TSESLint.RuleFix | null {
                return fixer.replaceTextRange(
                  [node.name.range[1], node.constraint.range[1]],
                  shouldAddTrailingComma() ? ',' : '',
                );
              },
            },
          ],
        });
      }
    }

shouldAddTrailingComma(): boolean

Returns: boolean

Calls:

  • context.sourceCode.getTokensAfter

Internal Comments:

// Only <T>() => {} would need trailing comma

Code
function shouldAddTrailingComma(): boolean {
        if (!inArrowFunction || !requiresGenericDeclarationDisambiguation) {
          return false;
        }
        // Only <T>() => {} would need trailing comma
        return (
          (node.parent as TSESTree.TSTypeParameterDeclaration).params.length ===
            1 &&
          context.sourceCode.getTokensAfter(node)[0].value !== ',' &&
          !node.default
        );
      }

Type Aliases

TypeParameterWithConstraint

type TypeParameterWithConstraint = MakeRequired<
  TSESTree.TSTypeParameter,
  'constraint'
>;

Generated by Syntax Scribe