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