Permalink
Browse files

[[FEAT]] Implement `module` option

As per the ECMAScript 6 specification, all module code is always strict
code. This option allows users to signal when the input code is intended
to be interpreted as a JavaScript module. This enables JSHint's
strict-mode-specific functionality without requiring a "use strict"
directive appear in the source (as this is redundant in compliant
runtimes).

Introduce a new error to restrict this option to code written as
ECMAScript 6 or later.
  • Loading branch information...
jugglinmike committed Apr 13, 2015
1 parent 678da76 commit 290280c29d47baad06d8958ef4a90807f4a67e40
Showing with 154 additions and 4 deletions.
  1. +29 −1 src/jshint.js
  2. +4 −2 src/messages.js
  3. +6 −0 src/options.js
  4. +2 −1 src/state.js
  5. +113 −0 tests/unit/options.js
@@ -202,6 +202,22 @@ var JSHINT = (function() {
combine(predefined, vars.ecmaIdentifiers[6]);
}

if (state.option.module) {
/**
* TODO: Extend this restriction to *all* "environmental" options.
*/
if (!hasParsedCode(funct)) {
error("E055", state.tokens.next, "module");
}

/**
* TODO: Extend this restriction to *all* ES6-specific options.
*/
if (!state.inESNext()) {
warning("W134", state.tokens.next, "module", 6);
}
}

if (state.option.couch) {
combine(predefined, vars.couch);
}
@@ -2954,6 +2970,17 @@ var JSHINT = (function() {
return "(scope)" in token;
}

/**
* Determine if the parser has begun parsing executable code.
*
* @param {Token} funct - The current "functor" token
*
* @returns {boolean}
*/
function hasParsedCode(funct) {
return funct["(global)"] && !funct["(verb)"];
}

function doTemplateLiteral(left) {
// ASSERT: this.type === "(template)"
// jshint validthis: true
@@ -5317,7 +5344,8 @@ var JSHINT = (function() {

if (state.isStrict()) {
if (!state.option.globalstrict) {
if (!(state.option.node || state.option.phantom || state.option.browserify)) {
if (!(state.option.module || state.option.node || state.option.phantom ||
state.option.browserify)) {
warning("W097", state.tokens.prev);
}
}
@@ -69,7 +69,8 @@ var errors = {
E051: "Regular parameters cannot come after default parameters.",
E052: "Unclosed template literal.",
E053: "Export declaration must be in global scope.",
E054: "Class properties must be methods. Expected '(' but instead saw '{a}'."
E054: "Class properties must be methods. Expected '(' but instead saw '{a}'.",
E055: "The '{a}' option cannot be set after any executable code."
};

var warnings = {
@@ -208,7 +209,8 @@ var warnings = {
W130: "Invalid element after rest element.",
W131: "Invalid parameter after rest parameter.",
W132: "`var` declarations are forbidden. Use `let` or `const` instead.",
W133: "Invalid for-{a} loop left-hand-side: {b}."
W133: "Invalid for-{a} loop left-hand-side: {b}.",
W134: "The '{a}' option is only available when linting ECMAScript {b} code."
};

var info = {
@@ -641,6 +641,12 @@ exports.bool = {
*/
mocha : true,

/**
* This option informs JSHint that the input code describes an ECMAScript 6
* module. All module code is interpreted as strict mode code.
*/
module : true,

/**
* This option defines globals available when your code is running as a
* script for the [Windows Script
@@ -10,7 +10,8 @@ var state = {
* @returns {boolean}
*/
isStrict: function() {
return this.directive["use strict"] || this.inClassBody;
return this.directive["use strict"] || this.inClassBody ||
this.option.module;
},

// Assumption: chronologically ES3 < ES5 < ES6/ESNext < Moz
@@ -2397,3 +2397,116 @@ exports.errorI003 = function(test) {

test.done();
};

exports.module = {};
exports.module.behavior = function(test) {
var code = [
"var package = 3;",
"function f() { return this; }"
];

TestRun(test)
.test(code, {});

TestRun(test)
.addError(0, "The 'module' option is only available when linting ECMAScript 6 code.")
.addError(1, "Expected an identifier and instead saw 'package' (a reserved word).")
.addError(2, "Possible strict violation.")
.test(code, { module: true });

TestRun(test)
.addError(1, "Expected an identifier and instead saw 'package' (a reserved word).")
.addError(2, "Possible strict violation.")
.test(code, { module: true, esnext: true });

code = [
"/* jshint module: true */",
"var package = 3;",
"function f() { return this; }"
];

TestRun(test)
.addError(1, "The 'module' option is only available when linting ECMAScript 6 code.")
.addError(2, "Expected an identifier and instead saw 'package' (a reserved word).")
.addError(3, "Possible strict violation.")
.test(code);

code[0] = "/* jshint module: true, esnext: true */";

TestRun(test)
.addError(2, "Expected an identifier and instead saw 'package' (a reserved word).")
.addError(3, "Possible strict violation.")
.test(code);

test.done();
};

exports.module.declarationRestrictions = function( test ) {
TestRun(test)
.addError(2, "The 'module' option cannot be set after any executable code.")
.test([
"(function() {",
" /* jshint module: true */",
"})();"
], { esnext: true });

TestRun(test)
.addError(2, "The 'module' option cannot be set after any executable code.")
.test([
"void 0;",
"/* jshint module: true */"
], { esnext: true });

TestRun(test)
.addError(3, "The 'module' option cannot be set after any executable code.")
.test([
"void 0;",
"// hide",
"/* jshint module: true */"
], { esnext: true });

TestRun(test, "First line (following statement)")
.addError(1, "The 'module' option cannot be set after any executable code.")
.test([
"(function() {})(); /* jshint module: true */"
], { esnext: true });

TestRun(test, "First line (within statement)")
.addError(1, "The 'module' option cannot be set after any executable code.")
.test([
"(function() { /* jshint module: true */",
"})();"
], { esnext: true });

TestRun(test, "First line (before statement)")
.test([
"/* jshint module: true */ (function() {",
"})();"
], { esnext: true });

TestRun(test, "First line (within expression)")
.addError(1, "The 'module' option cannot be set after any executable code.")
.test("Math.abs(/*jshint module: true */4);", { esnext: true });

TestRun(test, "Following single-line comment")
.test([
"// License boilerplate",
"/* jshint module: true */"
], { esnext: true });

TestRun(test, "Following multi-line comment")
.test([
"/**",
" * License boilerplate",
" */",
" /* jshint module: true */"
], { esnext: true });

TestRun(test, "Following shebang")
.test([
"#!/usr/bin/env node",
"/* jshint module: true */"
], { esnext: true });

test.done();
};

0 comments on commit 290280c

Please sign in to comment.