/**
 * @name Variable not declared before use
 * @description Variables should be declared before their first use.
 * @kind problem
 * @problem.severity warning
 * @id js/use-before-declaration
 * @tags maintainability
 *       readability
 * @precision very-high
 */

import javascript
private import Declarations

from VarAccess acc, VarDecl decl, Variable var, StmtContainer sc
where // the first reference to `var` in `sc` is `acc` (that is, an access, not a declaration)
      acc = firstRefInContainer(var, Ref(), sc) and
      // `decl` is a declaration of `var` in `sc` (which must come after `acc`)
      decl = refInContainer(var, Decl(), sc) and
      // exclude globals declared by a linter directive
      not exists(Linting::GlobalDeclaration glob |
        glob.declaresGlobalForAccess(acc)
      ) and
      // exclude declarations in synthetic constructors
      not acc.getEnclosingFunction() instanceof SyntheticConstructor and
      // exclude results in ambient contexts
      not acc.isAmbient() and
      // a class may be referenced in its own decorators
      not exists (ClassDefinition cls |
        decl = cls.getIdentifier() and
        acc.getParent*() = cls.getADecorator())
select acc, "Variable '" + acc.getName() + "' is used before its $@.", decl, "declaration"
