Permalink
Browse files

[[FIX]] Distinguish between directive and mode

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 Apr 12, 2015
1 parent c0edd9f commit 51059bd050d7ff0a1aa2ebeb93da0754261534eb
Showing with 28 additions and 20 deletions.
  1. +15 −17 src/jshint.js
  2. +3 −3 src/lex.js
  3. +10 −0 src/state.js
@@ -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;
}
}
@@ -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);
}

@@ -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);
}

@@ -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");
@@ -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");
}
}
@@ -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");
}
}
@@ -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);
}
});
@@ -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);
@@ -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;
@@ -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")) {
@@ -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) {
@@ -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);
@@ -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);
@@ -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);
@@ -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;
}
}
@@ -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", {
@@ -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,
@@ -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;

0 comments on commit 51059bd

Please sign in to comment.