Repository: jshint/jshint
Pull request #3400 (2019-05-19 17:50:09)

Improve internal variable names
JSHint's "scopeManager" utility was initially implemented to refer to ECMAScript bindings using the term "label." Because ECMAScript uses the term "label" to describe a completely different language feature, this choice of names was somewhat confusing.

Modify all references to "JSHint labels" to use the more accurate term, "binding." Shorten the term "break label" to simply "label."

coveralls: 
[![Coverage Status](https://coveralls.io/builds/23478352/badge)](https://coveralls.io/builds/23478352)

Coverage remained the same at 100.0% when pulling **d049c995ed91723722db66cc42dbf3b6b2088e66 on jugglinmike:labels-to-bindings** into **02f20b23bafb1aacbde41bfcdafe23e419c955d3 on jshint:master**.


Patch:
diff --git a/src/jshint.js b/src/jshint.js
index cd88e19e..fca4c732 100644
--- a/src/jshint.js
+++ b/src/jshint.js
@@ -1579,7 +1579,7 @@ var JSHINT = (function() {
       return true;
     } else if (left.identifier && !isReserved(context, left) && !left.isMetaProperty &&
       left.value !== "eval" && left.value !== "arguments") {
-      if (state.funct["(scope)"].labeltype(left.value) === "exception") {
+      if (state.funct["(scope)"].bindingtype(left.value) === "exception") {
         warning("W022", left);
       }
       state.nameStack.set(left);
@@ -1894,7 +1894,7 @@ var JSHINT = (function() {
 
       hasOwnScope = true;
       state.funct["(scope)"].stack();
-      state.funct["(scope)"].block.addBreakLabel(t.value, { token: state.tokens.curr });
+      state.funct["(scope)"].block.addLabel(t.value, { token: state.tokens.curr });
 
       if (!state.tokens.next.labelled && state.tokens.next.value !== "{") {
         warning("W028", state.tokens.next, t.value, state.tokens.next.value);
@@ -2734,7 +2734,7 @@ var JSHINT = (function() {
       className = classNameToken.value;
       identifier(context);
       // unintialized, so that the 'extends' clause is parsed while the class is in TDZ
-      state.funct["(scope)"].addlabel(className, {
+      state.funct["(scope)"].addbinding(className, {
         type: "class",
         initialized: false,
         token: classNameToken
@@ -2789,7 +2789,7 @@ var JSHINT = (function() {
     state.funct["(scope)"].stack();
     if (classNameToken) {
       this.name = className;
-      state.funct["(scope)"].addlabel(className, {
+      state.funct["(scope)"].addbinding(className, {
         type: "class",
         initialized: true,
         token: classNameToken
@@ -3173,6 +3173,12 @@ var JSHINT = (function() {
       ret = first = last = exprs[0];
 
       if (!isNecessary) {
+        // async functions are identified after parsing due to the complexity
+        // of disambiguating the `async` keyword.
+        if (!triggerFnExpr) {
+          triggerFnExpr = ret.id === "async";
+        }
+
         isNecessary =
           // Used to distinguish from an ExpressionStatement which may not
           // begin with the `{` and `function` tokens
@@ -4338,7 +4344,7 @@ var JSHINT = (function() {
             }
           }
           if (t.id) {
-            state.funct["(scope)"].addlabel(t.id, {
+            state.funct["(scope)"].addbinding(t.id, {
               type: type,
               token: t.token });
             names.push(t.token);
@@ -4514,7 +4520,7 @@ var JSHINT = (function() {
             }
           }
           if (t.id) {
-            state.funct["(scope)"].addlabel(t.id, {
+            state.funct["(scope)"].addbinding(t.id, {
               type: "var",
               token: t.token });
 
@@ -4597,7 +4603,7 @@ var JSHINT = (function() {
         warning("W025");
       }
     } else {
-      state.funct["(scope)"].addlabel(nameToken.value, {
+      state.funct["(scope)"].addbinding(nameToken.value, {
         type: labelType,
         token: state.tokens.curr,
         initialized: true });
@@ -5212,7 +5218,7 @@ var JSHINT = (function() {
 
     if (state.tokens.next.id !== ";" && !state.tokens.next.reach &&
         state.tokens.curr.line === startLine(state.tokens.next)) {
-      if (!state.funct["(scope)"].funct.hasBreakLabel(v)) {
+      if (!state.funct["(scope)"].funct.hasLabel(v)) {
         warning("W090", state.tokens.next, v);
       }
       this.first = state.tokens.next;
@@ -5240,7 +5246,7 @@ var JSHINT = (function() {
 
     if (state.tokens.next.id !== ";" && !state.tokens.next.reach) {
       if (state.tokens.curr.line === startLine(state.tokens.next)) {
-        if (!state.funct["(scope)"].funct.hasBreakLabel(v)) {
+        if (!state.funct["(scope)"].funct.hasLabel(v)) {
           warning("W090", state.tokens.next, v);
         }
         this.first = state.tokens.next;
@@ -5292,7 +5298,7 @@ var JSHINT = (function() {
         error("E024", this, "await");
       }
 
-      expression(context, 0);
+      expression(context, 10);
       return this;
     } else {
       this.exps = false;
@@ -5485,7 +5491,7 @@ var JSHINT = (function() {
       // ImportClause :: ImportedDefaultBinding
       this.name = identifier(context);
       // Import bindings are immutable (see ES6 8.1.1.5.5)
-      state.funct["(scope)"].addlabel(this.name, {
+      state.funct["(scope)"].addbinding(this.name, {
         type: "import",
         initialized: true,
         token: state.tokens.curr });
@@ -5512,7 +5518,7 @@ var JSHINT = (function() {
       if (state.tokens.next.identifier) {
         this.name = identifier(context);
         // Import bindings are immutable (see ES6 8.1.1.5.5)
-        state.funct["(scope)"].addlabel(this.name, {
+        state.funct["(scope)"].addbinding(this.name, {
           type: "import",
           initialized: true,
           token: state.tokens.curr });
@@ -5538,7 +5544,7 @@ var JSHINT = (function() {
         }
 
         // Import bindings are immutable (see ES6 8.1.1.5.5)
-        state.funct["(scope)"].addlabel(importName, {
+        state.funct["(scope)"].addbinding(importName, {
           type: "import",
           initialized: true,
           token: state.tokens.curr });
@@ -5549,9 +5555,7 @@ var JSHINT = (function() {
           advance("}");
           break;
         } else {
-          /* istanbul ignore next */
           error("E024", state.tokens.next, state.tokens.next.value);
-          /* istanbul ignore next */
           break;
         }
       }
diff --git a/src/scope-manager.js b/src/scope-manager.js
index 9224eaf4..e99feaa4 100644
--- a/src/scope-manager.js
+++ b/src/scope-manager.js
@@ -10,22 +10,16 @@ var marker = {};
 
 /**
  * A factory function for creating scope managers. A scope manager tracks
- * variables and JSHint "labels", detecting when variables are referenced
- * (through "usages").
- *
- * Note that in this context, the term "label" describes an implementation
- * detail of JSHint and is not related to the ECMAScript language construct of
- * the same name. Where possible, the former is referred to as a "JSHint label"
- * to avoid confusion.
+ * bindings, detecting when variables are referenced (through "usages").
  *
  * @param {object} state - the global state object (see `state.js`)
- * @param {Array} predefined - a set of JSHint label names for built-in
- *                             bindings provided by the environment
- * @param {object} exported - a hash for JSHint label names that are intended
- *                            to be referenced in contexts beyond the current
- *                            program code
- * @param {object} declared - a hash for JSHint label names that were defined
- *                            as global bindings via linting configuration
+ * @param {Array} predefined - a set of binding names for built-in bindings
+ *                             provided by the environment
+ * @param {object} exported - a hash for binding names that are intended to be
+ *                            referenced in contexts beyond the current program
+ *                            code
+ * @param {object} declared - a hash for binding names that were defined as
+ *                            global bindings via linting configuration
  *
  * @returns {object} - a scope manager
  */
@@ -36,9 +30,9 @@ var scopeManager = function(state, predefined, exported, declared) {
 
   function _newScope(type) {
     _current = {
-      "(labels)": Object.create(null),
+      "(bindings)": Object.create(null),
       "(usages)": Object.create(null),
-      "(breakLabels)": Object.create(null),
+      "(labels)": Object.create(null),
       "(parent)": _current,
       "(type)": type,
       "(params)": (type === "functionparams" || type === "catchparams") ? [] : null
@@ -72,9 +66,9 @@ var scopeManager = function(state, predefined, exported, declared) {
     });
   }
 
-  function _setupUsages(labelName) {
-    if (!_current["(usages)"][labelName]) {
-      _current["(usages)"][labelName] = {
+  function _setupUsages(bindingName) {
+    if (!_current["(usages)"][bindingName]) {
+      _current["(usages)"][bindingName] = {
         "(modified)": [],
         "(reassigned)": [],
         "(tokens)": []
@@ -133,11 +127,11 @@ var scopeManager = function(state, predefined, exported, declared) {
       _checkParams();
       return;
     }
-    var curentLabels = _current["(labels)"];
-    for (var labelName in curentLabels) {
-      if (curentLabels[labelName]["(type)"] !== "exception" &&
-        curentLabels[labelName]["(unused)"]) {
-        _warnUnused(labelName, curentLabels[labelName]["(token)"], "var");
+    var currentBindings = _current["(bindings)"];
+    for (var bindingName in currentBindings) {
+      if (currentBindings[bindingName]["(type)"] !== "exception" &&
+        currentBindings[bindingName]["(unused)"]) {
+        _warnUnused(bindingName, currentBindings[bindingName]["(token)"], "var");
       }
     }
   }
@@ -159,7 +153,7 @@ var scopeManager = function(state, predefined, exported, declared) {
     var unused_opt;
 
     while (param) {
-      var label = _current["(labels)"][param];
+      var binding = _current["(bindings)"][param];
 
       unused_opt = _getUnusedOption(state.funct["(unusedOption)"]);
 
@@ -172,8 +166,8 @@ var scopeManager = function(state, predefined, exported, declared) {
       if (param === "undefined")
         return;
 
-      if (label["(unused)"]) {
-        _warnUnused(param, label["(token)"], "param", state.funct["(unusedOption)"]);
+      if (binding["(unused)"]) {
+        _warnUnused(param, binding["(token)"], "param", state.funct["(unusedOption)"]);
       } else if (unused_opt === "last-param") {
         return;
       }
@@ -183,36 +177,36 @@ var scopeManager = function(state, predefined, exported, declared) {
   }
 
   /**
-   * Find the relevant JSHint label's scope. The owning scope is located by
-   * first inspecting the current scope and then moving "downward" through the
-   * stack of scopes.
+   * Find the relevant binding's scope. The owning scope is located by first
+   * inspecting the current scope and then moving "downward" through the stack
+   * of scopes.
    *
-   * @param {string} labelName - the value of the identifier
+   * @param {string} bindingName - the value of the identifier
    *
-   * @returns {Object} - the scope in which the JSHint label was found
+   * @returns {Object} - the scope in which the binding was found
    */
-  function _getLabel(labelName) {
+  function _getBinding(bindingName) {
     for (var i = _scopeStack.length - 1 ; i >= 0; --i) {
-      var scopeLabels = _scopeStack[i]["(labels)"];
-      if (scopeLabels[labelName]) {
-        return scopeLabels;
+      var scopeBindings = _scopeStack[i]["(bindings)"];
+      if (scopeBindings[bindingName]) {
+        return scopeBindings;
       }
     }
   }
 
   /**
-   * Determine if a given JSHint label name has been referenced within the
-   * current function or any function defined within.
+   * Determine if a given binding name has been referenced within the current
+   * function or any function defined within.
    *
-   * @param {string} labelName - the value of the identifier
+   * @param {string} bindingName - the value of the identifier
    *
    * @returns {boolean}
    */
-  function usedSoFarInCurrentFunction(labelName) {
+  function usedSoFarInCurrentFunction(bindingName) {
     for (var i = _scopeStack.length - 1; i >= 0; i--) {
       var current = _scopeStack[i];
-      if (current["(usages)"][labelName]) {
-        return current["(usages)"][labelName];
+      if (current["(usages)"][bindingName]) {
+        return current["(usages)"][bindingName];
       }
       if (current === _currentFunctBody) {
         break;
@@ -221,7 +215,7 @@ var scopeManager = function(state, predefined, exported, declared) {
     return false;
   }
 
-  function _checkOuterShadow(labelName, token) {
+  function _checkOuterShadow(bindingName, token) {
 
     // only check if shadow is outer
     if (state.option.shadow !== "outer") {
@@ -238,16 +232,16 @@ var scopeManager = function(state, predefined, exported, declared) {
       if (!isNewFunction && _scopeStack[i + 1] === _currentFunctBody) {
         outsideCurrentFunction = false;
       }
-      if (outsideCurrentFunction && stackItem["(labels)"][labelName]) {
-        warning("W123", token, labelName);
+      if (outsideCurrentFunction && stackItem["(bindings)"][bindingName]) {
+        warning("W123", token, bindingName);
       }
-      if (stackItem["(breakLabels)"][labelName]) {
-        warning("W123", token, labelName);
+      if (stackItem["(labels)"][bindingName]) {
+        warning("W123", token, bindingName);
       }
     }
   }
 
-  function _latedefWarning(type, labelName, token) {
+  function _latedefWarning(type, bindingName, token) {
     var isFunction;
 
     if (state.option.latedef) {
@@ -257,7 +251,7 @@ var scopeManager = function(state, predefined, exported, declared) {
       // if either latedef is strict and this is a function
       //    or this is not a function
       if ((state.option.latedef === true && isFunction) || !isFunction) {
-        warning("W003", token, labelName);
+        warning("W003", token, bindingName);
       }
     }
   }
@@ -270,8 +264,8 @@ var scopeManager = function(state, predefined, exported, declared) {
       });
     },
 
-    isPredefined: function(labelName) {
-      return !this.has(labelName) && _.has(_scopeStack[0]["(predefined)"], labelName);
+    isPredefined: function(bindingName) {
+      return !this.has(bindingName) && _.has(_scopeStack[0]["(predefined)"], bindingName);
     },
 
     /**
@@ -307,52 +301,52 @@ var scopeManager = function(state, predefined, exported, declared) {
 
       var i, j, isImmutable, isFunction;
       var currentUsages = _current["(usages)"];
-      var currentLabels = _current["(labels)"];
-      var usedLabelNameList = Object.keys(currentUsages);
+      var currentBindings = _current["(bindings)"];
+      var usedBindingNameList = Object.keys(currentUsages);
 
       /* istanbul ignore if */
-      if (currentUsages.__proto__ && usedLabelNameList.indexOf("__proto__") === -1) {
-        usedLabelNameList.push("__proto__");
+      if (currentUsages.__proto__ && usedBindingNameList.indexOf("__proto__") === -1) {
+        usedBindingNameList.push("__proto__");
       }
 
-      for (i = 0; i < usedLabelNameList.length; i++) {
-        var usedLabelName = usedLabelNameList[i];
+      for (i = 0; i < usedBindingNameList.length; i++) {
+        var usedBindingName = usedBindingNameList[i];
 
-        var usage = currentUsages[usedLabelName];
-        var usedLabel = currentLabels[usedLabelName];
-        if (usedLabel) {
-          var usedLabelType = usedLabel["(type)"];
-          isImmutable = usedLabelType === "const" || usedLabelType === "import";
+        var usage = currentUsages[usedBindingName];
+        var usedBinding = currentBindings[usedBindingName];
+        if (usedBinding) {
+          var usedBindingType = usedBinding["(type)"];
+          isImmutable = usedBindingType === "const" || usedBindingType === "import";
 
-          if (usedLabel["(useOutsideOfScope)"] && !state.option.funcscope) {
+          if (usedBinding["(useOutsideOfScope)"] && !state.option.funcscope) {
             var usedTokens = usage["(tokens)"];
             for (j = 0; j < usedTokens.length; j++) {
               // Keep the consistency of https://github.com/jshint/jshint/issues/2409
-              if (usedLabel["(function)"] === usedTokens[j]["(function)"]) {
-                error("W038", usedTokens[j], usedLabelName);
+              if (usedBinding["(function)"] === usedTokens[j]["(function)"]) {
+                error("W038", usedTokens[j], usedBindingName);
               }
             }
           }
 
-          // mark the label used
-          _current["(labels)"][usedLabelName]["(unused)"] = false;
+          // mark the binding used
+          _current["(bindings)"][usedBindingName]["(unused)"] = false;
 
           // check for modifying a const
           if (isImmutable && usage["(modified)"]) {
             for (j = 0; j < usage["(modified)"].length; j++) {
-              error("E013", usage["(modified)"][j], usedLabelName);
+              error("E013", usage["(modified)"][j], usedBindingName);
             }
           }
 
-          isFunction = usedLabelType === "function" ||
-            usedLabelType === "generator function" ||
-            usedLabelType === "async function";
+          isFunction = usedBindingType === "function" ||
+            usedBindingType === "generator function" ||
+            usedBindingType === "async function";
 
           // check for re-assigning a function declaration
-          if ((isFunction || usedLabelType === "class") && usage["(reassigned)"]) {
+          if ((isFunction || usedBindingType === "class") && usage["(reassigned)"]) {
             for (j = 0; j < usage["(reassigned)"].length; j++) {
               if (!usage["(reassigned)"][j].ignoreW021) {
-                warning("W021", usage["(reassigned)"][j], usedLabelName, usedLabelType);
+                warning("W021", usage["(reassigned)"][j], usedBindingName, usedBindingType);
               }
             }
           }
@@ -360,24 +354,24 @@ var scopeManager = function(state, predefined, exported, declared) {
         }
 
         if (subScope) {
-          var labelType = this.labeltype(usedLabelName);
-          isImmutable = labelType === "const" ||
-            (labelType === null && _scopeStack[0]["(predefined)"][usedLabelName] === false);
+          var bindingType = this.bindingtype(usedBindingName);
+          isImmutable = bindingType === "const" ||
+            (bindingType === null && _scopeStack[0]["(predefined)"][usedBindingName] === false);
           if (isUnstackingFunctionOuter && !isImmutable) {
             if (!state.funct["(outerMutables)"]) {
               state.funct["(outerMutables)"] = [];
             }
-            state.funct["(outerMutables)"].push(usedLabelName);
+            state.funct["(outerMutables)"].push(usedBindingName);
           }
 
           // not exiting the global scope, so copy the usage down in case its an out of scope usage
-          if (!subScope["(usages)"][usedLabelName]) {
-            subScope["(usages)"][usedLabelName] = usage;
+          if (!subScope["(usages)"][usedBindingName]) {
+            subScope["(usages)"][usedBindingName] = usage;
             if (isUnstackingFunctionBody) {
-              subScope["(usages)"][usedLabelName]["(onlyUsedSubFunction)"] = true;
+              subScope["(usages)"][usedBindingName]["(onlyUsedSubFunction)"] = true;
             }
           } else {
-            var subScopeUsage = subScope["(usages)"][usedLabelName];
+            var subScopeUsage = subScope["(usages)"][usedBindingName];
             subScopeUsage["(modified)"] = subScopeUsage["(modified)"].concat(usage["(modified)"]);
             subScopeUsage["(tokens)"] = subScopeUsage["(tokens)"].concat(usage["(tokens)"]);
             subScopeUsage["(reassigned)"] =
@@ -385,16 +379,16 @@ var scopeManager = function(state, predefined, exported, declared) {
           }
         } else {
           // this is exiting global scope, so we finalise everything here - we are at the end of the file
-          if (typeof _current["(predefined)"][usedLabelName] === "boolean") {
+          if (typeof _current["(predefined)"][usedBindingName] === "boolean") {
 
             // remove the declared token, so we know it is used
-            delete declared[usedLabelName];
+            delete declared[usedBindingName];
 
             // note it as used so it can be reported
-            usedPredefinedAndGlobals[usedLabelName] = marker;
+            usedPredefinedAndGlobals[usedBindingName] = marker;
 
             // check for re-assigning a read-only (set to false) predefined
-            if (_current["(predefined)"][usedLabelName] === false && usage["(reassigned)"]) {
+            if (_current["(predefined)"][usedBindingName] === false && usage["(reassigned)"]) {
               for (j = 0; j < usage["(reassigned)"].length; j++) {
                 if (!usage["(reassigned)"][j].ignoreW020) {
                   warning("W020", usage["(reassigned)"][j]);
@@ -403,7 +397,7 @@ var scopeManager = function(state, predefined, exported, declared) {
             }
           }
           else {
-            // label usage is not predefined and we have not found a declaration
+            // binding usage is not predefined and we have not found a declaration
             // so report as undeclared
             for (j = 0; j < usage["(tokens)"].length; j++) {
               var undefinedToken = usage["(tokens)"][j];
@@ -411,13 +405,13 @@ var scopeManager = function(state, predefined, exported, declared) {
               if (!undefinedToken.forgiveUndef) {
                 // if undef is on and undef was on when the token was defined
                 if (state.option.undef && !undefinedToken.ignoreUndef) {
-                  warning("W117", undefinedToken, usedLabelName);
+                  warning("W117", undefinedToken, usedBindingName);
                 }
-                if (impliedGlobals[usedLabelName]) {
-                  impliedGlobals[usedLabelName].line.push(undefinedToken.line);
+                if (impliedGlobals[usedBindingName]) {
+                  impliedGlobals[usedBindingName].line.push(undefinedToken.line);
                 } else {
-                  impliedGlobals[usedLabelName] = {
-                    name: usedLabelName,
+                  impliedGlobals[usedBindingName] = {
+                    name: usedBindingName,
                     line: [undefinedToken.line]
                   };
                 }
@@ -430,49 +424,50 @@ var scopeManager = function(state, predefined, exported, declared) {
       // if exiting the global scope, we can warn about declared globals that haven't been used yet
       if (!subScope) {
         Object.keys(declared)
-          .forEach(function(labelNotUsed) {
-            _warnUnused(labelNotUsed, declared[labelNotUsed], "var");
+          .forEach(function(bindingNotUsed) {
+            _warnUnused(bindingNotUsed, declared[bindingNotUsed], "var");
           });
       }
 
-      // If this is not a function boundary, transfer function-scoped labels to
+      // If this is not a function boundary, transfer function-scoped bindings to
       // the parent block (a rough simulation of variable hoisting). Previously
-      // existing labels in the parent block should take precedence so that things and stuff.
+      // existing bindings in the parent block should take precedence so that
+      // prior usages are not discarded.
       if (subScope && !isUnstackingFunctionBody &&
         !isUnstackingFunctionParams && !isUnstackingFunctionOuter) {
-        var labelNames = Object.keys(currentLabels);
-        for (i = 0; i < labelNames.length; i++) {
+        var bindingNames = Object.keys(currentBindings);
+        for (i = 0; i < bindingNames.length; i++) {
 
-          var defLabelName = labelNames[i];
-          var defLabel = currentLabels[defLabelName];
+          var defBindingName = bindingNames[i];
+          var defBinding = currentBindings[defBindingName];
 
-          if (!defLabel["(blockscoped)"] && defLabel["(type)"] !== "exception") {
-            var shadowed = subScope["(labels)"][defLabelName];
+          if (!defBinding["(blockscoped)"] && defBinding["(type)"] !== "exception") {
+            var shadowed = subScope["(bindings)"][defBindingName];
 
-            // Do not overwrite a label if it exists in the parent scope
+            // Do not overwrite a binding if it exists in the parent scope
             // because it is shared by adjacent blocks. Copy the `unused`
             // property so that any references found within the current block
             // are counted toward that higher-level declaration.
             if (shadowed) {
-              shadowed["(unused)"] &= defLabel["(unused)"];
+              shadowed["(unused)"] &= defBinding["(unused)"];
 
-            // "Hoist" the variable to the parent block, decorating the label
+            // "Hoist" the variable to the parent block, decorating the binding
             // so that future references, though technically valid, can be
             // reported as "out-of-scope" in the absence of the `funcscope`
             // option.
             } else {
-              defLabel["(useOutsideOfScope)"] =
+              defBinding["(useOutsideOfScope)"] =
                 // Do not warn about out-of-scope usages in the global scope
                 _currentFunctBody["(type)"] !== "global" &&
-                // When a higher scope contains a binding for the label, the
-                // label is a re-declaration and should not prompt "used
+                // When a higher scope contains a binding for the binding, the
+                // binding is a re-declaration and should not prompt "used
                 // out-of-scope" warnings.
-                !this.funct.has(defLabelName, { excludeCurrent: true });
+                !this.funct.has(defBindingName, { excludeCurrent: true });
 
-              subScope["(labels)"][defLabelName] = defLabel;
+              subScope["(bindings)"][defBindingName] = defBinding;
             }
 
-            delete currentLabels[defLabelName];
+            delete currentBindings[defBindingName];
           }
         }
       }
@@ -493,53 +488,53 @@ var scopeManager = function(state, predefined, exported, declared) {
     /**
      * Add a function parameter to the current scope.
      *
-     * @param {string} labelName - the value of the identifier
+     * @param {string} bindingName - the value of the identifier
      * @param {Token} token
-     * @param {string} [type] - JSHint label type; defaults to "param"
+     * @param {string} [type] - binding type; defaults to "param"
      */
-    addParam: function(labelName, token, type) {
+    addParam: function(bindingName, token, type) {
       type = type || "param";
 
       if (type === "exception") {
         // if defined in the current function
-        var previouslyDefinedLabelType = this.funct.labeltype(labelName);
-        if (previouslyDefinedLabelType && previouslyDefinedLabelType !== "exception") {
+        var previouslyDefinedBindingType = this.funct.bindingtype(bindingName);
+        if (previouslyDefinedBindingType && previouslyDefinedBindingType !== "exception") {
           // and has not been used yet in the current function scope
           if (!state.option.node) {
-            warning("W002", state.tokens.next, labelName);
+            warning("W002", state.tokens.next, bindingName);
           }
         }
 
-        if (state.isStrict() && (labelName === "arguments" || labelName === "eval")) {
+        if (state.isStrict() && (bindingName === "arguments" || bindingName === "eval")) {
           warning("E008", token);
         }
       }
 
       // The variable was declared in the current scope
-      if (_.has(_current["(labels)"], labelName)) {
-        _current["(labels)"][labelName].duplicated = true;
+      if (_.has(_current["(bindings)"], bindingName)) {
+        _current["(bindings)"][bindingName].duplicated = true;
 
       // The variable was declared in an outer scope
       } else {
         // if this scope has the variable defined, it's a re-definition error
-        _checkOuterShadow(labelName, token);
+        _checkOuterShadow(bindingName, token);
 
-        _current["(labels)"][labelName] = {
+        _current["(bindings)"][bindingName] = {
           "(type)" : type,
           "(token)": token,
           "(unused)": true };
 
-        _current["(params)"].push(labelName);
+        _current["(params)"].push(bindingName);
       }
 
-      if (_.has(_current["(usages)"], labelName)) {
-        var usage = _current["(usages)"][labelName];
+      if (_.has(_current["(usages)"], bindingName)) {
+        var usage = _current["(usages)"][bindingName];
         // if its in a sub function it is not necessarily an error, just latedef
         if (usage["(onlyUsedSubFunction)"]) {
-          _latedefWarning(type, labelName, token);
+          _latedefWarning(type, bindingName, token);
         } else {
           // this is a clear illegal usage for block scoped variables
-          warning("E056", token, labelName, type);
+          warning("E056", token, bindingName, type);
         }
       }
     },
@@ -566,19 +561,19 @@ var scopeManager = function(state, predefined, exported, declared) {
         return;
       }
 
-      currentFunctParamScope["(params)"].forEach(function(labelName) {
-        var label = currentFunctParamScope["(labels)"][labelName];
+      currentFunctParamScope["(params)"].forEach(function(bindingName) {
+        var binding = currentFunctParamScope["(bindings)"][bindingName];
 
-        if (label.duplicated) {
+        if (binding.duplicated) {
           if (isStrict || isArrow || isMethod || !isSimple) {
-            warning("E011", label["(token)"], labelName);
+            warning("E011", binding["(token)"], bindingName);
           } else if (state.option.shadow !== true) {
-            warning("W004", label["(token)"], labelName);
+            warning("W004", binding["(token)"], bindingName);
           }
         }
 
-        if (isStrict && (labelName === "arguments" || labelName === "eval")) {
-          warning("E008", label["(token)"]);
+        if (isStrict && (bindingName === "arguments" || bindingName === "eval")) {
+          warning("E008", binding["(token)"]);
         }
       });
     },
@@ -639,26 +634,26 @@ var scopeManager = function(state, predefined, exported, declared) {
      * Determine if a given name has been defined in the current scope or any
      * lower scope.
      *
-     * @param {string} labelName - the value of the identifier
+     * @param {string} bindingName - the value of the identifier
      *
      * @return {boolean}
      */
-    has: function(labelName) {
-      return Boolean(_getLabel(labelName));
+    has: function(bindingName) {
+      return Boolean(_getBinding(bindingName));
     },
 
     /**
-     * Retrieve  described by `labelName` or null
+     * Retrieve binding described by `bindingName` or null
      *
-     * @param {string} labelName - the value of the identifier
+     * @param {string} bindingName - the value of the identifier
      *
-     * @returns {string|null} - the type of the JSHint label or `null` if no
-     *                          such label exists
+     * @returns {string|null} - the type of the binding or `null` if no such
+     *                          binding exists
      */
-    labeltype: function(labelName) {
-      var scopeLabels = _getLabel(labelName);
-      if (scopeLabels) {
-        return scopeLabels[labelName]["(type)"];
+    bindingtype: function(bindingName) {
+      var scopeBindings = _getBinding(bindingName);
+      if (scopeBindings) {
+        return scopeBindings[bindingName]["(type)"];
       }
       return null;
     },
@@ -666,23 +661,23 @@ var scopeManager = function(state, predefined, exported, declared) {
     /**
      * For the exported options, indicating a variable is used outside the file
      *
-     * @param {string} labelName - the value of the identifier
+     * @param {string} bindingName - the value of the identifier
      */
-    addExported: function(labelName) {
-      var globalLabels = _scopeStack[0]["(labels)"];
-      if (_.has(declared, labelName)) {
+    addExported: function(bindingName) {
+      var globalBindings = _scopeStack[0]["(bindings)"];
+      if (_.has(declared, bindingName)) {
         // remove the declared token, so we know it is used
-        delete declared[labelName];
-      } else if (_.has(globalLabels, labelName)) {
-        globalLabels[labelName]["(unused)"] = false;
+        delete declared[bindingName];
+      } else if (_.has(globalBindings, bindingName)) {
+        globalBindings[bindingName]["(unused)"] = false;
       } else {
         for (var i = 1; i < _scopeStack.length; i++) {
           var scope = _scopeStack[i];
           // if `scope.(type)` is not defined, it is a block scope
           if (!scope["(type)"]) {
-            if (_.has(scope["(labels)"], labelName) &&
-                !scope["(labels)"][labelName]["(blockscoped)"]) {
-              scope["(labels)"][labelName]["(unused)"] = false;
+            if (_.has(scope["(bindings)"], bindingName) &&
+                !scope["(bindings)"][bindingName]["(blockscoped)"]) {
+              scope["(bindings)"][bindingName]["(unused)"] = false;
               return;
             }
           } else {
@@ -690,41 +685,41 @@ var scopeManager = function(state, predefined, exported, declared) {
             break;
           }
         }
-        exported[labelName] = true;
+        exported[bindingName] = true;
       }
     },
 
     /**
-     * Mark a JSHint label as "exported" by an ES2015 module
+     * Mark a binding as "exported" by an ES2015 module
      *
-     * @param {string} labelName - the value of the identifier
+     * @param {string} bindingName - the value of the identifier
      * @param {object} token
      */
-    setExported: function(labelName, token) {
-      this.block.use(labelName, token);
+    setExported: function(bindingName, token) {
+      this.block.use(bindingName, token);
     },
 
     /**
-     * Mark a JSHint label as "initialized." This is necessary to enforce the
+     * Mark a binding as "initialized." This is necessary to enforce the
      * "temporal dead zone" (TDZ) of block-scoped bindings which are not
      * hoisted.
      *
-     * @param {string} labelName - the value of the identifier
+     * @param {string} bindingName - the value of the identifier
      */
-    initialize: function(labelName) {
-      if (_current["(labels)"][labelName]) {
-        _current["(labels)"][labelName]["(initialized)"] = true;
+    initialize: function(bindingName) {
+      if (_current["(bindings)"][bindingName]) {
+        _current["(bindings)"][bindingName]["(initialized)"] = true;
       }
     },
 
     /**
-     * Create a new JSHint label and add it to the current scope. Delegates to
-     * the internal `block.add` or `func.add` methods depending on the type.
+     * Create a new binding and add it to the current scope. Delegates to the
+     * internal `block.add` or `func.add` methods depending on the type.
      * Produces warnings and errors as necessary.
      *
-     * @param {string} labelName
+     * @param {string} bindingName
      * @param {Object} opts
-     * @param {String} opts.type - the type of the label e.g. "param", "var",
+     * @param {String} opts.type - the type of the binding e.g. "param", "var",
      *                             "let, "const", "import", "function",
      *                             "generator function", "async function",
      *                             "async generator function"
@@ -732,7 +727,7 @@ var scopeManager = function(state, predefined, exported, declared) {
      * @param {boolean} opts.initialized - whether the binding should be
      *                                     created in an "initialized" state.
      */
-    addlabel: function(labelName, opts) {
+    addbinding: function(bindingName, opts) {
 
       var type  = opts.type;
       var token = opts.token;
@@ -742,34 +737,34 @@ var scopeManager = function(state, predefined, exported, declared) {
       var ishoisted = type === "function" || type === "generator function" ||
         type === "async function" || type === "import";
       var isexported    = (isblockscoped ? _current : _currentFunctBody)["(type)"] === "global" &&
-                          _.has(exported, labelName);
+                          _.has(exported, bindingName);
 
       // outer shadow check (inner is only on non-block scoped)
-      _checkOuterShadow(labelName, token);
+      _checkOuterShadow(bindingName, token);
 
-      if (state.isStrict() && (labelName === "arguments" || labelName === "eval")) {
+      if (state.isStrict() && (bindingName === "arguments" || bindingName === "eval")) {
         warning("E008", token);
       }
 
       if (isblockscoped) {
 
-        var declaredInCurrentScope = _current["(labels)"][labelName];
+        var declaredInCurrentScope = _current["(bindings)"][bindingName];
         // for block scoped variables, params are seen in the current scope as the root function
         // scope, so check these too.
         if (!declaredInCurrentScope && _current === _currentFunctBody &&
           _current["(type)"] !== "global") {
-          declaredInCurrentScope = !!_currentFunctBody["(parent)"]["(labels)"][labelName];
+          declaredInCurrentScope = !!_currentFunctBody["(parent)"]["(bindings)"][bindingName];
         }
 
         // if its not already defined (which is an error, so ignore) and is used in TDZ
-        if (!declaredInCurrentScope && _current["(usages)"][labelName]) {
-          var usage = _current["(usages)"][labelName];
+        if (!declaredInCurrentScope && _current["(usages)"][bindingName]) {
+          var usage = _current["(usages)"][bindingName];
           // if its in a sub function it is not necessarily an error, just latedef
           if (usage["(onlyUsedSubFunction)"] || ishoisted) {
-            _latedefWarning(type, labelName, token);
+            _latedefWarning(type, bindingName, token);
           } else if (!ishoisted) {
             // this is a clear illegal usage for block scoped variables
-            warning("E056", token, labelName, type);
+            warning("E056", token, bindingName, type);
           }
         }
 
@@ -782,75 +777,75 @@ var scopeManager = function(state, predefined, exported, declared) {
         // 2. this is not a "hoisted" block-scoped binding
         if (declaredInCurrentScope &&
           (!ishoisted || (_current["(type)"] !== "global" || type === "import"))) {
-          warning("E011", token, labelName);
+          warning("E011", token, bindingName);
         }
         else if (state.option.shadow === "outer") {
 
           // if shadow is outer, for block scope we want to detect any shadowing within this function
-          if (scopeManagerInst.funct.has(labelName)) {
-            warning("W004", token, labelName);
+          if (scopeManagerInst.funct.has(bindingName)) {
+            warning("W004", token, bindingName);
           }
         }
 
         scopeManagerInst.block.add(
-          labelName, type, token, !isexported, opts.initialized
+          bindingName, type, token, !isexported, opts.initialized
         );
 
       } else {
 
-        var declaredInCurrentFunctionScope = scopeManagerInst.funct.has(labelName);
+        var declaredInCurrentFunctionScope = scopeManagerInst.funct.has(bindingName);
 
         // check for late definition, ignore if already declared
-        if (!declaredInCurrentFunctionScope && usedSoFarInCurrentFunction(labelName)) {
-          _latedefWarning(type, labelName, token);
+        if (!declaredInCurrentFunctionScope && usedSoFarInCurrentFunction(bindingName)) {
+          _latedefWarning(type, bindingName, token);
         }
 
         // defining with a var or a function when a block scope variable of the same name
         // is in scope is an error
-        if (scopeManagerInst.funct.has(labelName, { onlyBlockscoped: true })) {
-          warning("E011", token, labelName);
+        if (scopeManagerInst.funct.has(bindingName, { onlyBlockscoped: true })) {
+          warning("E011", token, bindingName);
         } else if (state.option.shadow !== true) {
           // now since we didn't get any block scope variables, test for var/function
           // shadowing
-          if (declaredInCurrentFunctionScope && labelName !== "__proto__") {
+          if (declaredInCurrentFunctionScope && bindingName !== "__proto__") {
 
             // see https://github.com/jshint/jshint/issues/2400
             if (_currentFunctBody["(type)"] !== "global") {
-              warning("W004", token, labelName);
+              warning("W004", token, bindingName);
             }
           }
         }
 
-        scopeManagerInst.funct.add(labelName, type, token, !isexported);
+        scopeManagerInst.funct.add(bindingName, type, token, !isexported);
 
         if (_currentFunctBody["(type)"] === "global" && !state.impliedClosure()) {
-          usedPredefinedAndGlobals[labelName] = marker;
+          usedPredefinedAndGlobals[bindingName] = marker;
         }
       }
     },
 
     funct: {
       /**
-       * Return the type of the provided JSHint label given certain options
+       * Return the type of the provided binding given certain options
        *
-       * @param {string} labelName
+       * @param {string} bindingName
        * @param {Object=} [options]
        * @param {boolean} [options.onlyBlockscoped] - only include block scoped
-       *                                              labels
+       *                                              bindings
        * @param {boolean} [options.excludeParams] - exclude the param scope
        * @param {boolean} [options.excludeCurrent] - exclude the current scope
        *
        * @returns {String}
        */
-      labeltype: function(labelName, options) {
+      bindingtype: function(bindingName, options) {
         var onlyBlockscoped = options && options.onlyBlockscoped;
         var excludeParams = options && options.excludeParams;
         var currentScopeIndex = _scopeStack.length - (options && options.excludeCurrent ? 2 : 1);
         for (var i = currentScopeIndex; i >= 0; i--) {
           var current = _scopeStack[i];
-          if (current["(labels)"][labelName] &&
-            (!onlyBlockscoped || current["(labels)"][labelName]["(blockscoped)"])) {
-            return current["(labels)"][labelName]["(type)"];
+          if (current["(bindings)"][bindingName] &&
+            (!onlyBlockscoped || current["(bindings)"][bindingName]["(blockscoped)"])) {
+            return current["(bindings)"][bindingName]["(type)"];
           }
           var scopeCheck = excludeParams ? _scopeStack[ i - 1 ] : current;
           if (scopeCheck && scopeCheck["(type)"] === "functionparams") {
@@ -868,11 +863,11 @@ var scopeManager = function(state, predefined, exported, declared) {
        *
        * @returns {boolean}
        */
-      hasBreakLabel: function(labelName) {
+      hasLabel: function(labelName) {
         for (var i = _scopeStack.length - 1; i >= 0; i--) {
           var current = _scopeStack[i];
 
-          if (current["(breakLabels)"][labelName]) {
+          if (current["(labels)"][labelName]) {
             return true;
           }
           if (current["(type)"] === "functionparams") {
@@ -886,30 +881,30 @@ var scopeManager = function(state, predefined, exported, declared) {
        * Determine if a given name has been defined in the current function
        * scope.
        *
-       * @param {string} labelName - the value of the identifier
+       * @param {string} bindingName - the value of the identifier
        * @param {object} options - options as supported by the
-       *                           `funct.labeltype` method
+       *                           `funct.bindingtype` method
        *
        * @return {boolean}
        */
-      has: function(labelName, options) {
-        return Boolean(this.labeltype(labelName, options));
+      has: function(bindingName, options) {
+        return Boolean(this.bindingtype(bindingName, options));
       },
 
       /**
-       * Create a new function-scoped JSHint label and add it to the current
-       * scope. See the `block.add` method for coresponding logic to create
-       * block-scoped JSHint labels.
+       * Create a new function-scoped binding and add it to the current scope.
+       * See the `block.add` method for coresponding logic to create
+       * block-scoped bindings.
        *
-       * @param {string} labelName - the value of the identifier
-       * @param {string} type - the type of the JSHint label; either "function"
-       *                        or "var"
+       * @param {string} bindingName - the value of the identifier
+       * @param {string} type - the type of the binding; either "function" or
+       *                        "var"
        * @param {object} tok - the token that triggered the definition
-       * @param {boolean} unused - `true` if the JSHint label has not been
+       * @param {boolean} unused - `true` if the binding has not been
        *                           referenced
        */
-      add: function(labelName, type, tok, unused) {
-        _current["(labels)"][labelName] = {
+      add: function(bindingName, type, tok, unused) {
+        _current["(bindings)"][bindingName] = {
           "(type)" : type,
           "(token)": tok,
           "(blockscoped)": false,
@@ -930,13 +925,13 @@ var scopeManager = function(state, predefined, exported, declared) {
       },
 
       /**
-       * Resolve a reference to a binding and mark the corresponding JSHint
-       * label as "used."
+       * Resolve a reference to a binding and mark the corresponding binding as
+       * "used."
        *
-       * @param {string} labelName - the value of the identifier
+       * @param {string} bindingName - the value of the identifier
        * @param {object} token - the token value that triggered the reference
        */
-      use: function(labelName, token) {
+      use: function(bindingName, token) {
         // If the name resolves to a parameter of the current function, then do
         // not store usage. This is because in cases such as the following:
         //
@@ -948,13 +943,13 @@ var scopeManager = function(state, predefined, exported, declared) {
         // the usage of `a` will resolve to the parameter, not to the unset
         // variable binding.
         var paramScope = _currentFunctBody["(parent)"];
-        if (paramScope && paramScope["(labels)"][labelName] &&
-          paramScope["(labels)"][labelName]["(type)"] === "param") {
+        if (paramScope && paramScope["(bindings)"][bindingName] &&
+          paramScope["(bindings)"][bindingName]["(type)"] === "param") {
 
           // then check its not declared by a block scope variable
-          if (!scopeManagerInst.funct.has(labelName,
+          if (!scopeManagerInst.funct.has(bindingName,
                 { excludeParams: true, onlyBlockscoped: true })) {
-            paramScope["(labels)"][labelName]["(unused)"] = false;
+            paramScope["(bindings)"][bindingName]["(unused)"] = false;
           }
         }
 
@@ -962,58 +957,58 @@ var scopeManager = function(state, predefined, exported, declared) {
           token.ignoreUndef = true;
         }
 
-        _setupUsages(labelName);
+        _setupUsages(bindingName);
 
-        _current["(usages)"][labelName]["(onlyUsedSubFunction)"] = false;
+        _current["(usages)"][bindingName]["(onlyUsedSubFunction)"] = false;
 
         if (token) {
           token["(function)"] = _currentFunctBody;
-          _current["(usages)"][labelName]["(tokens)"].push(token);
+          _current["(usages)"][bindingName]["(tokens)"].push(token);
         }
 
         // Block-scoped bindings can't be used within their initializer due to
         // "temporal dead zone" (TDZ) restrictions.
-        var label = _current["(labels)"][labelName];
-        if (label && label["(blockscoped)"] && !label["(initialized)"]) {
-          error("E056", token, labelName, label["(type)"]);
+        var binding = _current["(bindings)"][bindingName];
+        if (binding && binding["(blockscoped)"] && !binding["(initialized)"]) {
+          error("E056", token, bindingName, binding["(type)"]);
         }
       },
 
-      reassign: function(labelName, token) {
+      reassign: function(bindingName, token) {
         token.ignoreW020 = state.ignored.W020;
         token.ignoreW021 = state.ignored.W021;
 
-        this.modify(labelName, token);
+        this.modify(bindingName, token);
 
-        _current["(usages)"][labelName]["(reassigned)"].push(token);
+        _current["(usages)"][bindingName]["(reassigned)"].push(token);
       },
 
-      modify: function(labelName, token) {
+      modify: function(bindingName, token) {
 
-        _setupUsages(labelName);
+        _setupUsages(bindingName);
 
-        _current["(usages)"][labelName]["(onlyUsedSubFunction)"] = false;
-        _current["(usages)"][labelName]["(modified)"].push(token);
+        _current["(usages)"][bindingName]["(onlyUsedSubFunction)"] = false;
+        _current["(usages)"][bindingName]["(modified)"].push(token);
       },
 
       /**
-       * Create a new block-scoped JSHint label and add it to the current
-       * scope. See the `funct.add` method for coresponding logic to create
-       * function-scoped JSHint labels.
+       * Create a new block-scoped binding and add it to the current scope. See
+       * the `funct.add` method for coresponding logic to create
+       * function-scoped bindings.
        *
-       * @param {string} labelName - the value of the identifier
-       * @param {string} type - the type of the JSHint label; one of "class",
+       * @param {string} bindingName - the value of the identifier
+       * @param {string} type - the type of the binding; one of "class",
        *                        "const", "function", "import", or "let"
        * @param {object} tok - the token that triggered the definition
-       * @param {boolean} unused - `true` if the JSHint label has not been
+       * @param {boolean} unused - `true` if the binding has not been
        *                           referenced
-       * @param {boolean} initialized - `true` if the JSHint label has been
-       *                                initialized (as is the case with JSHint
-       *                                labels created via `import`
+       * @param {boolean} initialized - `true` if the binding has been
+       *                                initialized (as is the case with
+       *                                bindings created via `import`
        *                                declarations)
        */
-      add: function(labelName, type, tok, unused, initialized) {
-        _current["(labels)"][labelName] = {
+      add: function(bindingName, type, tok, unused, initialized) {
+        _current["(bindings)"][bindingName] = {
           "(type)" : type,
           "(token)": tok,
           "(initialized)": !!initialized,
@@ -1021,9 +1016,9 @@ var scopeManager = function(state, predefined, exported, declared) {
           "(unused)": unused };
       },
 
-      addBreakLabel: function(labelName, opts) {
+      addLabel: function(labelName, opts) {
         var token = opts.token;
-        if (scopeManagerInst.funct.hasBreakLabel(labelName)) {
+        if (scopeManagerInst.funct.hasLabel(labelName)) {
           warning("E011", token, labelName);
         }
         else if (state.option.shadow === "outer") {
@@ -1033,7 +1028,7 @@ var scopeManager = function(state, predefined, exported, declared) {
             _checkOuterShadow(labelName, token);
           }
         }
-        _current["(breakLabels)"][labelName] = token;
+        _current["(labels)"][labelName] = token;
       }
     }
   };


Test patch:
diff --git a/tests/unit/core.js b/tests/unit/core.js
index 1946e475..2ebb3c72 100644
--- a/tests/unit/core.js
+++ b/tests/unit/core.js
@@ -928,6 +928,19 @@ exports.testES6Modules = function (test) {
       "import afterLabelImported from 'elsewhere';"
     ], { esversion: 6 });
 
+  TestRun(test, "invalid ImportsList")
+    .addError(1, 12, "Unexpected 'y'.")
+    .addError(1, 12, "Expected 'from' and instead saw 'y'.")
+    .addError(1, 14, "Expected '(string)' and instead saw '}'.")
+    .addError(1, 15, "Missing semicolon.")
+    .addError(1, 16, "Expected an assignment or function call and instead saw an expression.")
+    .addError(1, 20, "Missing semicolon.")
+    .addError(1, 21, "Expected an assignment or function call and instead saw an expression.")
+    .addError(1, 16, "'from' is not defined.")
+    .test([
+      "import { x y } from 'elsewhere';"
+    ], { esversion: 6, module: true });
+
   TestRun(test, "async as Identifier")
     .test([
       "var async;",
diff --git a/tests/unit/options.js b/tests/unit/options.js
index ece32ae7..2f91f57e 100644
--- a/tests/unit/options.js
+++ b/tests/unit/options.js
@@ -3425,6 +3425,86 @@ singleGroups.exponentiation = function (test) {
   test.done();
 };
 
+singleGroups.asyncFunction = function (test) {
+  TestRun(test, "Async Function Expression")
+    .test([
+      "(async function() {})();",
+      "(async function a() {})();"
+    ], { singleGroups: true, esversion: 8 });
+
+  TestRun(test, "Async Generator Function Expression")
+    .test([
+      "(async function * () { yield; })();",
+      "(async function * a() { yield; })();"
+    ], { singleGroups: true, esversion: 9 });
+
+
+  TestRun(test, "Async Arrow Function")
+    .test([
+      "(async () => {})();",
+      "(async x => x)();"
+    ], { singleGroups: true, esversion: 8 });
+
+  TestRun(test, "async identifier")
+    .addError(1, 1, "Unnecessary grouping operator.")
+    .addError(2, 1, "Unnecessary grouping operator.")
+    .test([
+      "(async());",
+      "(async(x, y, z));"
+    ], { singleGroups: true, esversion: 8 });
+
+  test.done();
+};
+
+singleGroups.await = function (test) {
+  TestRun(test, "MultiplicativeExpression")
+    .addError(2, 3, "Unnecessary grouping operator.")
+    .test([
+      "(async function() {",
+      "  (await 2) * 3;",
+      "})();"
+    ], { singleGroups: true, expr: true, esversion: 8 });
+
+  TestRun(test, "ExponentiationExpression")
+    .addError(2, 3, "Unnecessary grouping operator.")
+    .test([
+      "(async function() {",
+      "  (await 2) ** 3;",
+      "})();"
+    ], { singleGroups: true, expr: true, esversion: 8 });
+
+  TestRun(test, "CallExpression")
+    .test([
+      "(async function() {",
+      "  (await 2)();",
+      "})();"
+    ], { singleGroups: true, expr: true, esversion: 8 });
+
+  TestRun(test, "EqualityExpression")
+    .addError(2, 3, "Unnecessary grouping operator.")
+    .addError(3, 3, "Unnecessary grouping operator.")
+    .addError(4, 3, "Unnecessary grouping operator.")
+    .addError(5, 3, "Unnecessary grouping operator.")
+    .test([
+      "(async function() {",
+      "  (await 2) == 2;",
+      "  (await 2) != 2;",
+      "  (await 2) === 2;",
+      "  (await 2) !== 2;",
+      "})();"
+    ], { singleGroups: true, expr: true, esversion: 8 });
+
+  TestRun(test, "Expression")
+    .addError(2, 3, "Unnecessary grouping operator.")
+    .test([
+      "(async function() {",
+      "  (await 0), 0;",
+      "})();"
+    ], { singleGroups: true, expr: true, esversion: 8 });
+
+  test.done();
+};
+
 singleGroups.objectLiterals = function (test) {
   var code = [
     "({}).method();",
diff --git a/tests/unit/parser.js b/tests/unit/parser.js
index cf1a9284..7386f845 100644
--- a/tests/unit/parser.js
+++ b/tests/unit/parser.js
@@ -9591,6 +9591,16 @@ exports.asyncFunctions.awaitOperator = function (test) {
       "};",
     ], { esversion: 8 });
 
+  // Regression test for gh-3395
+  TestRun(test, "within object initializer")
+    .test([
+      "void async function() {",
+      "  void {",
+      "    x: await 0,",
+      "  };",
+      "};"
+    ], { esversion: 8 });
+
   test.done();
 };