Repository: jshint/jshint
Pull request #3123 (2017-04-23 18:59:27)

Import hoisting
Hey @rwaldron: if this looks good to you, can you please merge as individual
commits? I want to make sure we stick to the contribution guidelines for commit
messages, but squashing this pull request via the GitHub.com interface will
create a commit that is invalid by those standards.


Patch:
diff --git a/src/jshint.js b/src/jshint.js
index 7c92cd84..7e2ff824 100644
--- a/src/jshint.js
+++ b/src/jshint.js
@@ -4587,7 +4587,7 @@ var JSHINT = (function() {
       this.name = identifier();
       // Import bindings are immutable (see ES6 8.1.1.5.5)
       state.funct["(scope)"].addlabel(this.name, {
-        type: "const",
+        type: "import",
         initialized: true,
         token: state.tokens.curr });
 
@@ -4614,7 +4614,7 @@ var JSHINT = (function() {
         this.name = identifier();
         // Import bindings are immutable (see ES6 8.1.1.5.5)
         state.funct["(scope)"].addlabel(this.name, {
-          type: "const",
+          type: "import",
           initialized: true,
           token: state.tokens.curr });
       }
@@ -4640,7 +4640,7 @@ var JSHINT = (function() {
 
         // Import bindings are immutable (see ES6 8.1.1.5.5)
         state.funct["(scope)"].addlabel(importName, {
-          type: "const",
+          type: "import",
           initialized: true,
           token: state.tokens.curr });
 
diff --git a/src/scope-manager.js b/src/scope-manager.js
index eeef5185..6adbfa0c 100644
--- a/src/scope-manager.js
+++ b/src/scope-manager.js
@@ -278,6 +278,7 @@ var scopeManager = function(state, predefined, exported, declared) {
         var usedLabel = currentLabels[usedLabelName];
         if (usedLabel) {
           var usedLabelType = usedLabel["(type)"];
+          var isImmutable = usedLabelType === "const" || usedLabelType === "import";
 
           if (usedLabel["(useOutsideOfScope)"] && !state.option.funcscope) {
             var usedTokens = usage["(tokens)"];
@@ -295,7 +296,7 @@ var scopeManager = function(state, predefined, exported, declared) {
           _current["(labels)"][usedLabelName]["(unused)"] = false;
 
           // check for modifying a const
-          if (usedLabelType === "const" && usage["(modified)"]) {
+          if (isImmutable && usage["(modified)"]) {
             for (j = 0; j < usage["(modified)"].length; j++) {
               error("E013", usage["(modified)"][j], usedLabelName);
             }
@@ -621,7 +622,7 @@ var scopeManager = function(state, predefined, exported, declared) {
      * adds an indentifier to the relevant current scope and creates warnings/errors as necessary
      * @param {string} labelName
      * @param {Object} opts
-     * @param {String} opts.type - the type of the label e.g. "param", "var", "let, "const", "function"
+     * @param {String} opts.type - the type of the label e.g. "param", "var", "let, "const", "import", "function"
      * @param {Token} opts.token - the token pointing at the declaration
      * @param {boolean} opts.initialized - whether the binding should be created in an "initialized" state.
      */
@@ -629,14 +630,15 @@ var scopeManager = function(state, predefined, exported, declared) {
 
       var type  = opts.type;
       var token = opts.token;
-      var isblockscoped = type === "let" || type === "const" || type === "class";
+      var isblockscoped = type === "let" || type === "const" ||
+        type === "class" || type === "import";
+      var ishoisted = type === "function" || type === "import";
       var isexported    = (isblockscoped ? _current : _currentFunctBody)["(type)"] === "global" &&
                           _.has(exported, labelName);
 
       // outer shadow check (inner is only on non-block scoped)
       _checkOuterShadow(labelName, token, type);
 
-      // if is block scoped (let or const)
       if (isblockscoped) {
 
         var declaredInCurrentScope = _current["(labels)"][labelName];
@@ -651,9 +653,9 @@ var scopeManager = function(state, predefined, exported, declared) {
         if (!declaredInCurrentScope && _current["(usages)"][labelName]) {
           var usage = _current["(usages)"][labelName];
           // if its in a sub function it is not necessarily an error, just latedef
-          if (usage["(onlyUsedSubFunction)"]) {
+          if (usage["(onlyUsedSubFunction)"] || ishoisted) {
             _latedefWarning(type, labelName, token);
-          } else {
+          } else if (!ishoisted) {
             // this is a clear illegal usage for block scoped variables
             warning("E056", token, labelName, type);
           }


Test patch:
diff --git a/tests/test262/expectations.txt b/tests/test262/expectations.txt
index b445dc7b..acea48a5 100644
--- a/tests/test262/expectations.txt
+++ b/tests/test262/expectations.txt
@@ -342,7 +342,6 @@ language/asi/S7.9_A6.1_T9.js
 language/asi/S7.9_A7_T8.js
 language/asi/S7.9_A7_T9.js
 language/function-code/S10.2.1_A4_T1.js
-language/global-code/decl-lex.js
 language/global-code/export.js
 language/global-code/import.js
 language/global-code/return.js
@@ -492,9 +491,6 @@ language/module-code/eval-rqstd-abrupt.js
 language/module-code/eval-rqstd-once.js
 language/module-code/eval-rqstd-order.js
 language/module-code/eval-self-once.js
-language/module-code/instn-iee-bndng-fun.js
-language/module-code/instn-iee-bndng-gen.js
-language/module-code/instn-iee-bndng-var.js
 language/module-code/instn-iee-err-ambiguous-as.js
 language/module-code/instn-iee-err-ambiguous.js
 language/module-code/instn-iee-err-circular-as.js
@@ -505,17 +501,6 @@ language/module-code/instn-iee-err-not-found-as.js
 language/module-code/instn-iee-err-not-found.js
 language/module-code/instn-iee-star-cycle.js
 language/module-code/instn-iee-trlng-comma.js
-language/module-code/instn-local-bndng-const.js
-language/module-code/instn-named-bndng-dflt-fun-anon.js
-language/module-code/instn-named-bndng-dflt-gen-anon.js
-language/module-code/instn-named-bndng-dflt-fun-named.js
-language/module-code/instn-named-bndng-dflt-gen-named.js
-language/module-code/instn-named-bndng-dflt-star.js
-language/module-code/instn-named-bndng-dflt-named.js
-language/module-code/instn-named-bndng-fun.js
-language/module-code/instn-named-bndng-gen.js
-language/module-code/instn-named-bndng-trlng-comma.js
-language/module-code/instn-named-bndng-var.js
 language/module-code/instn-named-err-ambiguous-as.js
 language/module-code/instn-named-err-ambiguous.js
 language/module-code/instn-named-err-dflt-thru-star-as.js
@@ -531,7 +516,6 @@ language/module-code/instn-resolve-err-reference.js
 language/module-code/instn-resolve-err-syntax.js
 language/module-code/instn-resolve-order-depth.js
 language/module-code/instn-resolve-order-src.js
-language/module-code/instn-star-binding.js
 language/module-code/instn-star-err-not-found.js
 language/module-code/instn-star-star-cycle.js
 language/module-code/parse-err-decl-pos-export-switch-case-dflt.js
@@ -764,7 +748,6 @@ language/expressions/assignment/dstr-array-elem-iter-thrw-close.js
 language/expressions/assignment/dstr-array-elem-nested-array-yield-expr.js
 language/expressions/assignment/dstr-array-elem-nested-array-yield-ident-valid.js
 language/expressions/assignment/dstr-array-elem-nested-obj-yield-ident-valid.js
-language/expressions/assignment/dstr-array-elem-put-const.js
 language/expressions/assignment/dstr-array-elem-target-simple-no-strict.js
 language/expressions/assignment/dstr-array-elem-target-yield-expr.js
 language/expressions/assignment/dstr-array-elem-target-yield-valid.js
@@ -800,20 +783,17 @@ language/expressions/assignment/dstr-array-rest-nested-obj-undefined.js
 language/expressions/assignment/dstr-array-rest-nested-obj-yield-expr.js
 language/expressions/assignment/dstr-array-rest-nested-obj-yield-ident-valid.js
 language/expressions/assignment/dstr-array-rest-nested-obj.js
-language/expressions/assignment/dstr-array-rest-put-const.js
 language/expressions/assignment/dstr-array-rest-yield-expr.js
 language/expressions/assignment/dstr-array-rest-yield-ident-valid.js
 language/expressions/assignment/dstr-obj-id-identifier-yield-ident-valid.js
 language/expressions/assignment/dstr-obj-id-init-simple-no-strict.js
 language/expressions/assignment/dstr-obj-id-init-yield-ident-valid.js
-language/expressions/assignment/dstr-obj-id-put-const.js
 language/expressions/assignment/dstr-obj-id-simple-no-strict.js
 language/expressions/assignment/dstr-obj-prop-elem-init-yield-ident-valid.js
 language/expressions/assignment/dstr-obj-prop-elem-target-yield-expr.js
 language/expressions/assignment/dstr-obj-prop-elem-target-yield-ident-valid.js
 language/expressions/assignment/dstr-obj-prop-nested-array-yield-ident-valid.js
 language/expressions/assignment/dstr-obj-prop-nested-obj-yield-ident-valid.js
-language/expressions/assignment/dstr-obj-prop-put-const.js
 language/expressions/object/cover-initialized-name.js
 language/expressions/object/dstr-gen-meth-ary-ptrn-rest-ary-elem.js
 language/expressions/object/dstr-gen-meth-ary-ptrn-rest-ary-elision.js
@@ -1292,8 +1272,6 @@ language/statements/const/function-local-use-before-initialization-in-prior-stat
 language/statements/const/global-use-before-initialization-in-declaration-statement.js
 language/statements/const/global-use-before-initialization-in-prior-statement.js
 language/statements/const/syntax/const-invalid-assignment-next-expression-for.js
-language/statements/const/syntax/const-invalid-assignment-statement-body-for-in.js
-language/statements/const/syntax/const-invalid-assignment-statement-body-for-of.js
 language/statements/const/syntax/const-outer-inner-let-bindings.js
 language/statements/const/syntax/with-initializer-label-statement.js
 language/statements/let/block-local-use-before-initialization-in-declaration-statement.js
diff --git a/tests/test262/test.js b/tests/test262/test.js
index 620938ec..35cfd87b 100644
--- a/tests/test262/test.js
+++ b/tests/test262/test.js
@@ -39,6 +39,11 @@ function forEachVersion(src, run) {
  */
 var incorrectSeverity = {
   E007: true,
+  // E013 describes a runtime error: the modification of a constant binding.
+  // Although (unlike the other errors referenced in this object) this
+  // condition is guaranteed to produce errors, it is not technically an early
+  // error and should therefore be ignored when interpreting Test262 tests.
+  E013: true,
   E034: true,
 
   W024: true,
diff --git a/tests/unit/core.js b/tests/unit/core.js
index aded47df..57a22167 100644
--- a/tests/unit/core.js
+++ b/tests/unit/core.js
@@ -814,8 +814,7 @@ exports.testES6Modules = function (test) {
     [58, "'emGet' has already been declared."],
     [58, "'set' has already been declared."],
     [59, "'_' has already been declared."],
-    [60, "'ember2' has already been declared."],
-    [65, "'newImport' was used before it was declared, which is illegal for 'const' variables."]
+    [60, "'ember2' has already been declared."]
   ];
 
   var testRun = TestRun(test);
diff --git a/tests/unit/fixtures/latedef-esnext.js b/tests/unit/fixtures/latedef-esnext.js
index d72323a5..078a373b 100644
--- a/tests/unit/fixtures/latedef-esnext.js
+++ b/tests/unit/fixtures/latedef-esnext.js
@@ -67,3 +67,11 @@ export class df {}
 export var dg;
 export let dh;
 export const di = {};
+
+void importedName;
+void importedModule;
+void importedNamespace;
+
+import { importedName } from './m.js';
+import importedModule from './m.js';
+import * as importedNamespace from './m.js';
diff --git a/tests/unit/options.js b/tests/unit/options.js
index 92b19235..7a13c0fb 100644
--- a/tests/unit/options.js
+++ b/tests/unit/options.js
@@ -250,7 +250,7 @@ exports.latedef = function (test) {
     .addError(18, "Inner functions should be listed at the top of the outer function.")
     .test(src, { es3: true, latedef: true });
 
-  TestRun(test)
+  var es2015Errors = TestRun(test)
       .addError(4, "'c' was used before it was defined.")
       .addError(6, "'e' was used before it was defined.")
       .addError(8, "'h' was used before it was defined.")
@@ -259,7 +259,14 @@ exports.latedef = function (test) {
       .addError(20, "'ai' was used before it was defined.")
       .addError(31, "'bi' was used before it was defined.")
       .addError(48, "'ci' was used before it was defined.")
-      .test(esnextSrc, {esnext: true, latedef: true});
+      .addError(75, "'importedName' was used before it was defined.")
+      .addError(76, "'importedModule' was used before it was defined.")
+      .addError(77, "'importedNamespace' was used before it was defined.");
+
+  es2015Errors
+      .test(esnextSrc, {esversion: 2015, latedef: true});
+  es2015Errors
+      .test(esnextSrc, {esversion: 2015, latedef: "nofunc"});
 
   TestRun(test, "shouldn't warn when marking a var as exported")
     .test("var a;", { exported: ["a"], latedef: true });