Skip to content

Commit

Permalink
Copied over JSHint Next source from jshint-next repo.
Browse files Browse the repository at this point in the history
All tests passed with minor path modifications!
  • Loading branch information
valueof committed Sep 29, 2012
1 parent f95b29e commit 26559a1
Show file tree
Hide file tree
Showing 30 changed files with 2,394 additions and 0 deletions.
1 change: 1 addition & 0 deletions grunt.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module.exports = function (grunt) {
all: [
"tests/stable/unit/*.js",
"tests/stable/regression/thirdparty.js",
"tests/next/unit/**/*.js"
]
}
});
Expand Down
82 changes: 82 additions & 0 deletions src/next/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"use strict";

var _ = require("underscore");

// Identifiers provided by the ECMAScript standard

exports.reservedVars = {
undefined : false,
arguments : false,
NaN : false
};

exports.ecmaIdentifiers = {
Array : false,
Boolean : false,
Date : false,
decodeURI : false,
decodeURIComponent : false,
encodeURI : false,
encodeURIComponent : false,
Error : false,
"eval" : false,
EvalError : false,
Function : false,
hasOwnProperty : false,
isFinite : false,
isNaN : false,
JSON : false,
Math : false,
Number : false,
Object : false,
parseInt : false,
parseFloat : false,
RangeError : false,
ReferenceError : false,
RegExp : false,
String : false,
SyntaxError : false,
TypeError : false,
URIError : false
};


// Errors and warnings

var errors = {
E001: "Trailing comma causes errors in some versions of IE.",
E002: "'with' statement is prohibited in strict mode.",
E003: "'return' can be used only within functions.",
E004: "'__iterator__' property is only available in JavaScript 1.7.",
E005: "'__proto___' property is deprecated.",
E006: "Missing semicolon.",
E007: "Unexpected debugger statement.",
E008: "'arguments.callee' is prohibited in strict mode.",
E009: "Undefined variable in strict mode."
};

var warnings = {
W001: "Bitwise operator. (mistyped logical operator?)",
W002: "Unsafe comparison.",
W003: "Redefined variable.",
W004: "Undefined variable.",
W005: "Avoid arguments.caller.",
W006: "Avoid arguments.callee.",
W007: "Object arguments outside of a function body.",
W008: "Assignment instead of a conditionial expression. (typo?)",
W009: "Insecure use of {sym} in a regular expression.",
W010: "Empty regular expression class.",
W011: "Unescaped {sym} in a regular expression.",
W012: "Don't extend native objects."
};

exports.errors = {};
exports.warnings = {};

_.each(errors, function (desc, code) {
exports.errors[code] = { code: code, desc: desc };
});

_.each(warnings, function (desc, code) {
exports.warnings[code] = { code: code, desc: desc };
});
194 changes: 194 additions & 0 deletions src/next/jshint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
"use strict";

var _ = require("underscore");
var parser = require("esprima");
var events = require("events");
var peakle = require("peakle");
var utils = require("./utils.js");
var reason = require("./reason.js");
var regexp = require("./regexp.js");
var constants = require("./constants.js");

var MAXERR = 50;

// Converts errors spitted out by Esprima into JSHint errors.

function esprima(linter) {
linter.on("lint:end", function () {
var mapping = {
"Illegal return statement": "E003",
"Strict mode code may not include a with statement": "E002"
};

_.each(linter.tree.errors, function (err) {
var msg = err.message.split(": ")[1];
linter.report.addError(mapping[msg], err.lineNumber);
});
});
}

function Linter(code) {
this.code = code;
this.config = {};
this.tree = {};
this.scopes = new utils.ScopeStack();
this.report = new utils.Report(code);
this.tokens = null;
this.modules = [];
this.emitter = new events.EventEmitter();

this.addModule(esprima);
this.addModule(reason.register);
this.addModule(regexp.register);

// Pre-populate globals array with reserved variables,
// standard ECMAScript globals and user-supplied globals.

this.setGlobals(constants.reservedVars);
this.setGlobals(constants.ecmaIdentifiers);
}

Linter.prototype = {
on: function (names, listener) {
var self = this;

names.split(" ").forEach(function (name) {
self.emitter.on(name, listener);
});
},

trigger: function () {
this.emitter.emit.apply(this.emitter, Array.prototype.slice.call(arguments));
},

addModule: function (func) {
this.modules.push(func);
},

setGlobals: function (globals) {
var scopes = this.scopes;

_.each(globals, function (writeable, name) {
scopes.addGlobalVariable({ name: name, writeable: writeable });
});
},

parse: function () {
var self = this;

self.tree = parser.parse(self.code, {
range: true, // Include range-based location data.
loc: true, // Include column-based location data.
comment: true, // Include a list of all found code comments.
tokens: true, // Include a list of all found tokens.
tolerant: true // Don't break on non-fatal errors.
});

self.tokens = new utils.Tokens(self.tree.tokens);

_.each(self.modules, function (func) {
func(self);
});

function _parseComments(from, to) {
var slice = self.tree.comments.filter(function (comment) {
return comment.range[0] >= from && comment.range[1] <= to;
});

slice.forEach(function (comment) {
comment = utils.parseComment(comment.value);

switch (comment.type) {
case "set":
comment.value.forEach(function (name) {
self.scopes.addSwitch(name);
});
break;
case "ignore":
comment.value.forEach(function (code) {
self.scopes.addIgnore(code);
});
break;
}
});
}

// Walk the tree using recursive* depth-first search and trigger
// appropriate events when needed.
//
// * - and probably horribly inefficient.

function _parse(tree) {
if (tree.type)
self.trigger(tree.type, tree);

if (self.report.length > MAXERR)
return;

_.each(tree, function (val, key) {
if (val === null)
return;

if (!_.isObject(val) && !_.isArray(val))
return;

switch (val.type) {
case "ExpressionStatement":
if (val.expression.type === "Literal" && val.expression.value === "use strict")
self.scopes.current.strict = true;
_parse(val);
break;
case "FunctionDeclaration":
self.scopes.addVariable({ name: val.id.name });
self.scopes.push(val.id.name);

// If this function is not empty, parse its leading comments (if any).
if (val.body.type === "BlockStatement" && val.body.body.length > 0)
_parseComments(val.range[0], val.body.body[0].range[0]);

_parse(val);
self.scopes.pop();
break;
case "FunctionExpression":
if (val.id && val.id.type === "Identifier")
self.scopes.addVariable({ name: val.id.name });
self.scopes.push("(anon)");

// If this function is not empty, parse its leading comments (if any).
if (val.body.type === "BlockStatement" && val.body.body.length > 0)
_parseComments(val.range[0], val.body.body[0].range[0]);

_parse(val);
self.scopes.pop();
break;
case "WithStatement":
self.scopes.runtimeOnly = true;
_parse(val);
self.scopes.runtimeOnly = false;
break;
default:
_parse(val);
}
});
}

self.trigger("lint:start");
_parseComments(0, self.tree.range[0]);
_parse(self.tree.body);
self.trigger("lint:end");
}
};

function JSHINT(args) {
var linter = new Linter(args.code);
linter.setGlobals(args.predefined || {});
linter.parse();

return {
tree: linter.tree,
report: linter.report
};
}

exports.Linter = Linter;
exports.lint = JSHINT;
Loading

0 comments on commit 26559a1

Please sign in to comment.