Repository: jshint/jshint
Pull request #3484 (2020-06-15 02:51:13)

[[FIX]] Limit positions for class declarations
JSHint already includes logic to disallow `let` and `const` declarations
in certain statement positions. Extend the definition of `class` tokens
so that it applies the same restrictions to class declarations.

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

Coverage remained the same at 100.0% when pulling **e0683662cc38e356f0f6230d6e2b6c48c5e076e8 on jugglinmike:restrict-class-declaration** into **cd3fda07c3dc6af74a0fb5ad087d54e45afb0e68 on jshint:master**.


Patch:
diff --git a/src/jshint.js b/src/jshint.js
index c4b8a396..5ae1ae39 100644
--- a/src/jshint.js
+++ b/src/jshint.js
@@ -2743,8 +2743,7 @@ var JSHINT = (function() {
   state.syntax["new"].exps = true;
 
 
-  // Class statement
-  blockstmt("class", function(context) {
+  var classDeclaration = blockstmt("class", function(context) {
     var className, classNameToken;
     var inexport = context & prodParams.export;
 
@@ -2782,7 +2781,9 @@ var JSHINT = (function() {
     state.funct["(scope)"].stack();
     classBody(this, context);
     return this;
-  }).exps = true;
+  });
+  classDeclaration.exps = true;
+  classDeclaration.declaration = true;
 
   /*
     Class expression
@@ -3522,6 +3523,8 @@ var JSHINT = (function() {
       // are added to the param scope
       var currentParams = [];
 
+      pastRest = spreadrest("rest");
+
       if (_.includes(["{", "["], state.tokens.next.id)) {
         hasDestructuring = true;
         tokens = destructuringPattern(context);
@@ -3533,7 +3536,6 @@ var JSHINT = (function() {
           }
         }
       } else {
-        pastRest = spreadrest("rest");
         ident = identifier(context);
 
         if (ident) {
diff --git a/src/lex.js b/src/lex.js
index c5431649..9a078ea7 100644
--- a/src/lex.js
+++ b/src/lex.js
@@ -966,11 +966,19 @@ Lexer.prototype = {
       }
     }
 
+    // TODO: Extend this check to other numeric literals
+    this.triggerAsync("warning", {
+      code: "W045",
+      line: this.line,
+      character: this.char + value.length,
+      data: [ value ]
+    }, checks, function() { return !isFinite(value); });
+
     return {
       type: Token.NumericLiteral,
       value: value,
       base: base,
-      isMalformed: !isFinite(value)
+      isMalformed: false
     };
   },
 
@@ -2143,10 +2151,8 @@ Lexer.prototype = {
 
       case Token.NumericLiteral:
         if (token.isMalformed) {
-          // This condition unequivocally describes a syntax error.
-          // TODO: Re-factor as an "error" (not a "warning").
-          this.trigger("warning", {
-            code: "W045",
+          this.trigger("error", {
+            code: "E067",
             line: this.line,
             character: this.char,
             data: [ token.value ]
diff --git a/src/messages.js b/src/messages.js
index cce95ff7..e541cdeb 100644
--- a/src/messages.js
+++ b/src/messages.js
@@ -82,7 +82,8 @@ var errors = {
   E064: "Super call may only be used within class method bodies.",
   E065: "Functions defined outside of strict mode with non-simple parameter lists may not " +
     "enable strict mode.",
-  E066: "Asynchronous iteration is only available with for-of loops."
+  E066: "Asynchronous iteration is only available with for-of loops.",
+  E067: "Malformed numeric literal: '{a}'."
 };
 
 var warnings = {
@@ -131,7 +132,8 @@ var warnings = {
   W042: "Avoid EOL escaping.",
   W043: "Bad escaping of EOL. Use option multistr if needed.",
   W044: "Bad or unnecessary escaping.", /* TODO(caitp): remove W044 */
-  W045: "Bad number '{a}'.",
+  W045: "Value described by numeric literal cannot be accurately " +
+    "represented with a number value: '{a}'.",
   W046: "Don't use extra leading zeros '{a}'.",
   W047: "A trailing decimal point can be confused with a dot: '{a}'.",
   W048: "Unexpected control character in regular expression.",


Test patch:
diff --git a/tests/test262/expectations.txt b/tests/test262/expectations.txt
index d90d2ea0..5b6f9861 100644
--- a/tests/test262/expectations.txt
+++ b/tests/test262/expectations.txt
@@ -143,22 +143,10 @@ test/language/identifiers/vals-rus-alpha-lower-via-escape-hex.js(default)
 test/language/identifiers/vals-rus-alpha-lower-via-escape-hex.js(strict mode)
 test/language/identifiers/vals-rus-alpha-upper-via-escape-hex.js(default)
 test/language/identifiers/vals-rus-alpha-upper-via-escape-hex.js(strict mode)
-test/language/rest-parameters/array-pattern.js(default)
-test/language/rest-parameters/array-pattern.js(strict mode)
-test/language/rest-parameters/object-pattern.js(default)
-test/language/rest-parameters/object-pattern.js(strict mode)
 test/language/arguments-object/10.6-13-c-1-s.js(strict mode)
 test/language/arguments-object/10.6-1gs.js(strict mode)
 test/language/arguments-object/10.6-2gs.js(strict mode)
-test/language/literals/numeric/S7.8.3_A6.1_T1.js(default)
-test/language/literals/numeric/S7.8.3_A6.1_T1.js(strict mode)
-test/language/literals/numeric/S7.8.3_A6.1_T2.js(default)
-test/language/literals/numeric/S7.8.3_A6.1_T2.js(strict mode)
-test/language/literals/numeric/binary-invalid-truncated.js(default)
-test/language/literals/numeric/binary-invalid-truncated.js(strict mode)
 test/language/literals/numeric/non-octal-decimal-integer-strict.js(strict mode)
-test/language/literals/numeric/octal-invalid-truncated.js(default)
-test/language/literals/numeric/octal-invalid-truncated.js(strict mode)
 test/language/block-scope/syntax/function-declarations/in-statement-position-if-expression-statement-else-statement.js(strict mode)
 test/language/block-scope/syntax/function-declarations/in-statement-position-if-expression-statement.js(strict mode)
 test/language/destructuring/binding/syntax/array-elements-with-initializer.js(default)
@@ -199,10 +187,6 @@ test/language/expressions/template-literal/invalid-hexidecimal-character-escape-
 test/language/expressions/template-literal/invalid-legacy-octal-escape-sequence.js(default)
 test/language/expressions/template-literal/tv-utf16-escape-sequence.js(default)
 test/language/expressions/template-literal/tv-utf16-escape-sequence.js(strict mode)
-test/language/expressions/function/scope-param-rest-elem-var-close.js(default)
-test/language/expressions/function/scope-param-rest-elem-var-open.js(default)
-test/language/expressions/generators/scope-param-rest-elem-var-close.js(default)
-test/language/expressions/generators/scope-param-rest-elem-var-open.js(default)
 test/language/expressions/generators/yield-as-function-expression-binding-identifier.js(default)
 test/language/expressions/generators/yield-as-logical-or-expression.js(default)
 test/language/expressions/generators/yield-as-logical-or-expression.js(strict mode)
@@ -210,10 +194,6 @@ test/language/expressions/generators/yield-star-after-newline.js(default)
 test/language/expressions/generators/yield-star-after-newline.js(strict mode)
 test/language/expressions/class/getter-param-dflt.js(default)
 test/language/expressions/class/getter-param-dflt.js(strict mode)
-test/language/expressions/object/scope-gen-meth-param-rest-elem-var-close.js(default)
-test/language/expressions/object/scope-gen-meth-param-rest-elem-var-open.js(default)
-test/language/expressions/object/scope-meth-param-rest-elem-var-close.js(default)
-test/language/expressions/object/scope-meth-param-rest-elem-var-open.js(default)
 test/language/expressions/object/method-definition/yield-as-expression-with-rhs.js(default)
 test/language/expressions/object/method-definition/yield-as-expression-with-rhs.js(strict mode)
 test/language/expressions/object/method-definition/yield-as-expression-without-rhs.js(default)
@@ -223,30 +203,16 @@ test/language/expressions/object/method-definition/yield-as-logical-or-expressio
 test/language/expressions/object/method-definition/yield-as-logical-or-expression.js(strict mode)
 test/language/expressions/object/method-definition/yield-star-after-newline.js(default)
 test/language/expressions/object/method-definition/yield-star-after-newline.js(strict mode)
-test/language/expressions/arrow-function/scope-param-rest-elem-var-close.js(default)
-test/language/expressions/arrow-function/scope-param-rest-elem-var-open.js(default)
 test/language/expressions/arrow-function/syntax/early-errors/asi-restriction-invalid-parenless-parameters-expression-body.js(default)
 test/language/expressions/arrow-function/syntax/early-errors/asi-restriction-invalid-parenless-parameters-expression-body.js(strict mode)
 test/language/expressions/arrow-function/syntax/early-errors/asi-restriction-invalid-parenless-parameters.js(default)
 test/language/expressions/arrow-function/syntax/early-errors/asi-restriction-invalid-parenless-parameters.js(strict mode)
-test/language/statements/do-while/decl-cls.js(default)
-test/language/statements/do-while/decl-cls.js(strict mode)
-test/language/statements/for-in/decl-cls.js(default)
-test/language/statements/for-in/decl-cls.js(strict mode)
 test/language/statements/for-in/head-const-bound-names-fordecl-tdz.js(default)
 test/language/statements/for-in/head-const-bound-names-fordecl-tdz.js(strict mode)
 test/language/statements/for-in/head-let-bound-names-fordecl-tdz.js(default)
 test/language/statements/for-in/head-let-bound-names-fordecl-tdz.js(strict mode)
 test/language/statements/for-in/head-lhs-let.js(default)
-test/language/statements/for/decl-cls.js(default)
-test/language/statements/for/decl-cls.js(strict mode)
 test/language/statements/for/head-lhs-let.js(default)
-test/language/statements/if/if-cls-else-cls.js(default)
-test/language/statements/if/if-cls-else-cls.js(strict mode)
-test/language/statements/if/if-cls-else-stmt.js(default)
-test/language/statements/if/if-cls-else-stmt.js(strict mode)
-test/language/statements/if/if-cls-no-else.js(default)
-test/language/statements/if/if-cls-no-else.js(strict mode)
 test/language/statements/if/if-decl-else-decl-strict.js(strict mode)
 test/language/statements/if/if-decl-else-stmt-strict.js(strict mode)
 test/language/statements/if/if-decl-no-else-strict.js(strict mode)
@@ -259,14 +225,10 @@ test/language/statements/if/if-gen-else-stmt.js(default)
 test/language/statements/if/if-gen-else-stmt.js(strict mode)
 test/language/statements/if/if-gen-no-else.js(default)
 test/language/statements/if/if-gen-no-else.js(strict mode)
-test/language/statements/if/if-stmt-else-cls.js(default)
-test/language/statements/if/if-stmt-else-cls.js(strict mode)
 test/language/statements/if/if-stmt-else-decl-strict.js(strict mode)
 test/language/statements/if/if-stmt-else-fun-strict.js(strict mode)
 test/language/statements/if/if-stmt-else-gen.js(default)
 test/language/statements/if/if-stmt-else-gen.js(strict mode)
-test/language/statements/generators/scope-param-rest-elem-var-close.js(default)
-test/language/statements/generators/scope-param-rest-elem-var-open.js(default)
 test/language/statements/generators/yield-as-function-expression-binding-identifier.js(default)
 test/language/statements/generators/yield-as-logical-or-expression.js(default)
 test/language/statements/generators/yield-as-logical-or-expression.js(strict mode)
@@ -283,8 +245,6 @@ test/language/statements/labeled/decl-gen.js(default)
 test/language/statements/labeled/decl-gen.js(strict mode)
 test/language/statements/labeled/decl-let.js(default)
 test/language/statements/labeled/decl-let.js(strict mode)
-test/language/statements/function/scope-param-rest-elem-var-close.js(default)
-test/language/statements/function/scope-param-rest-elem-var-open.js(default)
 test/language/statements/return/S12.9_A1_T1.js(default)
 test/language/statements/return/S12.9_A1_T1.js(strict mode)
 test/language/statements/return/S12.9_A1_T10.js(default)
@@ -305,21 +265,16 @@ test/language/statements/return/S12.9_A1_T8.js(default)
 test/language/statements/return/S12.9_A1_T8.js(strict mode)
 test/language/statements/return/S12.9_A1_T9.js(default)
 test/language/statements/return/S12.9_A1_T9.js(strict mode)
-test/language/statements/for-of/decl-cls.js(default)
-test/language/statements/for-of/decl-cls.js(strict mode)
 test/language/statements/for-of/head-const-bound-names-fordecl-tdz.js(default)
 test/language/statements/for-of/head-const-bound-names-fordecl-tdz.js(strict mode)
 test/language/statements/for-of/head-let-bound-names-fordecl-tdz.js(default)
 test/language/statements/for-of/head-let-bound-names-fordecl-tdz.js(strict mode)
 test/language/statements/switch/S12.11_A2_T1.js(default)
 test/language/statements/switch/S12.11_A2_T1.js(strict mode)
-test/language/statements/while/decl-cls.js(default)
-test/language/statements/while/decl-cls.js(strict mode)
 test/language/statements/try/early-catch-duplicates.js(default)
 test/language/statements/try/early-catch-duplicates.js(strict mode)
 test/language/statements/try/early-catch-lex.js(default)
 test/language/statements/try/early-catch-lex.js(strict mode)
-test/language/statements/with/decl-cls.js(default)
 test/language/statements/const/block-local-use-before-initialization-in-declaration-statement.js(default)
 test/language/statements/const/block-local-use-before-initialization-in-declaration-statement.js(strict mode)
 test/language/statements/const/block-local-use-before-initialization-in-prior-statement.js(default)
diff --git a/tests/unit/parser.js b/tests/unit/parser.js
index 034a186c..c1c81592 100644
--- a/tests/unit/parser.js
+++ b/tests/unit/parser.js
@@ -537,7 +537,7 @@ exports.numbers = function (test) {
   ];
 
   TestRun(test)
-    .addError(2, 15, "Bad number '10e308'.")
+    .addError(2, 15, "Value described by numeric literal cannot be accurately represented with a number value: '10e308'.")
     .addError(5, 11, "A leading decimal point can be confused with a dot: '.3'.")
     .addError(6, 9, "Unexpected '0'.")
     .addError(7, 1, "Expected an identifier and instead saw 'var'.")
@@ -547,7 +547,7 @@ exports.numbers = function (test) {
     .addError(9, 9, "A dot following a number can be confused with a decimal point.")
     .addError(11, 9, "'Octal integer literal' is only available in ES6 (use 'esversion: 6').")
     .addError(12, 9, "'Binary integer literal' is only available in ES6 (use 'esversion: 6').")
-    .addError(13, 11, "Bad number '0x'.")
+    .addError(13, 11, "Malformed numeric literal: '0x'.")
     .addError(15, 9, "Unexpected '1'.")
     .addError(16, 11, "Expected an identifier and instead saw ';'.")
     .addError(16, 1, "Expected an identifier and instead saw 'var'.")
diff --git a/tests/unit/unstable/bigint.js b/tests/unit/unstable/bigint.js
index 174505bc..a299cca8 100644
--- a/tests/unit/unstable/bigint.js
+++ b/tests/unit/unstable/bigint.js
@@ -75,19 +75,19 @@ exports.invalid = function (test) {
     .test("void 1e3n;", {esversion: 6, unstable: {bigint: true}});
 
   TestRun(test, "invalid hex digit")
-    .addError(1, 8, "Bad number '0x'.")
+    .addError(1, 8, "Malformed numeric literal: '0x'.")
     .addError(1, 8, "Missing semicolon.")
     .addError(1, 8, "Expected an assignment or function call and instead saw an expression.")
     .test("void 0xgn;", {esversion: 6, unstable: {bigint: true}});
 
   TestRun(test, "invalid binary digit")
-    .addError(1, 8, "Bad number '0b'.")
+    .addError(1, 8, "Malformed numeric literal: '0b'.")
     .addError(1, 8, "Missing semicolon.")
     .addError(1, 8, "Expected an assignment or function call and instead saw an expression.")
     .test("void 0b2n;", {esversion: 6, unstable: {bigint: true}});
 
   TestRun(test, "invalid octal digit")
-    .addError(1, 8, "Bad number '0o'.")
+    .addError(1, 8, "Malformed numeric literal: '0o'.")
     .addError(1, 8, "Missing semicolon.")
     .addError(1, 8, "Expected an assignment or function call and instead saw an expression.")
     .test("void 0o8n;", {esversion: 6, unstable: {bigint: true}});