/**
 * @name Access to let-bound variable in temporal dead zone
 * @description Accessing a let-bound variable before its declaration will lead to a runtime
 *              error on ECMAScript 2015-compatible platforms.
 * @kind problem
 * @problem.severity error
 * @id js/variable-use-in-temporal-dead-zone
 * @tags portability
 *       correctness
 * @precision very-high
 */

import javascript

/**
 * Gets the (0-based) index among all statements in block `blk` at which
 * variable `v` is declared by statement `let`.
 */
int letDeclAt(BlockStmt blk, Variable v, LetStmt let) {
  let.getADecl().getBindingPattern().getAVariable() = v and
  let.getParentStmt*() = blk.getStmt(result)
}

from VarAccess va, LetStmt let, BlockStmt blk, int i, int j, Variable v
where v = va.getVariable() and
      j = letDeclAt(blk, v, let) and
      blk.getStmt(i) = va.getEnclosingStmt().getParentStmt*() and
      i < j and
      // don't flag uses in different functions
      blk.getContainer() = va.getContainer() and
      not letDeclAt(blk, v, _) < i
select va, "This expression refers to $@ inside its temporal dead zone.",  let, va.getName()
