Permalink
Browse files

[[Fix]] Disallow ambiguous configuration values

In the presence of the recently-introduced `esversion` option, JSHint
should reject any occurrence of the equivalent legacy options: `es3`,
`es5`, and `esnext`.

Previous implementation conflated the user-specified option values and
the state of the linter itself. Without a formal distinction between
these two concepts, it was not possible to consistently enforce this
constraint. For example, the value of `state.option.esversion` could be
set to `5` directly (i.e. `esversion: 5`) or indirectly (i.e. `es5:
true`), but only in the former case would a later in-line configuration
of `esversion: 6` be acceptable.

Achieve this by making an explicit distinction between user-specified
option values (in other words, the `es3`, `es5`, `esnext`, and
`esversion` properties of the `state.option` object) and the state of
the linter itself (the new `state.esVersion` property).
  • Loading branch information...
jugglinmike committed Dec 13, 2015
1 parent 91fa4ea commit eb54a4c48898e1e3f1e3b0a8dfa188aa586afc5f
Showing with 49 additions and 40 deletions.
  1. +16 −30 src/jshint.js
  2. +6 −5 src/state.js
  3. +27 −5 tests/unit/options.js
@@ -187,19 +187,30 @@ var JSHINT = (function() {
}

function assume() {
var badESOpt = null;
processenforceall();

/**
* TODO: Remove in JSHint 3
*/
if (!state.option.esversion && !state.option.moz) {
if (state.option.esversion) {
if (state.option.es3) {
state.option.esversion = 3;
badESOpt = "es3";
} else if (state.option.es5) {
badESOpt = "es5";
} else if (state.option.esnext) {
state.option.esversion = 6;
} else {
state.option.esversion = 5;
badESOpt = "esnext";
}

if (badESOpt) {
quit("E059", state.tokens.next, "esversion", badESOpt);
}

state.esVersion = state.option.esversion;
} else if (state.option.es3) {
state.esVersion = 3;
} else if (state.option.esnext) {
state.esVersion = 6;
}

if (state.inES5()) {
@@ -675,31 +686,6 @@ var JSHINT = (function() {
}
}

/**
* TODO: Remove in JSHint 3
*/
var esversions = {
es3 : 3,
es5 : 5,
esnext: 6
};
if (_.has(esversions, key)) {
switch (val) {
case "true":
state.option.moz = false;
state.option.esversion = esversions[key];
break;
case "false":
if (!state.option.moz) {
state.option.esversion = 5;
}
break;
default:
error("E002", nt);
}
return;
}

if (key === "esversion") {
switch (val) {
case "5":
@@ -26,20 +26,20 @@ var state = {
*/
inES6: function(strict) {
if (strict) {
return this.option.esversion === 6;
return this.esVersion === 6;
}
return this.option.moz || this.option.esversion >= 6;
return this.option.moz || this.esVersion >= 6;
},

/**
* @param {boolean} strict - When `true`, return `true` only when
* esversion is exactly 5
* esVersion is exactly 5
*/
inES5: function(strict) {
if (strict) {
return (!this.option.esversion || this.option.esversion === 5) && !this.option.moz;
return (!this.esVersion || this.esVersion === 5) && !this.option.moz;
}
return !this.option.esversion || this.option.esversion >= 5 || this.option.moz;
return !this.esVersion || this.esVersion >= 5 || this.option.moz;
},


@@ -51,6 +51,7 @@ var state = {
};

this.option = {};
this.esVersion = 5;
this.funct = null;
this.ignored = {};
this.directive = {};
@@ -3446,22 +3446,44 @@ exports.esversion = function(test) {
.test(arrayComprehension, { esnext: true });


TestRun(test, "precedence over `es3`") // TODO: Remove in JSHint 3
TestRun(test, "incompatibility with `es3`") // TODO: Remove in JSHint 3
.addError(0, "Incompatible values for the 'esversion' and 'es3' linting options. (0% scanned).")
.test(es6code, { esversion: 6, es3: true });

TestRun(test, "precedence over `es5`") // TODO: Remove in JSHint 3
TestRun(test, "incompatibility with `es5`") // TODO: Remove in JSHint 3
.addError(0, "ES5 option is now set per default")
.addError(0, "Incompatible values for the 'esversion' and 'es5' linting options. (0% scanned).")
.test(es6code, { esversion: 6, es5: true });

TestRun(test, "precedence over `esnext`") // TODO: Remove in JSHint 3
.addError(2, "'computed property names' is only available in ES6 (use 'esversion: 6').")
TestRun(test, "incompatibility with `esnext`") // TODO: Remove in JSHint 3
.addError(0, "Incompatible values for the 'esversion' and 'esnext' linting options. (0% scanned).")
.test(es6code, { esversion: 3, esnext: true });

TestRun(test, "imcompatible option specified in-line")
.addError(2, "Incompatible values for the 'esversion' and 'es3' linting options. (66% scanned).")
.test(["", "// jshint esversion: 3", ""], { es3: true });

TestRun(test, "incompatible option specified in-line")
.addError(2, "Incompatible values for the 'esversion' and 'es3' linting options. (66% scanned).")
.test(["", "// jshint es3: true", ""], { esversion: 3 });

TestRun(test, "compatible option specified in-line")
.addError(3, "'class' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).")
.test(["", "// jshint esversion: 3", "class A {}"], { esversion: 3 });

TestRun(test, "compatible option specified in-line")
.addError(3, "'class' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).")
.test(["", "// jshint esversion: 3", "class A {}"], { esversion: 6 });

TestRun(test, "compatible option specified in-line")
.test(["", "// jshint esversion: 6", "class A {}"], { esversion: 3 });

var code2 = [ // TODO: Remove in JSHint 3
"/* jshint esversion: 3, esnext: true */"
].concat(es6code);

TestRun(test, "the last has the precedence (inline configuration)") // TODO: Remove in JSHint 3
TestRun(test, "incompatible options specified in-line") // TODO: Remove in JSHint 3
.addError(1, "Incompatible values for the 'esversion' and 'esnext' linting options. (25% scanned).")
.test(code2);

var code3 = [

0 comments on commit eb54a4c

Please sign in to comment.