Skip to content

Commit

Permalink
Add new module: regexp.
Browse files Browse the repository at this point in the history
So far it is pretty primitive--I reimplemented option 'regexp' but
hopefully in future it will be able to catch more subtle regular
expression oddities.

Also Esprima blows up on invalid regular expression so we don't need
to parse/verify it ourselves. However, this means that we really have
to make Esprima more tolerant.
  • Loading branch information
valueof committed Jul 19, 2012
1 parent 74c1bb8 commit 6746926
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 7 deletions.
4 changes: 3 additions & 1 deletion src/constants.js
Expand Up @@ -63,7 +63,9 @@ var warnings = {
W005: "Avoid arguments.caller.",
W006: "Avoid arguments.callee.",
W007: "Object arguments outside of a function body.",
W008: "Assignment instead of a conditionial expression. (typo?)"
W008: "Assignment instead of a conditionial expression. (typo?)",
W009: "Insecure use of {sym} in a regular expression.",
W010: "Empty regular expression class."
};

exports.errors = {};
Expand Down
2 changes: 2 additions & 0 deletions src/jshint.js
Expand Up @@ -4,6 +4,7 @@ var _ = require("underscore");
var parser = require("esprima");
var utils = require("./utils.js");
var reason = require("./reason.js");
var regexp = require("./regexp.js");
var constants = require("./constants.js");
var Events = require("./events.js").Events;

Expand Down Expand Up @@ -36,6 +37,7 @@ function Linter(code) {

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.
Expand Down
74 changes: 74 additions & 0 deletions src/regexp.js
@@ -0,0 +1,74 @@
"use strict";

var _ = require("underscore");
var utils = require("./utils.js");
var Events = require("./events.js").Events;

function Tokens(exp) {
this.exp = exp;
this.pos = 0;
}

Tokens.prototype = {
get current() {
return this.peak(0);
},

peak: function (offset) {
var pos = this.pos + (offset || 1);
var chr = this.exp.charAt(pos);

return chr === "" ? null : chr;
},

next: function () {
var chr = this.peak();

if (chr !== null)
this.pos += 1;

return chr;
}
};

_.extend(Tokens.prototype, Events);

exports.register = function (linter) {
var report = linter.report;

linter.on("Literal", function (literal) {
var value = (literal.value || "").toString();
var range = literal.range;
var tokens;

value = value.match(/^\/(.+)\/[igm]?$/);
if (value === null)
return;

tokens = new Tokens(value[1]);

tokens.on("[", function () {
tokens.next();

if (tokens.current === "^") {
report.addWarning("W009", literal.range, { sym: tokens.current });
tokens.next();
}

if (tokens.current === "]") {
report.addWarning("W010", literal.range);
}
});

tokens.on(".", function () {
if (tokens.peak(-1) !== "\\") {
report.addWarning("W009", literal.range, { sym: tokens.current });
}
});

while (tokens.current) {
tokens.trigger(tokens.current);
tokens.next();
}
});
};
27 changes: 21 additions & 6 deletions src/utils.js
Expand Up @@ -17,6 +17,17 @@ function safe(name) {
return name;
}

function interpolate(string, data) {
return string.replace(/\{([^{}]*)\}/g, function (match, key) {
var repl = data[key];

if (typeof repl === 'string' || typeof repl === 'number')
return repl;

return match;
});
}

// ScopeStack stores all the environments we encounter while
// traversing syntax trees. It also keeps track of all
// variables defined and/or used in these environments.
Expand Down Expand Up @@ -184,29 +195,33 @@ Report.prototype = {
this.length += 1;
},

addWarning: function (label, loc) {
addWarning: function (label, loc, data) {
var line = _.isArray(loc) ? this.lineFromRange(loc) : loc;
var warn = warnings[label];

if (!warnings[label])
if (!warn)
throw new Error("Warning " + label + "is not defined.");

warn.desc = interpolate(warn.desc, data);
this.addMessage({
type: this.WARNING,
line: line,
data: warnings[label]
data: warn
});
},

addError: function (label, loc) {
addError: function (label, loc, data) {
var line = _.isArray(loc) ? this.lineFromRange(loc) : loc;
var err = errors[label];

if (!errors[label])
if (!err)
throw new Error("Error " + label + " is not defined.");

err.desc = interpolate(err.desc, data);
this.addMessage({
type: this.ERROR,
line: line,
data: errors[label]
data: err
});
}
};
Expand Down
32 changes: 32 additions & 0 deletions test/unit/regexp.js
@@ -0,0 +1,32 @@
"use strict";

var _ = require("underscore");
var linter = require("../../src/jshint.js");
var helpers = require("../lib/helpers.js");
var runner = helpers.createRunner(__dirname, __filename);

exports.testSuccess = function (test) {
runner(test).test("/hello/g");
runner(test).test("/hello\s(world)/g");
test.done();
};

exports.testUnsafeSymbols = function (test) {
runner(test)
.addError(1, "W009")
.test("/h[^...]/");

runner(test)
.addError(1, "W009")
.test("/h.ey/");

test.done();
};

exports.testEmptyClass = function (test) {
runner(test)
.addError(1, "W010")
.test("/h[]/");

test.done();
};

0 comments on commit 6746926

Please sign in to comment.