Skip to content

⬅️ Back to Table of Contents

📄 no-use-before-define

📊 Analysis Summary

Metric Count
🔧 Functions 16
📦 Imports 6
📊 Variables & Constants 1
📐 Interfaces 1
📑 Type Aliases 2

📚 Table of Contents

🛠️ File Location:

📂 packages/eslint-plugin/src/rules/no-use-before-define.ts

📤 Default Export

export default createRule<Options, MessageIds>({ ... })
Property Value
name 'no-use-before-define'
meta.type 'problem'
meta.docs.description 'Disallow the use of variables before they are defined'
meta.docs.extendsBaseRule true
meta.messages.noUseBeforeDefine "'{{name}}' was used before it was defined."
meta.schema [ { oneOf: [ { type: 'string', description: 'Broadly set functions and allowNamedExports to false.', enum: ['nofunc']...
defaultOptions [ { allowNamedExports: false, classes: true, enums: true, functions: true, ignoreTypeReferences: true, typedefs: true...

Entry point: create — documented under Functions.


📦 Imports

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

Variables & Constants

Name Type Kind Value Exported
SENTINEL_TYPE RegExp const /^(?:(?:Function\|Class)(?:Declaration\|Expression)\|ArrowFunctionExpression\... ✗

Functions

create(context: any, optionsWithDefault: any): { Program(node: any): void; }

Parameters:

  • context any
  • optionsWithDefault any

Returns: { Program(node: any): void; }

Calls:

  • parseOptions
  • isTypeReference
  • isFunction
  • isOuterClass
  • isOuterVariable
  • isOuterEnum
  • isTypedef
  • isInInitializer
  • scope.references.forEach
  • context.report
  • isNamedExports
  • isDefinedBeforeUse
  • report
  • isForbidden
  • isClassRefInClassDecorator
  • scope.childScopes.forEach
  • findVariablesInScope
  • context.sourceCode.getScope

Internal Comments:

/**
     * Determines whether a given use-before-define case should be reported according to the options.
     * @param variable The variable that gets used before being defined
     * @param reference The reference to the variable
     */
/**
     * Finds and validates all variables in a given scope.
     */
// Skips when the reference is:
// - initializations.
// - referring to an undefined variable.
// - referring to a global environment variable (there're no identifiers).
// - located preceded by the variable (except in initializers).
// - allowed by options.
// Reports. (x3)

Code
create(context, optionsWithDefault) {
    const options = parseOptions(optionsWithDefault[0]);

    /**
     * Determines whether a given use-before-define case should be reported according to the options.
     * @param variable The variable that gets used before being defined
     * @param reference The reference to the variable
     */
    function isForbidden(
      variable: TSESLint.Scope.Variable,
      reference: TSESLint.Scope.Reference,
    ): boolean {
      if (options.ignoreTypeReferences && isTypeReference(reference)) {
        return false;
      }
      if (isFunction(variable)) {
        return options.functions;
      }
      if (isOuterClass(variable, reference)) {
        return options.classes;
      }
      if (isOuterVariable(variable, reference)) {
        return options.variables;
      }
      if (isOuterEnum(variable, reference)) {
        return options.enums;
      }
      if (isTypedef(variable)) {
        return options.typedefs;
      }

      return true;
    }

    function isDefinedBeforeUse(
      variable: TSESLint.Scope.Variable,
      reference: TSESLint.Scope.Reference,
    ): boolean {
      return (
        variable.identifiers[0].range[1] <= reference.identifier.range[1] &&
        !(reference.isValueReference && isInInitializer(variable, reference))
      );
    }

    /**
     * Finds and validates all variables in a given scope.
     */
    function findVariablesInScope(scope: TSESLint.Scope.Scope): void {
      scope.references.forEach(reference => {
        const variable = reference.resolved;

        function report(): void {
          context.report({
            node: reference.identifier,
            messageId: 'noUseBeforeDefine',
            data: {
              name: reference.identifier.name,
            },
          });
        }

        // Skips when the reference is:
        // - initializations.
        // - referring to an undefined variable.
        // - referring to a global environment variable (there're no identifiers).
        // - located preceded by the variable (except in initializers).
        // - allowed by options.
        if (reference.init) {
          return;
        }

        if (!options.allowNamedExports && isNamedExports(reference)) {
          if (!variable || !isDefinedBeforeUse(variable, reference)) {
            report();
          }
          return;
        }

        if (!variable) {
          return;
        }

        if (
          variable.identifiers.length === 0 ||
          isDefinedBeforeUse(variable, reference) ||
          !isForbidden(variable, reference) ||
          isClassRefInClassDecorator(variable, reference) ||
          reference.from.type === TSESLint.Scope.ScopeType.functionType
        ) {
          return;
        }

        // Reports.
        report();
      });

      scope.childScopes.forEach(findVariablesInScope);
    }

    return {
      Program(node): void {
        findVariablesInScope(context.sourceCode.getScope(node));
      },
    };
  }

parseOptions(options: string | Config | null): Required<Config>

Parses a given value as options.

Raw JSDoc
/**
 * Parses a given value as options.
 */
Code
function parseOptions(options: string | Config | null): Required<Config> {
  let functions = true;
  let classes = true;
  let enums = true;
  let variables = true;
  let typedefs = true;
  let ignoreTypeReferences = true;
  let allowNamedExports = false;

  if (typeof options === 'string') {
    functions = options !== 'nofunc';
  } else if (typeof options === 'object' && options != null) {
    functions = options.functions !== false;
    classes = options.classes !== false;
    enums = options.enums !== false;
    variables = options.variables !== false;
    typedefs = options.typedefs !== false;
    ignoreTypeReferences = options.ignoreTypeReferences !== false;
    allowNamedExports = options.allowNamedExports !== false;
  }

  return {
    allowNamedExports,
    classes,
    enums,
    functions,
    ignoreTypeReferences,
    typedefs,
    variables,
  };
}

isFunction(variable: TSESLint.Scope.Variable): boolean

Checks whether or not a given variable is a function declaration.

Raw JSDoc
/**
 * Checks whether or not a given variable is a function declaration.
 */
Code
function isFunction(variable: TSESLint.Scope.Variable): boolean {
  return variable.defs[0].type === DefinitionType.FunctionName;
}

isTypedef(variable: TSESLint.Scope.Variable): boolean

Checks whether or not a given variable is a type declaration.

Raw JSDoc
/**
 * Checks whether or not a given variable is a type declaration.
 */
Code
function isTypedef(variable: TSESLint.Scope.Variable): boolean {
  return variable.defs[0].type === DefinitionType.Type;
}

isOuterEnum(variable: TSESLint.Scope.Variable, reference: TSESLint.Scope.Reference): boolean

Checks whether or not a given variable is a enum declaration.

Raw JSDoc
/**
 * Checks whether or not a given variable is a enum declaration.
 */
Code
function isOuterEnum(
  variable: TSESLint.Scope.Variable,
  reference: TSESLint.Scope.Reference,
): boolean {
  return (
    variable.defs[0].type === DefinitionType.TSEnumName &&
    variable.scope.variableScope !== reference.from.variableScope
  );
}

isOuterClass(variable: TSESLint.Scope.Variable, reference: TSESLint.Scope.Reference): boolean

Checks whether or not a given variable is a class declaration in an upper function scope.

Raw JSDoc
/**
 * Checks whether or not a given variable is a class declaration in an upper function scope.
 */
Code
function isOuterClass(
  variable: TSESLint.Scope.Variable,
  reference: TSESLint.Scope.Reference,
): boolean {
  return (
    variable.defs[0].type === DefinitionType.ClassName &&
    variable.scope.variableScope !== reference.from.variableScope
  );
}

isOuterVariable(variable: TSESLint.Scope.Variable, reference: TSESLint.Scope.Reference): boolean

Checks whether or not a given variable is a variable declaration in an upper function scope.

Raw JSDoc
/**
 * Checks whether or not a given variable is a variable declaration in an upper function scope.
 */
Code
function isOuterVariable(
  variable: TSESLint.Scope.Variable,
  reference: TSESLint.Scope.Reference,
): boolean {
  return (
    variable.defs[0].type === DefinitionType.Variable &&
    variable.scope.variableScope !== reference.from.variableScope
  );
}

isNamedExports(reference: TSESLint.Scope.Reference): boolean

Checks whether or not a given reference is a export reference.

Raw JSDoc
/**
 * Checks whether or not a given reference is a export reference.
 */
Code
function isNamedExports(reference: TSESLint.Scope.Reference): boolean {
  const { identifier } = reference;
  return (
    identifier.parent.type === AST_NODE_TYPES.ExportSpecifier &&
    identifier.parent.local === identifier
  );
}

isTypeReference(reference: TSESLint.Scope.Reference): boolean

Checks whether or not a given reference is a type reference.

Raw JSDoc
/**
 * Checks whether or not a given reference is a type reference.
 */

Calls:

  • referenceContainsTypeQuery (from ../util/referenceContainsTypeQuery)
Code
function isTypeReference(reference: TSESLint.Scope.Reference): boolean {
  return (
    reference.isTypeReference ||
    referenceContainsTypeQuery(reference.identifier)
  );
}

isInRange(node: TSESTree.Expression | null | undefined, location: number): boolean

Checks whether or not a given location is inside of the range of a given node.

Raw JSDoc
/**
 * Checks whether or not a given location is inside of the range of a given node.
 */
Code
function isInRange(
  node: TSESTree.Expression | null | undefined,
  location: number,
): boolean {
  return !!node && node.range[0] <= location && location <= node.range[1];
}

isClassRefInClassDecorator(variable: TSESLint.Scope.Variable, reference: TSESLint.Scope.Reference): boolean

Decorators are transpiled such that the decorator is placed after the class declaration So it is considered safe

Raw JSDoc
/**
 * Decorators are transpiled such that the decorator is placed after the class declaration
 * So it is considered safe
 */
Code
function isClassRefInClassDecorator(
  variable: TSESLint.Scope.Variable,
  reference: TSESLint.Scope.Reference,
): boolean {
  if (
    variable.defs[0].type !== DefinitionType.ClassName ||
    variable.defs[0].node.decorators.length === 0
  ) {
    return false;
  }

  for (const deco of variable.defs[0].node.decorators) {
    if (
      reference.identifier.range[0] >= deco.range[0] &&
      reference.identifier.range[1] <= deco.range[1]
    ) {
      return true;
    }
  }

  return false;
}

isInInitializer(variable: TSESLint.Scope.Variable, reference: TSESLint.Scope.Reference): boolean

Checks whether or not a given reference is inside of the initializers of a given variable.

Returns: undefined true in the following cases: - var a = a - var [a = a] = list - var {a = a} = obj - for (var a in a) {} - for (var a of a) {}

Raw JSDoc
/**
 * Checks whether or not a given reference is inside of the initializers of a given variable.
 *
 * @returns `true` in the following cases:
 * - var a = a
 * - var [a = a] = list
 * - var {a = a} = obj
 * - for (var a in a) {}
 * - for (var a of a) {}
 */

Calls:

  • isInRange
  • SENTINEL_TYPE.test
Code
function isInInitializer(
  variable: TSESLint.Scope.Variable,
  reference: TSESLint.Scope.Reference,
): boolean {
  if (variable.scope !== reference.from) {
    return false;
  }

  let node: TSESTree.Node | undefined = variable.identifiers[0].parent;
  const location = reference.identifier.range[1];

  while (node) {
    if (node.type === AST_NODE_TYPES.VariableDeclarator) {
      if (isInRange(node.init, location)) {
        return true;
      }
      if (
        (node.parent.parent.type === AST_NODE_TYPES.ForInStatement ||
          node.parent.parent.type === AST_NODE_TYPES.ForOfStatement) &&
        isInRange(node.parent.parent.right, location)
      ) {
        return true;
      }
      break;
    } else if (node.type === AST_NODE_TYPES.AssignmentPattern) {
      if (isInRange(node.right, location)) {
        return true;
      }
    } else if (SENTINEL_TYPE.test(node.type)) {
      break;
    }

    node = node.parent;
  }

  return false;
}

Internal helpers

Declared inside another function in this file.

isForbidden(variable: TSESLint.Scope.Variable, reference: TSESLint.Scope.Reference): boolean

Determines whether a given use-before-define case should be reported according to the options.

Parameters:

  • variable any: The variable that gets used before being defined
  • reference any: The reference to the variable
Raw JSDoc
/**
     * Determines whether a given use-before-define case should be reported according to the options.
     * @param variable The variable that gets used before being defined
     * @param reference The reference to the variable
     */

Calls:

  • isTypeReference
  • isFunction
  • isOuterClass
  • isOuterVariable
  • isOuterEnum
  • isTypedef
Code
function isForbidden(
      variable: TSESLint.Scope.Variable,
      reference: TSESLint.Scope.Reference,
    ): boolean {
      if (options.ignoreTypeReferences && isTypeReference(reference)) {
        return false;
      }
      if (isFunction(variable)) {
        return options.functions;
      }
      if (isOuterClass(variable, reference)) {
        return options.classes;
      }
      if (isOuterVariable(variable, reference)) {
        return options.variables;
      }
      if (isOuterEnum(variable, reference)) {
        return options.enums;
      }
      if (isTypedef(variable)) {
        return options.typedefs;
      }

      return true;
    }

isDefinedBeforeUse(variable: TSESLint.Scope.Variable, reference: TSESLint.Scope.Reference): boolean

Parameters:

  • variable TSESLint.Scope.Variable
  • reference TSESLint.Scope.Reference

Returns: boolean

Calls:

  • isInInitializer
Code
function isDefinedBeforeUse(
      variable: TSESLint.Scope.Variable,
      reference: TSESLint.Scope.Reference,
    ): boolean {
      return (
        variable.identifiers[0].range[1] <= reference.identifier.range[1] &&
        !(reference.isValueReference && isInInitializer(variable, reference))
      );
    }

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

Finds and validates all variables in a given scope.

Raw JSDoc
/**
     * Finds and validates all variables in a given scope.
     */

Calls:

  • scope.references.forEach
  • context.report
  • isNamedExports
  • isDefinedBeforeUse
  • report
  • isForbidden
  • isClassRefInClassDecorator
  • scope.childScopes.forEach

Internal Comments:

// Skips when the reference is:
// - initializations.
// - referring to an undefined variable.
// - referring to a global environment variable (there're no identifiers).
// - located preceded by the variable (except in initializers).
// - allowed by options.
// Reports. (x3)

Code
function findVariablesInScope(scope: TSESLint.Scope.Scope): void {
      scope.references.forEach(reference => {
        const variable = reference.resolved;

        function report(): void {
          context.report({
            node: reference.identifier,
            messageId: 'noUseBeforeDefine',
            data: {
              name: reference.identifier.name,
            },
          });
        }

        // Skips when the reference is:
        // - initializations.
        // - referring to an undefined variable.
        // - referring to a global environment variable (there're no identifiers).
        // - located preceded by the variable (except in initializers).
        // - allowed by options.
        if (reference.init) {
          return;
        }

        if (!options.allowNamedExports && isNamedExports(reference)) {
          if (!variable || !isDefinedBeforeUse(variable, reference)) {
            report();
          }
          return;
        }

        if (!variable) {
          return;
        }

        if (
          variable.identifiers.length === 0 ||
          isDefinedBeforeUse(variable, reference) ||
          !isForbidden(variable, reference) ||
          isClassRefInClassDecorator(variable, reference) ||
          reference.from.type === TSESLint.Scope.ScopeType.functionType
        ) {
          return;
        }

        // Reports.
        report();
      });

      scope.childScopes.forEach(findVariablesInScope);
    }

report(): void

Returns: void

Calls:

  • context.report
Code
function report(): void {
          context.report({
            node: reference.identifier,
            messageId: 'noUseBeforeDefine',
            data: {
              name: reference.identifier.name,
            },
          });
        }

Interfaces

Config

Interface Code
export interface Config {
  allowNamedExports?: boolean;
  classes?: boolean;
  enums?: boolean;
  functions?: boolean;
  ignoreTypeReferences?: boolean;
  typedefs?: boolean;
  variables?: boolean;
}

Properties

Name Type Optional Description
allowNamedExports boolean ✓ not shown
classes boolean ✓ not shown
enums boolean ✓ not shown
functions boolean ✓ not shown
ignoreTypeReferences boolean ✓ not shown
typedefs boolean ✓ not shown
variables boolean ✓ not shown

Type Aliases

Options

type Options = ['nofunc' | Config];

MessageIds

type MessageIds = 'noUseBeforeDefine';

Generated by Syntax Scribe