update to 2018 jslint-es6 (#166)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 58826b5..4ba8f26 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,11 @@
 
 All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
 
+<a name="0.12.0"</a>
+## [0.12.0](https://github.com/reid/node-jslint/compare/v0.10.3...v0.12.0) (2018-02-03)
+
+### Update to es6 latest (2018-01-27)
+
 <a name="0.10.3"></a>
 ## [0.10.3](https://github.com/reid/node-jslint/compare/v0.10.1...v0.10.3) (2016-08-03)
 
diff --git a/lib/jslint-2018-01-26.js b/lib/jslint-2018-01-26.js
new file mode 100644
index 0000000..270ccf4
--- /dev/null
+++ b/lib/jslint-2018-01-26.js
@@ -0,0 +1,5014 @@
+// jslint.js
+// 2018-01-26
+// Copyright (c) 2015 Douglas Crockford  (www.JSLint.com)
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+
+// The Software shall be used for Good, not Evil.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+// jslint(source, option_object, global_array) is a function that takes 3
+// arguments. The second two arguments are optional.
+
+//      source          A text to analyze, a string or an array of strings.
+//      option_object   An object whose keys correspond to option names.
+//      global_array    An array of strings containing global variables that
+//                      the file is allowed readonly access.
+
+// jslint returns an object containing its results. The object contains a lot
+// of valuable information. It can be used to generate reports. The object
+// contains:
+
+//      directives: an array of directive comment tokens.
+//      edition: the version of JSLint that did the analysis.
+//      exports: the names exported from the module.
+//      froms: an array of strings representing each of the imports.
+//      functions: an array of objects that represent all of the functions
+//              declared in the file.
+//      global: an object representing the global object. Its .context property
+//              is an object containing a property for each global variable.
+//      id: "(JSLint)"
+//      json: true if the file is a JSON text.
+//      lines: an array of strings, the source.
+//      module: true if an import or export statement was used.
+//      ok: true if no warnings were generated. This is what you want.
+//      option: the option argument.
+//      property: a property object.
+//      stop: true if JSLint was unable to finish. You don't want this.
+//      tokens: an array of objects representing the tokens in the file.
+//      tree: the token objects arranged in a tree.
+//      warnings: an array of warning objects. A warning object can contain:
+//          name: "JSLintError"
+//          column: A column number in the file.
+//          line: A line number in the file.
+//          code: A warning code string.
+//          message: The warning message string.
+//          a: Exhibit A.
+//          b: Exhibit B.
+//          c: Exhibit C.
+//          d: Exhibit D.
+
+// jslint works in several phases. In any of these phases, errors might be
+// found. Sometimes JSLint is able to recover from an error and continue
+// parsing. In some cases, it cannot and will stop early. If that should happen,
+// repair your code and try again.
+
+// Phases:
+
+//      1. If the source is a single string, split it into an array of strings.
+//      2. Turn the source into an array of tokens.
+//      3. Furcate the tokens into a parse tree.
+//      4. Walk the tree, traversing all of the nodes of the tree. It is a
+//          recursive traversal. Each node may be processed on the way down
+//          (preaction) and on the way up (postaction).
+//      5. Check the whitespace between the tokens.
+
+// jslint can also examine JSON text. It decides that a file is JSON text if
+// the first token is "[" or "{". Processing of JSON text is much simpler than
+// the processing of JavaScript programs. Only the first three phases are
+// required.
+
+// WARNING: JSLint will hurt your feelings.
+
+/*jslint bitwise*/
+
+/*property
+    a, and, arity, assign, b, bad_assignment_a, bad_directive_a, bad_get,
+    bad_module_name_a, bad_option_a, bad_property_a, bad_set, bitwise, block,
+    body, browser, c, calls, catch, charCodeAt, closer, closure, code, column,
+    complex, concat, constant, context, convert, couch, create, d, dead,
+    default, devel, directive, directives, disrupt, dot, duplicate_a, edition,
+    ellipsis, else, empty_block, escape_mega, eval, every, expected_a,
+    expected_a_at_b_c, expected_a_b, expected_a_b_from_c_d,
+    expected_a_before_b, expected_a_next_at_b, expected_digits_after_a,
+    expected_four_digits, expected_identifier_a, expected_line_break_a_b,
+    expected_regexp_factor_a, expected_space_a_b, expected_statements_a,
+    expected_string_a, expected_type_string_a, exports, expression, extra,
+    finally, flag, for, forEach, free, from, froms, fud, fudge, function,
+    function_in_loop, functions, g, getset, global, i, id, identifier, import,
+    inc, indexOf, infix_in, init, initial, isArray, isFinite, isNaN, join,
+    json, keys, label, label_a, lbp, led, length, level, line, lines, live,
+    loop, m, margin, match, maxerr, maxlen, message, misplaced_a,
+    misplaced_directive_a, missing_browser, missing_m, module, multivar,
+    naked_block, name, names, nested_comment, new, node, not_label_a, nr, nud,
+    number_isNaN, ok, open, option, out_of_scope_a, parameters, pop, property,
+    push, qmark, quote, redefinition_a_b, replace, required_a_optional_b,
+    reserved_a, right, role, search, signature, single, slice, some, sort,
+    split, statement, stop, strict, subscript_a, switch, test, this, thru,
+    toString, todo_comment, tokens, too_long, too_many, too_many_digits, tree,
+    try, type, u, unclosed_comment, unclosed_mega, unclosed_string,
+    undeclared_a, unexpected_a, unexpected_a_after_b, unexpected_a_before_b,
+    unexpected_at_top_level_a, unexpected_char_a, unexpected_comment,
+    unexpected_directive_a, unexpected_expression_a, unexpected_label_a,
+    unexpected_parens, unexpected_space_a_b, unexpected_statement_a,
+    unexpected_trailing_space, unexpected_typeof_a, uninitialized_a,
+    unreachable_a, unregistered_property_a, unsafe, unused_a, use_double,
+    use_spaces, use_strict, used, value, var_loop, var_switch, variable,
+    warning, warnings, weird_condition_a, weird_expression_a, weird_loop,
+    weird_relation_a, white, wrap_assignment, wrap_condition, wrap_immediate,
+    wrap_parameter, wrap_regexp, wrap_unary, wrapped, writable, y
+*/
+
+const jslint = (function JSLint() {
+    "use strict";
+
+    function empty() {
+
+// The empty function produces a new empty object that inherits nothing. This is
+// much better than {} because confusions around accidental method names like
+// "constructor" are completely avoided.
+
+        return Object.create(null);
+    }
+
+    function populate(object, array, value) {
+
+// Augment an object by taking property names from an array of strings.
+
+        array.forEach(function (name) {
+            object[name] = value;
+        });
+    }
+
+    const allowed_option = {
+
+// These are the options that are recognized in the option object or that may
+// appear in a /*jslint*/ directive. Most options will have a boolean value,
+// usually true. Some options will also predefine some number of global
+// variables.
+
+        bitwise: true,
+        browser: [
+            "clearInterval",
+            "clearTimeout",
+            "document",
+            "event",
+            "FileReader",
+            "FormData",
+            "history",
+            "localStorage",
+            "location",
+            "name",
+            "navigator",
+            "screen",
+            "sessionStorage",
+            "setInterval",
+            "setTimeout",
+            "Storage",
+            "URL",
+            "XMLHttpRequest"
+        ],
+        couch: [
+            "emit", "getRow", "isArray", "log", "provides", "registerType",
+            "require", "send", "start", "sum", "toJSON"
+        ],
+        convert: true,
+        devel: [
+            "alert", "confirm", "console", "prompt"
+        ],
+        eval: true,
+        for: true,
+        fudge: true,
+        getset: true,
+        maxerr: 10000,
+        maxlen: 10000,
+        multivar: true,
+        node: [
+            "Buffer", "clearImmediate", "clearInterval", "clearTimeout",
+            "console", "exports", "module", "process", "querystring",
+            "require", "setImmediate", "setInterval", "setTimeout",
+            "__dirname", "__filename"
+        ],
+        single: true,
+        this: true,
+        white: true
+    };
+
+    const spaceop = {
+
+// This is the set of infix operators that require a space on each side.
+
+        "!=": true,
+        "!==": true,
+        "%": true,
+        "%=": true,
+        "&": true,
+        "&=": true,
+        "&&": true,
+        "*": true,
+        "*=": true,
+        "+=": true,
+        "-=": true,
+        "/": true,
+        "/=": true,
+        "<": true,
+        "<=": true,
+        "<<": true,
+        "<<=": true,
+        "=": true,
+        "==": true,
+        "===": true,
+        "=>": true,
+        ">": true,
+        ">=": true,
+        ">>": true,
+        ">>=": true,
+        ">>>": true,
+        ">>>=": true,
+        "^": true,
+        "^=": true,
+        "|": true,
+        "|=": true,
+        "||": true
+    };
+
+    const bitwiseop = {
+
+// These are the bitwise operators.
+
+        "~": true,
+        "^": true,
+        "^=": true,
+        "&": true,
+        "&=": true,
+        "|": true,
+        "|=": true,
+        "<<": true,
+        "<<=": true,
+        ">>": true,
+        ">>=": true,
+        ">>>": true,
+        ">>>=": true
+    };
+
+    const opener = {
+
+// The open and close pairs.
+
+        "(": ")",       // paren
+        "[": "]",       // bracket
+        "{": "}",       // brace
+        "${": "}"       // mega
+    };
+
+    const relationop = {
+
+// The relational operators.
+
+        "!=": true,
+        "!==": true,
+        "==": true,
+        "===": true,
+        "<": true,
+        "<=": true,
+        ">": true,
+        ">=": true
+    };
+
+    const standard = [
+
+// These are the globals that are provided by the language standard.
+
+        "Array", "ArrayBuffer", "Boolean", "DataView", "Date", "decodeURI",
+        "decodeURIComponent", "encodeURI", "encodeURIComponent", "Error",
+        "EvalError", "Float32Array", "Float64Array", "Generator",
+        "GeneratorFunction", "Int8Array", "Int16Array", "Int32Array", "Intl",
+        "JSON", "Map", "Math", "Number", "Object", "parseInt", "parseFloat",
+        "Promise", "Proxy", "RangeError", "ReferenceError", "Reflect", "RegExp",
+        "Set", "String", "Symbol", "SyntaxError", "System", "TypeError",
+        "Uint8Array", "Uint8ClampedArray", "Uint16Array", "Uint32Array",
+        "URIError", "WeakMap", "WeakSet"
+    ];
+
+    const bundle = {
+
+// The bundle contains the raw text messages that are generated by jslint. It
+// seems that they are all error messages and warnings. There are no "Atta
+// boy!" or "You are so awesome!" messages. There is no positive reinforcement
+// or encouragement. This relentless negativity can undermine self-esteem and
+// wound the inner child. But if you accept it as sound advice rather than as
+// personal criticism, it can make your programs better.
+
+        and: "The '&&' subexpression should be wrapped in parens.",
+        bad_assignment_a: "Bad assignment to '{a}'.",
+        bad_directive_a: "Bad directive '{a}'.",
+        bad_get: "A get function takes no parameters.",
+        bad_module_name_a: "Bad module name '{a}'.",
+        bad_option_a: "Bad option '{a}'.",
+        bad_property_a: "Bad property name '{a}'.",
+        bad_set: "A set function takes one parameter.",
+        duplicate_a: "Duplicate '{a}'.",
+        empty_block: "Empty block.",
+        escape_mega: "Unexpected escapement in mega literal.",
+        expected_a: "Expected '{a}'.",
+        expected_a_at_b_c: "Expected '{a}' at column {b}, not column {c}.",
+        expected_a_b: "Expected '{a}' and instead saw '{b}'.",
+        expected_a_b_from_c_d: "Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.",
+        expected_a_before_b: "Expected '{a}' before '{b}'.",
+        expected_a_next_at_b: "Expected '{a}' at column {b} on the next line.",
+        expected_digits_after_a: "Expected digits after '{a}'.",
+        expected_four_digits: "Expected four digits after '\\u'.",
+        expected_identifier_a: "Expected an identifier and instead saw '{a}'.",
+        expected_line_break_a_b: "Expected a line break between '{a}' and '{b}'.",
+        expected_regexp_factor_a: "Expected a regexp factor and instead saw '{a}'.",
+        expected_space_a_b: "Expected one space between '{a}' and '{b}'.",
+        expected_statements_a: "Expected statements before '{a}'.",
+        expected_string_a: "Expected a string and instead saw '{a}'.",
+        expected_type_string_a: "Expected a type string and instead saw '{a}'.",
+        function_in_loop: "Don't make functions within a loop.",
+        infix_in: "Unexpected 'in'. Compare with undefined, or use the hasOwnProperty method instead.",
+        label_a: "'{a}' is a statement label.",
+        misplaced_a: "Place '{a}' at the outermost level.",
+        misplaced_directive_a: "Place the '/*{a}*/' directive before the first statement.",
+        missing_browser: "/*global*/ requires the Assume a browser option.",
+        missing_m: "Expected 'm' flag on a multiline regular expression.",
+        naked_block: "Naked block.",
+        nested_comment: "Nested comment.",
+        not_label_a: "'{a}' is not a label.",
+        number_isNaN: "Use Number.isNaN function to compare with NaN.",
+        out_of_scope_a: "'{a}' is out of scope.",
+        redefinition_a_b: "Redefinition of '{a}' from line {b}.",
+        required_a_optional_b: "Required parameter '{a}' after optional parameter '{b}'.",
+        reserved_a: "Reserved name '{a}'.",
+        subscript_a: "['{a}'] is better written in dot notation.",
+        todo_comment: "Unexpected TODO comment.",
+        too_long: "Line too long.",
+        too_many: "Too many warnings.",
+        too_many_digits: "Too many digits.",
+        unclosed_comment: "Unclosed comment.",
+        unclosed_mega: "Unclosed mega literal.",
+        unclosed_string: "Unclosed string.",
+        undeclared_a: "Undeclared '{a}'.",
+        unexpected_a: "Unexpected '{a}'.",
+        unexpected_a_after_b: "Unexpected '{a}' after '{b}'.",
+        unexpected_a_before_b: "Unexpected '{a}' before '{b}'.",
+        unexpected_at_top_level_a: "Expected '{a}' to be in a function.",
+        unexpected_char_a: "Unexpected character '{a}'.",
+        unexpected_comment: "Unexpected comment.",
+        unexpected_directive_a: "When using modules, don't use directive '/*{a}'.",
+        unexpected_expression_a: "Unexpected expression '{a}' in statement position.",
+        unexpected_label_a: "Unexpected label '{a}'.",
+        unexpected_parens: "Don't wrap function literals in parens.",
+        unexpected_space_a_b: "Unexpected space between '{a}' and '{b}'.",
+        unexpected_statement_a: "Unexpected statement '{a}' in expression position.",
+        unexpected_trailing_space: "Unexpected trailing space.",
+        unexpected_typeof_a: "Unexpected 'typeof'. Use '===' to compare directly with {a}.",
+        uninitialized_a: "Uninitialized '{a}'.",
+        unreachable_a: "Unreachable '{a}'.",
+        unregistered_property_a: "Unregistered property name '{a}'.",
+        unsafe: "Unsafe character '{a}'.",
+        unused_a: "Unused '{a}'.",
+        use_double: "Use double quotes, not single quotes.",
+        use_spaces: "Use spaces, not tabs.",
+        use_strict: "This function needs a \"use strict\" pragma.",
+        var_loop: "Don't declare variables in a loop.",
+        var_switch: "Don't declare variables in a switch.",
+        weird_condition_a: "Weird condition '{a}'.",
+        weird_expression_a: "Weird expression '{a}'.",
+        weird_loop: "Weird loop.",
+        weird_relation_a: "Weird relation '{a}'.",
+        wrap_assignment: "Don't wrap assignment statements in parens.",
+        wrap_condition: "Wrap the condition in parens.",
+        wrap_immediate: (
+            "Wrap an immediate function invocation in parentheses to assist "
+            + "the reader in understanding that the expression is the result "
+            + "of a function, and not the function itself."
+        ),
+        wrap_parameter: "Wrap the parameter in parens.",
+        wrap_regexp: "Wrap this regexp in parens to avoid confusion.",
+        wrap_unary: "Wrap the unary expression in parens."
+    };
+
+// Regular expression literals:
+
+// supplant {variables}
+    const rx_supplant = /\{([^{}]*)\}/g;
+// carriage return, carriage return linefeed, or linefeed
+    const rx_crlf = /\n|\r\n?/;
+// unsafe characters that are silently deleted by one or more browsers
+    const rx_unsafe = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;
+// identifier
+    const rx_identifier = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/;
+    const rx_module = /^[a-zA-Z0-9_$:.@\-\/]+$/;
+    const rx_bad_property = /^_|\$|Sync\$|_$/;
+// star slash
+    const rx_star_slash = /\*\//;
+// slash star
+    const rx_slash_star = /\/\*/;
+// slash star or ending slash
+    const rx_slash_star_or_slash = /\/\*|\/$/;
+// uncompleted work comment
+    const rx_todo = /\b(?:todo|TO\s?DO|HACK)\b/;
+// tab
+    const rx_tab = /\t/g;
+// directive
+    const rx_directive = /^(jslint|property|global)\s+(.*)$/;
+    const rx_directive_part = /^([a-zA-Z$_][a-zA-Z0-9$_]*)\s*(?::\s*(true|false|[0-9]+)\s*)?(?:,\s*)?(.*)$/;
+// token (sorry it is so long)
+    const rx_token = /^((\s+)|([a-zA-Z_$][a-zA-Z0-9_$]*)|[(){}\[\]?,:;'"~`]|=(?:==?|>)?|\.+|[*\/][*\/=]?|\+[=+]?|-[=\-]?|[\^%]=?|&[&=]?|\|[|=]?|>{1,3}=?|<<?=?|!(?:!|==?)?|(0|[1-9][0-9]*))(.*)$/;
+    const rx_digits = /^([0-9]+)(.*)$/;
+    const rx_hexs = /^([0-9a-fA-F]+)(.*)$/;
+    const rx_octals = /^([0-7]+)(.*)$/;
+    const rx_bits = /^([01]+)(.*)$/;
+// mega
+    const rx_mega = /[`\\]|\$\{/;
+// indentation
+    const rx_colons = /^(.*)\?([:.]*)$/;
+    const rx_dot = /\.$/;
+// JSON number
+    const rx_JSON_number = /^-?\d+(?:\.\d*)?(?:e[\-+]?\d+)?$/i;
+// initial cap
+    const rx_cap = /^[A-Z]/;
+
+    function is_letter(string) {
+        return (
+            (string >= "a" && string <= "z\uffff")
+            || (string >= "A" && string <= "Z\uffff")
+        );
+    }
+
+    function supplant(string, object) {
+        return string.replace(rx_supplant, function (found, filling) {
+            const replacement = object[filling];
+            return (replacement !== undefined)
+                ? replacement
+                : found;
+        });
+    }
+
+    let anon = "anonymous"; // The guessed name for anonymous functions.
+    let blockage;           // The current block.
+    let block_stack;        // The stack of blocks.
+    let declared_globals;   // The object containing the global declarations.
+    let directives;         // The directive comments.
+    let directive_mode;     // true if directives are still allowed.
+    let early_stop;         // true if JSLint cannot finish.
+    let exports;            // The exported names and values.
+    let froms;              // The array collecting all import-from strings.
+    let fudge;              // true if the natural numbers start with 1.
+    let functionage;        // The current function.
+    let functions;          // The array containing all of the functions.
+    let global;             // The global object; the outermost context.
+    let json_mode;          // true if parsing JSON.
+    let lines;              // The array containing source lines.
+    let module_mode;        // true if import or export was used.
+    let next_token;         // The next token to be examined in the parse.
+    let option;             // The options parameter.
+    let property;           // The object containing the tallied property names.
+    let mega_mode;          // true if currently parsing a megastring literal.
+    let stack;              // The stack of functions.
+    let syntax;             // The object containing the parser.
+    let token;              // The current token being examined in the parse.
+    let token_nr;           // The number of the next token.
+    let tokens;             // The array of tokens.
+    let tenure;             // The predefined property registry.
+    let tree;               // The abstract parse tree.
+    let var_mode;           // "var" if using var; "let" if using let.
+    let warnings;           // The array collecting all generated warnings.
+
+// Error reportage functions:
+
+    function artifact(the_token) {
+
+// Return a string representing an artifact.
+
+        if (the_token === undefined) {
+            the_token = next_token;
+        }
+        return (the_token.id === "(string)" || the_token.id === "(number)")
+            ? String(the_token.value)
+            : the_token.id;
+    }
+
+    function artifact_line(the_token) {
+
+// Return the fudged line number of an artifact.
+
+        if (the_token === undefined) {
+            the_token = next_token;
+        }
+        return the_token.line + fudge;
+    }
+
+    function artifact_column(the_token) {
+
+// Return the fudged column number of an artifact.
+
+        if (the_token === undefined) {
+            the_token = next_token;
+        }
+        return the_token.from + fudge;
+    }
+
+    function warn_at(code, line, column, a, b, c, d) {
+
+// Report an error at some line and column of the program. The warning object
+// resembles an exception.
+
+        const warning = {         // ~~
+            name: "JSLintError",
+            column: column,
+            line: line,
+            code: code
+        };
+        if (a !== undefined) {
+            warning.a = a;
+        }
+        if (b !== undefined) {
+            warning.b = b;
+        }
+        if (c !== undefined) {
+            warning.c = c;
+        }
+        if (d !== undefined) {
+            warning.d = d;
+        }
+        warning.message = supplant(bundle[code] || code, warning);
+        warnings.push(warning);
+        return (
+            typeof option.maxerr === "number"
+            && warnings.length === option.maxerr
+        )   ? stop_at("too_many", line, column)
+            : warning;
+    }
+
+    function stop_at(code, line, column, a, b, c, d) {
+
+// Same as warn_at, except that it stops the analysis.
+
+        throw warn_at(code, line, column, a, b, c, d);
+    }
+
+    function warn(code, the_token, a, b, c, d) {
+
+// Same as warn_at, except the warning will be associated with a specific token.
+// If there is already a warning on this token, suppress the new one. It is
+// likely that the first warning will be the most meaningful.
+
+        if (the_token === undefined) {
+            the_token = next_token;
+        }
+        if (the_token.warning === undefined) {
+            the_token.warning = warn_at(
+                code,
+                the_token.line,
+                the_token.from,
+                a || artifact(the_token),
+                b,
+                c,
+                d
+            );
+            return the_token.warning;
+        }
+    }
+
+    function stop(code, the_token, a, b, c, d) {
+
+// Similar to warn and stop_at. If the token already had a warning, that
+// warning will be replaced with this new one. It is likely that the stopping
+// warning will be the more meaningful.
+
+        if (the_token === undefined) {
+            the_token = next_token;
+        }
+        delete the_token.warning;
+        throw warn(code, the_token, a, b, c, d);
+    }
+
+// Tokenize:
+
+    function tokenize(source) {
+
+// tokenize takes a source and produces from it an array of token objects.
+// JavaScript is notoriously difficult to tokenize because of the horrible
+// interactions between automatic semicolon insertion, regular expression
+// literals, and now megastring literals. JSLint benefits from eliminating
+// automatic semicolon insertion and nested megastring literals, which allows
+// full tokenization to precede parsing.
+
+// If the source is not an array, then it is split into lines at the
+// carriage return/linefeed.
+
+        lines = (Array.isArray(source))
+            ? source
+            : source.split(rx_crlf);
+        tokens = [];
+
+        let char;                   // a popular character
+        let column = 0;             // the column number of the next character
+        let first;                  // the first token
+        let from;                   // the starting column number of the token
+        let line = -1;              // the line number of the next character
+        let nr = 0;                 // the next token number
+        let previous = global;      // the previous token including comments
+        let prior = global;         // the previous token excluding comments
+        let mega_from;              // the starting column of megastring
+        let mega_line;              // the starting line of megastring
+        let snippet;                // a piece of string
+        let source_line;            // the current line source string
+
+        function next_line() {
+
+// Put the next line of source in source_line. If the line contains tabs,
+// replace them with spaces and give a warning. Also warn if the line contains
+// unsafe characters or is too damn long.
+
+            let at;
+            column = 0;
+            line += 1;
+            source_line = lines[line];
+            if (source_line !== undefined) {
+                at = source_line.search(rx_tab);
+                if (at >= 0) {
+                    if (!option.white) {
+                        warn_at("use_spaces", line, at + 1);
+                    }
+                    source_line = source_line.replace(rx_tab, " ");
+                }
+                at = source_line.search(rx_unsafe);
+                if (at >= 0) {
+                    warn_at(
+                        "unsafe",
+                        line,
+                        column + at,
+                        "U+" + source_line.charCodeAt(at).toString(16)
+                    );
+                }
+                if (option.maxlen && option.maxlen < source_line.length) {
+                    warn_at("too_long", line, source_line.length);
+                } else if (!option.white && source_line.slice(-1) === " ") {
+                    warn_at(
+                        "unexpected_trailing_space",
+                        line,
+                        source_line.length - 1
+                    );
+                }
+            }
+            return source_line;
+        }
+
+// Most tokens, including the identifiers, operators, and punctuators, can be
+// found with a regular expression. Regular expressions cannot correctly match
+// regular expression literals, so we will match those the hard way. String
+// literals and number literals can be matched by regular expressions, but they
+// don't provide good warnings. The functions snip, next_char, prev_char,
+// some_digits, and escape help in the parsing of literals.
+
+        function snip() {
+
+// Remove the last character from snippet.
+
+            snippet = snippet.slice(0, -1);
+        }
+
+        function next_char(match) {
+
+// Get the next character from the source line. Remove it from the source_line,
+// and append it to the snippet. Optionally check that the previous character
+// matched an expected value.
+
+            if (match !== undefined && char !== match) {
+                return stop_at(
+                    (char === "")
+                        ? "expected_a"
+                        : "expected_a_b",
+                    line,
+                    column - 1,
+                    match,
+                    char
+                );
+            }
+            if (source_line) {
+                char = source_line[0];
+                source_line = source_line.slice(1);
+                snippet += char;
+            } else {
+                char = "";
+                snippet += " ";
+            }
+            column += 1;
+            return char;
+        }
+
+        function back_char() {
+
+// Back up one character by moving a character from the end of the snippet to
+// the front of the source_line.
+
+            if (snippet) {
+                char = snippet.slice(-1);
+                source_line = char + source_line;
+                column -= 1;
+                snip();
+            } else {
+                char = "";
+            }
+            return char;
+        }
+
+        function some_digits(rx, quiet) {
+            const result = source_line.match(rx);
+            if (result) {
+                char = result[1];
+                column += char.length;
+                source_line = result[2];
+                snippet += char;
+            } else {
+                char = "";
+                if (!quiet) {
+                    warn_at(
+                        "expected_digits_after_a",
+                        line,
+                        column,
+                        snippet
+                    );
+                }
+            }
+            return char.length;
+        }
+
+        function escape(extra) {
+            switch (next_char("\\")) {
+            case "\\":
+            case "/":
+            case "`":
+            case "b":
+            case "f":
+            case "n":
+            case "r":
+            case "t":
+                break;
+            case "u":
+                if (next_char("u") === "{") {
+                    if (json_mode) {
+                        warn_at("unexpected_a", line, column - 1, char);
+                    }
+                    if (some_digits(rx_hexs) > 5) {
+                        warn_at("too_many_digits", line, column - 1);
+                    }
+                    if (next_char() !== "}") {
+                        stop_at("expected_a_before_b", line, column, "}", char);
+                    }
+                    next_char();
+                    return;
+                }
+                back_char();
+                if (some_digits(rx_hexs, true) < 4) {
+                    warn_at("expected_four_digits", line, column - 1);
+                }
+                break;
+            case "":
+                return stop_at("unclosed_string", line, column);
+            default:
+                if (!extra || extra.indexOf(char) < 0) {
+                    warn_at(
+                        "unexpected_a_before_b",
+                        line,
+                        column - 2,
+                        "\\",
+                        char
+                    );
+                }
+            }
+            next_char();
+        }
+
+        function make(id, value, identifier) {
+
+// Make the token object and append it to the tokens list.
+
+            const the_token = {
+                from: from,
+                id: id,
+                identifier: Boolean(identifier),
+                line: line,
+                nr: nr,
+                thru: column
+            };
+            tokens[nr] = the_token;
+            nr += 1;
+
+// Directives must appear before the first statement.
+
+            if (id !== "(comment)" && id !== ";") {
+                directive_mode = false;
+            }
+
+// If the token is to have a value, give it one.
+
+            if (value !== undefined) {
+                the_token.value = value;
+            }
+
+// If this token is an identifier that touches a preceding number, or
+// a "/", comment, or regular expression literal that touches a preceding
+// comment or regular expression literal, then give a missing space warning.
+// This warning is not suppressed by option.white.
+
+            if (
+                previous.line === line
+                && previous.thru === from
+                && (id === "(comment)" || id === "(regexp)" || id === "/")
+                && (previous.id === "(comment)" || previous.id === "(regexp)")
+            ) {
+                warn(
+                    "expected_space_a_b",
+                    the_token,
+                    artifact(previous),
+                    artifact(the_token)
+                );
+            }
+            if (previous.id === "." && id === "(number)") {
+                warn("expected_a_before_b", previous, "0", ".");
+            }
+            if (prior.id === "." && the_token.identifier) {
+                the_token.dot = true;
+            }
+
+// The previous token is used to detect adjacency problems.
+
+            previous = the_token;
+
+// The prior token is a previous token that was not a comment. The prior token
+// is used to disambiguate "/", which can mean division or regular expression
+// literal.
+
+            if (previous.id !== "(comment)") {
+                prior = previous;
+            }
+            return the_token;
+        }
+
+        function parse_directive(the_comment, body) {
+
+// JSLint recognizes three directives that can be encoded in comments. This
+// function processes one item, and calls itself recursively to process the
+// next one.
+
+            const result = body.match(rx_directive_part);
+            if (result) {
+                let allowed;
+                const name = result[1];
+                const value = result[2];
+                switch (the_comment.directive) {
+                case "jslint":
+                    allowed = allowed_option[name];
+                    switch (typeof allowed) {
+                    case "boolean":
+                    case "object":
+                        switch (value) {
+                        case "true":
+                        case "":
+                        case undefined:
+                            option[name] = true;
+                            if (Array.isArray(allowed)) {
+                                populate(declared_globals, allowed, false);
+                            }
+                            break;
+                        case "false":
+                            option[name] = false;
+                            break;
+                        default:
+                            warn(
+                                "bad_option_a",
+                                the_comment,
+                                name + ":" + value
+                            );
+                        }
+                        break;
+                    case "number":
+                        if (Number.isFinite(Number(value))) {
+                            option[name] = Number(value);
+                        } else {
+                            warn(
+                                "bad_option_a",
+                                the_comment,
+                                name + ":" + value
+                            );
+                        }
+                        break;
+                    default:
+                        warn("bad_option_a", the_comment, name);
+                    }
+                    break;
+                case "property":
+                    if (tenure === undefined) {
+                        tenure = empty();
+                    }
+                    tenure[name] = true;
+                    break;
+                case "global":
+                    if (value) {
+                        warn("bad_option_a", the_comment, name + ":" + value);
+                    }
+                    declared_globals[name] = false;
+                    module_mode = the_comment;
+                    break;
+                }
+                return parse_directive(the_comment, result[3]);
+            }
+            if (body) {
+                return stop("bad_directive_a", the_comment, body);
+            }
+        }
+
+        function comment(snippet) {
+
+// Make a comment object. Comments are not allowed in JSON text. Comments can
+// include directives and notices of incompletion.
+
+            const the_comment = make("(comment)", snippet);
+            if (Array.isArray(snippet)) {
+                snippet = snippet.join(" ");
+            }
+            if (!option.devel && rx_todo.test(snippet)) {
+                warn("todo_comment", the_comment);
+            }
+            const result = snippet.match(rx_directive);
+            if (result) {
+                if (!directive_mode) {
+                    warn_at("misplaced_directive_a", line, from, result[1]);
+                } else {
+                    the_comment.directive = result[1];
+                    parse_directive(the_comment, result[2]);
+                }
+                directives.push(the_comment);
+            }
+            return the_comment;
+        }
+
+        function regexp() {
+
+// Parse a regular expression literal.
+
+            let multi_mode = false;
+            let result;
+            let value;
+
+            function quantifier() {
+
+// Match an optional quantifier.
+
+                switch (char) {
+                case "?":
+                case "*":
+                case "+":
+                    next_char();
+                    break;
+                case "{":
+                    if (some_digits(rx_digits, true) === 0) {
+                        warn_at("expected_a", line, column, "0");
+                    }
+                    if (next_char() === ",") {
+                        some_digits(rx_digits, true);
+                        next_char();
+                    }
+                    next_char("}");
+                    break;
+                default:
+                    return;
+                }
+                if (char === "?") {
+                    next_char("?");
+                }
+            }
+
+            function subklass() {
+
+// Match a character in a character class.
+
+                switch (char) {
+                case "\\":
+                    escape("BbDdSsWw-[]^");
+                    return true;
+                case "[":
+                case "]":
+                case "/":
+                case "^":
+                case "-":
+                case "":
+                    return false;
+                case "`":
+                    if (mega_mode) {
+                        warn_at("unexpected_a", line, column, "`");
+                    }
+                    next_char();
+                    return true;
+                case " ":
+                    warn_at("expected_a_b", line, column, "\\u0020", " ");
+                    next_char();
+                    return true;
+                default:
+                    next_char();
+                    return true;
+                }
+            }
+
+            function ranges() {
+
+// Match a range of subclasses.
+
+                if (subklass()) {
+                    if (char === "-") {
+                        next_char("-");
+                        if (!subklass()) {
+                            return stop_at(
+                                "unexpected_a",
+                                line,
+                                column - 1,
+                                "-"
+                            );
+                        }
+                    }
+                    return ranges();
+                }
+            }
+
+            function klass() {
+
+// Match a class.
+
+                next_char("[");
+                if (char === "^") {
+                    next_char("^");
+                }
+                (function classy() {
+                    ranges();
+                    if (char !== "]" && char !== "") {
+                        warn_at(
+                            "expected_a_before_b",
+                            line,
+                            column,
+                            "\\",
+                            char
+                        );
+                        next_char();
+                        return classy();
+                    }
+                }());
+                next_char("]");
+            }
+
+            function choice() {
+
+                function group() {
+
+// Match a group that starts with left paren.
+
+                    next_char("(");
+                    if (char === "?") {
+                        next_char("?");
+                        switch (char) {
+                        case ":":
+                        case "=":
+                        case "!":
+                            next_char();
+                            break;
+                        default:
+                            next_char(":");
+                        }
+                    } else if (char === ":") {
+                        warn_at("expected_a_before_b", line, column, "?", ":");
+                    }
+                    choice();
+                    next_char(")");
+                }
+
+                function factor() {
+                    switch (char) {
+                    case "[":
+                        klass();
+                        return true;
+                    case "\\":
+                        escape("BbDdSsWw^${}[]():=!.-|*+?");
+                        return true;
+                    case "(":
+                        group();
+                        return true;
+                    case "?":
+                    case "+":
+                    case "*":
+                    case "}":
+                    case "{":
+                        warn_at("expected_a_before_b", line, column - 1, "\\", char);
+                        break;
+                    case "/":
+                    case "|":
+                    case "]":
+                    case ")":
+                    case "":
+                        return false;
+                    case "`":
+                        if (mega_mode) {
+                            warn_at("unexpected_a", line, column - 1, "`");
+                        }
+                        break;
+                    case " ":
+                        warn_at(
+                            "expected_a_b",
+                            line,
+                            column - 1,
+                            "\\s",
+                            " "
+                        );
+                        break;
+                    case "$":
+                        if (source_line[0] !== "/") {
+                            multi_mode = true;
+                        }
+                        break;
+                    case "^":
+                        if (snippet !== "^") {
+                            multi_mode = true;
+                        }
+                        break;
+                    }
+                    next_char();
+                    return true;
+                }
+
+                function sequence(follow) {
+                    if (factor()) {
+                        quantifier();
+                        return sequence(true);
+                    }
+                    if (!follow) {
+                        warn_at("expected_regexp_factor_a", line, column, char);
+                    }
+                }
+
+// Match a choice (a sequence that can be followed by | and another choice).
+
+                sequence();
+                if (char === "|") {
+                    next_char("|");
+                    return choice();
+                }
+            }
+
+// Scan the regexp literal. Give a warning if the first character is = because
+// /= looks like a division assignment operator.
+
+            snippet = "";
+            next_char();
+            if (char === "=") {
+                warn_at("expected_a_before_b", line, column, "\\", "=");
+            }
+            choice();
+
+// Make sure there is a closing slash.
+
+            snip();
+            value = snippet;
+            next_char("/");
+
+// Process dangling flag letters.
+
+            const allowed = {
+                g: true,
+                i: true,
+                m: true,
+                u: true,
+                y: true
+            };
+            const flag = empty();
+            (function make_flag() {
+                if (is_letter(char)) {
+                    if (allowed[char] !== true) {
+                        warn_at("unexpected_a", line, column, char);
+                    }
+                    allowed[char] = false;
+                    flag[char] = true;
+                    next_char();
+                    return make_flag();
+                }
+            }());
+            back_char();
+            if (char === "/" || char === "*") {
+                return stop_at("unexpected_a", line, from, char);
+            }
+            result = make("(regexp)", char);
+            result.flag = flag;
+            result.value = value;
+            if (multi_mode && !flag.m) {
+                warn_at("missing_m", line, column);
+            }
+            return result;
+        }
+
+        function string(quote) {
+
+// Make a string token.
+
+            let the_token;
+            snippet = "";
+            next_char();
+
+            return (function next() {
+                switch (char) {
+                case quote:
+                    snip();
+                    the_token = make("(string)", snippet);
+                    the_token.quote = quote;
+                    return the_token;
+                case "\\":
+                    escape(quote);
+                    break;
+                case "":
+                    return stop_at("unclosed_string", line, column);
+                case "`":
+                    if (mega_mode) {
+                        warn_at("unexpected_a", line, column, "`");
+                    }
+                    next_char("`");
+                    break;
+                default:
+                    next_char();
+                }
+                return next();
+            }());
+        }
+
+        function frack() {
+            if (char === ".") {
+                some_digits(rx_digits);
+                next_char();
+            }
+            if (char === "E" || char === "e") {
+                next_char();
+                if (char !== "+" && char !== "-") {
+                    back_char();
+                }
+                some_digits(rx_digits);
+                next_char();
+            }
+        }
+
+        function number() {
+            if (snippet === "0") {
+                switch (next_char()) {
+                case ".":
+                    frack();
+                    break;
+                case "b":
+                    some_digits(rx_bits);
+                    next_char();
+                    break;
+                case "o":
+                    some_digits(rx_octals);
+                    next_char();
+                    break;
+                case "x":
+                    some_digits(rx_hexs);
+                    next_char();
+                    break;
+                }
+            } else {
+                next_char();
+                frack();
+            }
+
+// If the next character after a number is a digit or letter, then something
+// unexpected is going on.
+
+            if (
+                (char >= "0" && char <= "9")
+                || (char >= "a" && char <= "z")
+                || (char >= "A" && char <= "Z")
+            ) {
+                return stop_at(
+                    "unexpected_a_after_b",
+                    line,
+                    column - 1,
+                    snippet.slice(-1),
+                    snippet.slice(0, -1)
+                );
+            }
+            back_char();
+            return make("(number)", snippet);
+        }
+
+        function lex() {
+            let array;
+            let i = 0;
+            let j = 0;
+            let last;
+            let result;
+            let the_token;
+            if (!source_line) {
+                source_line = next_line();
+                from = 0;
+                return (source_line === undefined)
+                    ? (mega_mode)
+                        ? stop_at("unclosed_mega", mega_line, mega_from)
+                        : make("(end)")
+                    : lex();
+            }
+            from = column;
+            result = source_line.match(rx_token);
+
+// result[1] token
+// result[2] whitespace
+// result[3] identifier
+// result[4] number
+// result[5] rest
+
+            if (!result) {
+                return stop_at(
+                    "unexpected_char_a",
+                    line,
+                    column,
+                    source_line[0]
+                );
+            }
+
+            snippet = result[1];
+            column += snippet.length;
+            source_line = result[5];
+
+// Whitespace was matched. Call lex again to get more.
+
+            if (result[2]) {
+                return lex();
+            }
+
+// The token is an identifier.
+
+            if (result[3]) {
+                return make(snippet, undefined, true);
+            }
+
+// The token is a number.
+
+            if (result[4]) {
+                return number(snippet);
+            }
+
+// The token is something miscellaneous.
+
+            switch (snippet) {
+
+// The token is a single or double quote string.
+
+            case "\"":
+                return string(snippet);
+
+            case "'":
+                if (!option.single) {
+                    warn_at("use_double", line, column);
+                }
+                return string(snippet);
+
+// The token is a megastring. We don't allow any kind of mega nesting.
+
+            case "`":
+                if (mega_mode) {
+                    return stop_at("expected_a_b", line, column, "}", "`");
+                }
+                snippet = "";
+                mega_from = from;
+                mega_line = line;
+                mega_mode = true;
+
+// Parsing a mega literal is tricky. First make a ` token.
+
+                make("`");
+                from += 1;
+
+// Then loop, building up a string, possibly from many lines, until seeing
+// the end of file, a closing `, or a ${ indicting an expression within the
+// string.
+
+                (function part() {
+                    const at = source_line.search(rx_mega);
+
+// If neither ` nor ${ is seen, then the whole line joins the snippet.
+
+                    if (at < 0) {
+                        snippet += source_line + "\n";
+                        return (next_line() === undefined)
+                            ? stop_at("unclosed_mega", mega_line, mega_from)
+                            : part();
+                    }
+
+// if either ` or ${ was found, then the preceding joins the snippet to become
+// a string token.
+
+                    snippet += source_line.slice(0, at);
+                    column += at;
+                    source_line = source_line.slice(at);
+                    if (source_line[0] === "\\") {
+                        stop_at("escape_mega", line, at);
+                    }
+                    make("(string)", snippet).quote = "`";
+                    snippet = "";
+
+// If ${, then make tokens that will become part of an expression until
+// a } token is made.
+
+                    if (source_line[0] === "$") {
+                        column += 2;
+                        make("${");
+                        source_line = source_line.slice(2);
+                        (function expr() {
+                            const id = lex().id;
+                            if (id === "{") {
+                                return stop_at(
+                                    "expected_a_b",
+                                    line,
+                                    column,
+                                    "}",
+                                    "{"
+                                );
+                            }
+                            if (id !== "}") {
+                                return expr();
+                            }
+                        }());
+                        return part();
+                    }
+                }());
+                source_line = source_line.slice(1);
+                column += 1;
+                mega_mode = false;
+                return make("`");
+
+// The token is a // comment.
+
+            case "//":
+                snippet = source_line;
+                source_line = "";
+                the_token = comment(snippet);
+                if (mega_mode) {
+                    warn("unexpected_comment", the_token, "`");
+                }
+                return the_token;
+
+// The token is a /* comment.
+
+            case "/*":
+                array = [];
+                if (source_line[0] === "/") {
+                    warn_at("unexpected_a", line, column + i, "/");
+                }
+                (function next() {
+                    if (source_line > "") {
+                        i = source_line.search(rx_star_slash);
+                        if (i >= 0) {
+                            return;
+                        }
+                        j = source_line.search(rx_slash_star);
+                        if (j >= 0) {
+                            warn_at("nested_comment", line, column + j);
+                        }
+                    }
+                    array.push(source_line);
+                    source_line = next_line();
+                    if (source_line === undefined) {
+                        return stop_at("unclosed_comment", line, column);
+                    }
+                    return next();
+                }());
+                snippet = source_line.slice(0, i);
+                j = snippet.search(rx_slash_star_or_slash);
+                if (j >= 0) {
+                    warn_at("nested_comment", line, column + j);
+                }
+                array.push(snippet);
+                column += i + 2;
+                source_line = source_line.slice(i + 2);
+                return comment(array);
+
+// The token is a slash.
+
+            case "/":
+
+// The / can be a division operator or the beginning of a regular expression
+// literal. It is not possible to know which without doing a complete parse.
+// We want to complete the tokenization before we begin to parse, so we will
+// estimate. This estimator can fail in some cases. For example, it cannot
+// know if "}" is ending a block or ending an object literal, so it can
+// behave incorrectly in that case; it is not meaningful to divide an
+// object, so it is likely that we can get away with it. We avoided the worst
+// cases by eliminating automatic semicolon insertion.
+
+                if (prior.identifier) {
+                    if (!prior.dot) {
+                        switch (prior.id) {
+                        case "return":
+                            return regexp();
+                        case "(begin)":
+                        case "case":
+                        case "delete":
+                        case "in":
+                        case "instanceof":
+                        case "new":
+                        case "typeof":
+                        case "void":
+                        case "yield":
+                            the_token = regexp();
+                            return stop("unexpected_a", the_token);
+                        }
+                    }
+                } else {
+                    last = prior.id[prior.id.length - 1];
+                    if ("(,=:?[".indexOf(last) >= 0) {
+                        return regexp();
+                    }
+                    if ("!&|{};~+-*%/^<>".indexOf(last) >= 0) {
+                        the_token = regexp();
+                        warn("wrap_regexp", the_token);
+                        return the_token;
+                    }
+                }
+                if (source_line[0] === "/") {
+                    column += 1;
+                    source_line = source_line.slice(1);
+                    snippet = "/=";
+                    warn_at("unexpected_a", line, column, "/=");
+                }
+                break;
+            }
+            return make(snippet);
+        }
+
+        first = lex();
+        json_mode = first.id === "{" || first.id === "[";
+
+// This is the only loop in JSLint. It will turn into a recursive call to lex
+// when ES6 has been finished and widely deployed and adopted.
+
+        while (true) {
+            if (lex().id === "(end)") {
+                break;
+            }
+        }
+    }
+
+// Parsing:
+
+// Parsing weaves the tokens into an abstract syntax tree. During that process,
+// a token may be given any of these properties:
+
+//      arity       string
+//      label       identifier
+//      name        identifier
+//      expression  expressions
+//      block       statements
+//      else        statements (else, default, catch)
+
+// Specialized tokens may have additional properties.
+
+    function survey(name) {
+        let id = name.id;
+
+// Tally the property name. If it is a string, only tally strings that conform
+// to the identifier rules.
+
+        if (id === "(string)") {
+            id = name.value;
+            if (!rx_identifier.test(id)) {
+                return id;
+            }
+        } else if (id === "`") {
+            if (name.value.length === 1) {
+                id = name.value[0].value;
+                if (!rx_identifier.test(id)) {
+                    return id;
+                }
+            }
+        } else if (!name.identifier) {
+            return stop("expected_identifier_a", name);
+        }
+
+// If we have seen this name before, increment its count.
+
+        if (typeof property[id] === "number") {
+            property[id] += 1;
+
+// If this is the first time seeing this property name, and if there is a
+// tenure list, then it must be on the list. Otherwise, it must conform to
+// the rules for good property names.
+
+        } else {
+            if (tenure !== undefined) {
+                if (tenure[id] !== true) {
+                    warn("unregistered_property_a", name);
+                }
+            } else {
+                if (name.identifier && rx_bad_property.test(id)) {
+                    warn("bad_property_a", name);
+                }
+            }
+            property[id] = 1;
+        }
+        return id;
+    }
+
+    function dispense() {
+
+// Deliver the next token, skipping the comments.
+
+        const cadet = tokens[token_nr];
+        token_nr += 1;
+        if (cadet.id === "(comment)") {
+            if (json_mode) {
+                warn("unexpected_a", cadet);
+            }
+            return dispense();
+        } else {
+            return cadet;
+        }
+    }
+
+    function lookahead() {
+
+// Look ahead one token without advancing.
+
+        const old_token_nr = token_nr;
+        const cadet = dispense(true);
+        token_nr = old_token_nr;
+        return cadet;
+    }
+
+    function advance(id, match) {
+
+// Produce the next token.
+
+// Attempt to give helpful names to anonymous functions.
+
+        if (token.identifier && token.id !== "function") {
+            anon = token.id;
+        } else if (token.id === "(string)" && rx_identifier.test(token.value)) {
+            anon = token.value;
+        }
+
+// Attempt to match next_token with an expected id.
+
+        if (id !== undefined && next_token.id !== id) {
+            return (match === undefined)
+                ? stop("expected_a_b", next_token, id, artifact())
+                : stop(
+                    "expected_a_b_from_c_d",
+                    next_token,
+                    id,
+                    artifact(match),
+                    artifact_line(match),
+                    artifact(next_token)
+                );
+        }
+
+// Promote the tokens, skipping comments.
+
+        token = next_token;
+        next_token = dispense();
+        if (next_token.id === "(end)") {
+            token_nr -= 1;
+        }
+    }
+
+// Parsing of JSON is simple:
+
+    function json_value() {
+        let negative;
+
+        function json_object() {
+            const brace = next_token;
+            const object = empty();
+            const properties = [];
+            brace.expression = properties;
+            advance("{");
+            if (next_token.id !== "}") {
+                (function next() {
+                    let name;
+                    let value;
+                    if (next_token.quote !== "\"") {
+                        warn("unexpected_a", next_token, next_token.quote);
+                    }
+                    name = next_token;
+                    advance("(string)");
+                    if (object[token.value] !== undefined) {
+                        warn("duplicate_a", token);
+                    } else if (token.value === "__proto__") {
+                        warn("bad_property_a", token);
+                    } else {
+                        object[token.value] = token;
+                    }
+                    advance(":");
+                    value = json_value();
+                    value.label = name;
+                    properties.push(value);
+                    if (next_token.id === ",") {
+                        advance(",");
+                        return next();
+                    }
+                }());
+            }
+            advance("}", brace);
+            return brace;
+        }
+
+        function json_array() {
+            const bracket = next_token;
+            const elements = [];
+            bracket.expression = elements;
+            advance("[");
+            if (next_token.id !== "]") {
+                (function next() {
+                    elements.push(json_value());
+                    if (next_token.id === ",") {
+                        advance(",");
+                        return next();
+                    }
+                }());
+            }
+            advance("]", bracket);
+            return bracket;
+        }
+
+        switch (next_token.id) {
+        case "{":
+            return json_object();
+        case "[":
+            return json_array();
+        case "true":
+        case "false":
+        case "null":
+            advance();
+            return token;
+        case "(number)":
+            if (!rx_JSON_number.test(next_token.value)) {
+                warn("unexpected_a");
+            }
+            advance();
+            return token;
+        case "(string)":
+            if (next_token.quote !== "\"") {
+                warn("unexpected_a", next_token, next_token.quote);
+            }
+            advance();
+            return token;
+        case "-":
+            negative = next_token;
+            negative.arity = "unary";
+            advance("-");
+            advance("(number)");
+            negative.expression = token;
+            return negative;
+        default:
+            stop("unexpected_a");
+        }
+    }
+
+// Now we parse JavaScript.
+
+    function enroll(name, role, readonly) {
+
+// Enroll a name into the current function context. The role can be exception,
+// function, label, parameter, or variable. We look for variable redefinition
+// because it causes confusion.
+
+        const id = name.id;
+
+// Reserved words may not be enrolled.
+
+        if (syntax[id] !== undefined && id !== "ignore") {
+            warn("reserved_a", name);
+        } else {
+
+// Has the name been enrolled in this context?
+
+            let earlier = functionage.context[id];
+            if (earlier) {
+                warn(
+                    "redefinition_a_b",
+                    name,
+                    name.id,
+                    earlier.line + fudge
+                );
+
+// Has the name been enrolled in an outer context?
+
+            } else {
+                stack.forEach(function (value) {
+                    const item = value.context[id];
+                    if (item !== undefined) {
+                        earlier = item;
+                    }
+                });
+                if (earlier) {
+                    if (id === "ignore") {
+                        if (earlier.role === "variable") {
+                            warn("unexpected_a", name);
+                        }
+                    } else {
+                        if (
+                            (
+                                role !== "exception"
+                                || earlier.role !== "exception"
+                            )
+                            && role !== "parameter"
+                            && role !== "function"
+                        ) {
+                            warn(
+                                "redefinition_a_b",
+                                name,
+                                name.id,
+                                earlier.line + fudge
+                            );
+                        }
+                    }
+                }
+
+// Enroll it.
+
+                functionage.context[id] = name;
+                name.dead = true;
+                name.function = functionage;
+                name.init = false;
+                name.role = role;
+                name.used = 0;
+                name.writable = !readonly;
+            }
+        }
+    }
+
+    function expression(rbp, initial) {
+
+// This is the heart of the Pratt parser. I retained Pratt's nomenclature.
+// They are elements of the parsing method called Top Down Operator Precedence.
+
+// nud     Null denotation
+// led     Left denotation
+// lbp     Left binding power
+// rbp     Right binding power
+
+// It processes a nud (variable, constant, prefix operator). It will then
+// process leds (infix operators) until the bind powers cause it to stop. It
+// returns the expression's parse tree.
+
+        let left;
+        let the_symbol;
+
+// Statements will have already advanced, so advance now only if the token is
+// not the first of a statement,
+
+        if (!initial) {
+            advance();
+        }
+        the_symbol = syntax[token.id];
+        if (the_symbol !== undefined && the_symbol.nud !== undefined) {
+            left = the_symbol.nud();
+        } else if (token.identifier) {
+            left = token;
+            left.arity = "variable";
+        } else {
+            return stop("unexpected_a", token);
+        }
+        (function right() {
+            the_symbol = syntax[next_token.id];
+            if (
+                the_symbol !== undefined
+                && the_symbol.led !== undefined
+                && rbp < the_symbol.lbp
+            ) {
+                advance();
+                left = the_symbol.led(left);
+                return right();
+            }
+        }());
+        return left;
+    }
+
+    function condition() {
+
+// Parse the condition part of a do, if, while.
+
+        const the_paren = next_token;
+        let the_value;
+        the_paren.free = true;
+        advance("(");
+        the_value = expression(0);
+        advance(")");
+        if (the_value.wrapped === true) {
+            warn("unexpected_a", the_paren);
+        }
+        switch (the_value.id) {
+        case "?":
+        case "~":
+        case "&":
+        case "|":
+        case "^":
+        case "<<":
+        case ">>":
+        case ">>>":
+        case "+":
+        case "-":
+        case "*":
+        case "/":
+        case "%":
+        case "typeof":
+        case "(number)":
+        case "(string)":
+            warn("unexpected_a", the_value);
+            break;
+        }
+        return the_value;
+    }
+
+    function is_weird(thing) {
+        return (
+            thing.id === "(regexp)"
+            || thing.id === "{"
+            || thing.id === "=>"
+            || thing.id === "function"
+            || (thing.id === "[" && thing.arity === "unary")
+        );
+    }
+
+    function are_similar(a, b) {
+        if (a === b) {
+            return true;
+        }
+        if (Array.isArray(a)) {
+            return (
+                Array.isArray(b)
+                && a.length === b.length
+                && a.every(function (value, index) {
+                    return are_similar(value, b[index]);
+                })
+            );
+        }
+        if (Array.isArray(b)) {
+            return false;
+        }
+        if (a.id === "(number)" && b.id === "(number)") {
+            return a.value === b.value;
+        }
+        let a_string;
+        let b_string;
+        if (a.id === "(string)") {
+            a_string = a.value;
+        } else if (a.id === "`" && a.constant) {
+            a_string = a.value[0];
+        }
+        if (b.id === "(string)") {
+            b_string = b.value;
+        } else if (b.id === "`" && b.constant) {
+            b_string = b.value[0];
+        }
+        if (typeof a_string === "string") {
+            return a_string === b_string;
+        }
+        if (is_weird(a) || is_weird(b)) {
+            return false;
+        }
+        if (a.arity === b.arity && a.id === b.id) {
+            if (a.id === ".") {
+                return (
+                    are_similar(a.expression, b.expression)
+                    && are_similar(a.name, b.name)
+                );
+            }
+            switch (a.arity) {
+            case "unary":
+                return are_similar(a.expression, b.expression);
+            case "binary":
+                return (
+                    a.id !== "("
+                    && are_similar(a.expression[0], b.expression[0])
+                    && are_similar(a.expression[1], b.expression[1])
+                );
+            case "ternary":
+                return (
+                    are_similar(a.expression[0], b.expression[0])
+                    && are_similar(a.expression[1], b.expression[1])
+                    && are_similar(a.expression[2], b.expression[2])
+                );
+            case "function":
+            case "regexp":
+                return false;
+            default:
+                return true;
+            }
+        }
+        return false;
+    }
+
+    function semicolon() {
+
+// Try to match a semicolon.
+
+        if (next_token.id === ";") {
+            advance(";");
+        } else {
+            warn_at(
+                "expected_a_b",
+                token.line,
+                token.thru,
+                ";",
+                artifact(next_token)
+            );
+        }
+        anon = "anonymous";
+    }
+
+    function statement() {
+
+// Parse a statement. Any statement may have a label, but only four statements
+// have use for one. A statement can be one of the standard statements, or
+// an assignment expression, or an invocation expression.
+
+        let first;
+        let the_label;
+        let the_statement;
+        let the_symbol;
+        advance();
+        if (token.identifier && next_token.id === ":") {
+            the_label = token;
+            if (the_label.id === "ignore") {
+                warn("unexpected_a", the_label);
+            }
+            advance(":");
+            switch (next_token.id) {
+            case "do":
+            case "for":
+            case "switch":
+            case "while":
+                enroll(the_label, "label", true);
+                the_label.init = true;
+                the_label.dead = false;
+                the_statement = statement();
+                the_statement.label = the_label;
+                the_statement.statement = true;
+                return the_statement;
+            default:
+                advance();
+                warn("unexpected_label_a", the_label);
+            }
+        }
+
+// Parse the statement.
+
+        first = token;
+        first.statement = true;
+        the_symbol = syntax[first.id];
+        if (the_symbol !== undefined && the_symbol.fud !== undefined) {
+            the_symbol.disrupt = false;
+            the_symbol.statement = true;
+            the_statement = the_symbol.fud();
+        } else {
+
+// It is an expression statement.
+
+            the_statement = expression(0, true);
+            if (the_statement.wrapped && the_statement.id !== "(") {
+                warn("unexpected_a", first);
+            }
+            semicolon();
+        }
+        if (the_label !== undefined) {
+            the_label.dead = true;
+        }
+        return the_statement;
+    }
+
+    function statements() {
+
+// Parse a list of statements. Give a warning if an unreachable statement
+// follows a disruptive statement.
+
+        const array = [];
+        (function next(disrupt) {
+            let a_statement;
+            switch (next_token.id) {
+            case "}":
+            case "case":
+            case "default":
+            case "else":
+            case "(end)":
+                break;
+            default:
+                a_statement = statement();
+                array.push(a_statement);
+                if (disrupt) {
+                    warn("unreachable_a", a_statement);
+                }
+                return next(a_statement.disrupt);
+            }
+        }(false));
+        return array;
+    }
+
+    function not_top_level(thing) {
+
+// Some features should not be at the outermost level.
+
+        if (functionage === global) {
+            warn("unexpected_at_top_level_a", thing);
+        }
+    }
+
+    function top_level_only(the_thing) {
+
+// Some features must be at the most outermost level.
+
+        if (blockage !== global) {
+            warn("misplaced_a", the_thing);
+        }
+    }
+
+    function block(special) {
+
+// Parse a block, a sequence of statements wrapped in braces.
+//  special "body"      The block is a function body.
+//          "ignore"    No warning on an empty block.
+//          "naked"     No advance.
+//          undefined   An ordinary block.
+
+        let stmts;
+        let the_block;
+        if (special !== "naked") {
+            advance("{");
+        }
+        the_block = token;
+        the_block.arity = "statement";
+        the_block.body = special === "body";
+
+// All top level function bodies should include the "use strict" pragma unless
+// the whole file is strict or the file is a module or the function parameters
+// use es6 syntax.
+
+        if (
+            special === "body"
+            && stack.length === 1
+            && next_token.value === "use strict"
+        ) {
+            the_block.strict = next_token;
+            next_token.statement = true;
+            advance("(string)");
+            advance(";");
+        }
+        stmts = statements();
+        the_block.block = stmts;
+        if (stmts.length === 0) {
+            if (!option.devel && special !== "ignore") {
+                warn("empty_block", the_block);
+            }
+            the_block.disrupt = false;
+        } else {
+            the_block.disrupt = stmts[stmts.length - 1].disrupt;
+        }
+        advance("}");
+        return the_block;
+    }
+
+    function mutation_check(the_thing) {
+
+// The only expressions that may be assigned to are
+//      e.b
+//      e[b]
+//      v
+//      [destructure]
+//      {destructure}
+
+        if (
+            the_thing.arity !== "variable"
+            && the_thing.id !== "."
+            && the_thing.id !== "["
+            && the_thing.id !== "{"
+        ) {
+            warn("bad_assignment_a", the_thing);
+            return false;
+        }
+        return true;
+    }
+
+    function left_check(left, right) {
+
+// Warn if the left is not one of these:
+//      e.b
+//      e[b]
+//      e()
+//      identifier
+
+        const id = left.id;
+        if (
+            !left.identifier
+            && (
+                left.arity !== "binary"
+                || (id !== "." && id !== "(" && id !== "[")
+            )
+        ) {
+            warn("unexpected_a", right);
+            return false;
+        }
+        return true;
+    }
+
+// These functions are used to specify the grammar of our language:
+
+    function symbol(id, bp) {
+
+// Make a symbol if it does not already exist in the language's syntax.
+
+        let the_symbol = syntax[id];
+        if (the_symbol === undefined) {
+            the_symbol = empty();
+            the_symbol.id = id;
+            the_symbol.lbp = bp || 0;
+            syntax[id] = the_symbol;
+        }
+        return the_symbol;
+    }
+
+    function assignment(id) {
+
+// Make an assignment operator. The one true assignment is different because
+// its left side, when it is a variable, is not treated as an expression.
+// That case is special because that is when a variable gets initialized. The
+// other assignment operators can modify, but they cannot initialize.
+
+        const the_symbol = symbol(id, 20);
+        the_symbol.led = function (left) {
+            const the_token = token;
+            let right;
+            the_token.arity = "assignment";
+            right = expression(20 - 1);
+            if (id === "=" && left.arity === "variable") {
+                the_token.names = left;
+                the_token.expression = right;
+            } else {
+                the_token.expression = [left, right];
+            }
+            switch (right.arity) {
+            case "assignment":
+            case "pre":
+            case "post":
+                warn("unexpected_a", right);
+                break;
+            }
+            mutation_check(left);
+            return the_token;
+        };
+        return the_symbol;
+    }
+
+    function constant(id, type, value) {
+
+// Make a constant symbol.
+
+        const the_symbol = symbol(id);
+        the_symbol.constant = true;
+        the_symbol.nud = (typeof value === "function")
+            ? value
+            : function () {
+                token.constant = true;
+                if (value !== undefined) {
+                    token.value = value;
+                }
+                return token;
+            };
+        the_symbol.type = type;
+        the_symbol.value = value;
+        return the_symbol;
+    }
+
+    function infix(id, bp, f) {
+
+// Make an infix operator.
+
+        const the_symbol = symbol(id, bp);
+        the_symbol.led = function (left) {
+            const the_token = token;
+            the_token.arity = "binary";
+            if (f !== undefined) {
+                return f(left);
+            }
+            the_token.expression = [left, expression(bp)];
+            return the_token;
+        };
+        return the_symbol;
+    }
+
+    function infixr(id, bp) {
+
+// Make a right associative infix operator.
+
+        const the_symbol = symbol(id, bp);
+        the_symbol.led = function (left) {
+            const the_token = token;
+            the_token.arity = "binary";
+            the_token.expression = [left, expression(bp - 1)];
+            return the_token;
+        };
+        return the_symbol;
+    }
+
+    function post(id) {
+
+// Make one of the post operators.
+
+        const the_symbol = symbol(id, 150);
+        the_symbol.led = function (left) {
+            token.expression = left;
+            token.arity = "post";
+            mutation_check(token.expression);
+            return token;
+        };
+        return the_symbol;
+    }
+
+    function pre(id) {
+
+// Make one of the pre operators.
+
+        const the_symbol = symbol(id);
+        the_symbol.nud = function () {
+            const the_token = token;
+            the_token.arity = "pre";
+            the_token.expression = expression(150);
+            mutation_check(the_token.expression);
+            return the_token;
+        };
+        return the_symbol;
+    }
+
+    function prefix(id, f) {
+
+// Make a prefix operator.
+
+        const the_symbol = symbol(id);
+        the_symbol.nud = function () {
+            const the_token = token;
+            the_token.arity = "unary";
+            if (typeof f === "function") {
+                return f();
+            }
+            the_token.expression = expression(150);
+            return the_token;
+        };
+        return the_symbol;
+    }
+
+    function stmt(id, f) {
+
+// Make a statement.
+
+        const the_symbol = symbol(id);
+        the_symbol.fud = function () {
+            token.arity = "statement";
+            return f();
+        };
+        return the_symbol;
+    }
+
+    function ternary(id1, id2) {
+
+// Make a ternary operator.
+
+        const the_symbol = symbol(id1, 30);
+        the_symbol.led = function (left) {
+            const the_token = token;
+            const second = expression(20);
+            advance(id2);
+            token.arity = "ternary";
+            the_token.arity = "ternary";
+            the_token.expression = [left, second, expression(10)];
+            return the_token;
+        };
+        return the_symbol;
+    }
+
+// Begin defining the language.
+
+    syntax = empty();
+
+    symbol("}");
+    symbol(")");
+    symbol("]");
+    symbol(",");
+    symbol(";");
+    symbol(":");
+    symbol("*/");
+    symbol("await");
+    symbol("case");
+    symbol("catch");
+    symbol("class");
+    symbol("default");
+    symbol("else");
+    symbol("enum");
+    symbol("finally");
+    symbol("implements");
+    symbol("interface");
+    symbol("package");
+    symbol("private");
+    symbol("protected");
+    symbol("public");
+    symbol("static");
+    symbol("super");
+    symbol("void");
+    symbol("yield");
+
+    constant("(number)", "number");
+    constant("(regexp)", "regexp");
+    constant("(string)", "string");
+    constant("arguments", "object", function () {
+        warn("unexpected_a", token);
+        return token;
+    });
+    constant("eval", "function", function () {
+        if (!option.eval) {
+            warn("unexpected_a", token);
+        } else if (next_token.id !== "(") {
+            warn("expected_a_before_b", next_token, "(", artifact());
+        }
+        return token;
+    });
+    constant("false", "boolean", false);
+    constant("Function", "function", function () {
+        if (!option.eval) {
+            warn("unexpected_a", token);
+        } else if (next_token.id !== "(") {
+            warn("expected_a_before_b", next_token, "(", artifact());
+        }
+        return token;
+    });
+    constant("ignore", "undefined", function () {
+        warn("unexpected_a", token);
+        return token;
+    });
+    constant("Infinity", "number", Infinity);
+    constant("isFinite", "function", function () {
+        warn("expected_a_b", token, "Number.isFinite", "isFinite");
+        return token;
+    });
+    constant("isNaN", "function", function () {
+        warn("number_isNaN", token);
+        return token;
+    });
+    constant("NaN", "number", NaN);
+    constant("null", "null", null);
+    constant("this", "object", function () {
+        if (!option.this) {
+            warn("unexpected_a", token);
+        }
+        return token;
+    });
+    constant("true", "boolean", true);
+    constant("undefined", "undefined");
+
+    assignment("=");
+    assignment("+=");
+    assignment("-=");
+    assignment("*=");
+    assignment("/=");
+    assignment("%=");
+    assignment("&=");
+    assignment("|=");
+    assignment("^=");
+    assignment("<<=");
+    assignment(">>=");
+    assignment(">>>=");
+
+    infix("||", 40);
+    infix("&&", 50);
+    infix("|", 70);
+    infix("^", 80);
+    infix("&", 90);
+    infix("==", 100);
+    infix("===", 100);
+    infix("!=", 100);
+    infix("!==", 100);
+    infix("<", 110);
+    infix(">", 110);
+    infix("<=", 110);
+    infix(">=", 110);
+    infix("in", 110);
+    infix("instanceof", 110);
+    infix("<<", 120);
+    infix(">>", 120);
+    infix(">>>", 120);
+    infix("+", 130);
+    infix("-", 130);
+    infix("*", 140);
+    infix("/", 140);
+    infix("%", 140);
+    infixr("**", 150);
+    infix("(", 160, function (left) {
+        const the_paren = token;
+        let the_argument;
+        if (left.id !== "function") {
+            left_check(left, the_paren);
+        }
+        if (functionage.arity === "statement" && left.identifier) {
+            functionage.name.calls[left.id] = left;
+        }
+        the_paren.expression = [left];
+        if (next_token.id !== ")") {
+            (function next() {
+                let ellipsis;
+                if (next_token.id === "...") {
+                    ellipsis = true;
+                    advance("...");
+                }
+                the_argument = expression(10);
+                if (ellipsis) {
+                    the_argument.ellipsis = true;
+                }
+                the_paren.expression.push(the_argument);
+                if (next_token.id === ",") {
+                    advance(",");
+                    return next();
+                }
+            }());
+        }
+        advance(")", the_paren);
+        if (the_paren.expression.length === 2) {
+            the_paren.free = true;
+            if (the_argument.wrapped === true) {
+                warn("unexpected_a", the_paren);
+            }
+            if (the_argument.id === "(") {
+                the_argument.wrapped = true;
+            }
+        } else {
+            the_paren.free = false;
+        }
+        return the_paren;
+    });
+    infix(".", 170, function (left) {
+        const the_token = token;
+        const name = next_token;
+        if (
+            (
+                left.id !== "(string)"
+                || (name.id !== "indexOf" && name.id !== "repeat")
+            )
+            && (
+                left.id !== "["
+                || (
+                    name.id !== "concat"
+                    && name.id !== "forEach"
+                    && name.id !== "join"
+                    && name.id !== "map"
+                )
+            )
+            && (left.id !== "+" || name.id !== "slice")
+            && (
+                left.id !== "(regexp)"
+                || (name.id !== "exec" && name.id !== "test")
+            )
+        ) {
+            left_check(left, the_token);
+        }
+        if (!name.identifier) {
+            stop("expected_identifier_a");
+        }
+        advance();
+        survey(name);
+
+// The property name is not an expression.
+
+        the_token.name = name;
+        the_token.expression = left;
+        return the_token;
+    });
+    infix("[", 170, function (left) {
+        const the_token = token;
+        const the_subscript = expression(0);
+        if (the_subscript.id === "(string)" || the_subscript.id === "`") {
+            const name = survey(the_subscript);
+            if (rx_identifier.test(name)) {
+                warn("subscript_a", the_subscript, name);
+            }
+        }
+        left_check(left, the_token);
+        the_token.expression = [left, the_subscript];
+        advance("]");
+        return the_token;
+    });
+    infix("=>", 170, function (left) {
+        return stop("wrap_parameter", left);
+    });
+
+    function do_tick() {
+        const the_tick = token;
+        the_tick.value = [];
+        the_tick.expression = [];
+        if (next_token.id !== "`") {
+            (function part() {
+                advance("(string)");
+                the_tick.value.push(token);
+                if (next_token.id === "${") {
+                    advance("${");
+                    the_tick.expression.push(expression(0));
+                    advance("}");
+                    return part();
+                }
+            }());
+        }
+        advance("`");
+        return the_tick;
+    }
+
+    infix("`", 160, function (left) {
+        const the_tick = do_tick();
+        left_check(left, the_tick);
+        the_tick.expression = [left].concat(the_tick.expression);
+        return the_tick;
+    });
+
+    post("++");
+    post("--");
+    pre("++");
+    pre("--");
+
+    prefix("+");
+    prefix("-");
+    prefix("~");
+    prefix("!");
+    prefix("!!");
+    prefix("[", function () {
+        const the_token = token;
+        the_token.expression = [];
+        if (next_token.id !== "]") {
+            (function next() {
+                let element;
+                let ellipsis = false;
+                if (next_token.id === "...") {
+                    ellipsis = true;
+                    advance("...");
+                }
+                element = expression(10);
+                if (ellipsis) {
+                    element.ellipsis = true;
+                }
+                the_token.expression.push(element);
+                if (next_token.id === ",") {
+                    advance(",");
+                    return next();
+                }
+            }());
+        }
+        advance("]");
+        return the_token;
+    });
+    prefix("/=", function () {
+        stop("expected_a_b", token, "/\\=", "/=");
+    });
+    prefix("=>", function () {
+        return stop("expected_a_before_b", token, "()", "=>");
+    });
+    prefix("new", function () {
+        const the_new = token;
+        const right = expression(160);
+        if (next_token.id !== "(") {
+            warn("expected_a_before_b", next_token, "()", artifact(next_token));
+        }
+        the_new.expression = right;
+        return the_new;
+    });
+    prefix("typeof");
+    prefix("void", function () {
+        const the_void = token;
+        warn("unexpected_a", the_void);
+        the_void.expression = expression(0);
+        return the_void;
+    });
+
+    function parameter_list() {
+        let complex = false;
+        const list = [];
+        let optional;
+        const signature = ["("];
+        if (next_token.id !== ")" && next_token.id !== "(end)") {
+            (function parameter() {
+                let ellipsis = false;
+                let param;
+                if (next_token.id === "{") {
+                    complex = true;
+                    if (optional !== undefined) {
+                        warn(
+                            "required_a_optional_b",
+                            next_token,
+                            next_token.id,
+                            optional.id
+                        );
+                    }
+                    param = next_token;
+                    param.names = [];
+                    advance("{");
+                    signature.push("{");
+                    (function subparameter() {
+                        let subparam = next_token;
+                        if (!subparam.identifier) {
+                            return stop("expected_identifier_a");
+                        }
+                        survey(subparam);
+                        advance();
+                        signature.push(subparam.id);
+                        if (next_token.id === ":") {
+                            advance(":");
+                            advance();
+                            token.label = subparam;
+                            subparam = token;
+                            if (!subparam.identifier) {
+                                return stop("expected_identifier_a");
+                            }
+                        }
+                        param.names.push(subparam);
+                        if (next_token.id === ",") {
+                            advance(",");
+                            signature.push(", ");
+                            return subparameter();
+                        }
+                    }());
+                    list.push(param);
+                    advance("}");
+                    signature.push("}");
+                    if (next_token.id === ",") {
+                        advance(",");
+                        signature.push(", ");
+                        return parameter();
+                    }
+                } else if (next_token.id === "[") {
+                    complex = true;
+                    if (optional !== undefined) {
+                        warn(
+                            "required_a_optional_b",
+                            next_token,
+                            next_token.id,
+                            optional.id
+                        );
+                    }
+                    param = next_token;
+                    param.names = [];
+                    advance("[");
+                    signature.push("[]");
+                    (function subparameter() {
+                        const subparam = next_token;
+                        if (!subparam.identifier) {
+                            return stop("expected_identifier_a");
+                        }
+                        advance();
+                        param.names.push(subparam);
+                        if (next_token.id === ",") {
+                            advance(",");
+                            return subparameter();
+                        }
+                    }());
+                    list.push(param);
+                    advance("]");
+                    if (next_token.id === ",") {
+                        advance(",");
+                        signature.push(", ");
+                        return parameter();
+                    }
+                } else {
+                    if (next_token.id === "...") {
+                        complex = true;
+                        ellipsis = true;
+                        signature.push("...");
+                        advance("...");
+                        if (optional !== undefined) {
+                            warn(
+                                "required_a_optional_b",
+                                next_token,
+                                next_token.id,
+                                optional.id
+                            );
+                        }
+                    }
+                    if (!next_token.identifier) {
+                        return stop("expected_identifier_a");
+                    }
+                    param = next_token;
+                    list.push(param);
+                    advance();
+                    signature.push(param.id);
+                    if (ellipsis) {
+                        param.ellipsis = true;
+                    } else {
+                        if (next_token.id === "=") {
+                            complex = true;
+                            optional = param;
+                            advance("=");
+                            param.expression = expression(0);
+                        } else {
+                            if (optional !== undefined) {
+                                warn(
+                                    "required_a_optional_b",
+                                    param,
+                                    param.id,
+                                    optional.id
+                                );
+                            }
+                        }
+                        if (next_token.id === ",") {
+                            advance(",");
+                            signature.push(", ");
+                            return parameter();
+                        }
+                    }
+                }
+            }());
+        }
+        advance(")");
+        signature.push(")");
+        return [list, signature.join(""), complex];
+    }
+
+    function do_function(the_function) {
+        let name;
+        if (the_function === undefined) {
+            the_function = token;
+
+// A function statement must have a name that will be in the parent's scope.
+
+            if (the_function.arity === "statement") {
+                if (!next_token.identifier) {
+                    return stop("expected_identifier_a", next_token);
+                }
+                name = next_token;
+                enroll(name, "variable", true);
+                the_function.name = name;
+                name.init = true;
+                name.calls = empty();
+                advance();
+            } else if (name === undefined) {
+
+// A function expression may have an optional name.
+
+                if (next_token.identifier) {
+                    name = next_token;
+                    the_function.name = name;
+                    advance();
+                } else {
+                    the_function.name = anon;
+                }
+            }
+        } else {
+            name = the_function.name;
+        }
+        the_function.level = functionage.level + 1;
+        if (mega_mode) {
+            warn("unexpected_a", the_function);
+        }
+
+// Don't make functions in loops. It is inefficient, and it can lead to scoping
+// errors.
+
+        if (functionage.loop > 0) {
+            warn("function_in_loop", the_function);
+        }
+
+// Give the function properties for storing its names and for observing the
+// depth of loops and switches.
+
+        the_function.context = empty();
+        the_function.finally = 0;
+        the_function.loop = 0;
+        the_function.switch = 0;
+        the_function.try = 0;
+
+// Push the current function context and establish a new one.
+
+        stack.push(functionage);
+        functions.push(the_function);
+        functionage = the_function;
+        if (the_function.arity !== "statement" && typeof name === "object") {
+            enroll(name, "function", true);
+            name.dead = false;
+            name.init = true;
+            name.used = 1;
+        }
+
+// Parse the parameter list.
+
+        advance("(");
+        token.free = false;
+        token.arity = "function";
+        const pl = parameter_list();
+        functionage.parameters = pl[0];
+        functionage.signature = pl[1];
+        functionage.complex = pl[2];
+        functionage.parameters.forEach(function enroll_parameter(name) {
+            if (name.identifier) {
+                enroll(name, "parameter", false);
+            } else {
+                name.names.forEach(enroll_parameter);
+            }
+        });
+
+// The function's body is a block.
+
+        the_function.block = block("body");
+        if (
+            the_function.arity === "statement"
+            && next_token.line === token.line
+        ) {
+            return stop("unexpected_a", next_token);
+        }
+        if (next_token.id === "." || next_token.id === "[") {
+            warn("unexpected_a");
+        }
+
+// Restore the previous context.
+
+        functionage = stack.pop();
+        return the_function;
+    }
+
+    prefix("function", do_function);
+
+    function fart(pl) {
+        if (next_token.id === ";") {
+            stop("wrap_assignment", token);
+        }
+        advance("=>");
+        const the_fart = token;
+        the_fart.arity = "binary";
+        the_fart.name = "=>";
+        the_fart.level = functionage.level + 1;
+        functions.push(the_fart);
+        if (functionage.loop > 0) {
+            warn("function_in_loop", the_fart);
+        }
+
+// Give the function properties storing its names and for observing the depth
+// of loops and switches.
+
+        the_fart.context = empty();
+        the_fart.finally = 0;
+        the_fart.loop = 0;
+        the_fart.switch = 0;
+        the_fart.try = 0;
+
+// Push the current function context and establish a new one.
+
+        stack.push(functionage);
+        functionage = the_fart;
+        the_fart.parameters = pl[0];
+        the_fart.signature = pl[1];
+        the_fart.complex = true;
+        the_fart.parameters.forEach(function (name) {
+            enroll(name, "parameter", true);
+        });
+        if (next_token.id === "{") {
+            warn("expected_a_b", the_fart, "function", "=>");
+            the_fart.block = block("body");
+        } else {
+            the_fart.expression = expression(0);
+        }
+        functionage = stack.pop();
+        return the_fart;
+    }
+
+    prefix("(", function () {
+        const the_paren = token;
+        let the_value;
+        const cadet = lookahead().id;
+
+// We can distinguish between a parameter list for => and a wrapped expression
+// with one token of lookahead.
+
+        if (
+            next_token.id === ")"
+            || next_token.id === "..."
+            || (next_token.identifier && (cadet === "," || cadet === "="))
+        ) {
+            the_paren.free = false;
+            return fart(parameter_list());
+        }
+        the_paren.free = true;
+        the_value = expression(0);
+        if (the_value.wrapped === true) {
+            warn("unexpected_a", the_paren);
+        }
+        the_value.wrapped = true;
+        advance(")", the_paren);
+        if (next_token.id === "=>") {
+            if (the_value.arity !== "variable") {
+                if (the_value.id === "{" || the_value.id === "[") {
+                    warn("expected_a_before_b", the_paren, "function", "(");
+                    return stop("expected_a_b", next_token, "{", "=>");
+                }
+                return stop("expected_identifier_a", the_value);
+            }
+            the_paren.expression = [the_value];
+            return fart([the_paren.expression, "(" + the_value.id + ")"]);
+        }
+        return the_value;
+    });
+    prefix("`", do_tick);
+    prefix("{", function () {
+        const the_brace = token;
+        const seen = empty();
+        the_brace.expression = [];
+        if (next_token.id !== "}") {
+            (function member() {
+                let extra;
+                let full;
+                let id;
+                let name = next_token;
+                let value;
+                advance();
+                if (
+                    (name.id === "get" || name.id === "set")
+                    && next_token.identifier
+                ) {
+                    if (!option.getset) {
+                        warn("unexpected_a", name);
+                    }
+                    extra = name.id;
+                    full = extra + " " + next_token.id;
+                    name = next_token;
+                    advance();
+                    id = survey(name);
+                    if (seen[full] === true || seen[id] === true) {
+                        warn("duplicate_a", name);
+                    }
+                    seen[id] = false;
+                    seen[full] = true;
+                } else {
+                    id = survey(name);
+                    if (typeof seen[id] === "boolean") {
+                        warn("duplicate_a", name);
+                    }
+                    seen[id] = true;
+                }
+                if (name.identifier) {
+                    switch (next_token.id) {
+                    case "}":
+                    case ",":
+                        if (typeof extra === "string") {
+                            advance("(");
+                        }
+                        value = expression(Infinity, true);
+                        break;
+                    case "(":
+                        value = do_function({
+                            arity: "unary",
+                            from: name.from,
+                            id: "function",
+                            line: name.line,
+                            name: (typeof extra === "string")
+                                ? extra
+                                : id,
+                            thru: name.from
+                        });
+                        break;
+                    default:
+                        if (typeof extra === "string") {
+                            advance("(");
+                        }
+                        advance(":");
+                        value = expression(0);
+                    }
+                    value.label = name;
+                    if (typeof extra === "string") {
+                        value.extra = extra;
+                    }
+                    the_brace.expression.push(value);
+                } else {
+                    advance(":");
+                    value = expression(0);
+                    value.label = name;
+                    the_brace.expression.push(value);
+                }
+                if (next_token.id === ",") {
+                    advance(",");
+                    return member();
+                }
+            }());
+        }
+        advance("}");
+        return the_brace;
+    });
+
+    stmt(";", function () {
+        warn("unexpected_a", token);
+        return token;
+    });
+    stmt("{", function () {
+        warn("naked_block", token);
+        return block("naked");
+    });
+    stmt("break", function () {
+        const the_break = token;
+        let the_label;
+        if (
+            (functionage.loop < 1 && functionage.switch < 1)
+            || functionage.finally > 0
+        ) {
+            warn("unexpected_a", the_break);
+        }
+        the_break.disrupt = true;
+        if (next_token.identifier && token.line === next_token.line) {
+            the_label = functionage.context[next_token.id];
+            if (
+                the_label === undefined
+                || the_label.role !== "label"
+                || the_label.dead
+            ) {
+                warn((the_label !== undefined && the_label.dead)
+                    ? "out_of_scope_a"
+                    : "not_label_a");
+            } else {
+                the_label.used += 1;
+            }
+            the_break.label = next_token;
+            advance();
+        }
+        advance(";");
+        return the_break;
+    });
+
+    function do_var() {
+        const the_statement = token;
+        const is_const = the_statement.id === "const";
+        the_statement.names = [];
+
+// A program may use var or let, but not both.
+
+        if (!is_const) {
+            if (var_mode === undefined) {
+                var_mode = the_statement.id;
+            } else if (the_statement.id !== var_mode) {
+                warn(
+                    "expected_a_b",
+                    the_statement,
+                    var_mode,
+                    the_statement.id
+                );
+            }
+        }
+
+// We don't expect to see variables created in switch statements.
+
+        if (functionage.switch > 0) {
+            warn("var_switch", the_statement);
+        }
+        if (functionage.loop > 0 && the_statement.id === "var") {
+            warn("var_loop", the_statement);
+        }
+        (function next() {
+            if (next_token.id === "{" && the_statement.id !== "var") {
+                const the_brace = next_token;
+                the_brace.names = [];
+                advance("{");
+                (function pair() {
+                    if (!next_token.identifier) {
+                        return stop("expected_identifier_a", next_token);
+                    }
+                    const name = next_token;
+                    survey(name);
+                    advance();
+                    if (next_token.id === ":") {
+                        advance(":");
+                        if (!next_token.identifier) {
+                            return stop("expected_identifier_a", next_token);
+                        }
+                        next_token.label = name;
+                        the_brace.names.push(next_token);
+                        enroll(next_token, "variable", is_const);
+                        advance();
+                    } else {
+                        the_brace.names.push(name);
+                        enroll(name, "variable", is_const);
+                    }
+                    if (next_token.id === ",") {
+                        advance(",");
+                        return pair();
+                    }
+                }());
+                advance("}");
+                advance("=");
+                the_brace.expression = expression(0);
+                the_statement.names.push(the_brace);
+            } else if (next_token.id === "[" && the_statement.id !== "var") {
+                const the_bracket = next_token;
+                the_bracket.names = [];
+                advance("[");
+                (function element() {
+                    let ellipsis;
+                    if (next_token.id === "...") {
+                        ellipsis = true;
+                        advance("...");
+                    }
+                    if (!next_token.identifier) {
+                        return stop("expected_identifier_a", next_token);
+                    }
+                    const name = next_token;
+                    advance();
+                    the_bracket.names.push(name);
+                    enroll(name, "variable", the_statement.id === "const");
+                    if (ellipsis) {
+                        name.ellipsis = true;
+                    } else if (next_token.id === ",") {
+                        advance(",");
+                        return element();
+                    }
+                }());
+                advance("]");
+                advance("=");
+                the_bracket.expression = expression(0);
+                the_statement.names.push(the_bracket);
+            } else if (next_token.identifier) {
+                const name = next_token;
+                advance();
+                if (name.id === "ignore") {
+                    warn("unexpected_a", name);
+                }
+                enroll(name, "variable", is_const);
+                if (next_token.id === "=" || is_const) {
+                    advance("=");
+                    name.expression = expression(0);
+                    name.init = true;
+                }
+                the_statement.names.push(name);
+            } else {
+                return stop("expected_identifier_a", next_token);
+            }
+            if (next_token.id === ",") {
+                if (!option.multivar) {
+                    warn("expected_a_b", next_token, ";", ",");
+                }
+                advance(",");
+                return next();
+            }
+        }());
+        the_statement.open = (
+            the_statement.names.length > 1
+            && the_statement.line !== the_statement.names[1].line
+        );
+        semicolon();
+        return the_statement;
+    }
+
+    stmt("const", do_var);
+    stmt("continue", function () {
+        const the_continue = token;
+        if (functionage.loop < 1 || functionage.finally > 0) {
+            warn("unexpected_a", the_continue);
+        }
+        not_top_level(the_continue);
+        the_continue.disrupt = true;
+        warn("unexpected_a", the_continue);
+        advance(";");
+        return the_continue;
+    });
+    stmt("debugger", function () {
+        const the_debug = token;
+        if (!option.devel) {
+            warn("unexpected_a", the_debug);
+        }
+        semicolon();
+        return the_debug;
+    });
+    stmt("delete", function () {
+        const the_token = token;
+        const the_value = expression(0);
+        if (
+            (the_value.id !== "." && the_value.id !== "[")
+            || the_value.arity !== "binary"
+        ) {
+            stop("expected_a_b", the_value, ".", artifact(the_value));
+        }
+        the_token.expression = the_value;
+        semicolon();
+        return the_token;
+    });
+    stmt("do", function () {
+        const the_do = token;
+        not_top_level(the_do);
+        functionage.loop += 1;
+        the_do.block = block();
+        advance("while");
+        the_do.expression = condition();
+        semicolon();
+        if (the_do.block.disrupt === true) {
+            warn("weird_loop", the_do);
+        }
+        functionage.loop -= 1;
+        return the_do;
+    });
+    stmt("export", function () {
+        const the_export = token;
+        let the_id;
+        let the_name;
+        let the_thing;
+
+        function export_id() {
+            if (!next_token.identifier) {
+                stop("expected_identifier_a");
+            }
+            the_id = next_token.id;
+            the_name = global.context[the_id];
+            if (the_name === undefined) {
+                warn("unexpected_a");
+            } else {
+                the_name.used += 1;
+                if (exports[the_id] !== undefined) {
+                    warn("duplicate_a");
+                }
+                exports[the_id] = the_name;
+            }
+            advance();
+            the_export.expression.push(the_thing);
+        }
+
+        the_export.expression = [];
+        if (next_token.id === "default") {
+            if (exports.default !== undefined) {
+                warn("duplicate_a");
+            }
+            advance("default");
+            the_thing = expression(0);
+            semicolon();
+            exports.default = the_thing;
+            the_export.expression.push(the_thing);
+        } else {
+            switch (next_token.id) {
+            case "function":
+                the_thing = statement();
+                the_name = the_thing.name;
+                the_id = the_name.id;
+                the_name.used += 1;
+                if (exports[the_id] !== undefined) {
+                    warn("duplicate_a", the_name);
+                }
+                exports[the_id] = the_thing;
+                the_export.expression.push(the_thing);
+                the_thing.statement = false;
+                the_thing.arity = "unary";
+                break;
+            case "var":
+            case "let":
+            case "const":
+                warn("unexpected_a");
+                break;
+            case "{":
+                advance("{");
+                (function loop() {
+                    export_id();
+                    if (next_token.id === ",") {
+                        advance(",");
+                        return loop();
+                    }
+                }());
+                advance("}");
+                semicolon();
+                break;
+            default:
+                export_id();
+                if (the_name.writable !== true) {
+                    warn("unexpected_a", token);
+                }
+                semicolon();
+            }
+        }
+        module_mode = true;
+        return the_export;
+    });
+    stmt("for", function () {
+        let first;
+        const the_for = token;
+        if (!option.for) {
+            warn("unexpected_a", the_for);
+        }
+        not_top_level(the_for);
+        functionage.loop += 1;
+        advance("(");
+        token.free = true;
+        if (next_token.id === ";") {
+            return stop("expected_a_b", the_for, "while (", "for (;");
+        }
+        switch (next_token.id) {
+        case "var":
+        case "let":
+        case "const":
+            return stop("unexpected_a");
+        }
+        first = expression(0);
+        if (first.id === "in") {
+            if (first.expression[0].arity !== "variable") {
+                warn("bad_assignment_a", first.expression[0]);
+            }
+            the_for.name = first.expression[0];
+            the_for.expression = first.expression[1];
+            warn("expected_a_b", the_for, "Object.keys", "for in");
+        } else {
+            the_for.initial = first;
+            advance(";");
+            the_for.expression = expression(0);
+            advance(";");
+            the_for.inc = expression(0);
+            if (the_for.inc.id === "++") {
+                warn("expected_a_b", the_for.inc, "+= 1", "++");
+            }
+        }
+        advance(")");
+        the_for.block = block();
+        if (the_for.block.disrupt === true) {
+            warn("weird_loop", the_for);
+        }
+        functionage.loop -= 1;
+        return the_for;
+    });
+    stmt("function", do_function);
+    stmt("if", function () {
+        let the_else;
+        const the_if = token;
+        the_if.expression = condition();
+        the_if.block = block();
+        if (next_token.id === "else") {
+            advance("else");
+            the_else = token;
+            the_if.else = (next_token.id === "if")
+                ? statement()
+                : block();
+            if (the_if.block.disrupt === true) {
+                if (the_if.else.disrupt === true) {
+                    the_if.disrupt = true;
+                } else {
+                    warn("unexpected_a", the_else);
+                }
+            }
+        }
+        return the_if;
+    });
+    stmt("import", function () {
+        const the_import = token;
+        let name;
+        if (typeof module_mode === "object") {
+            warn("unexpected_directive_a", module_mode, module_mode.directive);
+        }
+        module_mode = true;
+        if (next_token.identifier) {
+            name = next_token;
+            advance();
+            if (name.id === "ignore") {
+                warn("unexpected_a", name);
+            }
+            enroll(name, "variable", true);
+            the_import.name = name;
+        } else {
+            const names = [];
+            advance("{");
+            if (next_token.id !== "}") {
+                while (true) {
+                    if (!next_token.identifier) {
+                        stop("expected_identifier_a");
+                    }
+                    name = next_token;
+                    advance();
+                    if (name.id === "ignore") {
+                        warn("unexpected_a", name);
+                    }
+                    enroll(name, "variable", true);
+                    names.push(name);
+                    if (next_token.id !== ",") {
+                        break;
+                    }
+                    advance(",");
+                }
+            }
+            advance("}");
+            the_import.name = names;
+        }
+        advance("from");
+        advance("(string)");
+        the_import.import = token;
+        if (!rx_module.test(token.value)) {
+            warn("bad_module_name_a", token);
+        }
+        froms.push(token.value);
+        semicolon();
+        return the_import;
+    });
+    stmt("let", do_var);
+    stmt("return", function () {
+        const the_return = token;
+        not_top_level(the_return);
+        if (functionage.finally > 0) {
+            warn("unexpected_a", the_return);
+        }
+        the_return.disrupt = true;
+        if (next_token.id !== ";" && the_return.line === next_token.line) {
+            the_return.expression = expression(10);
+        }
+        advance(";");
+        return the_return;
+    });
+    stmt("switch", function () {
+        let dups = [];
+        let last;
+        let stmts;
+        const the_cases = [];
+        let the_disrupt = true;
+        const the_switch = token;
+        not_top_level(the_switch);
+        if (functionage.finally > 0) {
+            warn("unexpected_a", the_switch);
+        }
+        functionage.switch += 1;
+        advance("(");
+        token.free = true;
+        the_switch.expression = expression(0);
+        the_switch.block = the_cases;
+        advance(")");
+        advance("{");
+        (function major() {
+            const the_case = next_token;
+            the_case.arity = "statement";
+            the_case.expression = [];
+            (function minor() {
+                advance("case");
+                token.switch = true;
+                const exp = expression(0);
+                if (dups.some(function (thing) {
+                    return are_similar(thing, exp);
+                })) {
+                    warn("unexpected_a", exp);
+                }
+                dups.push(exp);
+                the_case.expression.push(exp);
+                advance(":");
+                if (next_token.id === "case") {
+                    return minor();
+                }
+            }());
+            stmts = statements();
+            if (stmts.length < 1) {
+                warn("expected_statements_a");
+                return;
+            }
+            the_case.block = stmts;
+            the_cases.push(the_case);
+            last = stmts[stmts.length - 1];
+            if (last.disrupt) {
+                if (last.id === "break" && last.label === undefined) {
+                    the_disrupt = false;
+                }
+            } else {
+                warn(
+                    "expected_a_before_b",
+                    next_token,
+                    "break;",
+                    artifact(next_token)
+                );
+            }
+            if (next_token.id === "case") {
+                return major();
+            }
+        }());
+        dups = undefined;
+        if (next_token.id === "default") {
+            const the_default = next_token;
+            advance("default");
+            token.switch = true;
+            advance(":");
+            the_switch.else = statements();
+            if (the_switch.else.length < 1) {
+                warn("unexpected_a", the_default);
+                the_disrupt = false;
+            } else {
+                const the_last = the_switch.else[the_switch.else.length - 1];
+                if (the_last.id === "break" && the_last.label === undefined) {
+                    warn("unexpected_a", the_last);
+                    the_last.disrupt = false;
+                }
+                the_disrupt = the_disrupt && the_last.disrupt;
+            }
+        } else {
+            the_disrupt = false;
+        }
+        advance("}", the_switch);
+        functionage.switch -= 1;
+        the_switch.disrupt = the_disrupt;
+        return the_switch;
+    });
+    stmt("throw", function () {
+        const the_throw = token;
+        the_throw.disrupt = true;
+        the_throw.expression = expression(10);
+        semicolon();
+        return the_throw;
+    });
+    stmt("try", function () {
+        let the_catch;
+        let the_disrupt;
+        const the_try = token;
+        if (functionage.try > 0) {
+            warn("unexpected_a", the_try);
+        }
+        functionage.try += 1;
+        the_try.block = block();
+        the_disrupt = the_try.block.disrupt;
+        if (next_token.id === "catch") {
+            let ignored = "ignore";
+            the_catch = next_token;
+            the_try.catch = the_catch;
+            advance("catch");
+            advance("(");
+            if (!next_token.identifier) {
+                return stop("expected_identifier_a", next_token);
+            }
+            if (next_token.id !== "ignore") {
+                ignored = undefined;
+                the_catch.name = next_token;
+                enroll(next_token, "exception", true);
+            }
+            advance();
+            advance(")");
+            the_catch.block = block(ignored);
+            if (the_catch.block.disrupt !== true) {
+                the_disrupt = false;
+            }
+        } else {
+            warn(
+                "expected_a_before_b",
+                next_token,
+                "catch",
+                artifact(next_token)
+            );
+
+        }
+        if (next_token.id === "finally") {
+            functionage.finally += 1;
+            advance("finally");
+            the_try.else = block();
+            the_disrupt = the_try.else.disrupt;
+            functionage.finally -= 1;
+        }
+        the_try.disrupt = the_disrupt;
+        functionage.try -= 1;
+        return the_try;
+    });
+    stmt("var", do_var);
+    stmt("while", function () {
+        const the_while = token;
+        not_top_level(the_while);
+        functionage.loop += 1;
+        the_while.expression = condition();
+        the_while.block = block();
+        if (the_while.block.disrupt === true) {
+            warn("weird_loop", the_while);
+        }
+        functionage.loop -= 1;
+        return the_while;
+    });
+    stmt("with", function () {
+        stop("unexpected_a", token);
+    });
+
+    ternary("?", ":");
+
+// Ambulation of the parse tree.
+
+    function action(when) {
+
+// Produce a function that will register task functions that will be called as
+// the tree is traversed.
+
+        return function (arity, id, task) {
+            let a_set = when[arity];
+            let i_set;
+
+// The id parameter is optional. If excluded, the task will be applied to all
+// ids.
+
+            if (typeof id !== "string") {
+                task = id;
+                id = "(all)";
+            }
+
+// If this arity has no registrations yet, then create a set object to hold
+// them.
+
+            if (a_set === undefined) {
+                a_set = empty();
+                when[arity] = a_set;
+            }
+
+// If this id has no registrations yet, then create a set array to hold them.
+
+            i_set = a_set[id];
+            if (i_set === undefined) {
+                i_set = [];
+                a_set[id] = i_set;
+            }
+
+// Register the task with the arity and the id.
+
+            i_set.push(task);
+        };
+    }
+
+    function amble(when) {
+
+// Produce a function that will act on the tasks registered by an action
+// function while walking the tree.
+
+        return function (the_token) {
+
+// Given a task set that was built by an action function, run all of the
+// relevant tasks on the token.
+
+            let a_set = when[the_token.arity];
+            let i_set;
+
+// If there are tasks associated with the token's arity...
+
+            if (a_set !== undefined) {
+
+// If there are tasks associated with the token's id...
+
+                i_set = a_set[the_token.id];
+                if (i_set !== undefined) {
+                    i_set.forEach(function (task) {
+                        return task(the_token);
+                    });
+                }
+
+// If there are tasks for all ids.
+
+                i_set = a_set["(all)"];
+                if (i_set !== undefined) {
+                    i_set.forEach(function (task) {
+                        return task(the_token);
+                    });
+                }
+            }
+        };
+    }
+
+    const posts = empty();
+    const pres = empty();
+    const preaction = action(pres);
+    const postaction = action(posts);
+    const preamble = amble(pres);
+    const postamble = amble(posts);
+
+    function walk_expression(thing) {
+        if (thing) {
+            if (Array.isArray(thing)) {
+                thing.forEach(walk_expression);
+            } else {
+                preamble(thing);
+                walk_expression(thing.expression);
+                if (thing.id === "function") {
+                    walk_statement(thing.block);
+                }
+                switch (thing.arity) {
+                case "post":
+                case "pre":
+                    warn("unexpected_a", thing);
+                    break;
+                case "statement":
+                case "assignment":
+                    warn("unexpected_statement_a", thing);
+                    break;
+                }
+                postamble(thing);
+            }
+        }
+    }
+
+    function walk_statement(thing) {
+        if (thing) {
+            if (Array.isArray(thing)) {
+                thing.forEach(walk_statement);
+            } else {
+                preamble(thing);
+                walk_expression(thing.expression);
+                switch (thing.arity) {
+                case "statement":
+                case "assignment":
+                    break;
+                case "binary":
+                    if (thing.id !== "(") {
+                        warn("unexpected_expression_a", thing);
+                    }
+                    break;
+                default:
+                    warn((
+                        thing.id === "(string)"
+                        && thing.value === "use strict"
+                    )
+                        ? "unexpected_a"
+                        : "unexpected_expression_a", thing);
+                }
+                walk_statement(thing.block);
+                walk_statement(thing.else);
+                postamble(thing);
+            }
+        }
+    }
+
+    function lookup(thing) {
+        if (thing.arity === "variable") {
+
+// Look up the variable in the current context.
+
+            let the_variable = functionage.context[thing.id];
+
+// If it isn't local, search all the other contexts. If there are name
+// collisions, take the most recent.
+
+            if (the_variable === undefined) {
+                stack.forEach(function (outer) {
+                    const a_variable = outer.context[thing.id];
+                    if (
+                        a_variable !== undefined
+                        && a_variable.role !== "label"
+                    ) {
+                        the_variable = a_variable;
+                    }
+                });
+
+// If it isn't in any of those either, perhaps it is a predefined global.
+// If so, add it to the global context.
+
+                if (the_variable === undefined) {
+                    if (declared_globals[thing.id] === undefined) {
+                        warn("undeclared_a", thing);
+                        return;
+                    }
+                    the_variable = {
+                        dead: false,
+                        function: global,
+                        id: thing.id,
+                        init: true,
+                        role: "variable",
+                        used: 0,
+                        writable: false
+                    };
+                    global.context[thing.id] = the_variable;
+                }
+                the_variable.closure = true;
+                functionage.context[thing.id] = the_variable;
+            } else if (the_variable.role === "label") {
+                warn("label_a", thing);
+            }
+            if (the_variable.dead) {
+                warn("out_of_scope_a", thing);
+            }
+            return the_variable;
+        }
+    }
+
+    function subactivate(name) {
+        name.init = true;
+        name.dead = false;
+        blockage.live.push(name);
+    }
+
+    function preaction_function(thing) {
+        if (thing.arity === "statement" && blockage.body !== true) {
+            warn("unexpected_a", thing);
+        }
+        if (thing.level === 1) {
+            if (
+                module_mode === true
+                || global.strict !== undefined
+                || thing.complex
+            ) {
+                if (thing.id !== "=>" && thing.block.strict !== undefined) {
+                    warn("unexpected_a", thing.block.strict);
+                }
+            } else {
+                if (thing.block.strict === undefined) {
+                    warn("use_strict", thing);
+                }
+            }
+        }
+        stack.push(functionage);
+        block_stack.push(blockage);
+        functionage = thing;
+        blockage = thing;
+        thing.live = [];
+        if (typeof thing.name === "object") {
+            thing.name.dead = false;
+            thing.name.init = true;
+        }
+        switch (thing.extra) {
+        case "get":
+            if (thing.parameters.length !== 0) {
+                warn("bad_get", thing);
+            }
+            break;
+        case "set":
+            if (thing.parameters.length !== 1) {
+                warn("bad_set", thing);
+            }
+            break;
+        }
+        thing.parameters.forEach(function (name) {
+            walk_expression(name.expression);
+            if (name.id === "{" || name.id === "[") {
+                name.names.forEach(subactivate);
+            } else {
+                name.dead = false;
+                name.init = true;
+            }
+        });
+    }
+
+    function bitwise_check(thing) {
+        if (!option.bitwise && bitwiseop[thing.id] === true) {
+            warn("unexpected_a", thing);
+        }
+        if (
+            thing.id !== "("
+            && thing.id !== "&&"
+            && thing.id !== "||"
+            && thing.id !== "="
+            && Array.isArray(thing.expression)
+            && thing.expression.length === 2
+            && (
+                relationop[thing.expression[0].id] === true
+                || relationop[thing.expression[1].id] === true
+            )
+        ) {
+            warn("unexpected_a", thing);
+        }
+    }
+
+    function pop_block() {
+        blockage.live.forEach(function (name) {
+            name.dead = true;
+        });
+        delete blockage.live;
+        blockage = block_stack.pop();
+    }
+
+    function activate(name) {
+        if (name.expression !== undefined) {
+            walk_expression(name.expression);
+            if (name.id === "{" || name.id === "[") {
+                name.names.forEach(subactivate);
+            } else {
+                name.init = true;
+            }
+        }
+        name.dead = false;
+        blockage.live.push(name);
+    }
+
+    function action_var(thing) {
+        thing.names.forEach(activate);
+    }
+
+    preaction("assignment", bitwise_check);
+    preaction("binary", bitwise_check);
+    preaction("binary", function (thing) {
+        if (relationop[thing.id] === true) {
+            const left = thing.expression[0];
+            const right = thing.expression[1];
+            if (left.id === "NaN" || right.id === "NaN") {
+                warn("number_isNaN", thing);
+            } else if (left.id === "typeof") {
+                if (right.id !== "(string)") {
+                    if (right.id !== "typeof") {
+                        warn("expected_string_a", right);
+                    }
+                } else {
+                    const value = right.value;
+                    if (value === "null" || value === "undefined") {
+                        warn("unexpected_typeof_a", right, value);
+                    } else if (
+                        value !== "boolean"
+                        && value !== "function"
+                        && value !== "number"
+                        && value !== "object"
+                        && value !== "string"
+                        && value !== "symbol"
+                    ) {
+                        warn("expected_type_string_a", right, value);
+                    }
+                }
+            }
+        }
+    });
+    preaction("binary", "==", function (thing) {
+        warn("expected_a_b", thing, "===", "==");
+    });
+    preaction("binary", "!=", function (thing) {
+        warn("expected_a_b", thing, "!==", "!=");
+    });
+    preaction("binary", "=>", preaction_function);
+    preaction("binary", "||", function (thing) {
+        thing.expression.forEach(function (thang) {
+            if (thang.id === "&&" && !thang.wrapped) {
+                warn("and", thang);
+            }
+        });
+    });
+    preaction("binary", "(", function (thing) {
+        const left = thing.expression[0];
+        if (
+            left.identifier
+            && functionage.context[left.id] === undefined
+            && typeof functionage.name === "object"
+        ) {
+            const parent = functionage.name.function;
+            if (parent) {
+                const left_variable = parent.context[left.id];
+                if (
+                    left_variable !== undefined
+                    && left_variable.dead
+                    && left_variable.function === parent
+                    && left_variable.calls !== undefined
+                    && left_variable.calls[functionage.name.id] !== undefined
+                ) {
+                    left_variable.dead = false;
+                }
+            }
+        }
+    });
+    preaction("binary", "in", function (thing) {
+        warn("infix_in", thing);
+    });
+    preaction("binary", "instanceof", function (thing) {
+        warn("unexpected_a", thing);
+    });
+    preaction("binary", ".", function (thing) {
+        if (thing.expression.new) {
+            thing.new = true;
+        }
+    });
+    preaction("statement", "{", function (thing) {
+        block_stack.push(blockage);
+        blockage = thing;
+        thing.live = [];
+    });
+    preaction("statement", "for", function (thing) {
+        if (thing.name !== undefined) {
+            const the_variable = lookup(thing.name);
+            if (the_variable !== undefined) {
+                the_variable.init = true;
+                if (!the_variable.writable) {
+                    warn("bad_assignment_a", thing.name);
+                }
+            }
+        }
+        walk_statement(thing.initial);
+    });
+    preaction("statement", "function", preaction_function);
+    preaction("unary", "~", bitwise_check);
+    preaction("unary", "function", preaction_function);
+    preaction("variable", function (thing) {
+        const the_variable = lookup(thing);
+        if (the_variable !== undefined) {
+            thing.variable = the_variable;
+            the_variable.used += 1;
+        }
+    });
+
+    function init_variable(name) {
+        const the_variable = lookup(name);
+        if (the_variable !== undefined) {
+            if (the_variable.writable) {
+                the_variable.init = true;
+                return;
+            }
+        }
+        warn("bad_assignment_a", name);
+    }
+
+    postaction("assignment", "+=", function (thing) {
+        let right = thing.expression[1];
+        if (right.constant) {
+            if (
+                right.value === ""
+                || (right.id === "(number)" && right.value === "0")
+                || right.id === "(boolean)"
+                || right.id === "null"
+                || right.id === "undefined"
+                || Number.isNaN(right.value)
+            ) {
+                warn("unexpected_a", right);
+            }
+        }
+    });
+    postaction("assignment", function (thing) {
+
+// Assignment using = sets the init property of a variable. No other assignment
+// operator can do this. A = token keeps that variable (or array of variables
+// in case of destructuring) in its name property.
+
+        const lvalue = thing.expression[0];
+        if (thing.id === "=") {
+            if (thing.names !== undefined) {
+                if (Array.isArray(thing.names)) {
+                    thing.names.forEach(init_variable);
+                } else {
+                    init_variable(thing.names);
+                }
+            } else {
+                if (
+                    lvalue.id === "."
+                    && thing.expression[1].id === "undefined"
+                ) {
+                    warn(
+                        "expected_a_b",
+                        lvalue.expression,
+                        "delete",
+                        "undefined"
+                    );
+                }
+            }
+        } else {
+            if (lvalue.arity === "variable") {
+                if (!lvalue.variable || lvalue.variable.writable !== true) {
+                    warn("bad_assignment_a", lvalue);
+                }
+            }
+            const right = syntax[thing.expression[1].id];
+            if (
+                right !== undefined
+                && (
+                    right.id === "function"
+                    || right.id === "=>"
+                    || (
+                        right.constant
+                        && right.id !== "(number)"
+                        && (right.id !== "(string)" || thing.id !== "+=")
+                    )
+                )
+            ) {
+                warn("unexpected_a", thing.expression[1]);
+            }
+        }
+    });
+
+    function postaction_function(thing) {
+        delete functionage.finally;
+        delete functionage.loop;
+        delete functionage.switch;
+        delete functionage.try;
+        functionage = stack.pop();
+        if (thing.wrapped) {
+            warn("unexpected_parens", thing);
+        }
+        return pop_block();
+    }
+
+    postaction("binary", function (thing) {
+        let right;
+        if (relationop[thing.id]) {
+            if (
+                is_weird(thing.expression[0])
+                || is_weird(thing.expression[1])
+                || are_similar(thing.expression[0], thing.expression[1])
+                || (
+                    thing.expression[0].constant === true
+                    && thing.expression[1].constant === true
+                )
+            ) {
+                warn("weird_relation_a", thing);
+            }
+        }
+        switch (thing.id) {
+        case "+":
+            if (!option.convert) {
+                if (thing.expression[0].value === "") {
+                    warn("expected_a_b", thing, "String(...)", "\"\" +");
+                } else if (thing.expression[1].value === "") {
+                    warn("expected_a_b", thing, "String(...)", "+ \"\"");
+                }
+            }
+            break;
+        case "=>":
+        case "(":
+        case "[":
+            break;
+        case ".":
+            if (thing.expression.id === "RegExp") {
+                warn("weird_expression_a", thing);
+            }
+            break;
+        default:
+            right = thing.expression[1];
+            if (
+                (thing.id === "+" || thing.id === "-")
+                && right.id === thing.id
+                && right.arity === "unary"
+                && !right.wrapped
+            ) {
+                warn("wrap_unary", right);
+            }
+            if (
+                thing.expression[0].constant === true
+                && right.constant === true
+            ) {
+                thing.constant = true;
+            }
+        }
+    });
+    postaction("binary", "&&", function (thing) {
+        if (
+            is_weird(thing.expression[0])
+            || are_similar(thing.expression[0], thing.expression[1])
+            || thing.expression[0].constant === true
+            || thing.expression[1].constant === true
+        ) {
+            warn("weird_condition_a", thing);
+        }
+    });
+    postaction("binary", "||", function (thing) {
+        if (
+            is_weird(thing.expression[0])
+            || are_similar(thing.expression[0], thing.expression[1])
+            || thing.expression[0].constant === true
+        ) {
+            warn("weird_condition_a", thing);
+        }
+    });
+    postaction("binary", "=>", postaction_function);
+    postaction("binary", "(", function (thing) {
+        let left = thing.expression[0];
+        let the_new;
+        let arg;
+        if (left.id === "new") {
+            the_new = left;
+            left = left.expression;
+        }
+        if (left.id === "function") {
+            if (!thing.wrapped) {
+                warn("wrap_immediate", thing);
+            }
+        } else if (left.identifier) {
+            if (the_new !== undefined) {
+                if (
+                    left.id[0] > "Z"
+                    || left.id === "Boolean"
+                    || left.id === "Number"
+                    || left.id === "String"
+                    || left.id === "Symbol"
+                ) {
+                    warn("unexpected_a", the_new);
+                } else if (left.id === "Function") {
+                    if (!option.eval) {
+                        warn("unexpected_a", left, "new Function");
+                    }
+                } else if (left.id === "Array") {
+                    arg = thing.expression;
+                    if (
+                        arg.length !== 2
+                        || (
+                            (
+                                arg[1].id !== "(number)"
+                                || Number(arg[1].value) !== (arg[1].value | 0)
+                            )
+                            && arg[1].arity !== "binary"
+                        )
+                    ) {
+                        warn("expected_a_b", left, "[]", "new Array");
+                    }
+                } else if (left.id === "Object") {
+                    warn(
+                        "expected_a_b",
+                        left,
+                        "Object.create(null)",
+                        "new Object"
+                    );
+                }
+            } else {
+                if (
+                    left.id[0] >= "A"
+                    && left.id[0] <= "Z"
+                    && left.id !== "Boolean"
+                    && left.id !== "Number"
+                    && left.id !== "String"
+                    && left.id !== "Symbol"
+                ) {
+                    warn(
+                        "expected_a_before_b",
+                        left,
+                        "new",
+                        artifact(left)
+                    );
+                }
+            }
+        } else if (left.id === ".") {
+            let cack = the_new !== undefined;
+            if (left.expression.id === "Date" && left.name.id === "UTC") {
+                cack = !cack;
+            }
+            if (rx_cap.test(left.name.id) !== cack) {
+                if (the_new !== undefined) {
+                    warn("unexpected_a", the_new);
+                } else {
+                    warn(
+                        "expected_a_before_b",
+                        left.expression,
+                        "new",
+                        left.name.id
+                    );
+                }
+            }
+            if (left.name.id === "getTime") {
+                const paren = left.expression;
+                if (paren.id === "(") {
+                    const array = paren.expression;
+                    if (array.length === 1) {
+                        const new_date = array[0];
+                        if (
+                            new_date.id === "new"
+                            && new_date.expression.id === "Date"
+                        ) {
+                            warn(
+                                "expected_a_b",
+                                new_date,
+                                "Date.now()",
+                                "new Date().getTime()"
+                            );
+                        }
+                    }
+                }
+            }
+        }
+    });
+    postaction("binary", "[", function (thing) {
+        if (thing.expression[0].id === "RegExp") {
+            warn("weird_expression_a", thing);
+        }
+        if (is_weird(thing.expression[1])) {
+            warn("weird_expression_a", thing.expression[1]);
+        }
+    });
+    postaction("statement", "{", pop_block);
+    postaction("statement", "const", action_var);
+    postaction("statement", "export", top_level_only);
+    postaction("statement", "for", function (thing) {
+        walk_statement(thing.inc);
+    });
+    postaction("statement", "function", postaction_function);
+    postaction("statement", "import", function (the_thing) {
+        const name = the_thing.name;
+        if (Array.isArray(name)) {
+            name.forEach(function (name) {
+                name.dead = false;
+                name.init = true;
+                blockage.live.push(name);
+            });
+        } else {
+            name.dead = false;
+            name.init = true;
+            blockage.live.push(name);
+        }
+        return top_level_only(the_thing);
+    });
+    postaction("statement", "let", action_var);
+    postaction("statement", "try", function (thing) {
+        if (thing.catch !== undefined) {
+            const the_name = thing.catch.name;
+            if (the_name !== undefined) {
+                const the_variable = functionage.context[the_name.id];
+                the_variable.dead = false;
+                the_variable.init = true;
+            }
+            walk_statement(thing.catch.block);
+        }
+    });
+    postaction("statement", "var", action_var);
+    postaction("ternary", function (thing) {
+        if (
+            is_weird(thing.expression[0])
+            || thing.expression[0].constant === true
+            || are_similar(thing.expression[1], thing.expression[2])
+        ) {
+            warn("unexpected_a", thing);
+        } else if (are_similar(thing.expression[0], thing.expression[1])) {
+            warn("expected_a_b", thing, "||", "?");
+        } else if (are_similar(thing.expression[0], thing.expression[2])) {
+            warn("expected_a_b", thing, "&&", "?");
+        } else if (
+            thing.expression[1].id === "true"
+            && thing.expression[2].id === "false"
+        ) {
+            warn("expected_a_b", thing, "!!", "?");
+        } else if (
+            thing.expression[1].id === "false"
+            && thing.expression[2].id === "true"
+        ) {
+            warn("expected_a_b", thing, "!", "?");
+        } else if (
+            thing.expression[0].wrapped !== true
+            && (
+                thing.expression[0].id === "||"
+                || thing.expression[0].id === "&&"
+            )
+        ) {
+            warn("wrap_condition", thing.expression[0]);
+        }
+    });
+    postaction("unary", function (thing) {
+        switch (thing.id) {
+        case "[":
+        case "{":
+        case "function":
+        case "new":
+            break;
+        case "`":
+            if (thing.expression.every(function (thing) {
+                return thing.constant;
+            })) {
+                thing.constant = true;
+            }
+            break;
+        case "!":
+            if (thing.expression.constant === true) {
+                warn("unexpected_a", thing);
+            }
+            break;
+        case "!!":
+            if (!option.convert) {
+                warn("expected_a_b", thing, "Boolean(...)", "!!");
+            }
+            break;
+        default:
+            if (thing.expression.constant === true) {
+                thing.constant = true;
+            }
+        }
+    });
+    postaction("unary", "function", postaction_function);
+    postaction("unary", "+", function (thing) {
+        if (!option.convert) {
+            warn("expected_a_b", thing, "Number(...)", "+");
+        }
+        const right = thing.expression;
+        if (right.id === "(" && right.expression[0].id === "new") {
+            warn("unexpected_a_before_b", thing, "+", "new");
+        } else if (
+            right.constant
+            || right.id === "{"
+            || (right.id === "[" && right.arity !== "binary")
+        ) {
+            warn("unexpected_a", thing, "+");
+        }
+    });
+
+    function delve(the_function) {
+        Object.keys(the_function.context).forEach(function (id) {
+            if (id !== "ignore") {
+                const name = the_function.context[id];
+                if (name.function === the_function) {
+                    if (
+                        name.used === 0 && (
+                            name.role !== "function"
+                            || name.function.arity !== "unary"
+                        )
+                    ) {
+                        warn("unused_a", name);
+                    } else if (!name.init) {
+                        warn("uninitialized_a", name);
+                    }
+                }
+            }
+        });
+    }
+
+    function uninitialized_and_unused() {
+
+// Delve into the functions looking for variables that were not initialized
+// or used. If the file imports or exports, then its global object is also
+// delved.
+
+        if (module_mode === true || option.node) {
+            delve(global);
+        }
+        functions.forEach(delve);
+    }
+
+// Go through the token list, looking at usage of whitespace.
+
+    function whitage() {
+        let closer = "(end)";
+        let free = false;
+        let left = global;
+        let margin = 0;
+        let nr_comments_skipped = 0;
+        let open = true;
+        let qmark = "";
+        let result;
+        let right;
+
+        function expected_at(at) {
+            warn(
+                "expected_a_at_b_c",
+                right,
+                artifact(right),
+                fudge + at,
+                artifact_column(right)
+            );
+        }
+
+        function at_margin(fit) {
+            const at = margin + fit;
+            if (right.from !== at) {
+                return expected_at(at);
+            }
+        }
+
+        function no_space_only() {
+            if (
+                left.id !== "(global)"
+                && left.nr + 1 === right.nr
+                && (
+                    left.line !== right.line
+                    || left.thru !== right.from
+                )
+            ) {
+                warn(
+                    "unexpected_space_a_b",
+                    right,
+                    artifact(left),
+                    artifact(right)
+                );
+            }
+        }
+
+        function no_space() {
+            if (left.line === right.line) {
+                if (left.thru !== right.from && nr_comments_skipped === 0) {
+                    warn(
+                        "unexpected_space_a_b",
+                        right,
+                        artifact(left),
+                        artifact(right)
+                    );
+                }
+            } else {
+                if (open) {
+                    const at = (free)
+                        ? margin
+                        : margin + 8;
+                    if (right.from < at) {
+                        expected_at(at);
+                    }
+                } else {
+                    if (right.from !== margin + 8) {
+                        expected_at(margin + 8);
+                    }
+                }
+            }
+        }
+
+        function one_space_only() {
+            if (left.line !== right.line || left.thru + 1 !== right.from) {
+                warn(
+                    "expected_space_a_b",
+                    right,
+                    artifact(left),
+                    artifact(right)
+                );
+            }
+        }
+
+        function one_space() {
+            if (left.line === right.line) {
+                if (left.thru + 1 !== right.from && nr_comments_skipped === 0) {
+                    warn(
+                        "expected_space_a_b",
+                        right,
+                        artifact(left),
+                        artifact(right)
+                    );
+                }
+            } else {
+                if (free) {
+                    if (right.from < margin) {
+                        expected_at(margin);
+                    }
+                } else {
+                    const mislaid = (stack.length) > 0
+                        ? stack[stack.length - 1].right
+                        : undefined;
+                    if (!open && mislaid !== undefined) {
+                        warn(
+                            "expected_a_next_at_b",
+                            mislaid,
+                            artifact(mislaid.id),
+                            margin + 4 + fudge
+                        );
+                    } else if (right.from !== margin + 8) {
+                        expected_at(margin + 8);
+                    }
+                }
+            }
+        }
+
+        function unqmark() {
+
+// Undo the effects of dangling nested ternary operators.
+
+            const level = qmark.length;
+            if (level > 0) {
+                margin -= level * 4;
+            }
+            qmark = "";
+        }
+
+        stack = [];
+        tokens.forEach(function (the_token) {
+            right = the_token;
+            if (right.id === "(comment)" || right.id === "(end)") {
+                nr_comments_skipped += 1;
+            } else {
+
+// If left is an opener and right is not the closer, then push the previous
+// state. If the token following the opener is on the next line, then this is
+// an open form. If the tokens are on the same line, then it is a closed form.
+// Open form is more readable, with each item (statement, argument, parameter,
+// etc) starting on its own line. Closed form is more compact. Statement blocks
+// are always in open form.
+
+                const new_closer = opener[left.id];
+                if (typeof new_closer === "string") {
+                    if (new_closer !== right.id) {
+                        stack.push({
+                            closer: closer,
+                            free: free,
+                            margin: margin,
+                            open: open,
+                            qmark: qmark,
+                            right: right
+                        });
+                        qmark = "";
+                        closer = new_closer;
+                        if (left.line !== right.line) {
+                            free = closer === ")" && left.free;
+                            open = true;
+                            margin += 4;
+                            if (right.role === "label") {
+                                if (right.from !== 0) {
+                                    expected_at(0);
+                                }
+                            } else if (right.switch) {
+                                unqmark();
+                                at_margin(-4);
+                            } else {
+                                at_margin(0);
+                            }
+                        } else {
+                            if (right.statement || right.role === "label") {
+                                warn(
+                                    "expected_line_break_a_b",
+                                    right,
+                                    artifact(left),
+                                    artifact(right)
+                                );
+                            }
+                            free = false;
+                            open = false;
+                            no_space_only();
+                        }
+                    } else {
+
+// If left and right are opener and closer, then the placement of right depends
+// on the openness. Illegal pairs (like {]) have already been detected.
+
+                        if (left.line === right.line) {
+                            no_space();
+                        } else {
+                            at_margin(0);
+                        }
+                    }
+                } else {
+
+// If right is a closer, then pop the previous state.
+
+                    if (right.id === closer) {
+                        const previous = stack.pop();
+                        margin = previous.margin;
+                        if (open && right.id !== ";") {
+                            at_margin(0);
+                        } else {
+                            no_space_only();
+                        }
+                        closer = previous.closer;
+                        free = previous.free;
+                        open = previous.open;
+                        qmark = previous.qmark;
+                    } else {
+
+// Left is not an opener, and right is not a closer. The nature of left and
+// right will determine the space between them.
+
+// If left is , or ; or right is a statement then if open, right must go at the
+// margin, or if closed, a space between.
+
+
+                        if (right.switch) {
+                            unqmark();
+                            at_margin(-4);
+                        } else if (right.role === "label") {
+                            if (right.from !== 0) {
+                                expected_at(0);
+                            }
+                        } else if (left.id === ",") {
+                            unqmark();
+                            if (!open || (
+                                (free || closer === "]")
+                                && left.line === right.line
+                            )) {
+                                one_space();
+                            } else {
+                                at_margin(0);
+                            }
+
+// If right is a ternary operator, line it up on the margin. Use qmark to
+// deal with nested ternary operators.
+
+                        } else if (right.arity === "ternary") {
+                            if (right.id === "?") {
+                                margin += 4;
+                                qmark += "?";
+                            } else {
+                                result = qmark.match(rx_colons);
+                                qmark = result[1] + ":";
+                                margin -= 4 * result[2].length;
+                            }
+                            at_margin(0);
+                        } else if (
+                            right.arity === "binary"
+                            && right.id === "("
+                            && free
+                        ) {
+                            no_space();
+                        } else if (
+                            left.id === "."
+                            || left.id === "..."
+                            || right.id === ","
+                            || right.id === ";"
+                            || right.id === ":"
+                            || (
+                                right.arity === "binary"
+                                && (right.id === "(" || right.id === "[")
+                            )
+                            || (
+                                right.arity === "function"
+                                && left.id !== "function"
+                            )
+                        ) {
+                            no_space_only();
+                        } else if (right.id === ".") {
+                            if (left.line === right.line) {
+                                no_space();
+                            } else {
+                                if (!rx_dot.test(qmark)) {
+                                    qmark += ".";
+                                    margin += 4;
+                                }
+                                at_margin(0);
+                            }
+                        } else if (left.id === ";") {
+                            unqmark();
+                            if (open) {
+                                at_margin(0);
+                            } else {
+                                one_space();
+                            }
+                        } else if (
+                            left.arity === "ternary"
+                            || left.id === "case"
+                            || left.id === "catch"
+                            || left.id === "else"
+                            || left.id === "finally"
+                            || left.id === "while"
+                            || right.id === "catch"
+                            || right.id === "else"
+                            || right.id === "finally"
+                            || (right.id === "while" && !right.statement)
+                            || (left.id === ")" && right.id === "{")
+                        ) {
+                            one_space_only();
+                        } else if (right.statement === true) {
+                            if (open) {
+                                at_margin(0);
+                            } else {
+                                one_space();
+                            }
+                        } else if (
+                            left.id === "var"
+                            || left.id === "const"
+                            || left.id === "let"
+                        ) {
+                            stack.push({
+                                closer: closer,
+                                free: free,
+                                margin: margin,
+                                open: open,
+                                qmark: qmark
+                            });
+                            closer = ";";
+                            free = false;
+                            open = left.open;
+                            qmark = "";
+                            if (open) {
+                                margin = margin + 4;
+                                at_margin(0);
+                            } else {
+                                one_space_only();
+                            }
+                        } else if (
+
+// There is a space between left and right.
+
+                            spaceop[left.id] === true
+                            || spaceop[right.id] === true
+                            || (
+                                left.arity === "binary"
+                                && (left.id === "+" || left.id === "-")
+                            )
+                            || (
+                                right.arity === "binary"
+                                && (right.id === "+" || right.id === "-")
+                            )
+                            || left.id === "function"
+                            || left.id === ":"
+                            || (
+                                (
+                                    left.identifier
+                                    || left.id === "(string)"
+                                    || left.id === "(number)"
+                                )
+                                && (
+                                    right.identifier
+                                    || right.id === "(string)"
+                                    || right.id === "(number)"
+                                )
+                            )
+                            || (left.arity === "statement" && right.id !== ";")
+                        ) {
+                            one_space();
+                        } else if (left.arity === "unary" && left.id !== "`") {
+                            no_space_only();
+                        }
+                    }
+                }
+                nr_comments_skipped = 0;
+                delete left.calls;
+                delete left.dead;
+                delete left.free;
+                delete left.init;
+                delete left.open;
+                delete left.used;
+                left = right;
+            }
+        });
+    }
+
+// The jslint function itself.
+
+    return function jslint(source, option_object, global_array) {
+        try {
+            warnings = [];
+            option = Object.assign(empty(), option_object);
+            anon = "anonymous";
+            block_stack = [];
+            declared_globals = empty();
+            directive_mode = true;
+            directives = [];
+            early_stop = true;
+            exports = empty();
+            froms = [];
+            fudge = (option.fudge)
+                ? 1
+                : 0;
+            functions = [];
+            global = {
+                id: "(global)",
+                body: true,
+                context: empty(),
+                from: 0,
+                level: 0,
+                line: 0,
+                live: [],
+                loop: 0,
+                switch: 0,
+                thru: 0
+            };
+            blockage = global;
+            functionage = global;
+            json_mode = false;
+            mega_mode = false;
+            module_mode = false;
+            next_token = global;
+            property = empty();
+            stack = [];
+            tenure = undefined;
+            token = global;
+            token_nr = 0;
+            var_mode = undefined;
+            populate(declared_globals, standard, false);
+            if (global_array !== undefined) {
+                populate(declared_globals, global_array, false);
+            }
+            Object.keys(option).forEach(function (name) {
+                if (option[name] === true) {
+                    const allowed = allowed_option[name];
+                    if (Array.isArray(allowed)) {
+                        populate(declared_globals, allowed, false);
+                    }
+                }
+            });
+            tokenize(source);
+            advance();
+            if (json_mode) {
+                tree = json_value();
+                advance("(end)");
+            } else {
+
+// Because browsers encourage combining of script files, the first token might
+// be a semicolon to defend against a missing semicolon in the preceding file.
+
+                if (option.browser) {
+                    if (next_token.id === ";") {
+                        advance(";");
+                    }
+                } else {
+
+// If we are not in a browser, then the file form of strict pragma may be used.
+
+                    if (
+                        next_token.value === "use strict"
+                    ) {
+                        global.strict = next_token;
+                        advance("(string)");
+                        advance(";");
+                    }
+                }
+                tree = statements();
+                advance("(end)");
+                functionage = global;
+                walk_statement(tree);
+                if (module_mode && global.strict !== undefined) {
+                    warn("unexpected_a", global.strict);
+                }
+                if (warnings.length > 0) {
+                    uninitialized_and_unused();
+                    if (!option.white) {
+                        whitage();
+                    }
+                }
+            }
+            if (!option.browser) {
+                directives.forEach(function (comment) {
+                    if (comment.directive === "global") {
+                        warn("missing_browser", comment);
+                    }
+                });
+            }
+            early_stop = false;
+        } catch (e) {
+            if (e.name !== "JSLintError") {
+                warnings.push(e);
+            }
+        }
+        return {
+            directives: directives,
+            edition: "2018-01-26",
+            exports: exports,
+            froms: froms,
+            functions: functions,
+            global: global,
+            id: "(JSLint)",
+            json: json_mode,
+            lines: lines,
+            module: module_mode === true,
+            ok: warnings.length === 0 && !early_stop,
+            option: option,
+            property: property,
+            stop: early_stop,
+            tokens: tokens,
+            tree: tree,
+            warnings: warnings.sort(function (a, b) {
+                return a.line - b.line || a.column - b.column;
+            })
+        };
+    };
+}());
+
+/*node module.exports = jslint;*/
diff --git a/lib/jslint-es6.js b/lib/jslint-es6.js
index 88295f3..7ce6fd5 100755
--- a/lib/jslint-es6.js
+++ b/lib/jslint-es6.js
@@ -1,5 +1,5 @@
 // jslint.js
-// 2017-07-01
+// 2018-01-26
 // Copyright (c) 2015 Douglas Crockford  (www.JSLint.com)
 
 // Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -90,39 +90,38 @@
 /*property
     a, and, arity, assign, b, bad_assignment_a, bad_directive_a, bad_get,
     bad_module_name_a, bad_option_a, bad_property_a, bad_set, bitwise, block,
-    body, browser, c, calls, catch, charAt, charCodeAt, closer, closure, code,
-    column, complex, concat, constant, context, couch, create, d, dead,
+    body, browser, c, calls, catch, charCodeAt, closer, closure, code, column,
+    complex, concat, constant, context, convert, couch, create, d, dead,
     default, devel, directive, directives, disrupt, dot, duplicate_a, edition,
-    ellipsis, else, empty_block, es6, escape_mega, eval, every, expected_a,
+    ellipsis, else, empty_block, escape_mega, eval, every, expected_a,
     expected_a_at_b_c, expected_a_b, expected_a_b_from_c_d,
     expected_a_before_b, expected_a_next_at_b, expected_digits_after_a,
     expected_four_digits, expected_identifier_a, expected_line_break_a_b,
     expected_regexp_factor_a, expected_space_a_b, expected_statements_a,
     expected_string_a, expected_type_string_a, exports, expression, extra,
     finally, flag, for, forEach, free, from, froms, fud, fudge, function,
-    function_in_loop, functions, g, global, i, id, identifier, import, inc,
-    indexOf, infix_in, init, initial, isArray, isNaN, join, json, keys, label,
-    label_a, lbp, led, length, level, line, lines, live, loop, m, margin, match,
-    maxerr, maxlen, message, misplaced_a, misplaced_directive_a,
-    missing_browser, missing_m, module, multivar, naked_block, name, names,
-    nested_comment, new, node, not_label_a, nr, nud, number_isNaN, ok, open,
-    option, out_of_scope_a,
-    parameters, pop, property, push, qmark, quote, redefinition_a_b, replace,
-    required_a_optional_b, reserved_a, right, role, search, signature, single,
-    slice, some, sort, split, statement, stop, strict, subscript_a, switch,
-    test, this, thru, toString, todo_comment, tokens, too_long, too_many,
-    too_many_digits, tree, try, type, u, unclosed_comment, unclosed_mega,
-    unclosed_string, undeclared_a, unexpected_a, unexpected_a_after_b,
-    unexpected_a_before_b, unexpected_at_top_level_a, unexpected_char_a,
-    unexpected_comment, unexpected_directive_a, unexpected_expression_a,
-    unexpected_label_a, unexpected_parens, unexpected_space_a_b,
-    unexpected_statement_a, unexpected_trailing_space, unexpected_typeof_a,
-    uninitialized_a, unreachable_a, unregistered_property_a, unsafe, unused_a,
-    use_double, use_spaces, use_strict, used, value, var_loop, var_switch,
-    variable, warning, warnings, weird_condition_a, weird_expression_a,
-    weird_loop, weird_relation_a, white, wrap_assignment, wrap_condition,
-    wrap_immediate, wrap_parameter, wrap_regexp, wrap_unary, wrapped, writable,
-    y
+    function_in_loop, functions, g, getset, global, i, id, identifier, import,
+    inc, indexOf, infix_in, init, initial, isArray, isFinite, isNaN, join,
+    json, keys, label, label_a, lbp, led, length, level, line, lines, live,
+    loop, m, margin, match, maxerr, maxlen, message, misplaced_a,
+    misplaced_directive_a, missing_browser, missing_m, module, multivar,
+    naked_block, name, names, nested_comment, new, node, not_label_a, nr, nud,
+    number_isNaN, ok, open, option, out_of_scope_a, parameters, pop, property,
+    push, qmark, quote, redefinition_a_b, replace, required_a_optional_b,
+    reserved_a, right, role, search, signature, single, slice, some, sort,
+    split, statement, stop, strict, subscript_a, switch, test, this, thru,
+    toString, todo_comment, tokens, too_long, too_many, too_many_digits, tree,
+    try, type, u, unclosed_comment, unclosed_mega, unclosed_string,
+    undeclared_a, unexpected_a, unexpected_a_after_b, unexpected_a_before_b,
+    unexpected_at_top_level_a, unexpected_char_a, unexpected_comment,
+    unexpected_directive_a, unexpected_expression_a, unexpected_label_a,
+    unexpected_parens, unexpected_space_a_b, unexpected_statement_a,
+    unexpected_trailing_space, unexpected_typeof_a, uninitialized_a,
+    unreachable_a, unregistered_property_a, unsafe, unused_a, use_double,
+    use_spaces, use_strict, used, value, var_loop, var_switch, variable,
+    warning, warnings, weird_condition_a, weird_expression_a, weird_loop,
+    weird_relation_a, white, wrap_assignment, wrap_condition, wrap_immediate,
+    wrap_parameter, wrap_regexp, wrap_unary, wrapped, writable, y
 */
 
 var jslint = (function JSLint() {
@@ -146,7 +145,7 @@
         });
     }
 
-    var allowed_option = {
+    const allowed_option = {
 
 // These are the options that are recognized in the option object or that may
 // appear in a /*jslint*/ directive. Most options will have a boolean value,
@@ -155,7 +154,6 @@
 
         bitwise: true,
         browser: [
-            "Audio",
             "clearInterval",
             "clearTimeout",
             "document",
@@ -163,42 +161,36 @@
             "FileReader",
             "FormData",
             "history",
-            "Image",
             "localStorage",
             "location",
             "name",
             "navigator",
-            "Option",
             "screen",
             "sessionStorage",
             "setInterval",
             "setTimeout",
             "Storage",
+            "URL",
             "XMLHttpRequest"
         ],
         couch: [
             "emit", "getRow", "isArray", "log", "provides", "registerType",
             "require", "send", "start", "sum", "toJSON"
         ],
+        convert: true,
         devel: [
             "alert", "confirm", "console", "prompt"
         ],
-        es6: [
-            "ArrayBuffer", "DataView", "Float32Array", "Float64Array",
-            "Generator", "GeneratorFunction", "Int8Array", "Int16Array",
-            "Int32Array", "Intl", "Map", "Promise", "Proxy", "Reflect",
-            "Set", "Symbol", "System", "Uint8Array", "Uint8ClampedArray",
-            "Uint16Array", "Uint32Array", "WeakMap", "WeakSet"
-        ],
         eval: true,
         for: true,
         fudge: true,
+        getset: true,
         maxerr: 10000,
         maxlen: 10000,
         multivar: true,
         node: [
             "Buffer", "clearImmediate", "clearInterval", "clearTimeout",
-            "console", "exports", "global", "module", "process", "querystring",
+            "console", "exports", "module", "process", "querystring",
             "require", "setImmediate", "setInterval", "setTimeout",
             "__dirname", "__filename"
         ],
@@ -207,7 +199,7 @@
         white: true
     };
 
-    var spaceop = {
+    const spaceop = {
 
 // This is the set of infix operators that require a space on each side.
 
@@ -245,7 +237,7 @@
         "||": true
     };
 
-    var bitwiseop = {
+    const bitwiseop = {
 
 // These are the bitwise operators.
 
@@ -264,7 +256,7 @@
         ">>>=": true
     };
 
-    var opener = {
+    const opener = {
 
 // The open and close pairs.
 
@@ -274,7 +266,7 @@
         "${": "}"       // mega
     };
 
-    var relationop = {
+    const relationop = {
 
 // The relational operators.
 
@@ -288,18 +280,22 @@
         ">=": true
     };
 
-    var standard = [
+    const standard = [
 
-// These are the globals that are provided by the ES5 language standard.
+// These are the globals that are provided by the language standard.
 
-        "Array", "Boolean", "Date", "decodeURI", "decodeURIComponent",
-        "encodeURI", "encodeURIComponent", "Error", "EvalError", "isFinite",
-        "JSON", "Math", "Number", "Object", "parseInt", "parseFloat",
-        "RangeError", "ReferenceError", "RegExp", "String", "SyntaxError",
-        "TypeError", "URIError"
+        "Array", "ArrayBuffer", "Boolean", "DataView", "Date", "decodeURI",
+        "decodeURIComponent", "encodeURI", "encodeURIComponent", "Error",
+        "EvalError", "Float32Array", "Float64Array", "Generator",
+        "GeneratorFunction", "Int8Array", "Int16Array", "Int32Array", "Intl",
+        "JSON", "Map", "Math", "Number", "Object", "parseInt", "parseFloat",
+        "Promise", "Proxy", "RangeError", "ReferenceError", "Reflect", "RegExp",
+        "Set", "String", "Symbol", "SyntaxError", "System", "TypeError",
+        "Uint8Array", "Uint8ClampedArray", "Uint16Array", "Uint32Array",
+        "URIError", "WeakMap", "WeakSet"
     ];
 
-    var bundle = {
+    const bundle = {
 
 // The bundle contains the raw text messages that are generated by jslint. It
 // seems that they are all error messages and warnings. There are no "Atta
@@ -318,7 +314,6 @@
         bad_set: "A set function takes one parameter.",
         duplicate_a: "Duplicate '{a}'.",
         empty_block: "Empty block.",
-        es6: "Unexpected ES6 feature '{a}'.",
         escape_mega: "Unexpected escapement in mega literal.",
         expected_a: "Expected '{a}'.",
         expected_a_at_b_c: "Expected '{a}' at column {b}, not column {c}.",
@@ -337,7 +332,6 @@
         expected_type_string_a: "Expected a type string and instead saw '{a}'.",
         function_in_loop: "Don't make functions within a loop.",
         infix_in: "Unexpected 'in'. Compare with undefined, or use the hasOwnProperty method instead.",
-        isNaN: "Use the isNaN function to compare with NaN.",
         label_a: "'{a}' is a statement label.",
         misplaced_a: "Place '{a}' at the outermost level.",
         misplaced_directive_a: "Place the '/*{a}*/' directive before the first statement.",
@@ -390,10 +384,11 @@
         weird_relation_a: "Weird relation '{a}'.",
         wrap_assignment: "Don't wrap assignment statements in parens.",
         wrap_condition: "Wrap the condition in parens.",
-        wrap_immediate: "Wrap an immediate function invocation in "
-                + "parentheses to assist the reader in understanding that the "
-                + "expression is the result of a function, and not the "
-                + "function itself.",
+        wrap_immediate: (
+            "Wrap an immediate function invocation in parentheses to assist "
+            + "the reader in understanding that the expression is the result "
+            + "of a function, and not the function itself."
+        ),
         wrap_parameter: "Wrap the parameter in parens.",
         wrap_regexp: "Wrap this regexp in parens to avoid confusion.",
         wrap_unary: "Wrap the unary expression in parens."
@@ -402,43 +397,43 @@
 // Regular expression literals:
 
 // supplant {variables}
-    var rx_supplant = /\{([^{}]*)\}/g;
+    const rx_supplant = /\{([^{}]*)\}/g;
 // carriage return, carriage return linefeed, or linefeed
-    var rx_crlf = /\n|\r\n?/;
+    const rx_crlf = /\n|\r\n?/;
 // unsafe characters that are silently deleted by one or more browsers
-    var rx_unsafe = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;
+    const rx_unsafe = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;
 // identifier
-    var rx_identifier = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/;
-    var rx_module = /^[a-zA-Z0-9_$:.@\-\/]+$/;
-    var rx_bad_property = /^_|\$|Sync\$|_$/;
+    const rx_identifier = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/;
+    const rx_module = /^[a-zA-Z0-9_$:.@\-\/]+$/;
+    const rx_bad_property = /^_|\$|Sync\$|_$/;
 // star slash
-    var rx_star_slash = /\*\//;
+    const rx_star_slash = /\*\//;
 // slash star
-    var rx_slash_star = /\/\*/;
+    const rx_slash_star = /\/\*/;
 // slash star or ending slash
-    var rx_slash_star_or_slash = /\/\*|\/$/;
+    const rx_slash_star_or_slash = /\/\*|\/$/;
 // uncompleted work comment
-    var rx_todo = /\b(?:todo|TO\s?DO|HACK)\b/;
+    const rx_todo = /\b(?:todo|TO\s?DO|HACK)\b/;
 // tab
-    var rx_tab = /\t/g;
+    const rx_tab = /\t/g;
 // directive
-    var rx_directive = /^(jslint|property|global)\s+(.*)$/;
-    var rx_directive_part = /^([a-zA-Z$_][a-zA-Z0-9$_]*)\s*(?::\s*(true|false|[0-9]+)\s*)?(?:,\s*)?(.*)$/;
+    const rx_directive = /^(jslint|property|global)\s+(.*)$/;
+    const rx_directive_part = /^([a-zA-Z$_][a-zA-Z0-9$_]*)\s*(?::\s*(true|false|[0-9]+)\s*)?(?:,\s*)?(.*)$/;
 // token (sorry it is so long)
-    var rx_token = /^((\s+)|([a-zA-Z_$][a-zA-Z0-9_$]*)|[(){}\[\]?,:;'"~`]|=(?:==?|>)?|\.+|\/[=*\/]?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|[\^%]=?|&[&=]?|\|[|=]?|>{1,3}=?|<<?=?|!={0,2}|(0|[1-9][0-9]*))(.*)$/;
-    var rx_digits = /^([0-9]+)(.*)$/;
-    var rx_hexs = /^([0-9a-fA-F]+)(.*)$/;
-    var rx_octals = /^([0-7]+)(.*)$/;
-    var rx_bits = /^([01]+)(.*)$/;
+    const rx_token = /^((\s+)|([a-zA-Z_$][a-zA-Z0-9_$]*)|[(){}\[\]?,:;'"~`]|=(?:==?|>)?|\.+|[*\/][*\/=]?|\+[=+]?|-[=\-]?|[\^%]=?|&[&=]?|\|[|=]?|>{1,3}=?|<<?=?|!(?:!|==?)?|(0|[1-9][0-9]*))(.*)$/;
+    const rx_digits = /^([0-9]+)(.*)$/;
+    const rx_hexs = /^([0-9a-fA-F]+)(.*)$/;
+    const rx_octals = /^([0-7]+)(.*)$/;
+    const rx_bits = /^([01]+)(.*)$/;
 // mega
-    var rx_mega = /[`\\]|\$\{/;
+    const rx_mega = /[`\\]|\$\{/;
 // indentation
-    var rx_colons = /^(.*)\?([:.]*)$/;
-    var rx_dot = /\.$/;
+    const rx_colons = /^(.*)\?([:.]*)$/;
+    const rx_dot = /\.$/;
 // JSON number
-    var rx_JSON_number = /^-?\d+(?:\.\d*)?(?:e[\-+]?\d+)?$/i;
+    const rx_JSON_number = /^-?\d+(?:\.\d*)?(?:e[\-+]?\d+)?$/i;
 // initial cap
-    var rx_cap = /^[A-Z]/;
+    const rx_cap = /^[A-Z]/;
 
     function is_letter(string) {
         return (
@@ -449,42 +444,42 @@
 
     function supplant(string, object) {
         return string.replace(rx_supplant, function (found, filling) {
-            var replacement = object[filling];
+            const replacement = object[filling];
             return (replacement !== undefined)
                 ? replacement
                 : found;
         });
     }
 
-    var anon = "anonymous"; // The guessed name for anonymous functions.
-    var blockage;           // The current block.
-    var block_stack;        // The stack of blocks.
-    var declared_globals;   // The object containing the global declarations.
-    var directives;         // The directive comments.
-    var directive_mode;     // true if directives are still allowed.
-    var early_stop;         // true if JSLint cannot finish.
-    var exports;            // The exported names and values.
-    var froms;              // The array collecting all import-from strings.
-    var fudge;              // true if the natural numbers start with 1.
-    var functionage;        // The current function.
-    var functions;          // The array containing all of the functions.
-    var global;             // The global object; the outermost context.
-    var json_mode;          // true if parsing JSON.
-    var lines;              // The array containing source lines.
-    var module_mode;        // true if import or export was used.
-    var next_token;         // The next token to be examined in the parse.
-    var option;             // The options parameter.
-    var property;           // The object containing the tallied property names.
-    var mega_mode;          // true if currently parsing a megastring literal.
-    var stack;              // The stack of functions.
-    var syntax;             // The object containing the parser.
-    var token;              // The current token being examined in the parse.
-    var token_nr;           // The number of the next token.
-    var tokens;             // The array of tokens.
-    var tenure;             // The predefined property registry.
-    var tree;               // The abstract parse tree.
-    var var_mode;           // true if using var; false if using let.
-    var warnings;           // The array collecting all generated warnings.
+    let anon = "anonymous"; // The guessed name for anonymous functions.
+    let blockage;           // The current block.
+    let block_stack;        // The stack of blocks.
+    let declared_globals;   // The object containing the global declarations.
+    let directives;         // The directive comments.
+    let directive_mode;     // true if directives are still allowed.
+    let early_stop;         // true if JSLint cannot finish.
+    let exports;            // The exported names and values.
+    let froms;              // The array collecting all import-from strings.
+    let fudge;              // true if the natural numbers start with 1.
+    let functionage;        // The current function.
+    let functions;          // The array containing all of the functions.
+    let global;             // The global object; the outermost context.
+    let json_mode;          // true if parsing JSON.
+    let lines;              // The array containing source lines.
+    let module_mode;        // true if import or export was used.
+    let next_token;         // The next token to be examined in the parse.
+    let option;             // The options parameter.
+    let property;           // The object containing the tallied property names.
+    let mega_mode;          // true if currently parsing a megastring literal.
+    let stack;              // The stack of functions.
+    let syntax;             // The object containing the parser.
+    let token;              // The current token being examined in the parse.
+    let token_nr;           // The number of the next token.
+    let tokens;             // The array of tokens.
+    let tenure;             // The predefined property registry.
+    let tree;               // The abstract parse tree.
+    let var_mode;           // "var" if using var; "let" if using let.
+    let warnings;           // The array collecting all generated warnings.
 
 // Error reportage functions:
 
@@ -525,7 +520,7 @@
 // Report an error at some line and column of the program. The warning object
 // resembles an exception.
 
-        var warning = {         // ~~
+        const warning = {         // ~~
             name: "JSLintError",
             column: column,
             line: line,
@@ -614,18 +609,18 @@
             : source.split(rx_crlf);
         tokens = [];
 
-        var char;                   // a popular character
-        var column = 0;             // the column number of the next character
-        var first;                  // the first token
-        var from;                   // the starting column number of the token
-        var line = -1;              // the line number of the next character
-        var nr = 0;                 // the next token number
-        var previous = global;      // the previous token including comments
-        var prior = global;         // the previous token excluding comments
-        var mega_from;              // the starting column of megastring
-        var mega_line;              // the starting line of megastring
-        var snippet;                // a piece of string
-        var source_line;            // the current line source string
+        let char;                   // a popular character
+        let column = 0;             // the column number of the next character
+        let first;                  // the first token
+        let from;                   // the starting column number of the token
+        let line = -1;              // the line number of the next character
+        let nr = 0;                 // the next token number
+        let previous = global;      // the previous token including comments
+        let prior = global;         // the previous token excluding comments
+        let mega_from;              // the starting column of megastring
+        let mega_line;              // the starting line of megastring
+        let snippet;                // a piece of string
+        let source_line;            // the current line source string
 
         function next_line() {
 
@@ -633,7 +628,7 @@
 // replace them with spaces and give a warning. Also warn if the line contains
 // unsafe characters or is too damn long.
 
-            var at;
+            let at;
             column = 0;
             line += 1;
             source_line = lines[line];
@@ -699,7 +694,7 @@
                 );
             }
             if (source_line) {
-                char = source_line.charAt(0);
+                char = source_line[0];
                 source_line = source_line.slice(1);
                 snippet += char;
             } else {
@@ -727,7 +722,7 @@
         }
 
         function some_digits(rx, quiet) {
-            var result = source_line.match(rx);
+            const result = source_line.match(rx);
             if (result) {
                 char = result[1];
                 column += char.length;
@@ -751,6 +746,7 @@
             switch (next_char("\\")) {
             case "\\":
             case "/":
+            case "`":
             case "b":
             case "f":
             case "n":
@@ -765,9 +761,6 @@
                     if (some_digits(rx_hexs) > 5) {
                         warn_at("too_many_digits", line, column - 1);
                     }
-                    if (!option.es6) {
-                        warn_at("es6", line, column, "u{");
-                    }
                     if (next_char() !== "}") {
                         stop_at("expected_a_before_b", line, column, "}", char);
                     }
@@ -799,10 +792,10 @@
 
 // Make the token object and append it to the tokens list.
 
-            var the_token = {
+            const the_token = {
                 from: from,
                 id: id,
-                identifier: !!identifier,
+                identifier: Boolean(identifier),
                 line: line,
                 nr: nr,
                 thru: column
@@ -867,11 +860,11 @@
 // function processes one item, and calls itself recursively to process the
 // next one.
 
-            var result = body.match(rx_directive_part);
+            const result = body.match(rx_directive_part);
             if (result) {
-                var allowed;
-                var name = result[1];
-                var value = result[2];
+                let allowed;
+                const name = result[1];
+                const value = result[2];
                 switch (the_comment.directive) {
                 case "jslint":
                     allowed = allowed_option[name];
@@ -899,8 +892,8 @@
                         }
                         break;
                     case "number":
-                        if (isFinite(+value)) {
-                            option[name] = +value;
+                        if (Number.isFinite(Number(value))) {
+                            option[name] = Number(value);
                         } else {
                             warn(
                                 "bad_option_a",
@@ -939,14 +932,14 @@
 // Make a comment object. Comments are not allowed in JSON text. Comments can
 // include directives and notices of incompletion.
 
-            var the_comment = make("(comment)", snippet);
+            const the_comment = make("(comment)", snippet);
             if (Array.isArray(snippet)) {
                 snippet = snippet.join(" ");
             }
             if (!option.devel && rx_todo.test(snippet)) {
                 warn("todo_comment", the_comment);
             }
-            var result = snippet.match(rx_directive);
+            const result = snippet.match(rx_directive);
             if (result) {
                 if (!directive_mode) {
                     warn_at("misplaced_directive_a", line, from, result[1]);
@@ -963,9 +956,9 @@
 
 // Parse a regular expression literal.
 
-            var multi_mode = false;
-            var result;
-            var value;
+            let multi_mode = false;
+            let result;
+            let value;
 
             function quantifier() {
 
@@ -1135,7 +1128,7 @@
                         );
                         break;
                     case "$":
-                        if (source_line.charAt(0) !== "/") {
+                        if (source_line[0] !== "/") {
                             multi_mode = true;
                         }
                         break;
@@ -1186,25 +1179,17 @@
 
 // Process dangling flag letters.
 
-            var allowed = {
+            const allowed = {
                 g: true,
                 i: true,
                 m: true,
-                u: 6,
-                y: 6
+                u: true,
+                y: true
             };
-            var flag = empty();
+            const flag = empty();
             (function make_flag() {
                 if (is_letter(char)) {
-                    switch (allowed[char]) {
-                    case true:
-                        break;
-                    case 6:
-                        if (!option.es6) {
-                            warn_at("es6", line, column, char);
-                        }
-                        break;
-                    default:
+                    if (allowed[char] !== true) {
                         warn_at("unexpected_a", line, column, char);
                     }
                     allowed[char] = false;
@@ -1230,7 +1215,7 @@
 
 // Make a string token.
 
-            var the_token;
+            let the_token;
             snippet = "";
             next_char();
 
@@ -1302,9 +1287,9 @@
 // unexpected is going on.
 
             if (
-                (char >= "0" && char <= "9") ||
-                (char >= "a" && char <= "z") ||
-                (char >= "A" && char <= "Z")
+                (char >= "0" && char <= "9")
+                || (char >= "a" && char <= "z")
+                || (char >= "A" && char <= "Z")
             ) {
                 return stop_at(
                     "unexpected_a_after_b",
@@ -1319,12 +1304,12 @@
         }
 
         function lex() {
-            var array;
-            var i = 0;
-            var j = 0;
-            var last;
-            var result;
-            var the_token;
+            let array;
+            let i = 0;
+            let j = 0;
+            let last;
+            let result;
+            let the_token;
             if (!source_line) {
                 source_line = next_line();
                 from = 0;
@@ -1348,7 +1333,7 @@
                     "unexpected_char_a",
                     line,
                     column,
-                    source_line.charAt(0)
+                    source_line[0]
                 );
             }
 
@@ -1410,7 +1395,7 @@
 // string.
 
                 (function part() {
-                    var at = source_line.search(rx_mega);
+                    const at = source_line.search(rx_mega);
 
 // If neither ` nor ${ is seen, then the whole line joins the snippet.
 
@@ -1427,7 +1412,7 @@
                     snippet += source_line.slice(0, at);
                     column += at;
                     source_line = source_line.slice(at);
-                    if (source_line.charAt(0) === "\\") {
+                    if (source_line[0] === "\\") {
                         stop_at("escape_mega", line, at);
                     }
                     make("(string)", snippet).quote = "`";
@@ -1436,12 +1421,12 @@
 // If ${, then make tokens that will become part of an expression until
 // a } token is made.
 
-                    if (source_line.charAt(0) === "$") {
+                    if (source_line[0] === "$") {
                         column += 2;
                         make("${");
                         source_line = source_line.slice(2);
                         (function expr() {
-                            var id = lex().id;
+                            const id = lex().id;
                             if (id === "{") {
                                 return stop_at(
                                     "expected_a_b",
@@ -1478,7 +1463,7 @@
 
             case "/*":
                 array = [];
-                if (source_line.charAt(0) === "/") {
+                if (source_line[0] === "/") {
                     warn_at("unexpected_a", line, column + i, "/");
                 }
                 (function next() {
@@ -1541,7 +1526,7 @@
                         }
                     }
                 } else {
-                    last = prior.id.charAt(prior.id.length - 1);
+                    last = prior.id[prior.id.length - 1];
                     if ("(,=:?[".indexOf(last) >= 0) {
                         return regexp();
                     }
@@ -1551,7 +1536,7 @@
                         return the_token;
                     }
                 }
-                if (source_line.charAt(0) === "/") {
+                if (source_line[0] === "/") {
                     column += 1;
                     source_line = source_line.slice(1);
                     snippet = "/=";
@@ -1590,7 +1575,7 @@
 // Specialized tokens may have additional properties.
 
     function survey(name) {
-        var id = name.id;
+        let id = name.id;
 
 // Tally the property name. If it is a string, only tally strings that conform
 // to the identifier rules.
@@ -1639,7 +1624,7 @@
 
 // Deliver the next token, skipping the comments.
 
-        var cadet = tokens[token_nr];
+        const cadet = tokens[token_nr];
         token_nr += 1;
         if (cadet.id === "(comment)") {
             if (json_mode) {
@@ -1655,8 +1640,8 @@
 
 // Look ahead one token without advancing.
 
-        var old_token_nr = token_nr;
-        var cadet = dispense(true);
+        const old_token_nr = token_nr;
+        const cadet = dispense(true);
         token_nr = old_token_nr;
         return cadet;
     }
@@ -1700,18 +1685,18 @@
 // Parsing of JSON is simple:
 
     function json_value() {
-        var negative;
+        let negative;
 
         function json_object() {
-            var brace = next_token;
-            var object = empty();
-            var properties = [];
+            const brace = next_token;
+            const object = empty();
+            const properties = [];
             brace.expression = properties;
             advance("{");
             if (next_token.id !== "}") {
                 (function next() {
-                    var name;
-                    var value;
+                    let name;
+                    let value;
                     if (next_token.quote !== "\"") {
                         warn("unexpected_a", next_token, next_token.quote);
                     }
@@ -1739,8 +1724,8 @@
         }
 
         function json_array() {
-            var bracket = next_token;
-            var elements = [];
+            const bracket = next_token;
+            const elements = [];
             bracket.expression = elements;
             advance("[");
             if (next_token.id !== "]") {
@@ -1798,7 +1783,7 @@
 // function, label, parameter, or variable. We look for variable redefinition
 // because it causes confusion.
 
-        var id = name.id;
+        const id = name.id;
 
 // Reserved words may not be enrolled.
 
@@ -1808,7 +1793,7 @@
 
 // Has the name been enrolled in this context?
 
-            var earlier = functionage.context[id];
+            let earlier = functionage.context[id];
             if (earlier) {
                 warn(
                     "redefinition_a_b",
@@ -1821,7 +1806,7 @@
 
             } else {
                 stack.forEach(function (value) {
-                    var item = value.context[id];
+                    const item = value.context[id];
                     if (item !== undefined) {
                         earlier = item;
                     }
@@ -1832,10 +1817,14 @@
                             warn("unexpected_a", name);
                         }
                     } else {
-                        if ((
-                            role !== "exception" ||
-                            earlier.role !== "exception"
-                        ) && role !== "parameter") {
+                        if (
+                            (
+                                role !== "exception"
+                                || earlier.role !== "exception"
+                            )
+                            && role !== "parameter"
+                            && role !== "function"
+                        ) {
                             warn(
                                 "redefinition_a_b",
                                 name,
@@ -1873,8 +1862,8 @@
 // process leds (infix operators) until the bind powers cause it to stop. It
 // returns the expression's parse tree.
 
-        var left;
-        var the_symbol;
+        let left;
+        let the_symbol;
 
 // Statements will have already advanced, so advance now only if the token is
 // not the first of a statement,
@@ -1894,9 +1883,9 @@
         (function right() {
             the_symbol = syntax[next_token.id];
             if (
-                the_symbol !== undefined &&
-                the_symbol.led !== undefined &&
-                rbp < the_symbol.lbp
+                the_symbol !== undefined
+                && the_symbol.led !== undefined
+                && rbp < the_symbol.lbp
             ) {
                 advance();
                 left = the_symbol.led(left);
@@ -1910,8 +1899,8 @@
 
 // Parse the condition part of a do, if, while.
 
-        var the_paren = next_token;
-        var the_value;
+        const the_paren = next_token;
+        let the_value;
         the_paren.free = true;
         advance("(");
         the_value = expression(0);
@@ -1944,11 +1933,11 @@
 
     function is_weird(thing) {
         return (
-            thing.id === "(regexp)" ||
-            thing.id === "{" ||
-            thing.id === "=>" ||
-            thing.id === "function" ||
-            (thing.id === "[" && thing.arity === "unary")
+            thing.id === "(regexp)"
+            || thing.id === "{"
+            || thing.id === "=>"
+            || thing.id === "function"
+            || (thing.id === "[" && thing.arity === "unary")
         );
     }
 
@@ -1958,9 +1947,9 @@
         }
         if (Array.isArray(a)) {
             return (
-                Array.isArray(b) &&
-                a.length === b.length &&
-                a.every(function (value, index) {
+                Array.isArray(b)
+                && a.length === b.length
+                && a.every(function (value, index) {
                     return are_similar(value, b[index]);
                 })
             );
@@ -1971,8 +1960,8 @@
         if (a.id === "(number)" && b.id === "(number)") {
             return a.value === b.value;
         }
-        var a_string;
-        var b_string;
+        let a_string;
+        let b_string;
         if (a.id === "(string)") {
             a_string = a.value;
         } else if (a.id === "`" && a.constant) {
@@ -2045,10 +2034,10 @@
 // have use for one. A statement can be one of the standard statements, or
 // an assignment expression, or an invocation expression.
 
-        var first;
-        var the_label;
-        var the_statement;
-        var the_symbol;
+        let first;
+        let the_label;
+        let the_statement;
+        let the_symbol;
         advance();
         if (token.identifier && next_token.id === ":") {
             the_label = token;
@@ -2104,9 +2093,9 @@
 // Parse a list of statements. Give a warning if an unreachable statement
 // follows a disruptive statement.
 
-        var array = [];
+        const array = [];
         (function next(disrupt) {
-            var a_statement;
+            let a_statement;
             switch (next_token.id) {
             case "}":
             case "case":
@@ -2152,8 +2141,8 @@
 //          "naked"     No advance.
 //          undefined   An ordinary block.
 
-        var stmts;
-        var the_block;
+        let stmts;
+        let the_block;
         if (special !== "naked") {
             advance("{");
         }
@@ -2195,11 +2184,14 @@
 //      e.b
 //      e[b]
 //      v
+//      [destructure]
+//      {destructure}
 
         if (
-            the_thing.id !== "."
-            && the_thing.arity !== "variable"
-            && (the_thing.id !== "[" || the_thing.arity !== "binary")
+            the_thing.arity !== "variable"
+            && the_thing.id !== "."
+            && the_thing.id !== "["
+            && the_thing.id !== "{"
         ) {
             warn("bad_assignment_a", the_thing);
             return false;
@@ -2215,7 +2207,7 @@
 //      e()
 //      identifier
 
-        var id = left.id;
+        const id = left.id;
         if (
             !left.identifier
             && (
@@ -2235,7 +2227,7 @@
 
 // Make a symbol if it does not already exist in the language's syntax.
 
-        var the_symbol = syntax[id];
+        let the_symbol = syntax[id];
         if (the_symbol === undefined) {
             the_symbol = empty();
             the_symbol.id = id;
@@ -2252,10 +2244,10 @@
 // That case is special because that is when a variable gets initialized. The
 // other assignment operators can modify, but they cannot initialize.
 
-        var the_symbol = symbol(id, 20);
+        const the_symbol = symbol(id, 20);
         the_symbol.led = function (left) {
-            var the_token = token;
-            var right;
+            const the_token = token;
+            let right;
             the_token.arity = "assignment";
             right = expression(20 - 1);
             if (id === "=" && left.arity === "variable") {
@@ -2271,15 +2263,7 @@
                 warn("unexpected_a", right);
                 break;
             }
-            if (
-                option.es6 &&
-                left.arity === "unary" &&
-                (left.id === "[" || left.id === "{")
-            ) {
-                warn("expected_a_before_b", left, "const", left.id);
-            } else {
-                mutation_check(left);
-            }
+            mutation_check(left);
             return the_token;
         };
         return the_symbol;
@@ -2289,7 +2273,7 @@
 
 // Make a constant symbol.
 
-        var the_symbol = symbol(id);
+        const the_symbol = symbol(id);
         the_symbol.constant = true;
         the_symbol.nud = (typeof value === "function")
             ? value
@@ -2309,9 +2293,9 @@
 
 // Make an infix operator.
 
-        var the_symbol = symbol(id, bp);
+        const the_symbol = symbol(id, bp);
         the_symbol.led = function (left) {
-            var the_token = token;
+            const the_token = token;
             the_token.arity = "binary";
             if (f !== undefined) {
                 return f(left);
@@ -2322,11 +2306,25 @@
         return the_symbol;
     }
 
+    function infixr(id, bp) {
+
+// Make a right associative infix operator.
+
+        const the_symbol = symbol(id, bp);
+        the_symbol.led = function (left) {
+            const the_token = token;
+            the_token.arity = "binary";
+            the_token.expression = [left, expression(bp - 1)];
+            return the_token;
+        };
+        return the_symbol;
+    }
+
     function post(id) {
 
 // Make one of the post operators.
 
-        var the_symbol = symbol(id, 150);
+        const the_symbol = symbol(id, 150);
         the_symbol.led = function (left) {
             token.expression = left;
             token.arity = "post";
@@ -2340,9 +2338,9 @@
 
 // Make one of the pre operators.
 
-        var the_symbol = symbol(id);
+        const the_symbol = symbol(id);
         the_symbol.nud = function () {
-            var the_token = token;
+            const the_token = token;
             the_token.arity = "pre";
             the_token.expression = expression(150);
             mutation_check(the_token.expression);
@@ -2355,9 +2353,9 @@
 
 // Make a prefix operator.
 
-        var the_symbol = symbol(id);
+        const the_symbol = symbol(id);
         the_symbol.nud = function () {
-            var the_token = token;
+            const the_token = token;
             the_token.arity = "unary";
             if (typeof f === "function") {
                 return f();
@@ -2372,7 +2370,7 @@
 
 // Make a statement.
 
-        var the_symbol = symbol(id);
+        const the_symbol = symbol(id);
         the_symbol.fud = function () {
             token.arity = "statement";
             return f();
@@ -2384,10 +2382,10 @@
 
 // Make a ternary operator.
 
-        var the_symbol = symbol(id1, 30);
+        const the_symbol = symbol(id1, 30);
         the_symbol.led = function (left) {
-            var the_token = token;
-            var second = expression(20);
+            const the_token = token;
+            const second = expression(20);
             advance(id2);
             token.arity = "ternary";
             the_token.arity = "ternary";
@@ -2431,9 +2429,7 @@
     constant("(regexp)", "regexp");
     constant("(string)", "string");
     constant("arguments", "object", function () {
-        if (option.es6) {
-            warn("unexpected_a", token);
-        }
+        warn("unexpected_a", token);
         return token;
     });
     constant("eval", "function", function () {
@@ -2458,10 +2454,12 @@
         return token;
     });
     constant("Infinity", "number", Infinity);
+    constant("isFinite", "function", function () {
+        warn("expected_a_b", token, "Number.isFinite", "isFinite");
+        return token;
+    });
     constant("isNaN", "function", function () {
-        if (option.es6) {
-            warn("expected_a_b", token, "Number.isNaN", "isNaN");
-        }
+        warn("number_isNaN", token);
         return token;
     });
     constant("NaN", "number", NaN);
@@ -2511,9 +2509,10 @@
     infix("*", 140);
     infix("/", 140);
     infix("%", 140);
+    infixr("**", 150);
     infix("(", 160, function (left) {
-        var the_paren = token;
-        var the_argument;
+        const the_paren = token;
+        let the_argument;
         if (left.id !== "function") {
             left_check(left, the_paren);
         }
@@ -2523,11 +2522,8 @@
         the_paren.expression = [left];
         if (next_token.id !== ")") {
             (function next() {
-                var ellipsis;
+                let ellipsis;
                 if (next_token.id === "...") {
-                    if (!option.es6) {
-                        warn("es6");
-                    }
                     ellipsis = true;
                     advance("...");
                 }
@@ -2557,17 +2553,27 @@
         return the_paren;
     });
     infix(".", 170, function (left) {
-        var the_token = token;
-        var name = next_token;
+        const the_token = token;
+        const name = next_token;
         if (
-            (left.id !== "(string)" || name.id !== "indexOf") &&
-            (left.id !== "[" || (
-                name.id !== "concat" && name.id !== "forEach"
-            )) &&
-            (left.id !== "+" || name.id !== "slice") &&
-            (left.id !== "(regexp)" || (
-                name.id !== "exec" && name.id !== "test"
-            ))
+            (
+                left.id !== "(string)"
+                || (name.id !== "indexOf" && name.id !== "repeat")
+            )
+            && (
+                left.id !== "["
+                || (
+                    name.id !== "concat"
+                    && name.id !== "forEach"
+                    && name.id !== "join"
+                    && name.id !== "map"
+                )
+            )
+            && (left.id !== "+" || name.id !== "slice")
+            && (
+                left.id !== "(regexp)"
+                || (name.id !== "exec" && name.id !== "test")
+            )
         ) {
             left_check(left, the_token);
         }
@@ -2584,10 +2590,10 @@
         return the_token;
     });
     infix("[", 170, function (left) {
-        var the_token = token;
-        var the_subscript = expression(0);
+        const the_token = token;
+        const the_subscript = expression(0);
         if (the_subscript.id === "(string)" || the_subscript.id === "`") {
-            var name = survey(the_subscript);
+            const name = survey(the_subscript);
             if (rx_identifier.test(name)) {
                 warn("subscript_a", the_subscript, name);
             }
@@ -2602,10 +2608,7 @@
     });
 
     function do_tick() {
-        var the_tick = token;
-        if (!option.es6) {
-            warn("es6", the_tick);
-        }
+        const the_tick = token;
         the_tick.value = [];
         the_tick.expression = [];
         if (next_token.id !== "`") {
@@ -2625,7 +2628,7 @@
     }
 
     infix("`", 160, function (left) {
-        var the_tick = do_tick();
+        const the_tick = do_tick();
         left_check(left, the_tick);
         the_tick.expression = [left].concat(the_tick.expression);
         return the_tick;
@@ -2642,17 +2645,14 @@
     prefix("!");
     prefix("!!");
     prefix("[", function () {
-        var the_token = token;
+        const the_token = token;
         the_token.expression = [];
         if (next_token.id !== "]") {
             (function next() {
-                var element;
-                var ellipsis = false;
+                let element;
+                let ellipsis = false;
                 if (next_token.id === "...") {
                     ellipsis = true;
-                    if (!option.es6) {
-                        warn("es6");
-                    }
                     advance("...");
                 }
                 element = expression(10);
@@ -2676,8 +2676,8 @@
         return stop("expected_a_before_b", token, "()", "=>");
     });
     prefix("new", function () {
-        var the_new = token;
-        var right = expression(160);
+        const the_new = token;
+        const right = expression(160);
         if (next_token.id !== "(") {
             warn("expected_a_before_b", next_token, "()", artifact(next_token));
         }
@@ -2686,26 +2686,24 @@
     });
     prefix("typeof");
     prefix("void", function () {
-        var the_void = token;
+        const the_void = token;
         warn("unexpected_a", the_void);
         the_void.expression = expression(0);
         return the_void;
     });
 
     function parameter_list() {
-        var complex = false;
-        var list = [];
-        var optional;
-        var signature = ["("];
+        let complex = false;
+        const list = [];
+        let optional;
+        const signature = ["("];
         if (next_token.id !== ")" && next_token.id !== "(end)") {
             (function parameter() {
-                var ellipsis = false;
-                var param;
+                let ellipsis = false;
+                let param;
                 if (next_token.id === "{") {
                     complex = true;
-                    if (!option.es6) {
-                        warn("es6");
-                    } else if (optional !== undefined) {
+                    if (optional !== undefined) {
                         warn(
                             "required_a_optional_b",
                             next_token,
@@ -2718,7 +2716,7 @@
                     advance("{");
                     signature.push("{");
                     (function subparameter() {
-                        var subparam = next_token;
+                        let subparam = next_token;
                         if (!subparam.identifier) {
                             return stop("expected_identifier_a");
                         }
@@ -2751,9 +2749,7 @@
                     }
                 } else if (next_token.id === "[") {
                     complex = true;
-                    if (!option.es6) {
-                        warn("es6");
-                    } else if (optional !== undefined) {
+                    if (optional !== undefined) {
                         warn(
                             "required_a_optional_b",
                             next_token,
@@ -2766,7 +2762,7 @@
                     advance("[");
                     signature.push("[]");
                     (function subparameter() {
-                        var subparam = next_token;
+                        const subparam = next_token;
                         if (!subparam.identifier) {
                             return stop("expected_identifier_a");
                         }
@@ -2787,9 +2783,6 @@
                 } else {
                     if (next_token.id === "...") {
                         complex = true;
-                        if (!option.es6) {
-                            warn("es6");
-                        }
                         ellipsis = true;
                         signature.push("...");
                         advance("...");
@@ -2815,9 +2808,6 @@
                         if (next_token.id === "=") {
                             complex = true;
                             optional = param;
-                            if (!option.es6) {
-                                stop("unexpected_statement_a");
-                            }
                             advance("=");
                             param.expression = expression(0);
                         } else {
@@ -2845,7 +2835,7 @@
     }
 
     function do_function(the_function) {
-        var name;
+        let name;
         if (the_function === undefined) {
             the_function = token;
 
@@ -2914,7 +2904,7 @@
         advance("(");
         token.free = false;
         token.arity = "function";
-        var pl = parameter_list();
+        const pl = parameter_list();
         functionage.parameters = pl[0];
         functionage.signature = pl[1];
         functionage.complex = pl[2];
@@ -2930,8 +2920,8 @@
 
         the_function.block = block("body");
         if (
-            the_function.arity === "statement" &&
-            next_token.line === token.line
+            the_function.arity === "statement"
+            && next_token.line === token.line
         ) {
             return stop("unexpected_a", next_token);
         }
@@ -2952,7 +2942,7 @@
             stop("wrap_assignment", token);
         }
         advance("=>");
-        var the_fart = token;
+        const the_fart = token;
         the_fart.arity = "binary";
         the_fart.name = "=>";
         the_fart.level = functionage.level + 1;
@@ -2980,9 +2970,6 @@
         the_fart.parameters.forEach(function (name) {
             enroll(name, "parameter", true);
         });
-        if (!option.es6) {
-            warn("es6", the_fart);
-        }
         if (next_token.id === "{") {
             warn("expected_a_b", the_fart, "function", "=>");
             the_fart.block = block("body");
@@ -2994,9 +2981,9 @@
     }
 
     prefix("(", function () {
-        var the_paren = token;
-        var the_value;
-        var cadet = lookahead().id;
+        const the_paren = token;
+        let the_value;
+        const cadet = lookahead().id;
 
 // We can distinguish between a parameter list for => and a wrapped expression
 // with one token of lookahead.
@@ -3031,29 +3018,34 @@
     });
     prefix("`", do_tick);
     prefix("{", function () {
-        var the_brace = token;
-        var seen = empty();
+        const the_brace = token;
+        const seen = empty();
         the_brace.expression = [];
         if (next_token.id !== "}") {
             (function member() {
-                var extra;
-                var id;
-                var name = next_token;
-                var value;
+                let extra;
+                let full;
+                let id;
+                let name = next_token;
+                let value;
                 advance();
                 if (
-                    (name.id === "get" || name.id === "set") &&
-                    next_token.identifier
+                    (name.id === "get" || name.id === "set")
+                    && next_token.identifier
                 ) {
-                    extra = name.id + " " + next_token.id;
+                    if (!option.getset) {
+                        warn("unexpected_a", name);
+                    }
+                    extra = name.id;
+                    full = extra + " " + next_token.id;
                     name = next_token;
                     advance();
                     id = survey(name);
-                    if (seen[extra] === true || seen[id] === true) {
+                    if (seen[full] === true || seen[id] === true) {
                         warn("duplicate_a", name);
                     }
                     seen[id] = false;
-                    seen[extra] = true;
+                    seen[full] = true;
                 } else {
                     id = survey(name);
                     if (typeof seen[id] === "boolean") {
@@ -3065,18 +3057,12 @@
                     switch (next_token.id) {
                     case "}":
                     case ",":
-                        if (!option.es6) {
-                            warn("es6");
-                        }
                         if (typeof extra === "string") {
                             advance("(");
                         }
                         value = expression(Infinity, true);
                         break;
                     case "(":
-                        if (!option.es6 && typeof extra !== "string") {
-                            warn("es6");
-                        }
                         value = do_function({
                             arity: "unary",
                             from: name.from,
@@ -3125,8 +3111,8 @@
         return block("naked");
     });
     stmt("break", function () {
-        var the_break = token;
-        var the_label;
+        const the_break = token;
+        let the_label;
         if (
             (functionage.loop < 1 && functionage.switch < 1)
             || functionage.finally > 0
@@ -3137,9 +3123,9 @@
         if (next_token.identifier && token.line === next_token.line) {
             the_label = functionage.context[next_token.id];
             if (
-                the_label === undefined ||
-                the_label.role !== "label" ||
-                the_label.dead
+                the_label === undefined
+                || the_label.role !== "label"
+                || the_label.dead
             ) {
                 warn((the_label !== undefined && the_label.dead)
                     ? "out_of_scope_a"
@@ -3155,29 +3141,23 @@
     });
 
     function do_var() {
-        var the_statement = token;
-        var is_const = the_statement.id === "const";
+        const the_statement = token;
+        const is_const = the_statement.id === "const";
         the_statement.names = [];
 
-// A program may use var or let, but not both, and let and const require
-// option.es6.
+// A program may use var or let, but not both.
 
-        if (is_const) {
-            if (!option.es6) {
-                warn("es6", the_statement);
+        if (!is_const) {
+            if (var_mode === undefined) {
+                var_mode = the_statement.id;
+            } else if (the_statement.id !== var_mode) {
+                warn(
+                    "expected_a_b",
+                    the_statement,
+                    var_mode,
+                    the_statement.id
+                );
             }
-        } else if (var_mode === undefined) {
-            var_mode = the_statement.id;
-            if (!option.es6 && var_mode !== "var") {
-                warn("es6", the_statement);
-            }
-        } else if (the_statement.id !== var_mode) {
-            warn(
-                "expected_a_b",
-                the_statement,
-                var_mode,
-                the_statement.id
-            );
         }
 
 // We don't expect to see variables created in switch statements.
@@ -3190,14 +3170,14 @@
         }
         (function next() {
             if (next_token.id === "{" && the_statement.id !== "var") {
-                var the_brace = next_token;
+                const the_brace = next_token;
                 the_brace.names = [];
                 advance("{");
                 (function pair() {
                     if (!next_token.identifier) {
                         return stop("expected_identifier_a", next_token);
                     }
-                    var name = next_token;
+                    const name = next_token;
                     survey(name);
                     advance();
                     if (next_token.id === ":") {
@@ -3223,11 +3203,11 @@
                 the_brace.expression = expression(0);
                 the_statement.names.push(the_brace);
             } else if (next_token.id === "[" && the_statement.id !== "var") {
-                var the_bracket = next_token;
+                const the_bracket = next_token;
                 the_bracket.names = [];
                 advance("[");
                 (function element() {
-                    var ellipsis;
+                    let ellipsis;
                     if (next_token.id === "...") {
                         ellipsis = true;
                         advance("...");
@@ -3235,7 +3215,7 @@
                     if (!next_token.identifier) {
                         return stop("expected_identifier_a", next_token);
                     }
-                    var name = next_token;
+                    const name = next_token;
                     advance();
                     the_bracket.names.push(name);
                     enroll(name, "variable", the_statement.id === "const");
@@ -3251,7 +3231,7 @@
                 the_bracket.expression = expression(0);
                 the_statement.names.push(the_bracket);
             } else if (next_token.identifier) {
-                var name = next_token;
+                const name = next_token;
                 advance();
                 if (name.id === "ignore") {
                     warn("unexpected_a", name);
@@ -3274,16 +3254,17 @@
                 return next();
             }
         }());
-        the_statement.open =
-                the_statement.names.length > 1 &&
-                the_statement.line !== the_statement.names[1].line;
+        the_statement.open = (
+            the_statement.names.length > 1
+            && the_statement.line !== the_statement.names[1].line
+        );
         semicolon();
         return the_statement;
     }
 
     stmt("const", do_var);
     stmt("continue", function () {
-        var the_continue = token;
+        const the_continue = token;
         if (functionage.loop < 1 || functionage.finally > 0) {
             warn("unexpected_a", the_continue);
         }
@@ -3294,7 +3275,7 @@
         return the_continue;
     });
     stmt("debugger", function () {
-        var the_debug = token;
+        const the_debug = token;
         if (!option.devel) {
             warn("unexpected_a", the_debug);
         }
@@ -3302,8 +3283,8 @@
         return the_debug;
     });
     stmt("delete", function () {
-        var the_token = token;
-        var the_value = expression(0);
+        const the_token = token;
+        const the_value = expression(0);
         if (
             (the_value.id !== "." && the_value.id !== "[")
             || the_value.arity !== "binary"
@@ -3315,7 +3296,7 @@
         return the_token;
     });
     stmt("do", function () {
-        var the_do = token;
+        const the_do = token;
         not_top_level(the_do);
         functionage.loop += 1;
         the_do.block = block();
@@ -3329,10 +3310,10 @@
         return the_do;
     });
     stmt("export", function () {
-        var the_export = token;
-        var the_id;
-        var the_name;
-        var the_thing;
+        const the_export = token;
+        let the_id;
+        let the_name;
+        let the_thing;
 
         function export_id() {
             if (!next_token.identifier) {
@@ -3353,19 +3334,14 @@
             the_export.expression.push(the_thing);
         }
 
-        if (!option.es6) {
-            warn("es6", the_export);
-        }
         the_export.expression = [];
         if (next_token.id === "default") {
             if (exports.default !== undefined) {
                 warn("duplicate_a");
             }
             advance("default");
-            the_thing = expression();
-            if (the_thing.id !== "function") {
-                semicolon();
-            }
+            the_thing = expression(0);
+            semicolon();
             exports.default = the_thing;
             the_export.expression.push(the_thing);
         } else {
@@ -3412,8 +3388,8 @@
         return the_export;
     });
     stmt("for", function () {
-        var first;
-        var the_for = token;
+        let first;
+        const the_for = token;
         if (!option.for) {
             warn("unexpected_a", the_for);
         }
@@ -3458,8 +3434,8 @@
     });
     stmt("function", do_function);
     stmt("if", function () {
-        var the_else;
-        var the_if = token;
+        let the_else;
+        const the_if = token;
         the_if.expression = condition();
         the_if.block = block();
         if (next_token.id === "else") {
@@ -3479,11 +3455,9 @@
         return the_if;
     });
     stmt("import", function () {
-        var the_import = token;
-        var name;
-        if (!option.es6) {
-            warn("es6", the_import);
-        } else if (typeof module_mode === "object") {
+        const the_import = token;
+        let name;
+        if (typeof module_mode === "object") {
             warn("unexpected_directive_a", module_mode, module_mode.directive);
         }
         module_mode = true;
@@ -3496,7 +3470,7 @@
             enroll(name, "variable", true);
             the_import.name = name;
         } else {
-            var names = [];
+            const names = [];
             advance("{");
             if (next_token.id !== "}") {
                 while (true) {
@@ -3531,7 +3505,7 @@
     });
     stmt("let", do_var);
     stmt("return", function () {
-        var the_return = token;
+        const the_return = token;
         not_top_level(the_return);
         if (functionage.finally > 0) {
             warn("unexpected_a", the_return);
@@ -3544,12 +3518,12 @@
         return the_return;
     });
     stmt("switch", function () {
-        var dups = [];
-        var last;
-        var stmts;
-        var the_cases = [];
-        var the_disrupt = true;
-        var the_switch = token;
+        let dups = [];
+        let last;
+        let stmts;
+        const the_cases = [];
+        let the_disrupt = true;
+        const the_switch = token;
         not_top_level(the_switch);
         if (functionage.finally > 0) {
             warn("unexpected_a", the_switch);
@@ -3562,13 +3536,13 @@
         advance(")");
         advance("{");
         (function major() {
-            var the_case = next_token;
+            const the_case = next_token;
             the_case.arity = "statement";
             the_case.expression = [];
             (function minor() {
                 advance("case");
                 token.switch = true;
-                var exp = expression(0);
+                const exp = expression(0);
                 if (dups.some(function (thing) {
                     return are_similar(thing, exp);
                 })) {
@@ -3607,7 +3581,7 @@
         }());
         dups = undefined;
         if (next_token.id === "default") {
-            var the_default = next_token;
+            const the_default = next_token;
             advance("default");
             token.switch = true;
             advance(":");
@@ -3616,7 +3590,7 @@
                 warn("unexpected_a", the_default);
                 the_disrupt = false;
             } else {
-                var the_last = the_switch.else[the_switch.else.length - 1];
+                const the_last = the_switch.else[the_switch.else.length - 1];
                 if (the_last.id === "break" && the_last.label === undefined) {
                     warn("unexpected_a", the_last);
                     the_last.disrupt = false;
@@ -3632,16 +3606,16 @@
         return the_switch;
     });
     stmt("throw", function () {
-        var the_throw = token;
+        const the_throw = token;
         the_throw.disrupt = true;
         the_throw.expression = expression(10);
         semicolon();
         return the_throw;
     });
     stmt("try", function () {
-        var the_catch;
-        var the_disrupt;
-        var the_try = token;
+        let the_catch;
+        let the_disrupt;
+        const the_try = token;
         if (functionage.try > 0) {
             warn("unexpected_a", the_try);
         }
@@ -3649,7 +3623,7 @@
         the_try.block = block();
         the_disrupt = the_try.block.disrupt;
         if (next_token.id === "catch") {
-            var ignored = "ignore";
+            let ignored = "ignore";
             the_catch = next_token;
             the_try.catch = the_catch;
             advance("catch");
@@ -3690,7 +3664,7 @@
     });
     stmt("var", do_var);
     stmt("while", function () {
-        var the_while = token;
+        const the_while = token;
         not_top_level(the_while);
         functionage.loop += 1;
         the_while.expression = condition();
@@ -3715,8 +3689,8 @@
 // the tree is traversed.
 
         return function (arity, id, task) {
-            var a_set = when[arity];
-            var i_set;
+            let a_set = when[arity];
+            let i_set;
 
 // The id parameter is optional. If excluded, the task will be applied to all
 // ids.
@@ -3758,8 +3732,8 @@
 // Given a task set that was built by an action function, run all of the
 // relevant tasks on the token.
 
-            var a_set = when[the_token.arity];
-            var i_set;
+            let a_set = when[the_token.arity];
+            let i_set;
 
 // If there are tasks associated with the token's arity...
 
@@ -3786,12 +3760,12 @@
         };
     }
 
-    var posts = empty();
-    var pres = empty();
-    var preaction = action(pres);
-    var postaction = action(posts);
-    var preamble = amble(pres);
-    var postamble = amble(posts);
+    const posts = empty();
+    const pres = empty();
+    const preaction = action(pres);
+    const postaction = action(posts);
+    const preamble = amble(pres);
+    const postamble = amble(posts);
 
     function walk_expression(thing) {
         if (thing) {
@@ -3854,14 +3828,14 @@
 
 // Look up the variable in the current context.
 
-            var the_variable = functionage.context[thing.id];
+            let the_variable = functionage.context[thing.id];
 
 // If it isn't local, search all the other contexts. If there are name
 // collisions, take the most recent.
 
             if (the_variable === undefined) {
                 stack.forEach(function (outer) {
-                    var a_variable = outer.context[thing.id];
+                    const a_variable = outer.context[thing.id];
                     if (
                         a_variable !== undefined
                         && a_variable.role !== "label"
@@ -3913,9 +3887,9 @@
         }
         if (thing.level === 1) {
             if (
-                module_mode === true ||
-                global.strict !== undefined ||
-                thing.complex
+                module_mode === true
+                || global.strict !== undefined
+                || thing.complex
             ) {
                 if (thing.id !== "=>" && thing.block.strict !== undefined) {
                     warn("unexpected_a", thing.block.strict);
@@ -4007,26 +3981,18 @@
     preaction("binary", bitwise_check);
     preaction("binary", function (thing) {
         if (relationop[thing.id] === true) {
-            var left = thing.expression[0];
-            var right = thing.expression[1];
+            const left = thing.expression[0];
+            const right = thing.expression[1];
             if (left.id === "NaN" || right.id === "NaN") {
-                if (option.es6) {
-                    warn("number_isNaN", thing);
-                } else {
-                    warn("isNaN", thing);
-                }
+                warn("number_isNaN", thing);
             } else if (left.id === "typeof") {
                 if (right.id !== "(string)") {
                     if (right.id !== "typeof") {
                         warn("expected_string_a", right);
                     }
                 } else {
-                    var value = right.value;
-                    if (value === "symbol") {
-                        if (!option.es6) {
-                            warn("es6", right, value);
-                        }
-                    } else if (value === "null" || value === "undefined") {
+                    const value = right.value;
+                    if (value === "null" || value === "undefined") {
                         warn("unexpected_typeof_a", right, value);
                     } else if (
                         value !== "boolean"
@@ -4034,6 +4000,7 @@
                         && value !== "number"
                         && value !== "object"
                         && value !== "string"
+                        && value !== "symbol"
                     ) {
                         warn("expected_type_string_a", right, value);
                     }
@@ -4056,15 +4023,15 @@
         });
     });
     preaction("binary", "(", function (thing) {
-        var left = thing.expression[0];
+        const left = thing.expression[0];
         if (
             left.identifier
             && functionage.context[left.id] === undefined
             && typeof functionage.name === "object"
         ) {
-            var parent = functionage.name.function;
+            const parent = functionage.name.function;
             if (parent) {
-                var left_variable = parent.context[left.id];
+                const left_variable = parent.context[left.id];
                 if (
                     left_variable !== undefined
                     && left_variable.dead
@@ -4095,7 +4062,7 @@
     });
     preaction("statement", "for", function (thing) {
         if (thing.name !== undefined) {
-            var the_variable = lookup(thing.name);
+            const the_variable = lookup(thing.name);
             if (the_variable !== undefined) {
                 the_variable.init = true;
                 if (!the_variable.writable) {
@@ -4109,7 +4076,7 @@
     preaction("unary", "~", bitwise_check);
     preaction("unary", "function", preaction_function);
     preaction("variable", function (thing) {
-        var the_variable = lookup(thing);
+        const the_variable = lookup(thing);
         if (the_variable !== undefined) {
             thing.variable = the_variable;
             the_variable.used += 1;
@@ -4117,7 +4084,7 @@
     });
 
     function init_variable(name) {
-        var the_variable = lookup(name);
+        const the_variable = lookup(name);
         if (the_variable !== undefined) {
             if (the_variable.writable) {
                 the_variable.init = true;
@@ -4127,13 +4094,28 @@
         warn("bad_assignment_a", name);
     }
 
+    postaction("assignment", "+=", function (thing) {
+        let right = thing.expression[1];
+        if (right.constant) {
+            if (
+                right.value === ""
+                || (right.id === "(number)" && right.value === "0")
+                || right.id === "(boolean)"
+                || right.id === "null"
+                || right.id === "undefined"
+                || Number.isNaN(right.value)
+            ) {
+                warn("unexpected_a", right);
+            }
+        }
+    });
     postaction("assignment", function (thing) {
 
 // Assignment using = sets the init property of a variable. No other assignment
 // operator can do this. A = token keeps that variable (or array of variables
 // in case of destructuring) in its name property.
 
-        var lvalue = thing.expression[0];
+        const lvalue = thing.expression[0];
         if (thing.id === "=") {
             if (thing.names !== undefined) {
                 if (Array.isArray(thing.names)) {
@@ -4160,10 +4142,10 @@
                     warn("bad_assignment_a", lvalue);
                 }
             }
-            var right = syntax[thing.expression[1].id];
+            const right = syntax[thing.expression[1].id];
             if (
-                right !== undefined &&
-                (
+                right !== undefined
+                && (
                     right.id === "function"
                     || right.id === "=>"
                     || (
@@ -4191,21 +4173,30 @@
     }
 
     postaction("binary", function (thing) {
-        var right;
+        let right;
         if (relationop[thing.id]) {
             if (
                 is_weird(thing.expression[0])
                 || is_weird(thing.expression[1])
                 || are_similar(thing.expression[0], thing.expression[1])
                 || (
-                    thing.expression[0].constant === true &&
-                    thing.expression[1].constant === true
+                    thing.expression[0].constant === true
+                    && thing.expression[1].constant === true
                 )
             ) {
                 warn("weird_relation_a", thing);
             }
         }
         switch (thing.id) {
+        case "+":
+            if (!option.convert) {
+                if (thing.expression[0].value === "") {
+                    warn("expected_a_b", thing, "String(...)", "\"\" +");
+                } else if (thing.expression[1].value === "") {
+                    warn("expected_a_b", thing, "String(...)", "+ \"\"");
+                }
+            }
+            break;
         case "=>":
         case "(":
         case "[":
@@ -4218,16 +4209,16 @@
         default:
             right = thing.expression[1];
             if (
-                (thing.id === "+" || thing.id === "-") &&
-                right.id === thing.id &&
-                right.arity === "unary" &&
-                !right.wrapped
+                (thing.id === "+" || thing.id === "-")
+                && right.id === thing.id
+                && right.arity === "unary"
+                && !right.wrapped
             ) {
                 warn("wrap_unary", right);
             }
             if (
-                thing.expression[0].constant === true &&
-                right.constant === true
+                thing.expression[0].constant === true
+                && right.constant === true
             ) {
                 thing.constant = true;
             }
@@ -4254,9 +4245,9 @@
     });
     postaction("binary", "=>", postaction_function);
     postaction("binary", "(", function (thing) {
-        var left = thing.expression[0];
-        var the_new;
-        var arg;
+        let left = thing.expression[0];
+        let the_new;
+        let arg;
         if (left.id === "new") {
             the_new = left;
             left = left.expression;
@@ -4268,11 +4259,11 @@
         } else if (left.identifier) {
             if (the_new !== undefined) {
                 if (
-                    left.id.charAt(0) > "Z"
+                    left.id[0] > "Z"
                     || left.id === "Boolean"
                     || left.id === "Number"
                     || left.id === "String"
-                    || (left.id === "Symbol" && option.es6)
+                    || left.id === "Symbol"
                 ) {
                     warn("unexpected_a", the_new);
                 } else if (left.id === "Function") {
@@ -4281,13 +4272,16 @@
                     }
                 } else if (left.id === "Array") {
                     arg = thing.expression;
-                    if (arg.length !== 2 || (
-                        (
-                            arg[1].id !== "(number)"
-                            || +arg[1].value !== (arg[1].value | 0)
-                        ) &&
-                        arg[1].arity !== "binary"
-                    )) {
+                    if (
+                        arg.length !== 2
+                        || (
+                            (
+                                arg[1].id !== "(number)"
+                                || Number(arg[1].value) !== (arg[1].value | 0)
+                            )
+                            && arg[1].arity !== "binary"
+                        )
+                    ) {
                         warn("expected_a_b", left, "[]", "new Array");
                     }
                 } else if (left.id === "Object") {
@@ -4300,8 +4294,8 @@
                 }
             } else {
                 if (
-                    left.id.charAt(0) >= "A"
-                    && left.id.charAt(0) <= "Z"
+                    left.id[0] >= "A"
+                    && left.id[0] <= "Z"
                     && left.id !== "Boolean"
                     && left.id !== "Number"
                     && left.id !== "String"
@@ -4316,7 +4310,7 @@
                 }
             }
         } else if (left.id === ".") {
-            var cack = the_new !== undefined;
+            let cack = the_new !== undefined;
             if (left.expression.id === "Date" && left.name.id === "UTC") {
                 cack = !cack;
             }
@@ -4333,15 +4327,18 @@
                 }
             }
             if (left.name.id === "getTime") {
-                var l1 = left.expression;
-                if (l1.id === "(") {
-                    var l2 = l1.expression;
-                    if (l2.length === 1) {
-                        var l3 = l2[0];
-                        if (l3.id === "new" && l3.expression.id === "Date") {
+                const paren = left.expression;
+                if (paren.id === "(") {
+                    const array = paren.expression;
+                    if (array.length === 1) {
+                        const new_date = array[0];
+                        if (
+                            new_date.id === "new"
+                            && new_date.expression.id === "Date"
+                        ) {
                             warn(
                                 "expected_a_b",
-                                l3,
+                                new_date,
                                 "Date.now()",
                                 "new Date().getTime()"
                             );
@@ -4367,7 +4364,7 @@
     });
     postaction("statement", "function", postaction_function);
     postaction("statement", "import", function (the_thing) {
-        var name = the_thing.name;
+        const name = the_thing.name;
         if (Array.isArray(name)) {
             name.forEach(function (name) {
                 name.dead = false;
@@ -4384,9 +4381,9 @@
     postaction("statement", "let", action_var);
     postaction("statement", "try", function (thing) {
         if (thing.catch !== undefined) {
-            var the_name = thing.catch.name;
+            const the_name = thing.catch.name;
             if (the_name !== undefined) {
-                var the_variable = functionage.context[the_name.id];
+                const the_variable = functionage.context[the_name.id];
                 the_variable.dead = false;
                 the_variable.init = true;
             }
@@ -4406,19 +4403,22 @@
         } else if (are_similar(thing.expression[0], thing.expression[2])) {
             warn("expected_a_b", thing, "&&", "?");
         } else if (
-            thing.expression[1].id === "true" &&
-            thing.expression[2].id === "false"
+            thing.expression[1].id === "true"
+            && thing.expression[2].id === "false"
         ) {
             warn("expected_a_b", thing, "!!", "?");
         } else if (
-            thing.expression[1].id === "false" &&
-            thing.expression[2].id === "true"
+            thing.expression[1].id === "false"
+            && thing.expression[2].id === "true"
         ) {
             warn("expected_a_b", thing, "!", "?");
-        } else if (thing.expression[0].wrapped !== true && (
-            thing.expression[0].id === "||"
-            || thing.expression[0].id === "&&"
-        )) {
+        } else if (
+            thing.expression[0].wrapped !== true
+            && (
+                thing.expression[0].id === "||"
+                || thing.expression[0].id === "&&"
+            )
+        ) {
             warn("wrap_condition", thing.expression[0]);
         }
     });
@@ -4441,6 +4441,11 @@
                 warn("unexpected_a", thing);
             }
             break;
+        case "!!":
+            if (!option.convert) {
+                warn("expected_a_b", thing, "Boolean(...)", "!!");
+            }
+            break;
         default:
             if (thing.expression.constant === true) {
                 thing.constant = true;
@@ -4449,10 +4454,17 @@
     });
     postaction("unary", "function", postaction_function);
     postaction("unary", "+", function (thing) {
-        var right = thing.expression;
+        if (!option.convert) {
+            warn("expected_a_b", thing, "Number(...)", "+");
+        }
+        const right = thing.expression;
         if (right.id === "(" && right.expression[0].id === "new") {
             warn("unexpected_a_before_b", thing, "+", "new");
-        } else if (right.constant || right.id === "{" || right.id === "[") {
+        } else if (
+            right.constant
+            || right.id === "{"
+            || (right.id === "[" && right.arity !== "binary")
+        ) {
             warn("unexpected_a", thing, "+");
         }
     });
@@ -4460,12 +4472,14 @@
     function delve(the_function) {
         Object.keys(the_function.context).forEach(function (id) {
             if (id !== "ignore") {
-                var name = the_function.context[id];
+                const name = the_function.context[id];
                 if (name.function === the_function) {
-                    if (name.used === 0 && (
-                        name.role !== "function"
-                        || name.function.arity !== "unary"
-                    )) {
+                    if (
+                        name.used === 0 && (
+                            name.role !== "function"
+                            || name.function.arity !== "unary"
+                        )
+                    ) {
                         warn("unused_a", name);
                     } else if (!name.init) {
                         warn("uninitialized_a", name);
@@ -4490,15 +4504,15 @@
 // Go through the token list, looking at usage of whitespace.
 
     function whitage() {
-        var closer = "(end)";
-        var free = false;
-        var left = global;
-        var margin = 0;
-        var nr_comments_skipped = 0;
-        var open = true;
-        var qmark = "";
-        var result;
-        var right;
+        let closer = "(end)";
+        let free = false;
+        let left = global;
+        let margin = 0;
+        let nr_comments_skipped = 0;
+        let open = true;
+        let qmark = "";
+        let result;
+        let right;
 
         function expected_at(at) {
             warn(
@@ -4511,7 +4525,7 @@
         }
 
         function at_margin(fit) {
-            var at = margin + fit;
+            const at = margin + fit;
             if (right.from !== at) {
                 return expected_at(at);
             }
@@ -4519,10 +4533,11 @@
 
         function no_space_only() {
             if (
-                left.id !== "(global)" &&
-                left.nr + 1 === right.nr && (
-                    left.line !== right.line ||
-                    left.thru !== right.from
+                left.id !== "(global)"
+                && left.nr + 1 === right.nr
+                && (
+                    left.line !== right.line
+                    || left.thru !== right.from
                 )
             ) {
                 warn(
@@ -4546,7 +4561,7 @@
                 }
             } else {
                 if (open) {
-                    var at = (free)
+                    const at = (free)
                         ? margin
                         : margin + 8;
                     if (right.from < at) {
@@ -4587,7 +4602,9 @@
                         expected_at(margin);
                     }
                 } else {
-                    var mislaid = stack[stack.length - 1].right;
+                    const mislaid = (stack.length) > 0
+                        ? stack[stack.length - 1].right
+                        : undefined;
                     if (!open && mislaid !== undefined) {
                         warn(
                             "expected_a_next_at_b",
@@ -4606,7 +4623,7 @@
 
 // Undo the effects of dangling nested ternary operators.
 
-            var level = qmark.length;
+            const level = qmark.length;
             if (level > 0) {
                 margin -= level * 4;
             }
@@ -4627,7 +4644,7 @@
 // etc) starting on its own line. Closed form is more compact. Statement blocks
 // are always in open form.
 
-                var new_closer = opener[left.id];
+                const new_closer = opener[left.id];
                 if (typeof new_closer === "string") {
                     if (new_closer !== right.id) {
                         stack.push({
@@ -4683,7 +4700,7 @@
 // If right is a closer, then pop the previous state.
 
                     if (right.id === closer) {
-                        var previous = stack.pop();
+                        const previous = stack.pop();
                         margin = previous.margin;
                         if (open && right.id !== ";") {
                             at_margin(0);
@@ -4713,8 +4730,8 @@
                         } else if (left.id === ",") {
                             unqmark();
                             if (!open || (
-                                (free || closer === "]") &&
-                                left.line === right.line
+                                (free || closer === "]")
+                                && left.line === right.line
                             )) {
                                 one_space();
                             } else {
@@ -4746,13 +4763,13 @@
                             || right.id === ","
                             || right.id === ";"
                             || right.id === ":"
-                            || (right.arity === "binary" && (
-                                right.id === "("
-                                || right.id === "["
-                            ))
                             || (
-                                right.arity === "function" &&
-                                left.id !== "function"
+                                right.arity === "binary"
+                                && (right.id === "(" || right.id === "[")
+                            )
+                            || (
+                                right.arity === "function"
+                                && left.id !== "function"
                             )
                         ) {
                             no_space_only();
@@ -4822,12 +4839,12 @@
                             spaceop[left.id] === true
                             || spaceop[right.id] === true
                             || (
-                                left.arity === "binary" &&
-                                (left.id === "+" || left.id === "-")
+                                left.arity === "binary"
+                                && (left.id === "+" || left.id === "-")
                             )
                             || (
-                                right.arity === "binary" &&
-                                (right.id === "+" || right.id === "-")
+                                right.arity === "binary"
+                                && (right.id === "+" || right.id === "-")
                             )
                             || left.id === "function"
                             || left.id === ":"
@@ -4865,7 +4882,7 @@
 
 // The jslint function itself.
 
-    return function (source, option_object, global_array) {
+    return function jslint(source, option_object, global_array) {
         try {
             warnings = [];
             option = Object.assign(empty(), option_object);
@@ -4911,7 +4928,7 @@
             }
             Object.keys(option).forEach(function (name) {
                 if (option[name] === true) {
-                    var allowed = allowed_option[name];
+                    const allowed = allowed_option[name];
                     if (Array.isArray(allowed)) {
                         populate(declared_globals, allowed, false);
                     }
@@ -4950,9 +4967,11 @@
                 if (module_mode && global.strict !== undefined) {
                     warn("unexpected_a", global.strict);
                 }
-                uninitialized_and_unused();
-                if (!option.white) {
-                    whitage();
+                if (warnings.length > 0) {
+                    uninitialized_and_unused();
+                    if (!option.white) {
+                        whitage();
+                    }
                 }
             }
             if (!option.browser) {
@@ -4970,7 +4989,7 @@
         }
         return {
             directives: directives,
-            edition: "2017-07-01",
+            edition: "2018-01-26",
             exports: exports,
             froms: froms,
             functions: functions,
diff --git a/package.json b/package.json
index 463e961..b6944a5 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,7 @@
     "jshint",
     "eslint"
   ],
-  "version": "0.11.0",
+  "version": "0.12.0",
   "author": "Reid Burke <me@reidburke.com>",
   "contributors": [
     "Douglas Crockford <douglas@crockford.com>",