Skip to content

Commit

Permalink
[[FIX]] Distinguish between directive and mode
Browse files Browse the repository at this point in the history
All code within a class body is interpreted as "strict mode" code. This
can be simulated by explicitly setting the internal mechanism for the
"use strict" directive, but that approach is misleading. In such cases,
the code is in strict mode, but the "use strict" directive is *not* set.

Define a general-purpose `isStrict` method that determines the language
mode according to these two distinct conditions.

This approach enables a more expressive and accurate means for the
internals to control parsing behavior. In addition, it may be re-used
when JSHint is extended to lint module bodies as strict mode code.
  • Loading branch information
jugglinmike committed May 9, 2015
1 parent c0edd9f commit 51059bd
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 20 deletions.
32 changes: 15 additions & 17 deletions src/jshint.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ var JSHINT = (function() {
// Some ES5 FutureReservedWord identifiers are active only
// within a strict mode environment.
if (meta.strictOnly) {
if (!state.option.strict && !state.directive["use strict"]) {
if (!state.option.strict && !state.isStrict()) {
return false;
}
}
Expand Down Expand Up @@ -1337,7 +1337,7 @@ var JSHINT = (function() {
if (left.id === ".") {
if (!left.left) {
warning("E031", that);
} else if (left.left.value === "arguments" && !state.directive["use strict"]) {
} else if (left.left.value === "arguments" && !state.isStrict()) {
warning("E031", that);
}

Expand All @@ -1353,7 +1353,7 @@ var JSHINT = (function() {
});
} else if (!left.left) {
warning("E031", that);
} else if (left.left.value === "arguments" && !state.directive["use strict"]) {
} else if (left.left.value === "arguments" && !state.isStrict()) {
warning("E031", that);
}

Expand Down Expand Up @@ -1640,7 +1640,7 @@ var JSHINT = (function() {
r = expression(0, true);

if (r && (!r.identifier || r.value !== "function") && (r.type !== "(punctuator)")) {
if (!state.directive["use strict"] &&
if (!state.isStrict() &&
state.option.globalstrict &&
state.option.strict) {
warning("E007");
Expand Down Expand Up @@ -1793,7 +1793,7 @@ var JSHINT = (function() {
directives();

if (state.option.strict && funct["(context)"]["(global)"]) {
if (!m["use strict"] && !state.directive["use strict"]) {
if (!m["use strict"] && !state.isStrict()) {
warning("E007");
}
}
Expand Down Expand Up @@ -1832,7 +1832,7 @@ var JSHINT = (function() {
expression(10);

if (state.option.strict && funct["(context)"]["(global)"]) {
if (!m["use strict"] && !state.directive["use strict"]) {
if (!m["use strict"] && !state.isStrict()) {
warning("E007");
}
}
Expand Down Expand Up @@ -2107,7 +2107,7 @@ var JSHINT = (function() {
reserve("default").reach = true;
reserve("finally");
reservevar("arguments", function(x) {
if (state.directive["use strict"] && funct["(global)"]) {
if (state.isStrict() && funct["(global)"]) {
warning("E008", x);
}
});
Expand All @@ -2116,7 +2116,7 @@ var JSHINT = (function() {
reservevar("Infinity");
reservevar("null");
reservevar("this", function(x) {
if (state.directive["use strict"] && !isMethod() &&
if (state.isStrict() && !isMethod() &&
!state.option.validthis && ((funct["(statement)"] &&
funct["(name)"].charAt(0) > "Z") || funct["(global)"])) {
warning("W040", x);
Expand Down Expand Up @@ -2317,7 +2317,7 @@ var JSHINT = (function() {

// The `delete` operator accepts unresolvable references when not in strict
// mode, so the operand may be undefined.
if (p.identifier && !state.directive["use strict"]) {
if (p.identifier && !state.isStrict()) {
p.forgiveUndef = true;
}
return this;
Expand Down Expand Up @@ -2474,7 +2474,7 @@ var JSHINT = (function() {
if (left && left.value === "arguments" && (m === "callee" || m === "caller")) {
if (state.option.noarg)
warning("W059", left, m);
else if (state.directive["use strict"])
else if (state.isStrict())
error("E008");
} else if (!state.option.evil && left && left.value === "document" &&
(m === "write" || m === "writeln")) {
Expand Down Expand Up @@ -3653,21 +3653,19 @@ var JSHINT = (function() {
}

function classtail(c) {
var strictness = state.directive["use strict"];

var wasInClassBody = state.inClassBody;
// ClassHeritage(opt)
if (state.tokens.next.value === "extends") {
advance("extends");
c.heritage = expression(10);
}

// A ClassBody is always strict code.
state.directive["use strict"] = true;
state.inClassBody = true;
advance("{");
// ClassBody(opt)
c.body = classbody(c);
advance("}");
state.directive["use strict"] = strictness;
state.inClassBody = wasInClassBody;
}

function classbody(c) {
Expand Down Expand Up @@ -3975,7 +3973,7 @@ var JSHINT = (function() {

blockstmt("with", function() {
var t = state.tokens.next;
if (state.directive["use strict"]) {
if (state.isStrict()) {
error("E010", state.tokens.curr);
} else if (!state.option.withstmt) {
warning("W085", state.tokens.curr);
Expand Down Expand Up @@ -5344,7 +5342,7 @@ var JSHINT = (function() {
default:
directives();

if (state.directive["use strict"]) {
if (state.isStrict()) {
if (!state.option.globalstrict) {
if (!(state.option.node || state.option.phantom || state.option.browserify)) {
warning("W097", state.tokens.prev);
Expand Down
6 changes: 3 additions & 3 deletions src/lex.js
Original file line number Diff line number Diff line change
Expand Up @@ -962,7 +962,7 @@ Lexer.prototype = {
line: this.line,
character: this.char
}, checks,
function() { return n >= 0 && n <= 7 && state.directive["use strict"]; });
function() { return n >= 0 && n <= 7 && state.isStrict(); });
break;
case "u":
var hexCode = this.input.substr(1, 4);
Expand Down Expand Up @@ -1575,7 +1575,7 @@ Lexer.prototype = {
// Some ES5 FutureReservedWord identifiers are active only
// within a strict mode environment.
if (meta.strictOnly) {
if (!state.option.strict && !state.directive["use strict"]) {
if (!state.option.strict && !state.isStrict()) {
return false;
}
}
Expand Down Expand Up @@ -1785,7 +1785,7 @@ Lexer.prototype = {
line: this.line,
character: this.char
}, checks, function() {
return state.directive["use strict"] && token.base === 8 && token.isLegacy;
return state.isStrict() && token.base === 8 && token.isLegacy;
});

this.trigger("Number", {
Expand Down
10 changes: 10 additions & 0 deletions src/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ var NameStack = require("./name-stack.js");
var state = {
syntax: {},

/**
* Determine if the code currently being linted is strict mode code.
*
* @returns {boolean}
*/
isStrict: function() {
return this.directive["use strict"] || this.inClassBody;
},

reset: function() {
this.tokens = {
prev: null,
Expand All @@ -22,6 +31,7 @@ var state = {
this.ignoredLines = {};
this.forinifcheckneeded = false;
this.nameStack = new NameStack();
this.inClassBody = false;

// Blank out non-multi-line-commented lines when ignoring linter errors
this.ignoreLinterErrors = false;
Expand Down

0 comments on commit 51059bd

Please sign in to comment.