blob: 0846365e09080535711a7b8b00640a5f81930af4 [file] [log] [blame]
"use strict";
var _ = require("underscore");
var vars = require("../shared/vars.js");
exports.register = function (linter) {
var report = linter.report;
var tokens = linter.tokens;
var scopes = linter.scopes;
// Check for trailing commas in arrays and objects.
linter.on("ArrayExpression ObjectExpression", function (expr) {
var token = tokens.move(tokens.find(expr.range[1] - 3));
if (token.isPunctuator(","))
report.addError("E001", token.range);
});
// Check for properties named __iterator__. This is a special property
// available only in browsers with JavaScript 1.7 implementation.
linter.on("MemberExpression", function (expr) {
var prop = expr.property;
if (prop.type === "Identifier" && prop.name === "__iterator__")
report.addError("E004", prop.range);
});
// Check for properties named __proto__. This special property was
// deprecated long time ago.
linter.on("MemberExpression", function (expr) {
var prop = expr.property;
if (prop.type === "Identifier" && prop.name === "__proto__")
report.addError("E005", prop.range);
});
// Check for missing semicolons but only when they have a potential
// of breaking things due to automatic semicolon insertion.
linter.on("ExpressionStatement", function (expr) {
var type = expr.expression.type;
if (type !== "CallExpression" && type !== "MemberExpression")
return;
var slice = tokens.getRange(expr.range);
var token = slice.move(1);
var prev, curLine, prevLine;
while (token !== null) {
if (token.isPunctuator(["(", "["])) {
prev = slice.peak(-1);
curLine = report.lineFromRange(token.range);
prevLine = report.lineFromRange(prev.range);
if (curLine !== prevLine && !prev.isPunctuator(";")) {
report.addError("E006", prev.range);
}
}
token = slice.next();
}
});
linter.on("BinaryExpression", function (expr) {
var op = expr.operator;
if (op !== "+" && op !== "*" && op !== "/")
return;
if (expr.left.loc.end.line < expr.right.loc.start.line)
report.addError("E006", expr.range);
});
// Catch cases where you put a new line after a `return` statement
// by mistake.
linter.on("ReturnStatement", function (expr) {
var cur = tokens.move(tokens.find(expr.range[0]));
var next = tokens.peak();
if (report.lineFromRange(next.range) === report.lineFromRange(cur.range))
return;
if (next && next.isPunctuator(";"))
return;
if (next && next.isKeyword("var"))
return;
if (next && next.isKeyword("case"))
return;
report.addError("E006", cur.range);
});
// Check for debugger statements. You really don't want them in your
// production code.
linter.on("DebuggerStatement", function (expr) {
report.addError("E007", expr.range);
});
// Disallow bitwise operators: they are slow in JavaScript and
// more often than not are simply typoed logical operators.
linter.on("BinaryExpression UnaryExpression", function (expr) {
var ops = {
"|" : true,
"&" : true,
"^" : true,
"~" : true,
"<<" : true,
">>" : true,
">>>": true
};
if (expr.operator && ops[expr.operator] === true)
report.addWarning("W001", expr.range);
});
// Complain about comparisons that can blow up because of type
// coercion.
linter.on("BinaryExpression", function (expr) {
function isUnsafe(el) {
if (el.type === "Identifier" && el.name === "undefined")
return true;
if (el.type !== "Literal")
return false;
return _.any([
el.value === 0,
el.value === null,
el.value === "",
el.value === false,
el.value === true
]);
}
if (expr.operator !== "==" && expr.operator !== "!=")
return;
if (isUnsafe(expr.left))
report.addWarning("W002", expr.left.range);
if (isUnsafe(expr.right))
report.addWarning("W002", expr.right.range);
});
// Complain about variables defined twice.
function isRedefined(name, range) {
if (scopes.isDefined(name))
report.addWarning("W003", range);
}
linter.on("VariableDeclarator", function (expr) {
isRedefined(expr.id.name, expr.id.range);
scopes.addVariable({ name: expr.id.name });
});
linter.on("FunctionExpression FunctionDeclaration", function (expr) {
_.each(expr.params, function (param) {
isRedefined(param.name, param.range);
scopes.addVariable({ name: param.name });
});
});
// Check if identifier is a free variable and record its
// use. Later in the code we'll use that to spot undefined
// variables.
linter.on("Identifier", function (ident) {
var index = tokens.find(ident.range[0]);
var token, prev, next;
if (index > 0) {
token = tokens.move(index);
prev = tokens.peak(-1);
next = tokens.peak(1) || { isPunctuator: function () { return false; } };
// This identifier is a property key, not a free variable.
if (next.isPunctuator(":") && !prev.isPunctuator("?"))
return;
// This identifier is a property itself, not a free variable.
if (prev.isPunctuator("."))
return;
// Operators typeof and delete do not raise runtime errors
// even if the base object of a reference is null, so we don't
// need to display warnings in these cases.
if (prev.isKeyword("typeof") || prev.isKeyword("delete")) {
// Unless you're trying to subscript a null references. That
// will throw a runtime error.
if (!next.isPunctuator(".") && !next.isPunctuator("["))
return;
}
}
scopes.addUse(ident.name, ident.range);
});
// Look for arguments.callee and arguments.caller usage and warn about
// them. In strict mode, instead of warning about arguments.callee, return
// an error. This also supports [] notation.
linter.on("Identifier Literal", function (expr) {
if (scopes.current.name === "(global)") {
if (expr.type === "Identifier" && expr.name === "arguments")
report.addWarning("W007", expr.range);
return;
}
var name = expr.type === "Identifier" ? expr.name : expr.value;
var punc = expr.type === "Identifier" ? "." : "[";
var range = expr.range;
if (name !== "callee" && name !== "caller")
return;
var index = tokens.find(range[0]);
if (index < 1)
return;
tokens.move(index);
if (tokens.peak(-1).isPunctuator(punc) && tokens.peak(-2).isIdentifier("arguments")) {
switch (name) {
case "caller":
report.addWarning("W005", range);
break;
case "callee":
if (scopes.isStrictMode())
report.addError("E008", range);
else
report.addWarning("W006", range);
}
}
});
// Warn when assignments are used instead of conditionals.
linter.on("ForStatement IfStatement WhileStatement DoWhileStatement", function (expr) {
if (expr.test && expr.test.type === "AssignmentExpression")
report.addWarning("W008", expr.range);
});
// Warn when extending prototypes of built-in objects
linter.on("AssignmentExpression", function (expr) {
var left = expr.left;
var obj = left.object;
function isNativeProto(expr) {
if (!expr.property || !expr.object)
return false;
return expr.object.name in
vars.ecmaIdentifiers && expr.property.name === "prototype";
}
if (left.type !== "MemberExpression")
return;
// Check for Object.prototype.prop = ""
// Check for Native.prototype = {}
if (isNativeProto(left) || isNativeProto(obj))
report.addWarning("W012", expr.range);
});
// Go over all stacks and find all variables that were used but
// never defined.
//
// This is not very efficient--for starters we can mark visited
// scopes and not visit them again.
linter.on("lint:end", function () {
_.each(scopes.stack, function (env) {
_.each(env.uses, function (ranges, name) {
if (scopes.isDefined(name, env))
return;
_.each(ranges, function (range) {
if (scopes.isStrictMode(env))
return void linter.report.addError("E009", range);
linter.report.addWarning("W004", range);
});
});
});
});
};