2
\$\begingroup\$

I wrote a library that detects the version of the ECMAScript that is running on JavaScript, and that also allows to check support for few syntax-features.

For the ECMAScript version detection I run 4 checks, each check tests if JavaScript supports some features of a specific ECMAScript version. Until a check is successful, its specific ECMAScript version is returned. If no check matches, the return is null. Each check is just a detection for a specific ECMAScript version. Check 1 detects ES7, check 2 detects ES6, check 3 detects ES5 and check 4 detects ES3.

For the syntax-features detection I basically have an object, which is pre-declared in my library's factory, containing raw codes to be evaluated, that's syntax-features mixed with others. In this detection I build a string, which will be a code to be evaluated. The string will always contain an anonymous function, inside this anonymous function will be pushed raw codes according to the syntax features I want (if the raw code for any of these syntax features isn't specified in the earlier object, the non-existent specific(s) feature(s) won't affect anything), with an extra empty statement (semicolon). Finally, the result code will be evaluated. If an error is thrown, that means one or more of the features aren't supported, so we get false as detection result. Else, the detection result is true.

This is my library code:

;(function (root, name, factory) {
  'use strict';

  if (("function" === typeof define) && define.amd)
    define([exports], factory);

  else if (typeof exports === 'object')
    factory(exports);

  else
    factory(root[name] = {});

})(this, 'esx', function (exports) {
  'use strict';

  var features = {
      "arrayComprehensions": "[for(_ of [0])_]"
    , "arrowFunction": "(_=>_)"
    , "class": "(class{})"
    , "const": "const c=true"
    , "defaultParams": "(function(a=false){})"
    , "destructuring": "let {d}={a:true}"
    , "forOf": "for(var b of [])"
    , "generator": "(function*(){})"
    , "getter": "({get a(){}})"
    , "label": "l:0"
    , "let": "let o"
    , "reservedWords": "({catch:true})"
    , "setter": "({set a(v){}})"
    , "spread": "[...[]]"
    , "stringInterpolation": "`$\{0}`"
    , "stringLineBreak": "'\\\n'"
    , "super": "({b(){super.a}})"
    , "yield": "(function*(){yield true})"
  };

  // exports.features = features;

  function evaluate (code) {
    try {
      eval(code);
      return true;
    } catch(e) {
      return false;
    }
  }

  /**
   * Check if a set of features are supported.
   */
  function supports () {
    var code = "(function(){";
    var i = 0, len = arguments.length;

    for (; i < len; ++i) {
      var feature = arguments[i].toString();

      if (features.hasOwnProperty(feature))
        code += features[feature] + ';';
    }

    code += "})()";
    return evaluate(code);
  }

  exports.supports = supports;

  function checkES7 () {
    return supports("arrayComprehensions");
  }

  function checkES6 () {
    var methods = 'function' === typeof Object.assign &&
                  'function' === typeof Object.freeze;

    var syntax = supports(
                         "arrowFunction"
                       , "class"
                       , "const"
                       , "forOf"
                       , "defaultParams"
                       , "destructuring"
                       , "super"
                       , "yield"
                       );

    return methods && syntax;
  }

  function checkES5 () {
    var methods = 'function' === typeof [].filter &&
                  'function' === typeof Function.prototype.bind &&
                  'function' === typeof Object.defineProperty &&
                  'function' === typeof ''.trim &&
                  'object'   === typeof JSON;

    var syntax = supports("reservedWords");
    return methods && syntax;
  }

  function checkES3 () {
    return "function" === typeof [].hasOwnProperty;
  }

  /**
   * Check for ECMAScript version.
   */
  exports.detectVersion = function () {
    return checkES7() ? 7 :
           checkES6() ? 6 :
           checkES5() ? 5 :
           checkES3() ? 3 :
           null;
  };

});

My library can be used so, then:

console.log(esx.detectVersion() >= 6); // true here
console.log(esx.supports("arrowFunction", "super"));
// ^^^^^^ true here (supports arrow functions and super)

Please give your constructive criticisms and suggestions.

\$\endgroup\$
  • 1
    \$\begingroup\$ Apart from your style (not my cup of tea) I can not fault it. Though the few features used do not signify compliance to any particular ES version. As a useful library it would have to be comprehensive, but you have the working core. Also not sure why you are exposing esx.features as the first thing monkeys like me would do isesx.features.arrowFunctions = 'throw ":P"' and see what happens "possible exploit if ES version does not match expectation" Maybe do the tests within the libs context and esx.features.feature true/false and not let the monkeys get in before tests. Just a thought... \$\endgroup\$ – Blindman67 Nov 23 '16 at 18:00
  • \$\begingroup\$ I didn't test all features of each ES version, but I probably could, except for ES6 or ES7, because the first ES version check can also mean "higher" than the returned one (so a feature may be deprecated, or not), that's why in my example I've this condition esx.detectVersion() >= 6... Bad ! I accidentaly exposed the features raw because I worried about something, but I'll update that. The problem is that it's light-ening in here :/ (thundering) \$\endgroup\$ – Hydroper Nov 23 '16 at 18:41
  • 1
    \$\begingroup\$ For anyone finding this much later: Take note of Non-standard. Do not use! on developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…, which was used for checkES7. \$\endgroup\$ – janpio Jun 13 at 21:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.