Skip to content

⬅️ Back to Table of Contents

πŸ“„ no-redeclare

πŸ“Š Analysis Summary

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

πŸ“š Table of Contents

πŸ› οΈ File Location:

πŸ“‚ packages/eslint-plugin/src/rules/no-redeclare.ts

πŸ“€ Default Export

export default createRule<Options, MessageIds>({ ... })
Property Value
name 'no-redeclare'
meta.type 'suggestion'
meta.docs.description 'Disallow variable redeclaration'
meta.docs.extendsBaseRule true
meta.messages.redeclared "'{{id}}' is already defined."
meta.messages.redeclaredAsBuiltin "'{{id}}' is already defined as a built-in global variable."
meta.messages.redeclaredBySyntax "'{{id}}' is already defined by a variable declaration."
meta.schema [ { type: 'object', additionalProperties: false, properties: { builtinGlobals: { type: 'boolean', description: 'Wheth...
defaultOptions [ { builtinGlobals: true, ignoreDeclarationMerge: true, }, ]

Entry point: create β€” documented under Functions.


πŸ“¦ Imports

Name Source
TSESLint @typescript-eslint/utils
TSESTree @typescript-eslint/utils
ScopeType @typescript-eslint/scope-manager
AST_NODE_TYPES @typescript-eslint/utils
createRule ../util
getNameLocationInGlobalDirectiveComment ../util

Functions

create(context: any, [options]: any): { ArrowFunctionExpression: (node: TSESTree.Node) => void; B…

Parameters:

  • context any
  • [options] any

Returns: { ArrowFunctionExpression: (node: TSESTree.Node) => void; BlockStatement: (node: TSESTree.Node) => void; ForInStatement: (node: TSESTree.Node) => void; ForOfStatement: (node: TSESTree.Node) => void; ForStatement: (node: TSESTree.Node) => void; FunctionDeclaration: (node: TSESTree.Node) => void; FunctionExpression: (node: TSESTree.Node) => void; Program(node: any): void; SwitchStatement: (node: TSESTree.Node) => void; }

Calls:

  • getNameLocationInGlobalDirectiveComment (from ../util)
  • variable.identifiers .map(id => ({ identifier: id, parent: id.parent, })) // ignore function declarations because TS will treat them as an overload .filter
  • identifiers.every
  • CLASS_DECLARATION_MERGE_NODES.has
  • identifiers.filter
  • FUNCTION_DECLARATION_MERGE_NODES.has
  • ENUM_DECLARATION_MERGE_NODES.has
  • iterateDeclarations
  • context.report
  • context.sourceCode.getScope
  • findVariablesInScope

Internal Comments:

// interfaces merging (x3)
// namespace/module merging (x3)
// class + interface/namespace merging (x6)
// safe declaration merging (x3)
// there's more than one class declaration, which needs to be reported
// there's more than one function declaration, which needs to be reported
// enum + namespace merging (x3)
// there's more than one enum declaration, which needs to be reported
/*
         * If the type of a declaration is different from the type of
         * the first declaration, it shows the location of the first
         * declaration.
         */ (x2)
// Report extra declarations.
/**
     * Find variables in the current scope.
     */
/*
       * In ES5, some node type such as `BlockStatement` doesn't have that scope.
       * `scope.block` is a different node in such a case.
       */
// Node.js or ES modules has a special scope.
// The special scope's block is the Program node. (x3)

Code
create(context, [options]) {
    const CLASS_DECLARATION_MERGE_NODES = new Set<AST_NODE_TYPES>([
      AST_NODE_TYPES.ClassDeclaration,
      AST_NODE_TYPES.TSInterfaceDeclaration,
      AST_NODE_TYPES.TSModuleDeclaration,
    ]);
    const FUNCTION_DECLARATION_MERGE_NODES = new Set<AST_NODE_TYPES>([
      AST_NODE_TYPES.FunctionDeclaration,
      AST_NODE_TYPES.TSModuleDeclaration,
    ]);
    const ENUM_DECLARATION_MERGE_NODES = new Set<AST_NODE_TYPES>([
      AST_NODE_TYPES.TSEnumDeclaration,
      AST_NODE_TYPES.TSModuleDeclaration,
    ]);

    function* iterateDeclarations(variable: TSESLint.Scope.Variable): Generator<
      {
        loc?: TSESTree.SourceLocation;
        node?: TSESTree.Comment | TSESTree.Identifier;
        type: 'builtin' | 'comment' | 'syntax';
      },
      void
    > {
      if (
        options.builtinGlobals &&
        'eslintImplicitGlobalSetting' in variable &&
        (variable.eslintImplicitGlobalSetting === 'readonly' ||
          variable.eslintImplicitGlobalSetting === 'writable')
      ) {
        yield { type: 'builtin' };
      }

      if (
        'eslintExplicitGlobalComments' in variable &&
        variable.eslintExplicitGlobalComments
      ) {
        for (const comment of variable.eslintExplicitGlobalComments) {
          yield {
            loc: getNameLocationInGlobalDirectiveComment(
              context.sourceCode,
              comment,
              variable.name,
            ),
            node: comment,
            type: 'comment',
          };
        }
      }

      const identifiers = variable.identifiers
        .map(id => ({
          identifier: id,
          parent: id.parent,
        }))
        // ignore function declarations because TS will treat them as an overload
        .filter(
          ({ parent }) => parent.type !== AST_NODE_TYPES.TSDeclareFunction,
        );

      if (options.ignoreDeclarationMerge && identifiers.length > 1) {
        if (
          // interfaces merging
          identifiers.every(
            ({ parent }) =>
              parent.type === AST_NODE_TYPES.TSInterfaceDeclaration,
          )
        ) {
          return;
        }

        if (
          // namespace/module merging
          identifiers.every(
            ({ parent }) => parent.type === AST_NODE_TYPES.TSModuleDeclaration,
          )
        ) {
          return;
        }

        if (
          // class + interface/namespace merging
          identifiers.every(({ parent }) =>
            CLASS_DECLARATION_MERGE_NODES.has(parent.type),
          )
        ) {
          const classDecls = identifiers.filter(
            ({ parent }) => parent.type === AST_NODE_TYPES.ClassDeclaration,
          );
          if (classDecls.length === 1) {
            // safe declaration merging
            return;
          }

          // there's more than one class declaration, which needs to be reported
          for (const { identifier } of classDecls) {
            yield { loc: identifier.loc, node: identifier, type: 'syntax' };
          }
          return;
        }

        if (
          // class + interface/namespace merging
          identifiers.every(({ parent }) =>
            FUNCTION_DECLARATION_MERGE_NODES.has(parent.type),
          )
        ) {
          const functionDecls = identifiers.filter(
            ({ parent }) => parent.type === AST_NODE_TYPES.FunctionDeclaration,
          );
          if (functionDecls.length === 1) {
            // safe declaration merging
            return;
          }

          // there's more than one function declaration, which needs to be reported
          for (const { identifier } of functionDecls) {
            yield { loc: identifier.loc, node: identifier, type: 'syntax' };
          }
          return;
        }

        if (
          // enum + namespace merging
          identifiers.every(({ parent }) =>
            ENUM_DECLARATION_MERGE_NODES.has(parent.type),
          )
        ) {
          const enumDecls = identifiers.filter(
            ({ parent }) => parent.type === AST_NODE_TYPES.TSEnumDeclaration,
          );
          if (enumDecls.length === 1) {
            // safe declaration merging
            return;
          }

          // there's more than one enum declaration, which needs to be reported
          for (const { identifier } of enumDecls) {
            yield { loc: identifier.loc, node: identifier, type: 'syntax' };
          }
          return;
        }
      }

      for (const { identifier } of identifiers) {
        yield { loc: identifier.loc, node: identifier, type: 'syntax' };
      }
    }

    function findVariablesInScope(scope: TSESLint.Scope.Scope): void {
      for (const variable of scope.variables) {
        const [declaration, ...extraDeclarations] =
          iterateDeclarations(variable);

        if (extraDeclarations.length === 0) {
          continue;
        }

        /*
         * If the type of a declaration is different from the type of
         * the first declaration, it shows the location of the first
         * declaration.
         */
        const detailMessageId =
          declaration.type === 'builtin'
            ? 'redeclaredAsBuiltin'
            : 'redeclaredBySyntax';
        const data = { id: variable.name };

        // Report extra declarations.
        for (const { loc, node, type } of extraDeclarations) {
          const messageId =
            type === declaration.type ? 'redeclared' : detailMessageId;

          if (node) {
            context.report({ loc, node, messageId, data });
          } else if (loc) {
            context.report({ loc, messageId, data });
          }
        }
      }
    }

    /**
     * Find variables in the current scope.
     */
    function checkForBlock(node: TSESTree.Node): void {
      const scope = context.sourceCode.getScope(node);

      /*
       * In ES5, some node type such as `BlockStatement` doesn't have that scope.
       * `scope.block` is a different node in such a case.
       */
      if (scope.block === node) {
        findVariablesInScope(scope);
      }
    }

    return {
      ArrowFunctionExpression: checkForBlock,

      BlockStatement: checkForBlock,
      ForInStatement: checkForBlock,
      ForOfStatement: checkForBlock,

      ForStatement: checkForBlock,
      FunctionDeclaration: checkForBlock,
      FunctionExpression: checkForBlock,
      Program(node): void {
        const scope = context.sourceCode.getScope(node);

        findVariablesInScope(scope);

        // Node.js or ES modules has a special scope.
        if (
          scope.type === ScopeType.global &&
          // The special scope's block is the Program node.
          scope.block === scope.childScopes[0]?.block
        ) {
          findVariablesInScope(scope.childScopes[0]);
        }
      },
      SwitchStatement: checkForBlock,
    };
  }

Internal helpers

Declared inside another function in this file.

iterateDeclarations(variable: TSESLint.Scope.Variable): Generator< { loc?: TSESTree.SourceLocation; node?: TSESTree…

Parameters:

  • variable TSESLint.Scope.Variable

Returns: Generator< { loc?: TSESTree.SourceLocation; node?: TSESTree.Comment | TSESTree.Identifier; type: 'builtin' | 'comment' | 'syntax'; }, void >

Calls:

  • getNameLocationInGlobalDirectiveComment (from ../util)
  • variable.identifiers .map(id => ({ identifier: id, parent: id.parent, })) // ignore function declarations because TS will treat them as an overload .filter
  • identifiers.every
  • CLASS_DECLARATION_MERGE_NODES.has
  • identifiers.filter
  • FUNCTION_DECLARATION_MERGE_NODES.has
  • ENUM_DECLARATION_MERGE_NODES.has

Internal Comments:

// interfaces merging (x3)
// namespace/module merging (x3)
// class + interface/namespace merging (x6)
// safe declaration merging (x3)
// there's more than one class declaration, which needs to be reported
// there's more than one function declaration, which needs to be reported
// enum + namespace merging (x3)
// there's more than one enum declaration, which needs to be reported

Code
function* iterateDeclarations(variable: TSESLint.Scope.Variable): Generator<
      {
        loc?: TSESTree.SourceLocation;
        node?: TSESTree.Comment | TSESTree.Identifier;
        type: 'builtin' | 'comment' | 'syntax';
      },
      void
    > {
      if (
        options.builtinGlobals &&
        'eslintImplicitGlobalSetting' in variable &&
        (variable.eslintImplicitGlobalSetting === 'readonly' ||
          variable.eslintImplicitGlobalSetting === 'writable')
      ) {
        yield { type: 'builtin' };
      }

      if (
        'eslintExplicitGlobalComments' in variable &&
        variable.eslintExplicitGlobalComments
      ) {
        for (const comment of variable.eslintExplicitGlobalComments) {
          yield {
            loc: getNameLocationInGlobalDirectiveComment(
              context.sourceCode,
              comment,
              variable.name,
            ),
            node: comment,
            type: 'comment',
          };
        }
      }

      const identifiers = variable.identifiers
        .map(id => ({
          identifier: id,
          parent: id.parent,
        }))
        // ignore function declarations because TS will treat them as an overload
        .filter(
          ({ parent }) => parent.type !== AST_NODE_TYPES.TSDeclareFunction,
        );

      if (options.ignoreDeclarationMerge && identifiers.length > 1) {
        if (
          // interfaces merging
          identifiers.every(
            ({ parent }) =>
              parent.type === AST_NODE_TYPES.TSInterfaceDeclaration,
          )
        ) {
          return;
        }

        if (
          // namespace/module merging
          identifiers.every(
            ({ parent }) => parent.type === AST_NODE_TYPES.TSModuleDeclaration,
          )
        ) {
          return;
        }

        if (
          // class + interface/namespace merging
          identifiers.every(({ parent }) =>
            CLASS_DECLARATION_MERGE_NODES.has(parent.type),
          )
        ) {
          const classDecls = identifiers.filter(
            ({ parent }) => parent.type === AST_NODE_TYPES.ClassDeclaration,
          );
          if (classDecls.length === 1) {
            // safe declaration merging
            return;
          }

          // there's more than one class declaration, which needs to be reported
          for (const { identifier } of classDecls) {
            yield { loc: identifier.loc, node: identifier, type: 'syntax' };
          }
          return;
        }

        if (
          // class + interface/namespace merging
          identifiers.every(({ parent }) =>
            FUNCTION_DECLARATION_MERGE_NODES.has(parent.type),
          )
        ) {
          const functionDecls = identifiers.filter(
            ({ parent }) => parent.type === AST_NODE_TYPES.FunctionDeclaration,
          );
          if (functionDecls.length === 1) {
            // safe declaration merging
            return;
          }

          // there's more than one function declaration, which needs to be reported
          for (const { identifier } of functionDecls) {
            yield { loc: identifier.loc, node: identifier, type: 'syntax' };
          }
          return;
        }

        if (
          // enum + namespace merging
          identifiers.every(({ parent }) =>
            ENUM_DECLARATION_MERGE_NODES.has(parent.type),
          )
        ) {
          const enumDecls = identifiers.filter(
            ({ parent }) => parent.type === AST_NODE_TYPES.TSEnumDeclaration,
          );
          if (enumDecls.length === 1) {
            // safe declaration merging
            return;
          }

          // there's more than one enum declaration, which needs to be reported
          for (const { identifier } of enumDecls) {
            yield { loc: identifier.loc, node: identifier, type: 'syntax' };
          }
          return;
        }
      }

      for (const { identifier } of identifiers) {
        yield { loc: identifier.loc, node: identifier, type: 'syntax' };
      }
    }

findVariablesInScope(scope: TSESLint.Scope.Scope): void

Parameters:

  • scope TSESLint.Scope.Scope

Returns: void

Calls:

  • iterateDeclarations
  • context.report

Internal Comments:

/*
         * If the type of a declaration is different from the type of
         * the first declaration, it shows the location of the first
         * declaration.
         */ (x2)
// Report extra declarations.

Code
function findVariablesInScope(scope: TSESLint.Scope.Scope): void {
      for (const variable of scope.variables) {
        const [declaration, ...extraDeclarations] =
          iterateDeclarations(variable);

        if (extraDeclarations.length === 0) {
          continue;
        }

        /*
         * If the type of a declaration is different from the type of
         * the first declaration, it shows the location of the first
         * declaration.
         */
        const detailMessageId =
          declaration.type === 'builtin'
            ? 'redeclaredAsBuiltin'
            : 'redeclaredBySyntax';
        const data = { id: variable.name };

        // Report extra declarations.
        for (const { loc, node, type } of extraDeclarations) {
          const messageId =
            type === declaration.type ? 'redeclared' : detailMessageId;

          if (node) {
            context.report({ loc, node, messageId, data });
          } else if (loc) {
            context.report({ loc, messageId, data });
          }
        }
      }
    }

checkForBlock(node: TSESTree.Node): void

Find variables in the current scope.

Raw JSDoc
/**
     * Find variables in the current scope.
     */

Calls:

  • context.sourceCode.getScope
  • findVariablesInScope

Internal Comments:

/*
       * In ES5, some node type such as `BlockStatement` doesn't have that scope.
       * `scope.block` is a different node in such a case.
       */

Code
function checkForBlock(node: TSESTree.Node): void {
      const scope = context.sourceCode.getScope(node);

      /*
       * In ES5, some node type such as `BlockStatement` doesn't have that scope.
       * `scope.block` is a different node in such a case.
       */
      if (scope.block === node) {
        findVariablesInScope(scope);
      }
    }

Type Aliases

MessageIds

type MessageIds = 'redeclared' | 'redeclaredAsBuiltin' | 'redeclaredBySyntax';

Options

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

Generated by Syntax Scribe