function traverse(node, path, state) {
  /*jshint -W004*/
  // Create a scope stack entry if this is the first node we've encountered in
  // its local scope
  var startIndex = null;
  var parentNode = path[0];
  if (!Array.isArray(node) && state.localScope.parentNode !== parentNode) {
    if (_nodeIsClosureScopeBoundary(node, parentNode)) {
      var scopeIsStrict = state.scopeIsStrict;
      if (!scopeIsStrict
          && (node.type === Syntax.BlockStatement
              || node.type === Syntax.Program)) {
          scopeIsStrict =
            node.body.length > 0
            && node.body[0].type === Syntax.ExpressionStatement
            && node.body[0].expression.type === Syntax.Literal
            && node.body[0].expression.value === 'use ' + 'strict';
      }

      if (node.type === Syntax.Program) {
        startIndex = state.g.buffer.length;
        state = utils.updateState(state, {
          scopeIsStrict: scopeIsStrict
        });
      } else {
        startIndex = state.g.buffer.length + 1;
        state = utils.updateState(state, {
          localScope: {
            parentNode: parentNode,
            parentScope: state.localScope,
            identifiers: {},
            tempVarIndex: 0,
            tempVars: []
          },
          scopeIsStrict: scopeIsStrict
        });

        // All functions have an implicit 'arguments' object in scope
        declareIdentInScope('arguments', initScopeMetadata(node), state);

        // Include function arg identifiers in the scope boundaries of the
        // function
        if (parentNode.params.length > 0) {
          var param;
          var metadata = initScopeMetadata(parentNode, path.slice(1), path[0]);
          for (var i = 0; i < parentNode.params.length; i++) {
            param = parentNode.params[i];
            if (param.type === Syntax.Identifier) {
              declareIdentInScope(param.name, metadata, state);
            }
          }
        }

        // Include rest arg identifiers in the scope boundaries of their
        // functions
        if (parentNode.rest) {
          var metadata = initScopeMetadata(
            parentNode,
            path.slice(1),
            path[0]
          );
          declareIdentInScope(parentNode.rest.name, metadata, state);
        }

        // Named FunctionExpressions scope their name within the body block of
        // themselves only
        if (parentNode.type === Syntax.FunctionExpression && parentNode.id) {
          var metaData =
            initScopeMetadata(parentNode, path.parentNodeslice, parentNode);
          declareIdentInScope(parentNode.id.name, metaData, state);
        }
      }

      // Traverse and find all local identifiers in this closure first to
      // account for function/variable declaration hoisting
      collectClosureIdentsAndTraverse(node, path, state);
    }

    if (_nodeIsBlockScopeBoundary(node, parentNode)) {
      startIndex = state.g.buffer.length;
      state = utils.updateState(state, {
        localScope: {
          parentNode: parentNode,
          parentScope: state.localScope,
          identifiers: {},
          tempVarIndex: 0,
          tempVars: []
        }
      });

      if (parentNode.type === Syntax.CatchClause) {
        var metadata = initScopeMetadata(
          parentNode,
          path.slice(1),
          parentNode
        );
        declareIdentInScope(parentNode.param.name, metadata, state);
      }
      collectBlockIdentsAndTraverse(node, path, state);
    }
  }

  // Only catchup() before and after traversing a child node
  function traverser(node, path, state) {
    node.range && utils.catchup(node.range[0], state);
    traverse(node, path, state);
    node.range && utils.catchup(node.range[1], state);
  }

  utils.analyzeAndTraverse(walker, traverser, node, path, state);

  // Inject temp variables into the scope.
  if (startIndex !== null) {
    utils.injectTempVarDeclarations(state, startIndex);
  }
}
@param {object} node
@param {array} path
@param {object} state