Skip to content

⬅️ Back to Table of Contents

📄 no-unused-vars

📊 Analysis Summary

Metric Count
🔧 Functions 30
📦 Imports 19
📊 Variables & Constants 3
📐 Interfaces 2
📑 Type Aliases 5
🎯 Enums 1

📚 Table of Contents

🛠️ File Location:

📂 packages/eslint-plugin/src/rules/no-unused-vars.ts

📤 Default Export

export default createRule<Options, MessageIds>({ ... })
Property Value
name 'no-unused-vars'
meta.type 'problem'
meta.docs.description 'Disallow unused variables'
meta.docs.extendsBaseRule true
meta.docs.recommended 'recommended'
meta.fixable 'code'
meta.hasSuggestions true
meta.messages.removeUnusedImportDeclaration 'Remove unused import declaration.'
meta.messages.removeUnusedVar 'Remove unused variable "{{varName}}".'
meta.messages.unusedVar "'{{varName}}' is {{action}} but never used{{additional}}."
meta.messages.usedIgnoredVar "'{{varName}}' is marked as ignored but is used{{additional}}."
meta.messages.usedOnlyAsType "'{{varName}}' is {{action}} but only used as a type{{additional}}."
meta.schema [ { oneOf: [ { type: 'string', description: 'Broad setting for unused variables to target.', enum: ['all', 'local'], ...
defaultOptions [{}]

Entry point: create — documented under Functions.


📦 Imports

Name Source
Definition @typescript-eslint/scope-manager
ImportBindingDefinition @typescript-eslint/scope-manager
ScopeVariable @typescript-eslint/scope-manager
TSESTree @typescript-eslint/utils
DefinitionType @typescript-eslint/scope-manager
PatternVisitor @typescript-eslint/scope-manager
AST_NODE_TYPES @typescript-eslint/utils
AST_TOKEN_TYPES @typescript-eslint/utils
TSESLint @typescript-eslint/utils
MakeRequired ../util
collectVariables ../util
createRule ../util
getNameLocationInGlobalDirectiveComment ../util
isDefinitionFile ../util
isFunction ../util
nullThrows ../util
NullThrowsReasons ../util
referenceContainsTypePredicate ../util/referenceContainsTypePredicate
referenceContainsTypeQuery ../util/referenceContainsTypeQuery

Variables & Constants

Name Type Kind Value Exported
isCommaToken Predicate const { predicate: (token: TSESTree.Token): boolean => token.type === AST_TOKEN_TYP...
isLeftCurlyToken Predicate const { predicate: (token: TSESTree.Token): boolean => token.type === AST_TOKEN_TYP...
isRightCurlyToken Predicate const { predicate: (token: TSESTree.Token): boolean => token.type === AST_TOKEN_TYP...

Functions

create(context: any, [firstOption]: any): { [x: string]: (node: any) => void; 'Program:exit'(programN…

Parameters:

  • context any
  • [firstOption] any

Returns: { [x: string]: (node: any) => void; 'Program:exit'(programNode: any): void; }

Calls:

  • context.sourceCode.getDeclaredVariables(decl).every
  • reportedUnusedVariables.has
  • reportedUnusedVariables.add
  • unusedVar.references.filter
  • ref.isWrite
  • complex_call_9876
  • complex_call_9932
  • defToVariableType
  • getImportFixer
  • context.report
  • complex_call_13729
  • sourceCode.getIndexFromLoc
  • sourceCode.getText
  • sourceCode.text.slice(lineRangeStart, lineRangeEnd).trim
  • fixer.removeRange
  • fixer.remove
  • removeNodeWithTrailingNewline
  • areAllSpecifiersUnused
  • nullThrows (from ../util)
  • context.sourceCode.getTokenAfter
  • NullThrowsReasons.MissingToken
  • assertToken
  • Math.min
  • Math.max
  • context.sourceCode .getDeclaredVariables(importDecl) .map(variable => { if (reportedUnusedVariables.has(variable)) { return null; } const specifier = variable.defs[0].node; if (specifier.type !== AST_NODE_TYPES.ImportSpecifier) { return null; } return specifier; }) .filter
  • context.sourceCode.getFirstToken
  • context.sourceCode.getTokenBefore
  • isCommaToken.predicate
  • options.destructuredArrayIgnorePattern?.toString
  • options.caughtErrorsIgnorePattern?.toString
  • options.argsIgnorePattern?.toString
  • options.varsIgnorePattern?.toString
  • unusedVar.defs.at
  • getVariableDescription
  • variable.defs.some
  • hasRestSibling
  • variable.references.some
  • context.sourceCode.getDeclaredVariables
  • params.slice
  • params.indexOf
  • posteriorParams.some
  • collectVariables (from ../util)
  • Array.from
  • unusedVariablesReturn.push
  • options.destructuredArrayIgnorePattern?.test
  • report
  • getUsedIgnoredMessageData
  • def.node.body.body.some
  • options.caughtErrorsIgnorePattern?.test
  • options.argsIgnorePattern?.test
  • isFunction (from ../util)
  • isAfterLastUsedArg
  • options.varsIgnorePattern?.test
  • hasRestSpreadSibling
  • ambientDeclarationSelector
  • isDefinitionFile (from ../util)
  • checkForOverridingExportStatements
  • markDeclarationChildAsUsed
  • collectUnusedVariables
  • unusedVar.references.some
  • referenceContainsTypeQuery (from ../util/referenceContainsTypeQuery)
  • referenceContainsTypePredicate (from ../util/referenceContainsTypePredicate)
  • unusedVar.defs.some
  • getAssignedMessageData
  • getDefinedMessageData
  • getNameLocationInGlobalDirectiveComment (from ../util)
  • MODULE_DECL_CACHE.get
  • getStatementsOfNode
  • hasOverridingExportStatement
  • MODULE_DECL_CACHE.set
  • [ // Types are ambiently exported${parent} > :matches(${[ AST_NODE_TYPES.TSInterfaceDeclaration, AST_NODE_TYPES.TSTypeAliasDeclaration, ].join(', ')}), // Value things are ambiently exported if they are "declare"d${parent} > :matches(${[ AST_NODE_TYPES.ClassDeclaration, AST_NODE_TYPES.TSDeclareFunction, AST_NODE_TYPES.TSEnumDeclaration, AST_NODE_TYPES.TSModuleDeclaration, AST_NODE_TYPES.VariableDeclaration, ].join(', ')}), ].join
  • [ AST_NODE_TYPES.TSInterfaceDeclaration, AST_NODE_TYPES.TSTypeAliasDeclaration, ].join
  • [ AST_NODE_TYPES.ClassDeclaration, AST_NODE_TYPES.TSDeclareFunction, AST_NODE_TYPES.TSEnumDeclaration, AST_NODE_TYPES.TSModuleDeclaration, AST_NODE_TYPES.VariableDeclaration, ].join
  • identifiers.push
  • visitPattern
  • context.sourceCode.getScope
  • [ AST_NODE_TYPES.TSDeclareFunction, AST_NODE_TYPES.TSModuleDeclaration, ].includes
  • scope.set.get
  • visitor.visit

Internal Comments:

// If there's multiple definitions then we'd have to clean them all
// up! That's complicated and messy so for now let's just ignore it.
// TODO(bradzacher) -- this would be really easy to implement and (x5)
// is side-effect free! (x5)
// This would be easy to implement -- but classes can have
// side-effects in static initializers / static blocks. So it's
// dangerous to ever auto-fix remove them.
// (x30)
// Perhaps as an always-suggestion fixer...? (x3)
// We don't report these via this code path, so no fixer is possible
// This is easy to implement -- but we cannot implement it cos it
// changes the signature of the function which in turn might
// introduce type errors in consumers.
// Also parameters can have default values which might have
// side-effects.
// We don't report unused enum members so no fixer is ever possible
// This is easy to implement -- but TS namespaces are eagerly
// initialized -- meaning that they might have side-effects in
// the body. So it's dangerous to ever auto-fix remove them.
// TODO(bradzacher) -- this would be really easy to implement
// eslint-disable-next-line unicorn/no-lonely-if -- will add more cases later
// Expand range: start of first line to start of next line (or EOF) (x2)
// If node is the only non-whitespace on its line(s), remove full line(s)
// import equals declarations can only have one binding -- so we can
// just remove entire import declaration
// all specifiers are unused -- so we can just remove entire import (x2)
// declaration (x2)
// in this branch we know the following things: (x3)
// 1) there is at least one specifier that is used (x3)
// 2) the default specifier is unused
// by process of elimination we can deduce that there is at least one
// named specifier that is used
// i.e. the code must be import Unused, { Used, ... } from 'module';
// there's one or more unused named specifiers, so we must remove the
// default specifier in isolation including the trailing comma.
//     import Unused, { Used, ... } from 'module';
//            ^^^^^^^ remove this
// NOTE: we could also remove the spaces between the comma and the
// opening curly brace -- but this does risk removing comments. To be
// safe we'll be conservative for now
// TODO(bradzacher) -- consider removing the extra space whilst also
//                     preserving comments.
// guaranteed to NOT be in an export statement as we're inspecting an (x2)
// import (x2)
// 2) all named specifiers are unused (x2)
// by process of elimination we can deduce that there is a (x2)
// default specifier and it is the only used specifier (x2)
// i.e. the code must be import Used, { Unused, ... } from (x2)
//     'module'; (x2)
// So we can just remove the entire curly content and the comma (x2)
// before, eg import Used, { Unused, ... } from 'module'; (x2)
// ^^^^^^^^^^^^^^^^^ remove this (x2)
// in this branch we know there is at least one used named (x2)
// specifier which means we have to remove each unused specifier (x2)
// in isolation. (x2)
// there's 3 possible cases to care about: import { Unused, (x2)
//    Used... } from 'module'; import { ...Used, Unused } from (x2)
//    'module'; import { ...Used, Unused, } from 'module'; (x2)
// Note that because of the above usedNamedSpecifiers check we (x2)
// know that we don't have one of these cases: import { Unused } (x2)
// from 'module'; import { Unused, Unused... } from 'module'; (x2)
// import { ...Unused, Unused, } from 'module'; (x2)
// The result is that we know that there _must_ be a comma that (x2)
// needs cleaning up (x2)
// try to remove the leading comma first as it leads to a nicer (x2)
// fix output in most cases (x2)
// leading preferred: import { Used, Unused, Used } from 'module'; (x2)
//   ^^^^^^^^ remove import { Used, Used } from 'module'; (x2)
// trailing preferred: import { Used, Unused, Used } from (x2)
//   'module'; ^^^^^^^ remove import { Used,  Used } from (x2)
//   'module'; ^^ ugly double space (x2)
// But we need to still fallback to the trailing comma for cases (x2)
// where the unused specifier is the first in the import eg: (x2)
// import { Unused, Used } from 'module'; (x2)
// namespace specifiers cannot be used with any other specifier -- so (x2)
// we can just remove entire import declaration (x2)
/**
     * Determines what variable type a def is.
     * @param def the declaration to check
     * @returns a simple name for the types of variables that this rule supports
     */
/*
       * This `destructuredArrayIgnorePattern` error report works differently from the catch
       * clause and parameter error reports. _Both_ the `varsIgnorePattern` and the
       * `destructuredArrayIgnorePattern` will be checked for array destructuring. However,
       * for the purposes of the report, the currently defined behavior is to only inform the
       * user of the `destructuredArrayIgnorePattern` if it's present (regardless of the fact
       * that the `varsIgnorePattern` would also apply). If it's not present, the user will be
       * informed of the `varsIgnorePattern`, assuming that's present.
       */
/**
     * Gets a given variable's description and configured ignore pattern
     * based on the provided variableType
     * @param variableType a simple name for the types of variables that this rule supports
     * @returns the given variable's description and
     * ignore pattern
     */
/**
     * Generates the message data about the variable being defined and unused,
     * including the ignore pattern if configured.
     * @param unusedVar eslint-scope variable object.
     * @returns The message data to be used with this unused variable.
     */
/**
     * Generate the warning message about the variable being
     * assigned and unused, including the ignore pattern if configured.
     * @param unusedVar eslint-scope variable object.
     * @returns The message data to be used with this unused variable.
     */
/**
     * Generate the warning message about a variable being used even though
     * it is marked as being ignored.
     * @param variable eslint-scope variable object
     * @param variableType a simple name for the types of variables that this rule supports
     * @returns The message data to be used with this used ignored variable.
     */
/**
       * Checks whether a node is a sibling of the rest property or not.
       * @param node a node to check
       * @returns True if the node is a sibling of the rest property, otherwise false.
       */
/**
       * Determines if a variable has a sibling rest property
       * @param variable eslint-scope variable object.
       * @returns True if the variable is exported, false if not.
       */
/**
       * Checks whether the given variable is after the last used parameter.
       * @param variable The variable to check.
       * @returns `true` if the variable is defined after the last used parameter.
       */
// If any used parameters occur after this parameter, do not report.
// explicit global variables don't have definitions.
// skip variables in the global scope if configured to
// skip elements of array destructuring patterns
// skip catch variables
// skip ignored parameters (x2)
// if "args" option is "none", skip any parameter
// if "args" option is "after-used", skip used variables
/* enum members are always marked as 'used' by `collectVariables`, but in reality they may be used or
               unused. either way, don't complain about their naming. */ (x3)
// in case another rule has run and used the collectUnusedVariables,
// we want to ensure our selectors that marked variables as used are respected
// top-level declaration file handling (x2)
// children of a namespace that is a child of a declared namespace are auto-exported (x2)
// declared namespace handling (x2)
// namespace handling in definition files (x2)
// collect (x2)
// Report the first declaration.
// Types are ambiently exported (x2)
// Value things are ambiently exported if they are "declare"d (x2)

Code
create(context, [firstOption]) {
    const MODULE_DECL_CACHE = new Map<
      ModuleDeclarationWithBody | TSESTree.Program,
      boolean
    >();
    const reportedUnusedVariables = new Set<ScopeVariable>();
    function areAllSpecifiersUnused(decl: TSESTree.ImportDeclaration): boolean {
      return context.sourceCode.getDeclaredVariables(decl).every(variable => {
        return reportedUnusedVariables.has(variable);
      });
    }

    const report = (
      unusedVar: ScopeVariable,
      opts: {
        data: TSESLint.ReportDescriptorMessageData;
        messageId: MessageIds;
        node?: TSESTree.Node;
      },
    ) => {
      reportedUnusedVariables.add(unusedVar);

      const writeReferences = unusedVar.references.filter(
        ref =>
          ref.isWrite() &&
          ref.from.variableScope === unusedVar.scope.variableScope,
      );

      const id = writeReferences.length
        ? writeReferences[writeReferences.length - 1].identifier
        : unusedVar.identifiers[0];

      const { start } = id.loc;
      const idLength = id.name.length;

      const loc = {
        start,
        end: {
          column: start.column + idLength,
          line: start.line,
        },
      };

      const fixer = (() => {
        const { messageId, fix, useAutofix } = ((): {
          fix?: TSESLint.ReportFixFunction;
          messageId?: MessageIds;
          useAutofix?: boolean;
        } => {
          if (unusedVar.defs.length !== 1) {
            // If there's multiple definitions then we'd have to clean them all
            // up! That's complicated and messy so for now let's just ignore it.
            return {};
          }

          const { type, def } = defToVariableType(unusedVar.defs[0]);
          switch (type) {
            case VariableType.ArrayDestructure:
              // TODO(bradzacher) -- this would be really easy to implement and
              // is side-effect free!
              return {};

            case VariableType.CatchClause:
              // TODO(bradzacher) -- this would be really easy to implement and
              // is side-effect free!
              return {};

            case VariableType.ClassName:
              // This would be easy to implement -- but classes can have
              // side-effects in static initializers / static blocks. So it's
              // dangerous to ever auto-fix remove them.
              //
              // Perhaps as an always-suggestion fixer...?
              return {};

            case VariableType.FunctionName:
              // TODO(bradzacher) -- this would be really easy to implement and
              // is side-effect free!
              return {};

            case VariableType.ImportBinding:
              return {
                ...getImportFixer(def),
                useAutofix: options.enableAutofixRemoval.imports,
              };

            case VariableType.ImplicitGlobalVariable:
              // We don't report these via this code path, so no fixer is possible
              return {};

            case VariableType.Parameter:
              // This is easy to implement -- but we cannot implement it cos it
              // changes the signature of the function which in turn might
              // introduce type errors in consumers.
              //
              // Also parameters can have default values which might have
              // side-effects.
              //
              // Perhaps as an always-suggestion fixer...?
              return {};

            case VariableType.TSEnumMember:
              // We don't report unused enum members so no fixer is ever possible
              return {};

            case VariableType.TSEnumName:
              // TODO(bradzacher) -- this would be really easy to implement and
              // is side-effect free!
              return {};

            case VariableType.TSModuleName:
              // This is easy to implement -- but TS namespaces are eagerly
              // initialized -- meaning that they might have side-effects in
              // the body. So it's dangerous to ever auto-fix remove them.
              //
              // Perhaps as an always-suggestion fixer...?
              return {};

            case VariableType.Type:
              // TODO(bradzacher) -- this would be really easy to implement and
              // is side-effect free!
              return {};

            case VariableType.Variable:
              // TODO(bradzacher) -- this would be really easy to implement
              return {};
          }
        })();

        if (!fix) {
          return {};
        }

        if (useAutofix) {
          return { fix };
        }

        const data = {
          varName: unusedVar.name,
        };
        return {
          suggest: [
            {
              messageId: messageId ?? ('removeUnusedVar' as const),
              data,
              fix,
            },
          ],
        };
      })();
      context.report({
        ...opts,
        ...fixer,
        loc,
        node: id,
      });
    };

    const options = ((): TranslatedOptions => {
      const options: TranslatedOptions = {
        args: 'after-used',
        caughtErrors: 'all',
        enableAutofixRemoval: {
          imports: false,
        },
        ignoreClassWithStaticInitBlock: false,
        ignoreRestSiblings: false,
        ignoreUsingDeclarations: false,
        reportUsedIgnorePattern: false,
        vars: 'all',
      };

      if (typeof firstOption === 'string') {
        options.vars = firstOption;
      } else {
        options.vars = firstOption.vars ?? options.vars;
        options.args = firstOption.args ?? options.args;
        options.ignoreRestSiblings =
          firstOption.ignoreRestSiblings ?? options.ignoreRestSiblings;
        options.ignoreUsingDeclarations =
          firstOption.ignoreUsingDeclarations ??
          options.ignoreUsingDeclarations;
        options.caughtErrors = firstOption.caughtErrors ?? options.caughtErrors;
        options.ignoreClassWithStaticInitBlock =
          firstOption.ignoreClassWithStaticInitBlock ??
          options.ignoreClassWithStaticInitBlock;
        options.reportUsedIgnorePattern =
          firstOption.reportUsedIgnorePattern ??
          options.reportUsedIgnorePattern;

        if (firstOption.varsIgnorePattern) {
          options.varsIgnorePattern = new RegExp(
            firstOption.varsIgnorePattern,
            'u',
          );
        }

        if (firstOption.argsIgnorePattern) {
          options.argsIgnorePattern = new RegExp(
            firstOption.argsIgnorePattern,
            'u',
          );
        }

        if (firstOption.caughtErrorsIgnorePattern) {
          options.caughtErrorsIgnorePattern = new RegExp(
            firstOption.caughtErrorsIgnorePattern,
            'u',
          );
        }

        if (firstOption.destructuredArrayIgnorePattern) {
          options.destructuredArrayIgnorePattern = new RegExp(
            firstOption.destructuredArrayIgnorePattern,
            'u',
          );
        }

        if (firstOption.enableAutofixRemoval) {
          // eslint-disable-next-line unicorn/no-lonely-if -- will add more cases later
          if (firstOption.enableAutofixRemoval.imports != null) {
            options.enableAutofixRemoval.imports =
              firstOption.enableAutofixRemoval.imports;
          }
        }
      }

      return options;
    })();

    function removeNodeWithTrailingNewline(
      fixer: TSESLint.RuleFixer,
      node: TSESTree.Node,
    ): TSESLint.RuleFix {
      const sourceCode = context.sourceCode;
      const { line: startLine } = node.loc.start;
      const { line: endLine } = node.loc.end;
      // Expand range: start of first line to start of next line (or EOF)
      const lineRangeStart = sourceCode.getIndexFromLoc({
        column: 0,
        line: startLine,
      });
      const lineRangeEnd =
        endLine < sourceCode.lines.length
          ? sourceCode.getIndexFromLoc({ column: 0, line: endLine + 1 })
          : sourceCode.text.length;
      // If node is the only non-whitespace on its line(s), remove full line(s)
      if (
        sourceCode.getText(node) ===
        sourceCode.text.slice(lineRangeStart, lineRangeEnd).trim()
      ) {
        return fixer.removeRange([lineRangeStart, lineRangeEnd]);
      }
      return fixer.remove(node);
    }

    function getImportFixer(def: ImportBindingDefinition): {
      fix: TSESLint.ReportFixFunction;
      messageId: MessageIds;
    } {
      switch (def.node.type) {
        case AST_NODE_TYPES.TSImportEqualsDeclaration:
          // import equals declarations can only have one binding -- so we can
          // just remove entire import declaration
          return {
            messageId: 'removeUnusedImportDeclaration',
            fix: fixer => removeNodeWithTrailingNewline(fixer, def.node),
          };

        case AST_NODE_TYPES.ImportDefaultSpecifier: {
          const importDecl = def.node.parent;
          if (
            importDecl.specifiers.length === 1 ||
            areAllSpecifiersUnused(importDecl)
          ) {
            // all specifiers are unused -- so we can just remove entire import
            // declaration
            return {
              messageId: 'removeUnusedImportDeclaration',
              fix: fixer => removeNodeWithTrailingNewline(fixer, importDecl),
            };
          }

          // in this branch we know the following things:
          // 1) there is at least one specifier that is used
          // 2) the default specifier is unused
          //
          // by process of elimination we can deduce that there is at least one
          // named specifier that is used
          //
          // i.e. the code must be import Unused, { Used, ... } from 'module';
          //
          // there's one or more unused named specifiers, so we must remove the
          // default specifier in isolation including the trailing comma.
          //
          //     import Unused, { Used, ... } from 'module';
          //            ^^^^^^^ remove this
          //
          // NOTE: we could also remove the spaces between the comma and the
          // opening curly brace -- but this does risk removing comments. To be
          // safe we'll be conservative for now
          //
          // TODO(bradzacher) -- consider removing the extra space whilst also
          //                     preserving comments.
          return {
            messageId: 'removeUnusedVar',
            fix: fixer => {
              const comma = nullThrows(
                context.sourceCode.getTokenAfter(def.node),
                NullThrowsReasons.MissingToken(',', 'import specifier'),
              );
              assertToken(isCommaToken, comma);

              return fixer.removeRange([
                Math.min(def.node.range[0], comma.range[0]),
                Math.max(def.node.range[1], comma.range[1]),
              ]);
            },
          };
        }

        case AST_NODE_TYPES.ImportSpecifier: {
          // guaranteed to NOT be in an export statement as we're inspecting an
          // import
          const importDecl = def.node.parent as Exclude<
            typeof def.node.parent,
            TSESTree.ExportAllDeclaration | TSESTree.ExportNamedDeclaration
          >;
          if (
            importDecl.specifiers.length === 1 ||
            areAllSpecifiersUnused(importDecl)
          ) {
            // all specifiers are unused -- so we can just remove entire import
            // declaration
            return {
              messageId: 'removeUnusedImportDeclaration',
              fix: fixer => removeNodeWithTrailingNewline(fixer, importDecl),
            };
          }

          return {
            messageId: 'removeUnusedVar',
            fix: fixer => {
              const usedNamedSpecifiers = context.sourceCode
                .getDeclaredVariables(importDecl)
                .map(variable => {
                  if (reportedUnusedVariables.has(variable)) {
                    return null;
                  }
                  const specifier = variable.defs[0].node;
                  if (specifier.type !== AST_NODE_TYPES.ImportSpecifier) {
                    return null;
                  }
                  return specifier;
                })
                .filter(v => v != null);

              if (usedNamedSpecifiers.length === 0) {
                // in this branch we know the following things:
                // 1) there is at least one specifier that is used
                // 2) all named specifiers are unused
                //
                // by process of elimination we can deduce that there is a
                // default specifier and it is the only used specifier
                //
                // i.e. the code must be import Used, { Unused, ... } from
                //     'module';
                //
                // So we can just remove the entire curly content and the comma
                // before, eg import Used, { Unused, ... } from 'module';
                // ^^^^^^^^^^^^^^^^^ remove this

                const leftCurly = assertToken(
                  isLeftCurlyToken,
                  context.sourceCode.getFirstToken(
                    importDecl,
                    isLeftCurlyToken.predicate,
                  ),
                );
                const leftToken = assertToken(
                  isCommaToken,
                  context.sourceCode.getTokenBefore(leftCurly),
                );

                const rightToken = assertToken(
                  isRightCurlyToken,
                  context.sourceCode.getFirstToken(
                    importDecl,
                    isRightCurlyToken.predicate,
                  ),
                );
                return fixer.removeRange([
                  leftToken.range[0],
                  rightToken.range[1],
                ]);
              }

              // in this branch we know there is at least one used named
              // specifier which means we have to remove each unused specifier
              // in isolation.
              //
              // there's 3 possible cases to care about: import { Unused,
              //    Used... } from 'module'; import { ...Used, Unused } from
              //    'module'; import { ...Used, Unused, } from 'module';
              //
              // Note that because of the above usedNamedSpecifiers check we
              // know that we don't have one of these cases: import { Unused }
              // from 'module'; import { Unused, Unused... } from 'module';
              // import { ...Unused, Unused, } from 'module';
              //
              // The result is that we know that there _must_ be a comma that
              // needs cleaning up
              //
              // try to remove the leading comma first as it leads to a nicer
              // fix output in most cases
              //
              // leading preferred: import { Used, Unused, Used } from 'module';
              //   ^^^^^^^^ remove import { Used, Used } from 'module';
              //
              // trailing preferred: import { Used, Unused, Used } from
              //   'module'; ^^^^^^^ remove import { Used,  Used } from
              //   'module'; ^^ ugly double space
              //
              // But we need to still fallback to the trailing comma for cases
              // where the unused specifier is the first in the import eg:
              // import { Unused, Used } from 'module';
              const maybeComma = context.sourceCode.getTokenBefore(def.node);
              const comma =
                maybeComma && isCommaToken.predicate(maybeComma)
                  ? maybeComma
                  : assertToken(
                      isCommaToken,
                      context.sourceCode.getTokenAfter(def.node),
                    );
              return fixer.removeRange([
                Math.min(def.node.range[0], comma.range[0]),
                Math.max(def.node.range[1], comma.range[1]),
              ]);
            },
          };
        }

        case AST_NODE_TYPES.ImportNamespaceSpecifier: {
          // namespace specifiers cannot be used with any other specifier -- so
          // we can just remove entire import declaration
          const importDecl = def.node.parent;
          return {
            messageId: 'removeUnusedImportDeclaration',
            fix: fixer => removeNodeWithTrailingNewline(fixer, importDecl),
          };
        }
      }
    }

    /**
     * Determines what variable type a def is.
     * @param def the declaration to check
     * @returns a simple name for the types of variables that this rule supports
     */
    function defToVariableType(def: Definition): DefToVariableType {
      /*
       * This `destructuredArrayIgnorePattern` error report works differently from the catch
       * clause and parameter error reports. _Both_ the `varsIgnorePattern` and the
       * `destructuredArrayIgnorePattern` will be checked for array destructuring. However,
       * for the purposes of the report, the currently defined behavior is to only inform the
       * user of the `destructuredArrayIgnorePattern` if it's present (regardless of the fact
       * that the `varsIgnorePattern` would also apply). If it's not present, the user will be
       * informed of the `varsIgnorePattern`, assuming that's present.
       */
      if (
        options.destructuredArrayIgnorePattern &&
        def.name.parent.type === AST_NODE_TYPES.ArrayPattern
      ) {
        return { type: VariableType.ArrayDestructure, def };
      }

      switch (def.type) {
        case DefinitionType.CatchClause:
          return { type: VariableType.CatchClause, def };
        case DefinitionType.ClassName:
          return { type: VariableType.ClassName, def };
        case DefinitionType.FunctionName:
          return { type: VariableType.FunctionName, def };
        case DefinitionType.ImplicitGlobalVariable:
          return { type: VariableType.ImplicitGlobalVariable, def };
        case DefinitionType.ImportBinding:
          return { type: VariableType.ImportBinding, def };
        case DefinitionType.Parameter:
          return { type: VariableType.Parameter, def };
        case DefinitionType.TSEnumName:
          return { type: VariableType.TSEnumName, def };
        case DefinitionType.TSEnumMember:
          return { type: VariableType.TSEnumMember, def };
        case DefinitionType.TSModuleName:
          return { type: VariableType.TSModuleName, def };
        case DefinitionType.Type:
          return { type: VariableType.Type, def };
        case DefinitionType.Variable:
          return { type: VariableType.Variable, def };
      }
    }

    /**
     * Gets a given variable's description and configured ignore pattern
     * based on the provided variableType
     * @param variableType a simple name for the types of variables that this rule supports
     * @returns the given variable's description and
     * ignore pattern
     */
    function getVariableDescription(variableType: VariableType): {
      pattern: string | undefined;
      variableDescription: string;
    } {
      switch (variableType) {
        case VariableType.ArrayDestructure:
          return {
            pattern: options.destructuredArrayIgnorePattern?.toString(),
            variableDescription: 'elements of array destructuring',
          };

        case VariableType.CatchClause:
          return {
            pattern: options.caughtErrorsIgnorePattern?.toString(),
            variableDescription: 'caught errors',
          };

        case VariableType.Parameter:
          return {
            pattern: options.argsIgnorePattern?.toString(),
            variableDescription: 'args',
          };

        default:
          return {
            pattern: options.varsIgnorePattern?.toString(),
            variableDescription: 'vars',
          };
      }
    }

    /**
     * Generates the message data about the variable being defined and unused,
     * including the ignore pattern if configured.
     * @param unusedVar eslint-scope variable object.
     * @returns The message data to be used with this unused variable.
     */
    function getDefinedMessageData(
      unusedVar: ScopeVariable,
    ): Record<string, unknown> {
      const def = unusedVar.defs.at(0);
      let additionalMessageData = '';

      if (def) {
        const { pattern, variableDescription } = getVariableDescription(
          defToVariableType(def).type,
        );

        if (pattern && variableDescription) {
          additionalMessageData = `. Allowed unused ${variableDescription} must match ${pattern}`;
        }
      }

      return {
        action: 'defined',
        additional: additionalMessageData,
        varName: unusedVar.name,
      };
    }

    /**
     * Generate the warning message about the variable being
     * assigned and unused, including the ignore pattern if configured.
     * @param unusedVar eslint-scope variable object.
     * @returns The message data to be used with this unused variable.
     */
    function getAssignedMessageData(
      unusedVar: ScopeVariable,
    ): Record<string, unknown> {
      const def = unusedVar.defs.at(0);
      let additionalMessageData = '';

      if (def) {
        const { pattern, variableDescription } = getVariableDescription(
          defToVariableType(def).type,
        );

        if (pattern && variableDescription) {
          additionalMessageData = `. Allowed unused ${variableDescription} must match ${pattern}`;
        }
      }

      return {
        action: 'assigned a value',
        additional: additionalMessageData,
        varName: unusedVar.name,
      };
    }

    /**
     * Generate the warning message about a variable being used even though
     * it is marked as being ignored.
     * @param variable eslint-scope variable object
     * @param variableType a simple name for the types of variables that this rule supports
     * @returns The message data to be used with this used ignored variable.
     */
    function getUsedIgnoredMessageData(
      variable: ScopeVariable,
      variableType: VariableType,
    ): Record<string, unknown> {
      const { pattern, variableDescription } =
        getVariableDescription(variableType);

      let additionalMessageData = '';

      if (pattern && variableDescription) {
        additionalMessageData = `. Used ${variableDescription} must not match ${pattern}`;
      }

      return {
        additional: additionalMessageData,
        varName: variable.name,
      };
    }

    function collectUnusedVariables(): ScopeVariable[] {
      /**
       * Checks whether a node is a sibling of the rest property or not.
       * @param node a node to check
       * @returns True if the node is a sibling of the rest property, otherwise false.
       */
      function hasRestSibling(node: TSESTree.Node): boolean {
        return (
          node.type === AST_NODE_TYPES.Property &&
          node.parent.type === AST_NODE_TYPES.ObjectPattern &&
          node.parent.properties[node.parent.properties.length - 1].type ===
            AST_NODE_TYPES.RestElement
        );
      }

      /**
       * Determines if a variable has a sibling rest property
       * @param variable eslint-scope variable object.
       * @returns True if the variable is exported, false if not.
       */
      function hasRestSpreadSibling(variable: ScopeVariable): boolean {
        if (options.ignoreRestSiblings) {
          const hasRestSiblingDefinition = variable.defs.some(def =>
            hasRestSibling(def.name.parent),
          );
          const hasRestSiblingReference = variable.references.some(ref =>
            hasRestSibling(ref.identifier.parent),
          );

          return hasRestSiblingDefinition || hasRestSiblingReference;
        }

        return false;
      }

      /**
       * Checks whether the given variable is after the last used parameter.
       * @param variable The variable to check.
       * @returns `true` if the variable is defined after the last used parameter.
       */
      function isAfterLastUsedArg(variable: ScopeVariable): boolean {
        const def = variable.defs[0];
        const params = context.sourceCode.getDeclaredVariables(def.node);
        const posteriorParams = params.slice(params.indexOf(variable) + 1);

        // If any used parameters occur after this parameter, do not report.
        return !posteriorParams.some(
          v => v.references.length > 0 || v.eslintUsed,
        );
      }

      const analysisResults = collectVariables(context);
      const variables = [
        ...Array.from(analysisResults.unusedVariables, variable => ({
          used: false,
          variable,
        })),
        ...Array.from(analysisResults.usedVariables, variable => ({
          used: true,
          variable,
        })),
      ];
      const unusedVariablesReturn: ScopeVariable[] = [];
      for (const { used, variable } of variables) {
        // explicit global variables don't have definitions.
        if (variable.defs.length === 0) {
          if (!used) {
            unusedVariablesReturn.push(variable);
          }

          continue;
        }
        const def = variable.defs[0];

        if (
          variable.scope.type === TSESLint.Scope.ScopeType.global &&
          options.vars === 'local'
        ) {
          // skip variables in the global scope if configured to
          continue;
        }

        const refUsedInArrayPatterns = variable.references.some(
          ref => ref.identifier.parent.type === AST_NODE_TYPES.ArrayPattern,
        );

        // skip elements of array destructuring patterns
        if (
          (def.name.parent.type === AST_NODE_TYPES.ArrayPattern ||
            refUsedInArrayPatterns) &&
          def.name.type === AST_NODE_TYPES.Identifier &&
          options.destructuredArrayIgnorePattern?.test(def.name.name)
        ) {
          if (options.reportUsedIgnorePattern && used) {
            report(variable, {
              messageId: 'usedIgnoredVar',
              data: getUsedIgnoredMessageData(
                variable,
                VariableType.ArrayDestructure,
              ),
            });
          }
          continue;
        }

        if (def.type === TSESLint.Scope.DefinitionType.ClassName) {
          const hasStaticBlock = def.node.body.body.some(
            node => node.type === AST_NODE_TYPES.StaticBlock,
          );

          if (options.ignoreClassWithStaticInitBlock && hasStaticBlock) {
            continue;
          }
        }

        // skip catch variables
        if (def.type === TSESLint.Scope.DefinitionType.CatchClause) {
          if (options.caughtErrors === 'none') {
            continue;
          }
          // skip ignored parameters
          if (options.caughtErrorsIgnorePattern?.test(def.name.name)) {
            if (options.reportUsedIgnorePattern && used) {
              report(variable, {
                messageId: 'usedIgnoredVar',
                data: getUsedIgnoredMessageData(
                  variable,
                  VariableType.CatchClause,
                ),
              });
            }
            continue;
          }
        } else if (def.type === TSESLint.Scope.DefinitionType.Parameter) {
          // if "args" option is "none", skip any parameter
          if (options.args === 'none') {
            continue;
          }
          // skip ignored parameters
          if (
            def.name.type === AST_NODE_TYPES.Identifier &&
            options.argsIgnorePattern?.test(def.name.name)
          ) {
            if (options.reportUsedIgnorePattern && used) {
              report(variable, {
                messageId: 'usedIgnoredVar',
                data: getUsedIgnoredMessageData(
                  variable,
                  VariableType.Parameter,
                ),
              });
            }
            continue;
          }
          // if "args" option is "after-used", skip used variables
          if (
            options.args === 'after-used' &&
            isFunction(def.name.parent) &&
            !isAfterLastUsedArg(variable)
          ) {
            continue;
          }
        }
        // skip ignored variables
        else if (
          def.name.type === AST_NODE_TYPES.Identifier &&
          options.varsIgnorePattern?.test(def.name.name)
        ) {
          if (
            options.reportUsedIgnorePattern &&
            used &&
            /* enum members are always marked as 'used' by `collectVariables`, but in reality they may be used or
               unused. either way, don't complain about their naming. */
            def.type !== TSESLint.Scope.DefinitionType.TSEnumMember
          ) {
            report(variable, {
              messageId: 'usedIgnoredVar',
              data: getUsedIgnoredMessageData(variable, VariableType.Variable),
            });
          }
          continue;
        }

        if (
          def.type === TSESLint.Scope.DefinitionType.Variable &&
          options.ignoreUsingDeclarations &&
          (def.parent.kind === 'await using' || def.parent.kind === 'using')
        ) {
          continue;
        }

        if (hasRestSpreadSibling(variable)) {
          continue;
        }

        // in case another rule has run and used the collectUnusedVariables,
        // we want to ensure our selectors that marked variables as used are respected
        if (variable.eslintUsed) {
          continue;
        }

        if (!used) {
          unusedVariablesReturn.push(variable);
        }
      }

      return unusedVariablesReturn;
    }

    return {
      // top-level declaration file handling
      [ambientDeclarationSelector(AST_NODE_TYPES.Program)](
        node: DeclarationSelectorNode,
      ): void {
        if (!isDefinitionFile(context.filename)) {
          return;
        }

        const moduleDecl = nullThrows(
          node.parent,
          NullThrowsReasons.MissingParent,
        ) as TSESTree.Program;

        if (checkForOverridingExportStatements(moduleDecl)) {
          return;
        }

        markDeclarationChildAsUsed(node);
      },

      // children of a namespace that is a child of a declared namespace are auto-exported
      [ambientDeclarationSelector(
        'TSModuleDeclaration[declare = true] > TSModuleBlock TSModuleDeclaration > TSModuleBlock',
      )](node: DeclarationSelectorNode): void {
        const moduleDecl = nullThrows(
          node.parent.parent,
          NullThrowsReasons.MissingParent,
        ) as ModuleDeclarationWithBody;

        if (checkForOverridingExportStatements(moduleDecl)) {
          return;
        }

        markDeclarationChildAsUsed(node);
      },

      // declared namespace handling
      [ambientDeclarationSelector(
        'TSModuleDeclaration[declare = true] > TSModuleBlock',
      )](node: DeclarationSelectorNode): void {
        const moduleDecl = nullThrows(
          node.parent.parent,
          NullThrowsReasons.MissingParent,
        ) as ModuleDeclarationWithBody;

        if (checkForOverridingExportStatements(moduleDecl)) {
          return;
        }

        markDeclarationChildAsUsed(node);
      },

      // namespace handling in definition files
      [ambientDeclarationSelector('TSModuleDeclaration > TSModuleBlock')](
        node: DeclarationSelectorNode,
      ): void {
        if (!isDefinitionFile(context.filename)) {
          return;
        }
        const moduleDecl = nullThrows(
          node.parent.parent,
          NullThrowsReasons.MissingParent,
        ) as ModuleDeclarationWithBody;

        if (checkForOverridingExportStatements(moduleDecl)) {
          return;
        }

        markDeclarationChildAsUsed(node);
      },

      // collect
      'Program:exit'(programNode): void {
        const unusedVars = collectUnusedVariables();

        for (const unusedVar of unusedVars) {
          // Report the first declaration.
          if (unusedVar.defs.length > 0) {
            const usedOnlyAsType = unusedVar.references.some(
              ref =>
                referenceContainsTypeQuery(ref.identifier) ||
                referenceContainsTypePredicate(ref.identifier),
            );
            const messageId = usedOnlyAsType ? 'usedOnlyAsType' : 'unusedVar';

            const isImportUsedOnlyAsType =
              usedOnlyAsType &&
              unusedVar.defs.some(
                def => def.type === DefinitionType.ImportBinding,
              );
            if (isImportUsedOnlyAsType) {
              continue;
            }

            report(unusedVar, {
              messageId,
              data: unusedVar.references.some(ref => ref.isWrite())
                ? getAssignedMessageData(unusedVar)
                : getDefinedMessageData(unusedVar),
            });

            // If there are no regular declaration, report the first `/*globals*/` comment directive.
          } else if (
            'eslintExplicitGlobalComments' in unusedVar &&
            unusedVar.eslintExplicitGlobalComments
          ) {
            const directiveComment = unusedVar.eslintExplicitGlobalComments[0];

            context.report({
              loc: getNameLocationInGlobalDirectiveComment(
                context.sourceCode,
                directiveComment,
                unusedVar.name,
              ),
              node: programNode,
              messageId: 'unusedVar',
              data: getDefinedMessageData(unusedVar),
            });
          }
        }
      },
    };

    function checkForOverridingExportStatements(
      node: ModuleDeclarationWithBody | TSESTree.Program,
    ): boolean {
      const cached = MODULE_DECL_CACHE.get(node);
      if (cached != null) {
        return cached;
      }

      const body = getStatementsOfNode(node);

      if (hasOverridingExportStatement(body)) {
        MODULE_DECL_CACHE.set(node, true);
        return true;
      }

      MODULE_DECL_CACHE.set(node, false);
      return false;
    }

    type DeclarationSelectorNode =
      | TSESTree.ClassDeclaration
      | TSESTree.FunctionDeclaration
      | TSESTree.TSDeclareFunction
      | TSESTree.TSEnumDeclaration
      | TSESTree.TSInterfaceDeclaration
      | TSESTree.TSModuleDeclaration
      | TSESTree.TSTypeAliasDeclaration
      | TSESTree.VariableDeclaration;
    function ambientDeclarationSelector(parent: string): string {
      return [
        // Types are ambiently exported
        `${parent} > :matches(${[
          AST_NODE_TYPES.TSInterfaceDeclaration,
          AST_NODE_TYPES.TSTypeAliasDeclaration,
        ].join(', ')})`,
        // Value things are ambiently exported if they are "declare"d
        `${parent} > :matches(${[
          AST_NODE_TYPES.ClassDeclaration,
          AST_NODE_TYPES.TSDeclareFunction,
          AST_NODE_TYPES.TSEnumDeclaration,
          AST_NODE_TYPES.TSModuleDeclaration,
          AST_NODE_TYPES.VariableDeclaration,
        ].join(', ')})`,
      ].join(', ');
    }
    function markDeclarationChildAsUsed(node: DeclarationSelectorNode): void {
      const identifiers: TSESTree.Identifier[] = [];
      switch (node.type) {
        case AST_NODE_TYPES.TSInterfaceDeclaration:
        case AST_NODE_TYPES.TSTypeAliasDeclaration:
        case AST_NODE_TYPES.ClassDeclaration:
        case AST_NODE_TYPES.FunctionDeclaration:
        case AST_NODE_TYPES.TSDeclareFunction:
        case AST_NODE_TYPES.TSEnumDeclaration:
        case AST_NODE_TYPES.TSModuleDeclaration:
          if (node.id?.type === AST_NODE_TYPES.Identifier) {
            identifiers.push(node.id);
          }
          break;

        case AST_NODE_TYPES.VariableDeclaration:
          for (const declaration of node.declarations) {
            visitPattern(declaration, pattern => {
              identifiers.push(pattern);
            });
          }
          break;
      }

      let scope = context.sourceCode.getScope(node);
      const shouldUseUpperScope = [
        AST_NODE_TYPES.TSDeclareFunction,
        AST_NODE_TYPES.TSModuleDeclaration,
      ].includes(node.type);

      if (scope.variableScope !== scope) {
        scope = scope.variableScope;
      } else if (shouldUseUpperScope && scope.upper) {
        scope = scope.upper;
      }

      for (const id of identifiers) {
        const superVar = scope.set.get(id.name);
        if (superVar) {
          superVar.eslintUsed = true;
        }
      }
    }

    function visitPattern(
      node: TSESTree.Node,
      cb: (node: TSESTree.Identifier) => void,
    ): void {
      const visitor = new PatternVisitor({}, node, cb);
      visitor.visit(node);
    }
  }

isCommaToken.predicate(token: TSESTree.Token): boolean

Parameters:

  • token TSESTree.Token

Returns: boolean

Code
(token: TSESTree.Token): boolean =>
    token.type === AST_TOKEN_TYPES.Punctuator && token.value === ','

isLeftCurlyToken.predicate(token: TSESTree.Token): boolean

Parameters:

  • token TSESTree.Token

Returns: boolean

Code
(token: TSESTree.Token): boolean =>
    token.type === AST_TOKEN_TYPES.Punctuator && token.value === '{'

isRightCurlyToken.predicate(token: TSESTree.Token): boolean

Parameters:

  • token TSESTree.Token

Returns: boolean

Code
(token: TSESTree.Token): boolean =>
    token.type === AST_TOKEN_TYPES.Punctuator && token.value === '}'

assertToken({ predicate, tokenChar }: Predicate, token: TSESTree.Token | null): TSESTree.Token

Parameters:

  • { predicate, tokenChar } Predicate
  • token TSESTree.Token | null

Returns: TSESTree.Token

Calls:

  • predicate
Code
function assertToken(
  { predicate, tokenChar }: Predicate,
  token: TSESTree.Token | null,
): TSESTree.Token {
  if (token == null) {
    throw new Error(
      `Expected a valid "${tokenChar}" token, but found no token`,
    );
  }

  if (!predicate(token)) {
    throw new Error(
      `Expected a valid "${tokenChar}" token, but got "${token.value}" instead`,
    );
  }
  return token;
}

hasOverridingExportStatement(body: TSESTree.ProgramStatement[]): boolean

Parameters:

  • body TSESTree.ProgramStatement[]

Returns: boolean

Code
function hasOverridingExportStatement(
  body: TSESTree.ProgramStatement[],
): boolean {
  for (const statement of body) {
    if (
      (statement.type === AST_NODE_TYPES.ExportNamedDeclaration &&
        statement.declaration == null) ||
      statement.type === AST_NODE_TYPES.ExportAllDeclaration ||
      statement.type === AST_NODE_TYPES.TSExportAssignment
    ) {
      return true;
    }

    if (
      statement.type === AST_NODE_TYPES.ExportDefaultDeclaration &&
      statement.declaration.type === AST_NODE_TYPES.Identifier
    ) {
      return true;
    }
  }

  return false;
}

getStatementsOfNode(block: ModuleDeclarationWithBody | TSESTree.Pr…): TSESTree.ProgramStatement[]

Parameters:

  • block ModuleDeclarationWithBody | TSESTree.Program

Returns: TSESTree.ProgramStatement[]

Code
function getStatementsOfNode(
  block: ModuleDeclarationWithBody | TSESTree.Program,
): TSESTree.ProgramStatement[] {
  if (block.type === AST_NODE_TYPES.Program) {
    return block.body;
  }

  return block.body.body;
}

Internal helpers

Declared inside another function in this file.

areAllSpecifiersUnused(decl: TSESTree.ImportDeclaration): boolean

Parameters:

  • decl TSESTree.ImportDeclaration

Returns: boolean

Calls:

  • context.sourceCode.getDeclaredVariables(decl).every
  • reportedUnusedVariables.has
Code
function areAllSpecifiersUnused(decl: TSESTree.ImportDeclaration): boolean {
      return context.sourceCode.getDeclaredVariables(decl).every(variable => {
        return reportedUnusedVariables.has(variable);
      });
    }

report(unusedVar: ScopeVariable, opts: { data: TSESLint.ReportDescriptorMessag…): void

Parameters:

  • unusedVar ScopeVariable
  • opts { data: TSESLint.ReportDescriptorMessageData; messageId: MessageIds; node?: TSESTree.Node; }

Returns: void

Calls:

  • reportedUnusedVariables.add
  • unusedVar.references.filter
  • ref.isWrite
  • complex_call_9876
  • complex_call_9932
  • defToVariableType
  • getImportFixer
  • context.report

Internal Comments:

// If there's multiple definitions then we'd have to clean them all
// up! That's complicated and messy so for now let's just ignore it.
// TODO(bradzacher) -- this would be really easy to implement and (x5)
// is side-effect free! (x5)
// This would be easy to implement -- but classes can have
// side-effects in static initializers / static blocks. So it's
// dangerous to ever auto-fix remove them.
// (x4)
// Perhaps as an always-suggestion fixer...? (x3)
// We don't report these via this code path, so no fixer is possible
// This is easy to implement -- but we cannot implement it cos it
// changes the signature of the function which in turn might
// introduce type errors in consumers.
// Also parameters can have default values which might have
// side-effects.
// We don't report unused enum members so no fixer is ever possible
// This is easy to implement -- but TS namespaces are eagerly
// initialized -- meaning that they might have side-effects in
// the body. So it's dangerous to ever auto-fix remove them.
// TODO(bradzacher) -- this would be really easy to implement

Code
(
      unusedVar: ScopeVariable,
      opts: {
        data: TSESLint.ReportDescriptorMessageData;
        messageId: MessageIds;
        node?: TSESTree.Node;
      },
    ) => {
      reportedUnusedVariables.add(unusedVar);

      const writeReferences = unusedVar.references.filter(
        ref =>
          ref.isWrite() &&
          ref.from.variableScope === unusedVar.scope.variableScope,
      );

      const id = writeReferences.length
        ? writeReferences[writeReferences.length - 1].identifier
        : unusedVar.identifiers[0];

      const { start } = id.loc;
      const idLength = id.name.length;

      const loc = {
        start,
        end: {
          column: start.column + idLength,
          line: start.line,
        },
      };

      const fixer = (() => {
        const { messageId, fix, useAutofix } = ((): {
          fix?: TSESLint.ReportFixFunction;
          messageId?: MessageIds;
          useAutofix?: boolean;
        } => {
          if (unusedVar.defs.length !== 1) {
            // If there's multiple definitions then we'd have to clean them all
            // up! That's complicated and messy so for now let's just ignore it.
            return {};
          }

          const { type, def } = defToVariableType(unusedVar.defs[0]);
          switch (type) {
            case VariableType.ArrayDestructure:
              // TODO(bradzacher) -- this would be really easy to implement and
              // is side-effect free!
              return {};

            case VariableType.CatchClause:
              // TODO(bradzacher) -- this would be really easy to implement and
              // is side-effect free!
              return {};

            case VariableType.ClassName:
              // This would be easy to implement -- but classes can have
              // side-effects in static initializers / static blocks. So it's
              // dangerous to ever auto-fix remove them.
              //
              // Perhaps as an always-suggestion fixer...?
              return {};

            case VariableType.FunctionName:
              // TODO(bradzacher) -- this would be really easy to implement and
              // is side-effect free!
              return {};

            case VariableType.ImportBinding:
              return {
                ...getImportFixer(def),
                useAutofix: options.enableAutofixRemoval.imports,
              };

            case VariableType.ImplicitGlobalVariable:
              // We don't report these via this code path, so no fixer is possible
              return {};

            case VariableType.Parameter:
              // This is easy to implement -- but we cannot implement it cos it
              // changes the signature of the function which in turn might
              // introduce type errors in consumers.
              //
              // Also parameters can have default values which might have
              // side-effects.
              //
              // Perhaps as an always-suggestion fixer...?
              return {};

            case VariableType.TSEnumMember:
              // We don't report unused enum members so no fixer is ever possible
              return {};

            case VariableType.TSEnumName:
              // TODO(bradzacher) -- this would be really easy to implement and
              // is side-effect free!
              return {};

            case VariableType.TSModuleName:
              // This is easy to implement -- but TS namespaces are eagerly
              // initialized -- meaning that they might have side-effects in
              // the body. So it's dangerous to ever auto-fix remove them.
              //
              // Perhaps as an always-suggestion fixer...?
              return {};

            case VariableType.Type:
              // TODO(bradzacher) -- this would be really easy to implement and
              // is side-effect free!
              return {};

            case VariableType.Variable:
              // TODO(bradzacher) -- this would be really easy to implement
              return {};
          }
        })();

        if (!fix) {
          return {};
        }

        if (useAutofix) {
          return { fix };
        }

        const data = {
          varName: unusedVar.name,
        };
        return {
          suggest: [
            {
              messageId: messageId ?? ('removeUnusedVar' as const),
              data,
              fix,
            },
          ],
        };
      })();
      context.report({
        ...opts,
        ...fixer,
        loc,
        node: id,
      });
    }

removeNodeWithTrailingNewline(fixer: TSESLint.RuleFixer, node: TSESTree.Node): TSESLint.RuleFix

Parameters:

  • fixer TSESLint.RuleFixer
  • node TSESTree.Node

Returns: TSESLint.RuleFix

Calls:

  • sourceCode.getIndexFromLoc
  • sourceCode.getText
  • sourceCode.text.slice(lineRangeStart, lineRangeEnd).trim
  • fixer.removeRange
  • fixer.remove

Internal Comments:

// Expand range: start of first line to start of next line (or EOF) (x2)
// If node is the only non-whitespace on its line(s), remove full line(s)

Code
function removeNodeWithTrailingNewline(
      fixer: TSESLint.RuleFixer,
      node: TSESTree.Node,
    ): TSESLint.RuleFix {
      const sourceCode = context.sourceCode;
      const { line: startLine } = node.loc.start;
      const { line: endLine } = node.loc.end;
      // Expand range: start of first line to start of next line (or EOF)
      const lineRangeStart = sourceCode.getIndexFromLoc({
        column: 0,
        line: startLine,
      });
      const lineRangeEnd =
        endLine < sourceCode.lines.length
          ? sourceCode.getIndexFromLoc({ column: 0, line: endLine + 1 })
          : sourceCode.text.length;
      // If node is the only non-whitespace on its line(s), remove full line(s)
      if (
        sourceCode.getText(node) ===
        sourceCode.text.slice(lineRangeStart, lineRangeEnd).trim()
      ) {
        return fixer.removeRange([lineRangeStart, lineRangeEnd]);
      }
      return fixer.remove(node);
    }

getImportFixer(def: ImportBindingDefinition): { fix: TSESLint.ReportFixFunction; messageId: MessageIds; }

Parameters:

  • def ImportBindingDefinition

Returns: { fix: TSESLint.ReportFixFunction; messageId: MessageIds; }

Calls:

  • removeNodeWithTrailingNewline
  • areAllSpecifiersUnused
  • nullThrows (from ../util)
  • context.sourceCode.getTokenAfter
  • NullThrowsReasons.MissingToken
  • assertToken
  • fixer.removeRange
  • Math.min
  • Math.max
  • context.sourceCode .getDeclaredVariables(importDecl) .map(variable => { if (reportedUnusedVariables.has(variable)) { return null; } const specifier = variable.defs[0].node; if (specifier.type !== AST_NODE_TYPES.ImportSpecifier) { return null; } return specifier; }) .filter
  • context.sourceCode.getFirstToken
  • context.sourceCode.getTokenBefore
  • isCommaToken.predicate

Internal Comments:

// import equals declarations can only have one binding -- so we can
// just remove entire import declaration
// all specifiers are unused -- so we can just remove entire import (x2)
// declaration (x2)
// in this branch we know the following things: (x3)
// 1) there is at least one specifier that is used (x3)
// 2) the default specifier is unused
// (x26)
// by process of elimination we can deduce that there is at least one
// named specifier that is used
// i.e. the code must be import Unused, { Used, ... } from 'module';
// there's one or more unused named specifiers, so we must remove the
// default specifier in isolation including the trailing comma.
//     import Unused, { Used, ... } from 'module';
//            ^^^^^^^ remove this
// NOTE: we could also remove the spaces between the comma and the
// opening curly brace -- but this does risk removing comments. To be
// safe we'll be conservative for now
// TODO(bradzacher) -- consider removing the extra space whilst also
//                     preserving comments.
// guaranteed to NOT be in an export statement as we're inspecting an (x2)
// import (x2)
// 2) all named specifiers are unused (x2)
// by process of elimination we can deduce that there is a (x2)
// default specifier and it is the only used specifier (x2)
// i.e. the code must be import Used, { Unused, ... } from (x2)
//     'module'; (x2)
// So we can just remove the entire curly content and the comma (x2)
// before, eg import Used, { Unused, ... } from 'module'; (x2)
// ^^^^^^^^^^^^^^^^^ remove this (x2)
// in this branch we know there is at least one used named (x2)
// specifier which means we have to remove each unused specifier (x2)
// in isolation. (x2)
// there's 3 possible cases to care about: import { Unused, (x2)
//    Used... } from 'module'; import { ...Used, Unused } from (x2)
//    'module'; import { ...Used, Unused, } from 'module'; (x2)
// Note that because of the above usedNamedSpecifiers check we (x2)
// know that we don't have one of these cases: import { Unused } (x2)
// from 'module'; import { Unused, Unused... } from 'module'; (x2)
// import { ...Unused, Unused, } from 'module'; (x2)
// The result is that we know that there _must_ be a comma that (x2)
// needs cleaning up (x2)
// try to remove the leading comma first as it leads to a nicer (x2)
// fix output in most cases (x2)
// leading preferred: import { Used, Unused, Used } from 'module'; (x2)
//   ^^^^^^^^ remove import { Used, Used } from 'module'; (x2)
// trailing preferred: import { Used, Unused, Used } from (x2)
//   'module'; ^^^^^^^ remove import { Used,  Used } from (x2)
//   'module'; ^^ ugly double space (x2)
// But we need to still fallback to the trailing comma for cases (x2)
// where the unused specifier is the first in the import eg: (x2)
// import { Unused, Used } from 'module'; (x2)
// namespace specifiers cannot be used with any other specifier -- so (x2)
// we can just remove entire import declaration (x2)

Code
function getImportFixer(def: ImportBindingDefinition): {
      fix: TSESLint.ReportFixFunction;
      messageId: MessageIds;
    } {
      switch (def.node.type) {
        case AST_NODE_TYPES.TSImportEqualsDeclaration:
          // import equals declarations can only have one binding -- so we can
          // just remove entire import declaration
          return {
            messageId: 'removeUnusedImportDeclaration',
            fix: fixer => removeNodeWithTrailingNewline(fixer, def.node),
          };

        case AST_NODE_TYPES.ImportDefaultSpecifier: {
          const importDecl = def.node.parent;
          if (
            importDecl.specifiers.length === 1 ||
            areAllSpecifiersUnused(importDecl)
          ) {
            // all specifiers are unused -- so we can just remove entire import
            // declaration
            return {
              messageId: 'removeUnusedImportDeclaration',
              fix: fixer => removeNodeWithTrailingNewline(fixer, importDecl),
            };
          }

          // in this branch we know the following things:
          // 1) there is at least one specifier that is used
          // 2) the default specifier is unused
          //
          // by process of elimination we can deduce that there is at least one
          // named specifier that is used
          //
          // i.e. the code must be import Unused, { Used, ... } from 'module';
          //
          // there's one or more unused named specifiers, so we must remove the
          // default specifier in isolation including the trailing comma.
          //
          //     import Unused, { Used, ... } from 'module';
          //            ^^^^^^^ remove this
          //
          // NOTE: we could also remove the spaces between the comma and the
          // opening curly brace -- but this does risk removing comments. To be
          // safe we'll be conservative for now
          //
          // TODO(bradzacher) -- consider removing the extra space whilst also
          //                     preserving comments.
          return {
            messageId: 'removeUnusedVar',
            fix: fixer => {
              const comma = nullThrows(
                context.sourceCode.getTokenAfter(def.node),
                NullThrowsReasons.MissingToken(',', 'import specifier'),
              );
              assertToken(isCommaToken, comma);

              return fixer.removeRange([
                Math.min(def.node.range[0], comma.range[0]),
                Math.max(def.node.range[1], comma.range[1]),
              ]);
            },
          };
        }

        case AST_NODE_TYPES.ImportSpecifier: {
          // guaranteed to NOT be in an export statement as we're inspecting an
          // import
          const importDecl = def.node.parent as Exclude<
            typeof def.node.parent,
            TSESTree.ExportAllDeclaration | TSESTree.ExportNamedDeclaration
          >;
          if (
            importDecl.specifiers.length === 1 ||
            areAllSpecifiersUnused(importDecl)
          ) {
            // all specifiers are unused -- so we can just remove entire import
            // declaration
            return {
              messageId: 'removeUnusedImportDeclaration',
              fix: fixer => removeNodeWithTrailingNewline(fixer, importDecl),
            };
          }

          return {
            messageId: 'removeUnusedVar',
            fix: fixer => {
              const usedNamedSpecifiers = context.sourceCode
                .getDeclaredVariables(importDecl)
                .map(variable => {
                  if (reportedUnusedVariables.has(variable)) {
                    return null;
                  }
                  const specifier = variable.defs[0].node;
                  if (specifier.type !== AST_NODE_TYPES.ImportSpecifier) {
                    return null;
                  }
                  return specifier;
                })
                .filter(v => v != null);

              if (usedNamedSpecifiers.length === 0) {
                // in this branch we know the following things:
                // 1) there is at least one specifier that is used
                // 2) all named specifiers are unused
                //
                // by process of elimination we can deduce that there is a
                // default specifier and it is the only used specifier
                //
                // i.e. the code must be import Used, { Unused, ... } from
                //     'module';
                //
                // So we can just remove the entire curly content and the comma
                // before, eg import Used, { Unused, ... } from 'module';
                // ^^^^^^^^^^^^^^^^^ remove this

                const leftCurly = assertToken(
                  isLeftCurlyToken,
                  context.sourceCode.getFirstToken(
                    importDecl,
                    isLeftCurlyToken.predicate,
                  ),
                );
                const leftToken = assertToken(
                  isCommaToken,
                  context.sourceCode.getTokenBefore(leftCurly),
                );

                const rightToken = assertToken(
                  isRightCurlyToken,
                  context.sourceCode.getFirstToken(
                    importDecl,
                    isRightCurlyToken.predicate,
                  ),
                );
                return fixer.removeRange([
                  leftToken.range[0],
                  rightToken.range[1],
                ]);
              }

              // in this branch we know there is at least one used named
              // specifier which means we have to remove each unused specifier
              // in isolation.
              //
              // there's 3 possible cases to care about: import { Unused,
              //    Used... } from 'module'; import { ...Used, Unused } from
              //    'module'; import { ...Used, Unused, } from 'module';
              //
              // Note that because of the above usedNamedSpecifiers check we
              // know that we don't have one of these cases: import { Unused }
              // from 'module'; import { Unused, Unused... } from 'module';
              // import { ...Unused, Unused, } from 'module';
              //
              // The result is that we know that there _must_ be a comma that
              // needs cleaning up
              //
              // try to remove the leading comma first as it leads to a nicer
              // fix output in most cases
              //
              // leading preferred: import { Used, Unused, Used } from 'module';
              //   ^^^^^^^^ remove import { Used, Used } from 'module';
              //
              // trailing preferred: import { Used, Unused, Used } from
              //   'module'; ^^^^^^^ remove import { Used,  Used } from
              //   'module'; ^^ ugly double space
              //
              // But we need to still fallback to the trailing comma for cases
              // where the unused specifier is the first in the import eg:
              // import { Unused, Used } from 'module';
              const maybeComma = context.sourceCode.getTokenBefore(def.node);
              const comma =
                maybeComma && isCommaToken.predicate(maybeComma)
                  ? maybeComma
                  : assertToken(
                      isCommaToken,
                      context.sourceCode.getTokenAfter(def.node),
                    );
              return fixer.removeRange([
                Math.min(def.node.range[0], comma.range[0]),
                Math.max(def.node.range[1], comma.range[1]),
              ]);
            },
          };
        }

        case AST_NODE_TYPES.ImportNamespaceSpecifier: {
          // namespace specifiers cannot be used with any other specifier -- so
          // we can just remove entire import declaration
          const importDecl = def.node.parent;
          return {
            messageId: 'removeUnusedImportDeclaration',
            fix: fixer => removeNodeWithTrailingNewline(fixer, importDecl),
          };
        }
      }
    }

getImportFixer.fix(fixer: any): RuleFix

Parameters:

  • fixer any

Returns: RuleFix

Calls:

  • removeNodeWithTrailingNewline
Code
fixer => removeNodeWithTrailingNewline(fixer, def.node)

getImportFixer.fix(fixer: any): RuleFix

Parameters:

  • fixer any

Returns: RuleFix

Calls:

  • removeNodeWithTrailingNewline
Code
fixer => removeNodeWithTrailingNewline(fixer, importDecl)

getImportFixer.fix(fixer: any): any

Parameters:

  • fixer any

Returns: any

Calls:

  • nullThrows (from ../util)
  • context.sourceCode.getTokenAfter
  • NullThrowsReasons.MissingToken
  • assertToken
  • fixer.removeRange
  • Math.min
  • Math.max
Code
fixer => {
              const comma = nullThrows(
                context.sourceCode.getTokenAfter(def.node),
                NullThrowsReasons.MissingToken(',', 'import specifier'),
              );
              assertToken(isCommaToken, comma);

              return fixer.removeRange([
                Math.min(def.node.range[0], comma.range[0]),
                Math.max(def.node.range[1], comma.range[1]),
              ]);
            }

getImportFixer.fix(fixer: any): RuleFix

Parameters:

  • fixer any

Returns: RuleFix

Calls:

  • removeNodeWithTrailingNewline
Code
fixer => removeNodeWithTrailingNewline(fixer, importDecl)

getImportFixer.fix(fixer: any): any

Parameters:

  • fixer any

Returns: any

Calls:

  • context.sourceCode .getDeclaredVariables(importDecl) .map(variable => { if (reportedUnusedVariables.has(variable)) { return null; } const specifier = variable.defs[0].node; if (specifier.type !== AST_NODE_TYPES.ImportSpecifier) { return null; } return specifier; }) .filter
  • assertToken
  • context.sourceCode.getFirstToken
  • context.sourceCode.getTokenBefore
  • fixer.removeRange
  • isCommaToken.predicate
  • context.sourceCode.getTokenAfter
  • Math.min
  • Math.max

Internal Comments:

// in this branch we know the following things: (x2)
// 1) there is at least one specifier that is used (x2)
// 2) all named specifiers are unused (x2)
// (x20)
// by process of elimination we can deduce that there is a (x2)
// default specifier and it is the only used specifier (x2)
// i.e. the code must be import Used, { Unused, ... } from (x2)
//     'module'; (x2)
// So we can just remove the entire curly content and the comma (x2)
// before, eg import Used, { Unused, ... } from 'module'; (x2)
// ^^^^^^^^^^^^^^^^^ remove this (x2)
// in this branch we know there is at least one used named (x2)
// specifier which means we have to remove each unused specifier (x2)
// in isolation. (x2)
// there's 3 possible cases to care about: import { Unused, (x2)
//    Used... } from 'module'; import { ...Used, Unused } from (x2)
//    'module'; import { ...Used, Unused, } from 'module'; (x2)
// Note that because of the above usedNamedSpecifiers check we (x2)
// know that we don't have one of these cases: import { Unused } (x2)
// from 'module'; import { Unused, Unused... } from 'module'; (x2)
// import { ...Unused, Unused, } from 'module'; (x2)
// The result is that we know that there _must_ be a comma that (x2)
// needs cleaning up (x2)
// try to remove the leading comma first as it leads to a nicer (x2)
// fix output in most cases (x2)
// leading preferred: import { Used, Unused, Used } from 'module'; (x2)
//   ^^^^^^^^ remove import { Used, Used } from 'module'; (x2)
// trailing preferred: import { Used, Unused, Used } from (x2)
//   'module'; ^^^^^^^ remove import { Used,  Used } from (x2)
//   'module'; ^^ ugly double space (x2)
// But we need to still fallback to the trailing comma for cases (x2)
// where the unused specifier is the first in the import eg: (x2)
// import { Unused, Used } from 'module'; (x2)

Code
fixer => {
              const usedNamedSpecifiers = context.sourceCode
                .getDeclaredVariables(importDecl)
                .map(variable => {
                  if (reportedUnusedVariables.has(variable)) {
                    return null;
                  }
                  const specifier = variable.defs[0].node;
                  if (specifier.type !== AST_NODE_TYPES.ImportSpecifier) {
                    return null;
                  }
                  return specifier;
                })
                .filter(v => v != null);

              if (usedNamedSpecifiers.length === 0) {
                // in this branch we know the following things:
                // 1) there is at least one specifier that is used
                // 2) all named specifiers are unused
                //
                // by process of elimination we can deduce that there is a
                // default specifier and it is the only used specifier
                //
                // i.e. the code must be import Used, { Unused, ... } from
                //     'module';
                //
                // So we can just remove the entire curly content and the comma
                // before, eg import Used, { Unused, ... } from 'module';
                // ^^^^^^^^^^^^^^^^^ remove this

                const leftCurly = assertToken(
                  isLeftCurlyToken,
                  context.sourceCode.getFirstToken(
                    importDecl,
                    isLeftCurlyToken.predicate,
                  ),
                );
                const leftToken = assertToken(
                  isCommaToken,
                  context.sourceCode.getTokenBefore(leftCurly),
                );

                const rightToken = assertToken(
                  isRightCurlyToken,
                  context.sourceCode.getFirstToken(
                    importDecl,
                    isRightCurlyToken.predicate,
                  ),
                );
                return fixer.removeRange([
                  leftToken.range[0],
                  rightToken.range[1],
                ]);
              }

              // in this branch we know there is at least one used named
              // specifier which means we have to remove each unused specifier
              // in isolation.
              //
              // there's 3 possible cases to care about: import { Unused,
              //    Used... } from 'module'; import { ...Used, Unused } from
              //    'module'; import { ...Used, Unused, } from 'module';
              //
              // Note that because of the above usedNamedSpecifiers check we
              // know that we don't have one of these cases: import { Unused }
              // from 'module'; import { Unused, Unused... } from 'module';
              // import { ...Unused, Unused, } from 'module';
              //
              // The result is that we know that there _must_ be a comma that
              // needs cleaning up
              //
              // try to remove the leading comma first as it leads to a nicer
              // fix output in most cases
              //
              // leading preferred: import { Used, Unused, Used } from 'module';
              //   ^^^^^^^^ remove import { Used, Used } from 'module';
              //
              // trailing preferred: import { Used, Unused, Used } from
              //   'module'; ^^^^^^^ remove import { Used,  Used } from
              //   'module'; ^^ ugly double space
              //
              // But we need to still fallback to the trailing comma for cases
              // where the unused specifier is the first in the import eg:
              // import { Unused, Used } from 'module';
              const maybeComma = context.sourceCode.getTokenBefore(def.node);
              const comma =
                maybeComma && isCommaToken.predicate(maybeComma)
                  ? maybeComma
                  : assertToken(
                      isCommaToken,
                      context.sourceCode.getTokenAfter(def.node),
                    );
              return fixer.removeRange([
                Math.min(def.node.range[0], comma.range[0]),
                Math.max(def.node.range[1], comma.range[1]),
              ]);
            }

getImportFixer.fix(fixer: any): RuleFix

Parameters:

  • fixer any

Returns: RuleFix

Calls:

  • removeNodeWithTrailingNewline
Code
fixer => removeNodeWithTrailingNewline(fixer, importDecl)

defToVariableType(def: Definition): DefToVariableType

Determines what variable type a def is.

Parameters:

  • def any: the declaration to check

Returns: undefined a simple name for the types of variables that this rule supports

Raw JSDoc
/**
     * Determines what variable type a def is.
     * @param def the declaration to check
     * @returns a simple name for the types of variables that this rule supports
     */

Internal Comments:

/*
       * This `destructuredArrayIgnorePattern` error report works differently from the catch
       * clause and parameter error reports. _Both_ the `varsIgnorePattern` and the
       * `destructuredArrayIgnorePattern` will be checked for array destructuring. However,
       * for the purposes of the report, the currently defined behavior is to only inform the
       * user of the `destructuredArrayIgnorePattern` if it's present (regardless of the fact
       * that the `varsIgnorePattern` would also apply). If it's not present, the user will be
       * informed of the `varsIgnorePattern`, assuming that's present.
       */

Code
function defToVariableType(def: Definition): DefToVariableType {
      /*
       * This `destructuredArrayIgnorePattern` error report works differently from the catch
       * clause and parameter error reports. _Both_ the `varsIgnorePattern` and the
       * `destructuredArrayIgnorePattern` will be checked for array destructuring. However,
       * for the purposes of the report, the currently defined behavior is to only inform the
       * user of the `destructuredArrayIgnorePattern` if it's present (regardless of the fact
       * that the `varsIgnorePattern` would also apply). If it's not present, the user will be
       * informed of the `varsIgnorePattern`, assuming that's present.
       */
      if (
        options.destructuredArrayIgnorePattern &&
        def.name.parent.type === AST_NODE_TYPES.ArrayPattern
      ) {
        return { type: VariableType.ArrayDestructure, def };
      }

      switch (def.type) {
        case DefinitionType.CatchClause:
          return { type: VariableType.CatchClause, def };
        case DefinitionType.ClassName:
          return { type: VariableType.ClassName, def };
        case DefinitionType.FunctionName:
          return { type: VariableType.FunctionName, def };
        case DefinitionType.ImplicitGlobalVariable:
          return { type: VariableType.ImplicitGlobalVariable, def };
        case DefinitionType.ImportBinding:
          return { type: VariableType.ImportBinding, def };
        case DefinitionType.Parameter:
          return { type: VariableType.Parameter, def };
        case DefinitionType.TSEnumName:
          return { type: VariableType.TSEnumName, def };
        case DefinitionType.TSEnumMember:
          return { type: VariableType.TSEnumMember, def };
        case DefinitionType.TSModuleName:
          return { type: VariableType.TSModuleName, def };
        case DefinitionType.Type:
          return { type: VariableType.Type, def };
        case DefinitionType.Variable:
          return { type: VariableType.Variable, def };
      }
    }

getVariableDescription(variableType: VariableType): { pattern: string | undefined; variableDescription: string;…

Gets a given variable's description and configured ignore pattern based on the provided variableType

Parameters:

  • variableType any: a simple name for the types of variables that this rule supports

Returns: undefined the given variable's description and ignore pattern

Raw JSDoc
/**
     * Gets a given variable's description and configured ignore pattern
     * based on the provided variableType
     * @param variableType a simple name for the types of variables that this rule supports
     * @returns the given variable's description and
     * ignore pattern
     */

Calls:

  • options.destructuredArrayIgnorePattern?.toString
  • options.caughtErrorsIgnorePattern?.toString
  • options.argsIgnorePattern?.toString
  • options.varsIgnorePattern?.toString
Code
function getVariableDescription(variableType: VariableType): {
      pattern: string | undefined;
      variableDescription: string;
    } {
      switch (variableType) {
        case VariableType.ArrayDestructure:
          return {
            pattern: options.destructuredArrayIgnorePattern?.toString(),
            variableDescription: 'elements of array destructuring',
          };

        case VariableType.CatchClause:
          return {
            pattern: options.caughtErrorsIgnorePattern?.toString(),
            variableDescription: 'caught errors',
          };

        case VariableType.Parameter:
          return {
            pattern: options.argsIgnorePattern?.toString(),
            variableDescription: 'args',
          };

        default:
          return {
            pattern: options.varsIgnorePattern?.toString(),
            variableDescription: 'vars',
          };
      }
    }

getDefinedMessageData(unusedVar: ScopeVariable): Record<string, unknown>

Generates the message data about the variable being defined and unused, including the ignore pattern if configured.

Parameters:

  • unusedVar any: eslint-scope variable object.

Returns: undefined The message data to be used with this unused variable.

Raw JSDoc
/**
     * Generates the message data about the variable being defined and unused,
     * including the ignore pattern if configured.
     * @param unusedVar eslint-scope variable object.
     * @returns The message data to be used with this unused variable.
     */

Calls:

  • unusedVar.defs.at
  • getVariableDescription
  • defToVariableType
Code
function getDefinedMessageData(
      unusedVar: ScopeVariable,
    ): Record<string, unknown> {
      const def = unusedVar.defs.at(0);
      let additionalMessageData = '';

      if (def) {
        const { pattern, variableDescription } = getVariableDescription(
          defToVariableType(def).type,
        );

        if (pattern && variableDescription) {
          additionalMessageData = `. Allowed unused ${variableDescription} must match ${pattern}`;
        }
      }

      return {
        action: 'defined',
        additional: additionalMessageData,
        varName: unusedVar.name,
      };
    }

getAssignedMessageData(unusedVar: ScopeVariable): Record<string, unknown>

Generate the warning message about the variable being assigned and unused, including the ignore pattern if configured.

Parameters:

  • unusedVar any: eslint-scope variable object.

Returns: undefined The message data to be used with this unused variable.

Raw JSDoc
/**
     * Generate the warning message about the variable being
     * assigned and unused, including the ignore pattern if configured.
     * @param unusedVar eslint-scope variable object.
     * @returns The message data to be used with this unused variable.
     */

Calls:

  • unusedVar.defs.at
  • getVariableDescription
  • defToVariableType
Code
function getAssignedMessageData(
      unusedVar: ScopeVariable,
    ): Record<string, unknown> {
      const def = unusedVar.defs.at(0);
      let additionalMessageData = '';

      if (def) {
        const { pattern, variableDescription } = getVariableDescription(
          defToVariableType(def).type,
        );

        if (pattern && variableDescription) {
          additionalMessageData = `. Allowed unused ${variableDescription} must match ${pattern}`;
        }
      }

      return {
        action: 'assigned a value',
        additional: additionalMessageData,
        varName: unusedVar.name,
      };
    }

getUsedIgnoredMessageData(variable: ScopeVariable, variableType: VariableType): Record<string, unknown>

Generate the warning message about a variable being used even though it is marked as being ignored.

Parameters:

  • variable any: eslint-scope variable object
  • variableType any: a simple name for the types of variables that this rule supports

Returns: undefined The message data to be used with this used ignored variable.

Raw JSDoc
/**
     * Generate the warning message about a variable being used even though
     * it is marked as being ignored.
     * @param variable eslint-scope variable object
     * @param variableType a simple name for the types of variables that this rule supports
     * @returns The message data to be used with this used ignored variable.
     */

Calls:

  • getVariableDescription
Code
function getUsedIgnoredMessageData(
      variable: ScopeVariable,
      variableType: VariableType,
    ): Record<string, unknown> {
      const { pattern, variableDescription } =
        getVariableDescription(variableType);

      let additionalMessageData = '';

      if (pattern && variableDescription) {
        additionalMessageData = `. Used ${variableDescription} must not match ${pattern}`;
      }

      return {
        additional: additionalMessageData,
        varName: variable.name,
      };
    }

collectUnusedVariables(): ScopeVariable[]

Returns: ScopeVariable[]

Calls:

  • variable.defs.some
  • hasRestSibling
  • variable.references.some
  • context.sourceCode.getDeclaredVariables
  • params.slice
  • params.indexOf
  • posteriorParams.some
  • collectVariables (from ../util)
  • Array.from
  • unusedVariablesReturn.push
  • options.destructuredArrayIgnorePattern?.test
  • report
  • getUsedIgnoredMessageData
  • def.node.body.body.some
  • options.caughtErrorsIgnorePattern?.test
  • options.argsIgnorePattern?.test
  • isFunction (from ../util)
  • isAfterLastUsedArg
  • options.varsIgnorePattern?.test
  • hasRestSpreadSibling

Internal Comments:

/**
       * Checks whether a node is a sibling of the rest property or not.
       * @param node a node to check
       * @returns True if the node is a sibling of the rest property, otherwise false.
       */
/**
       * Determines if a variable has a sibling rest property
       * @param variable eslint-scope variable object.
       * @returns True if the variable is exported, false if not.
       */
/**
       * Checks whether the given variable is after the last used parameter.
       * @param variable The variable to check.
       * @returns `true` if the variable is defined after the last used parameter.
       */
// If any used parameters occur after this parameter, do not report.
// explicit global variables don't have definitions.
// skip variables in the global scope if configured to
// skip elements of array destructuring patterns
// skip catch variables
// skip ignored parameters (x2)
// if "args" option is "none", skip any parameter
// if "args" option is "after-used", skip used variables
/* enum members are always marked as 'used' by `collectVariables`, but in reality they may be used or
               unused. either way, don't complain about their naming. */ (x3)
// in case another rule has run and used the collectUnusedVariables,
// we want to ensure our selectors that marked variables as used are respected

Code
function collectUnusedVariables(): ScopeVariable[] {
      /**
       * Checks whether a node is a sibling of the rest property or not.
       * @param node a node to check
       * @returns True if the node is a sibling of the rest property, otherwise false.
       */
      function hasRestSibling(node: TSESTree.Node): boolean {
        return (
          node.type === AST_NODE_TYPES.Property &&
          node.parent.type === AST_NODE_TYPES.ObjectPattern &&
          node.parent.properties[node.parent.properties.length - 1].type ===
            AST_NODE_TYPES.RestElement
        );
      }

      /**
       * Determines if a variable has a sibling rest property
       * @param variable eslint-scope variable object.
       * @returns True if the variable is exported, false if not.
       */
      function hasRestSpreadSibling(variable: ScopeVariable): boolean {
        if (options.ignoreRestSiblings) {
          const hasRestSiblingDefinition = variable.defs.some(def =>
            hasRestSibling(def.name.parent),
          );
          const hasRestSiblingReference = variable.references.some(ref =>
            hasRestSibling(ref.identifier.parent),
          );

          return hasRestSiblingDefinition || hasRestSiblingReference;
        }

        return false;
      }

      /**
       * Checks whether the given variable is after the last used parameter.
       * @param variable The variable to check.
       * @returns `true` if the variable is defined after the last used parameter.
       */
      function isAfterLastUsedArg(variable: ScopeVariable): boolean {
        const def = variable.defs[0];
        const params = context.sourceCode.getDeclaredVariables(def.node);
        const posteriorParams = params.slice(params.indexOf(variable) + 1);

        // If any used parameters occur after this parameter, do not report.
        return !posteriorParams.some(
          v => v.references.length > 0 || v.eslintUsed,
        );
      }

      const analysisResults = collectVariables(context);
      const variables = [
        ...Array.from(analysisResults.unusedVariables, variable => ({
          used: false,
          variable,
        })),
        ...Array.from(analysisResults.usedVariables, variable => ({
          used: true,
          variable,
        })),
      ];
      const unusedVariablesReturn: ScopeVariable[] = [];
      for (const { used, variable } of variables) {
        // explicit global variables don't have definitions.
        if (variable.defs.length === 0) {
          if (!used) {
            unusedVariablesReturn.push(variable);
          }

          continue;
        }
        const def = variable.defs[0];

        if (
          variable.scope.type === TSESLint.Scope.ScopeType.global &&
          options.vars === 'local'
        ) {
          // skip variables in the global scope if configured to
          continue;
        }

        const refUsedInArrayPatterns = variable.references.some(
          ref => ref.identifier.parent.type === AST_NODE_TYPES.ArrayPattern,
        );

        // skip elements of array destructuring patterns
        if (
          (def.name.parent.type === AST_NODE_TYPES.ArrayPattern ||
            refUsedInArrayPatterns) &&
          def.name.type === AST_NODE_TYPES.Identifier &&
          options.destructuredArrayIgnorePattern?.test(def.name.name)
        ) {
          if (options.reportUsedIgnorePattern && used) {
            report(variable, {
              messageId: 'usedIgnoredVar',
              data: getUsedIgnoredMessageData(
                variable,
                VariableType.ArrayDestructure,
              ),
            });
          }
          continue;
        }

        if (def.type === TSESLint.Scope.DefinitionType.ClassName) {
          const hasStaticBlock = def.node.body.body.some(
            node => node.type === AST_NODE_TYPES.StaticBlock,
          );

          if (options.ignoreClassWithStaticInitBlock && hasStaticBlock) {
            continue;
          }
        }

        // skip catch variables
        if (def.type === TSESLint.Scope.DefinitionType.CatchClause) {
          if (options.caughtErrors === 'none') {
            continue;
          }
          // skip ignored parameters
          if (options.caughtErrorsIgnorePattern?.test(def.name.name)) {
            if (options.reportUsedIgnorePattern && used) {
              report(variable, {
                messageId: 'usedIgnoredVar',
                data: getUsedIgnoredMessageData(
                  variable,
                  VariableType.CatchClause,
                ),
              });
            }
            continue;
          }
        } else if (def.type === TSESLint.Scope.DefinitionType.Parameter) {
          // if "args" option is "none", skip any parameter
          if (options.args === 'none') {
            continue;
          }
          // skip ignored parameters
          if (
            def.name.type === AST_NODE_TYPES.Identifier &&
            options.argsIgnorePattern?.test(def.name.name)
          ) {
            if (options.reportUsedIgnorePattern && used) {
              report(variable, {
                messageId: 'usedIgnoredVar',
                data: getUsedIgnoredMessageData(
                  variable,
                  VariableType.Parameter,
                ),
              });
            }
            continue;
          }
          // if "args" option is "after-used", skip used variables
          if (
            options.args === 'after-used' &&
            isFunction(def.name.parent) &&
            !isAfterLastUsedArg(variable)
          ) {
            continue;
          }
        }
        // skip ignored variables
        else if (
          def.name.type === AST_NODE_TYPES.Identifier &&
          options.varsIgnorePattern?.test(def.name.name)
        ) {
          if (
            options.reportUsedIgnorePattern &&
            used &&
            /* enum members are always marked as 'used' by `collectVariables`, but in reality they may be used or
               unused. either way, don't complain about their naming. */
            def.type !== TSESLint.Scope.DefinitionType.TSEnumMember
          ) {
            report(variable, {
              messageId: 'usedIgnoredVar',
              data: getUsedIgnoredMessageData(variable, VariableType.Variable),
            });
          }
          continue;
        }

        if (
          def.type === TSESLint.Scope.DefinitionType.Variable &&
          options.ignoreUsingDeclarations &&
          (def.parent.kind === 'await using' || def.parent.kind === 'using')
        ) {
          continue;
        }

        if (hasRestSpreadSibling(variable)) {
          continue;
        }

        // in case another rule has run and used the collectUnusedVariables,
        // we want to ensure our selectors that marked variables as used are respected
        if (variable.eslintUsed) {
          continue;
        }

        if (!used) {
          unusedVariablesReturn.push(variable);
        }
      }

      return unusedVariablesReturn;
    }

hasRestSibling(node: TSESTree.Node): boolean

Checks whether a node is a sibling of the rest property or not.

Parameters:

  • node any: a node to check

Returns: undefined True if the node is a sibling of the rest property, otherwise false.

Raw JSDoc
/**
       * Checks whether a node is a sibling of the rest property or not.
       * @param node a node to check
       * @returns True if the node is a sibling of the rest property, otherwise false.
       */
Code
function hasRestSibling(node: TSESTree.Node): boolean {
        return (
          node.type === AST_NODE_TYPES.Property &&
          node.parent.type === AST_NODE_TYPES.ObjectPattern &&
          node.parent.properties[node.parent.properties.length - 1].type ===
            AST_NODE_TYPES.RestElement
        );
      }

hasRestSpreadSibling(variable: ScopeVariable): boolean

Determines if a variable has a sibling rest property

Parameters:

  • variable any: eslint-scope variable object.

Returns: undefined True if the variable is exported, false if not.

Raw JSDoc
/**
       * Determines if a variable has a sibling rest property
       * @param variable eslint-scope variable object.
       * @returns True if the variable is exported, false if not.
       */

Calls:

  • variable.defs.some
  • hasRestSibling
  • variable.references.some
Code
function hasRestSpreadSibling(variable: ScopeVariable): boolean {
        if (options.ignoreRestSiblings) {
          const hasRestSiblingDefinition = variable.defs.some(def =>
            hasRestSibling(def.name.parent),
          );
          const hasRestSiblingReference = variable.references.some(ref =>
            hasRestSibling(ref.identifier.parent),
          );

          return hasRestSiblingDefinition || hasRestSiblingReference;
        }

        return false;
      }

isAfterLastUsedArg(variable: ScopeVariable): boolean

Checks whether the given variable is after the last used parameter.

Parameters:

  • variable any: The variable to check.

Returns: undefined true if the variable is defined after the last used parameter.

Raw JSDoc
/**
       * Checks whether the given variable is after the last used parameter.
       * @param variable The variable to check.
       * @returns `true` if the variable is defined after the last used parameter.
       */

Calls:

  • context.sourceCode.getDeclaredVariables
  • params.slice
  • params.indexOf
  • posteriorParams.some

Internal Comments:

// If any used parameters occur after this parameter, do not report.

Code
function isAfterLastUsedArg(variable: ScopeVariable): boolean {
        const def = variable.defs[0];
        const params = context.sourceCode.getDeclaredVariables(def.node);
        const posteriorParams = params.slice(params.indexOf(variable) + 1);

        // If any used parameters occur after this parameter, do not report.
        return !posteriorParams.some(
          v => v.references.length > 0 || v.eslintUsed,
        );
      }

checkForOverridingExportStatements(node: ModuleDeclarationWithBody | TSESTree.Pr…): boolean

Parameters:

  • node ModuleDeclarationWithBody | TSESTree.Program

Returns: boolean

Calls:

  • MODULE_DECL_CACHE.get
  • getStatementsOfNode
  • hasOverridingExportStatement
  • MODULE_DECL_CACHE.set
Code
function checkForOverridingExportStatements(
      node: ModuleDeclarationWithBody | TSESTree.Program,
    ): boolean {
      const cached = MODULE_DECL_CACHE.get(node);
      if (cached != null) {
        return cached;
      }

      const body = getStatementsOfNode(node);

      if (hasOverridingExportStatement(body)) {
        MODULE_DECL_CACHE.set(node, true);
        return true;
      }

      MODULE_DECL_CACHE.set(node, false);
      return false;
    }

ambientDeclarationSelector(parent: string): string

Parameters:

  • parent string

Returns: string

Calls:

  • [ // Types are ambiently exported${parent} > :matches(${[ AST_NODE_TYPES.TSInterfaceDeclaration, AST_NODE_TYPES.TSTypeAliasDeclaration, ].join(', ')}), // Value things are ambiently exported if they are "declare"d${parent} > :matches(${[ AST_NODE_TYPES.ClassDeclaration, AST_NODE_TYPES.TSDeclareFunction, AST_NODE_TYPES.TSEnumDeclaration, AST_NODE_TYPES.TSModuleDeclaration, AST_NODE_TYPES.VariableDeclaration, ].join(', ')}), ].join
  • [ AST_NODE_TYPES.TSInterfaceDeclaration, AST_NODE_TYPES.TSTypeAliasDeclaration, ].join
  • [ AST_NODE_TYPES.ClassDeclaration, AST_NODE_TYPES.TSDeclareFunction, AST_NODE_TYPES.TSEnumDeclaration, AST_NODE_TYPES.TSModuleDeclaration, AST_NODE_TYPES.VariableDeclaration, ].join

Internal Comments:

// Types are ambiently exported (x2)
// Value things are ambiently exported if they are "declare"d (x2)

Code
function ambientDeclarationSelector(parent: string): string {
      return [
        // Types are ambiently exported
        `${parent} > :matches(${[
          AST_NODE_TYPES.TSInterfaceDeclaration,
          AST_NODE_TYPES.TSTypeAliasDeclaration,
        ].join(', ')})`,
        // Value things are ambiently exported if they are "declare"d
        `${parent} > :matches(${[
          AST_NODE_TYPES.ClassDeclaration,
          AST_NODE_TYPES.TSDeclareFunction,
          AST_NODE_TYPES.TSEnumDeclaration,
          AST_NODE_TYPES.TSModuleDeclaration,
          AST_NODE_TYPES.VariableDeclaration,
        ].join(', ')})`,
      ].join(', ');
    }

markDeclarationChildAsUsed(node: DeclarationSelectorNode): void

Parameters:

  • node DeclarationSelectorNode

Returns: void

Calls:

  • identifiers.push
  • visitPattern
  • context.sourceCode.getScope
  • [ AST_NODE_TYPES.TSDeclareFunction, AST_NODE_TYPES.TSModuleDeclaration, ].includes
  • scope.set.get
Code
function markDeclarationChildAsUsed(node: DeclarationSelectorNode): void {
      const identifiers: TSESTree.Identifier[] = [];
      switch (node.type) {
        case AST_NODE_TYPES.TSInterfaceDeclaration:
        case AST_NODE_TYPES.TSTypeAliasDeclaration:
        case AST_NODE_TYPES.ClassDeclaration:
        case AST_NODE_TYPES.FunctionDeclaration:
        case AST_NODE_TYPES.TSDeclareFunction:
        case AST_NODE_TYPES.TSEnumDeclaration:
        case AST_NODE_TYPES.TSModuleDeclaration:
          if (node.id?.type === AST_NODE_TYPES.Identifier) {
            identifiers.push(node.id);
          }
          break;

        case AST_NODE_TYPES.VariableDeclaration:
          for (const declaration of node.declarations) {
            visitPattern(declaration, pattern => {
              identifiers.push(pattern);
            });
          }
          break;
      }

      let scope = context.sourceCode.getScope(node);
      const shouldUseUpperScope = [
        AST_NODE_TYPES.TSDeclareFunction,
        AST_NODE_TYPES.TSModuleDeclaration,
      ].includes(node.type);

      if (scope.variableScope !== scope) {
        scope = scope.variableScope;
      } else if (shouldUseUpperScope && scope.upper) {
        scope = scope.upper;
      }

      for (const id of identifiers) {
        const superVar = scope.set.get(id.name);
        if (superVar) {
          superVar.eslintUsed = true;
        }
      }
    }

visitPattern(node: TSESTree.Node, cb: (node: TSESTree.Identifier) => void): void

Parameters:

  • node TSESTree.Node
  • cb (node: TSESTree.Identifier) => void

Returns: void

Calls:

  • visitor.visit
Code
function visitPattern(
      node: TSESTree.Node,
      cb: (node: TSESTree.Identifier) => void,
    ): void {
      const visitor = new PatternVisitor({}, node, cb);
      visitor.visit(node);
    }

Interfaces

TranslatedOptions

Interface Code
interface TranslatedOptions {
  args: 'after-used' | 'all' | 'none';
  argsIgnorePattern?: RegExp;
  caughtErrors: 'all' | 'none';
  caughtErrorsIgnorePattern?: RegExp;
  destructuredArrayIgnorePattern?: RegExp;
  enableAutofixRemoval: {
    imports: boolean;
  };
  ignoreClassWithStaticInitBlock: boolean;
  ignoreRestSiblings: boolean;
  ignoreUsingDeclarations: boolean;
  reportUsedIgnorePattern: boolean;
  vars: 'all' | 'local';
  varsIgnorePattern?: RegExp;
}

Properties

Name Type Optional Description
args 'after-used' \| 'all' \| 'none' not shown
argsIgnorePattern RegExp not shown
caughtErrors 'all' \| 'none' not shown
caughtErrorsIgnorePattern RegExp not shown
destructuredArrayIgnorePattern RegExp not shown
enableAutofixRemoval { imports: boolean; } not shown
ignoreClassWithStaticInitBlock boolean not shown
ignoreRestSiblings boolean not shown
ignoreUsingDeclarations boolean not shown
reportUsedIgnorePattern boolean not shown
vars 'all' \| 'local' not shown
varsIgnorePattern RegExp not shown

Predicate

Interface Code
interface Predicate {
  predicate: (token: TSESTree.Token) => boolean;
  tokenChar: string;
}

Properties

Name Type Optional Description
predicate (token: TSESTree.Token) => boolean not shown
tokenChar string not shown

Type Aliases

MessageIds

type MessageIds = | 'removeUnusedImportDeclaration'
  | 'removeUnusedVar'
  | 'unusedVar'
  | 'usedIgnoredVar'
  | 'usedOnlyAsType';

Options

type Options = [
  | 'all'
  | 'local'
  | {
      args?: 'after-used' | 'all' | 'none';
      argsIgnorePattern?: string;
      caughtErrors?: 'all' | 'none';
      caughtErrorsIgnorePattern?: string;
      destructuredArrayIgnorePattern?: string;
      enableAutofixRemoval?: {
        imports?: boolean;
      };
      ignoreClassWithStaticInitBlock?: boolean;
      ignoreRestSiblings?: boolean;
      ignoreUsingDeclarations?: boolean;
      reportUsedIgnorePattern?: boolean;
      vars?: 'all' | 'local';
      varsIgnorePattern?: string;
    },
];

DefToVariableType

type DefToVariableType = | {
      type: VariableType.ArrayDestructure;
      def: Definition;
    }
  | {
      type: VariableType.CatchClause;
      def: Definition & { type: DefinitionType.CatchClause };
    }
  | {
      type: VariableType.ClassName;
      def: Definition & { type: DefinitionType.ClassName };
    }
  | {
      type: VariableType.FunctionName;
      def: Definition & { type: DefinitionType.FunctionName };
    }
  | {
      type: VariableType.ImplicitGlobalVariable;
      def: Definition & { type: DefinitionType.ImplicitGlobalVariable };
    }
  | {
      type: VariableType.ImportBinding;
      def: Definition & { type: DefinitionType.ImportBinding };
    }
  | {
      type: VariableType.Parameter;
      def: Definition & { type: DefinitionType.Parameter };
    }
  | {
      type: VariableType.TSEnumMember;
      def: Definition & { type: DefinitionType.TSEnumMember };
    }
  | {
      type: VariableType.TSEnumName;
      def: Definition & { type: DefinitionType.TSEnumName };
    }
  | {
      type: VariableType.TSModuleName;
      def: Definition & { type: DefinitionType.TSModuleName };
    }
  | {
      type: VariableType.Type;
      def: Definition & { type: DefinitionType.Type };
    }
  | {
      type: VariableType.Variable;
      def: Definition & { type: DefinitionType.Variable };
    };

ModuleDeclarationWithBody

type ModuleDeclarationWithBody = MakeRequired<
  TSESTree.TSModuleDeclaration,
  'body'
>;

DeclarationSelectorNode

type DeclarationSelectorNode = | TSESTree.ClassDeclaration
      | TSESTree.FunctionDeclaration
      | TSESTree.TSDeclareFunction
      | TSESTree.TSEnumDeclaration
      | TSESTree.TSInterfaceDeclaration
      | TSESTree.TSModuleDeclaration
      | TSESTree.TSTypeAliasDeclaration
      | TSESTree.VariableDeclaration;

Enums

enum VariableType

Enum Code
enum VariableType {
  // New sub-types
  ArrayDestructure,

  // DefinitionType
  CatchClause,
  ClassName,
  FunctionName,
  ImportBinding,
  ImplicitGlobalVariable,
  Parameter,
  TSEnumMember,
  TSEnumName,
  TSModuleName,
  Type,
  Variable,
}

Members

Name Value Description
ArrayDestructure auto not shown
CatchClause auto not shown
ClassName auto not shown
FunctionName auto not shown
ImportBinding auto not shown
ImplicitGlobalVariable auto not shown
Parameter auto not shown
TSEnumMember auto not shown
TSEnumName auto not shown
TSModuleName auto not shown
Type auto not shown
Variable auto not shown

Generated by Syntax Scribe