text: 'use strict';
  
  var esprima;
  
  // Browserified version does not have esprima
  //
  // 1. For node.js just require module as deps
  // 2. For browser try to require mudule via external AMD system.
  //    If not found - try to fallback to window.esprima. If not
  //    found too - then fail to parse.
  //
  try {
    // workaround to exclude package from browserify list.
    // eslint-disable-next-line no-eval
    esprima = eval('require')('esprima');
  } catch (_) {
    /*global window */
    if (typeof window !== 'undefined') { esprima = window.esprima; }
  }
  
  var Type = require('../../type');
  
  function resolveJavascriptFunction(data) {
    if (data === null) return false;
  
    try {
      var source = '(' + data + ')',
          ast    = esprima.parse(source, { range: true });
  
      if (ast.type                    !== 'Program'             ||
          ast.body.length             !== 1                     ||
          ast.body[0].type            !== 'ExpressionStatement' ||
          (ast.body[0].expression.type !== 'ArrowFunctionExpression' &&
            ast.body[0].expression.type !== 'FunctionExpression')) {
        return false;
      }
  
      return true;
    } catch (err) {
      return false;
    }
  }
  
  function constructJavascriptFunction(data) {
    /*jslint evil:true*/
  
    var source = '(' + data + ')',
        ast    = esprima.parse(source, { range: true }),
        params = [],
        body,
        code;
  
    if (ast.type                    !== 'Program'             ||
        ast.body.length             !== 1                     ||
        ast.body[0].type            !== 'ExpressionStatement' ||
        (ast.body[0].expression.type !== 'ArrowFunctionExpression' &&
          ast.body[0].expression.type !== 'FunctionExpression')) {
      throw new Error('Failed to resolve function');
    }
  
    ast.body[0].expression.params.forEach(function (param) {
      params.push(param.name);
    });
  
    body = ast.body[0].expression.body.range;
  
    if (ast.body[0].expression.body.type === 'BlockStatement') {
      // Esprima's ranges include the first '{' and the last '}' characters on
      // function expressions. So cut them out.
      code = source.slice(body[0] + 1, body[1] - 1);
    } else {
      // ES6 arrow functions can omit the BlockStatement. In that case, just return
      // the body.
      code = 'return ' + source.slice(body[0], body[1]);
    }
  
    /*eslint-disable no-new-func*/
    if (ast.body[0].expression.async) {
      // we might be on earlier version of JS than ES2017, so we need the code
      // that retrieves the AsyncFunction constructor to be parsed at runtime
      /*eslint-disable no-eval*/
      var AsyncFunction = eval('Object.getPrototypeOf(async function(){}).constructor');
      return new AsyncFunction(params, code);
    }
    return new Function(params, code);
  }
  
  function representJavascriptFunction(object /*, style*/) {
    return object.toString();
  }
  
  function isFunction(object) {
    return Object.prototype.toString.call(object) === '[object Function]' ||
      Object.prototype.toString.call(object) === '[object AsyncFunction]';
  }
  
  module.exports = new Type('tag:yaml.org,2002:js/function', {
    kind: 'scalar',
    resolve: resolveJavascriptFunction,
    construct: constructJavascriptFunction,
    predicate: isFunction,
    represent: representJavascriptFunction
  });
  