forked from jshint/jshint
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Copied over JSHint Next source from jshint-next repo.
All tests passed with minor path modifications!
- Loading branch information
Showing
30 changed files
with
2,394 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.