Skip to content

Commit

Permalink
[[FIX]] Allow definition (but not assignment) of variables named `und…
Browse files Browse the repository at this point in the history
…efined`

Fix jshint#1604, fix jshint#732
  • Loading branch information
nicolo-ribaudo committed Jul 4, 2015
1 parent ab73e01 commit eeee386
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 39 deletions.
41 changes: 30 additions & 11 deletions src/jshint.js
Original file line number Diff line number Diff line change
Expand Up @@ -1177,7 +1177,7 @@ var JSHINT = (function() {
(node.type === "null" && !state.option.eqnull) ||
node.type === "true" ||
node.type === "false" ||
node.type === "undefined");
(node.value === "undefined" && node.identifier));
}

var typeofValues = {};
Expand Down Expand Up @@ -1422,10 +1422,6 @@ var JSHINT = (function() {
}
}

if (fnparam && val === "undefined") {
return val;
}

warning("W024", state.tokens.curr, state.tokens.curr.id);
return val;
}
Expand Down Expand Up @@ -1947,7 +1943,6 @@ var JSHINT = (function() {
}
});
reservevar("true");
reservevar("undefined");

assignop("=", "assign", 20);
assignop("+=", "assignadd", 20);
Expand Down Expand Up @@ -2740,6 +2735,9 @@ var JSHINT = (function() {
} else {
if (checkPunctuators(state.tokens.next, ["..."])) pastRest = true;
ident = identifier(true);
if (pastRest && state.tokens.curr.value === "undefined") {
warning("W136", state.tokens.curr);
}
if (ident) {
params.push({ id: ident, token: state.tokens.curr });
} else {
Expand All @@ -2758,6 +2756,9 @@ var JSHINT = (function() {
if (!state.inESNext()) {
warning("W119", state.tokens.next, "default parameters");
}
if (state.tokens.curr.value === "undefined") {
warning("W136", state.tokens.curr);
}
advance("=");
pastDefault = true;
expression(10);
Expand Down Expand Up @@ -3198,8 +3199,12 @@ var JSHINT = (function() {
} else {
var is_rest = checkPunctuators(state.tokens.next, ["..."]);
ident = identifier();
if (ident)
if (ident) {
identifiers.push({ id: ident, token: state.tokens.curr });
}
if (is_rest && state.tokens.curr.value === "undefined") {
warning("W136", state.tokens.curr);
}
return is_rest;
}
return false;
Expand Down Expand Up @@ -3236,12 +3241,15 @@ var JSHINT = (function() {
}
while (!checkPunctuators(state.tokens.next, ["]"])) {
if (checkPunctuators(state.tokens.next, ["="])) {
if (state.tokens.curr.value === "undefined") {
warning("W136", state.tokens.curr);
}
if (checkPunctuators(state.tokens.prev, ["..."])) {
advance("]");
} else {
advance("=");
}
if (state.tokens.next.id === "undefined") {
if (state.tokens.next.value === "undefined") {
warning("W080", state.tokens.prev, state.tokens.prev.value);
}
expression(10);
Expand All @@ -3265,8 +3273,11 @@ var JSHINT = (function() {
assignmentProperty();
while (!checkPunctuators(state.tokens.next, ["}"])) {
if (checkPunctuators(state.tokens.next, ["="])) {
if (state.tokens.curr.value === "undefined") {
warning("W136", state.tokens.curr);
}
advance("=");
if (state.tokens.next.id === "undefined") {
if (state.tokens.next.value === "undefined") {
warning("W080", state.tokens.prev, state.tokens.prev.value);
}
expression(10);
Expand Down Expand Up @@ -3345,8 +3356,12 @@ var JSHINT = (function() {
// absorb the assignment before processing the label so that use of the label
// can be detected as before declaration
if (state.tokens.next.id === "=") {
if (state.tokens.curr.value === "undefined") {
warning("W136", state.tokens.curr);
}

advance("=");
if (!prefix && state.tokens.next.id === "undefined") {
if (!prefix && state.tokens.next.value === "undefined") {
warning("W080", state.tokens.prev, state.tokens.prev.value);
}
if (!prefix && peek(0).id === "=" && state.tokens.next.identifier) {
Expand Down Expand Up @@ -3440,8 +3455,12 @@ var JSHINT = (function() {
if (state.tokens.next.id === "=") {
state.nameStack.set(state.tokens.curr);

if (state.tokens.curr.value === "undefined") {
warning("W136", state.tokens.curr);
}

advance("=");
if (!prefix && report && state.tokens.next.id === "undefined") {
if (!prefix && report && state.tokens.next.value === "undefined") {
warning("W080", state.tokens.prev, state.tokens.prev.value);
}
if (peek(0).id === "=" && state.tokens.next.identifier) {
Expand Down
3 changes: 2 additions & 1 deletion src/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ var warnings = {
W132: "`var` declarations are forbidden. Use `let` or `const` instead.",
W133: "Invalid for-{a} loop left-hand-side: {b}.",
W134: "The '{a}' option is only available when linting ECMAScript {b} code.",
W135: "{a} may not be supported by non-browser environments."
W135: "{a} may not be supported by non-browser environments.",
W136: "Assigning a value to 'undefined'."
};

var info = {
Expand Down
28 changes: 15 additions & 13 deletions src/scope-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -330,26 +330,28 @@ var scopeManager = function(state, predefined, exported, declared) {
warning("W020", usage["(reassigned)"][j]);
}
}
}
else {
} else {
// label usage is not predefined and we have not found a declaration
// so report as undeclared
if (usage["(tokens)"]) {
for (j = 0; j < usage["(tokens)"].length; j++) {
var undefinedToken = usage["(tokens)"][j];
// if its not a forgiven undefined (e.g. typof x)
if (!undefinedToken.forgiveUndef) {
// if undef is on and undef was on when the token was defined
if (state.option.undef && !undefinedToken.ignoreUndef) {
warning("W117", undefinedToken, usedLabelName);
}
if (_.has(impliedGlobals, usedLabelName)) {
impliedGlobals[usedLabelName].line.push(undefinedToken.line);
} else {
impliedGlobals[usedLabelName] = {
name: usedLabelName,
line: [undefinedToken.line]
};
// if it's not the 'undefined' identifier
if (undefinedToken.value !== "undefined") {
// if undef is on and undef was on when the token was defined
if (state.option.undef && !undefinedToken.ignoreUndef) {
warning("W117", undefinedToken, usedLabelName);
}
if (_.has(impliedGlobals, usedLabelName)) {
impliedGlobals[usedLabelName].line.push(undefinedToken.line);
} else {
impliedGlobals[usedLabelName] = {
name: usedLabelName,
line: [undefinedToken.line]
};
}
}
}
}
Expand Down
81 changes: 67 additions & 14 deletions tests/unit/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,24 +183,77 @@ exports.testNewNonNativeObject = function (test) {
test.done();
};

exports["undefined as variable name"] = function (test) {

/**
* Test that JSHint allows `undefined` to be a function parameter.
* It is a common pattern to protect against the case when somebody
* overwrites undefined. It also helps with minification.
*
* More info: https://gist.github.com/315916
*/
exports.testUndefinedAsParam = function (test) {
var code = '(function (undefined) {}());';
var code1 = 'var undefined = 1;';
TestRun(test)
.test("var undefined;");

TestRun(test).test(code);
TestRun(test)
.test("let undefined;", { esnext: true });

TestRun(test)
.addError(1, "const 'undefined' is initialized to 'undefined'.")
.test("const undefined;", { esnext: true });

TestRun(test)
.test("var { undefined } = {};", { esnext: true });

TestRun(test)
.test("var [ undefined ] = [];", { esnext: true });

var functions = [
"function a(undefined) {}",
"function b({ undefined }) {}",
"function c([ undefined ]) {}"
];

TestRun(test)
.test(functions, { esnext: true });

test.done();
};

exports["can't assign a value to undefined"] = function (test) {

TestRun(test)
.addError(1, "Assigning a value to 'undefined'.")
.test("var undefined = 2;");

TestRun(test)
.addError(1, "Assigning a value to 'undefined'.")
.test("let undefined = 2;", { esnext: true });

TestRun(test)
.addError(1, "Assigning a value to 'undefined'.")
.test("const undefined = 2;", { esnext: true });

TestRun(test)
.addError(1, "Assigning a value to 'undefined'.")
.test("var { undefined = 2 } = {};", { esnext: true });

TestRun(test)
.addError(1, "Assigning a value to 'undefined'.")
.test("var [ undefined = 2 ] = [];", { esnext: true });

TestRun(test) // An array is being assigned to undefined
.addError(1, "Assigning a value to 'undefined'.")
.test("var [ ...undefined ] = [];", { esnext: true });

var functions = [
"function a(undefined = 2) {}",
"function b(...undefined) {}",
"function c({ undefined = 2 }) {}",
"function d([ undefined = 2 ]) {}",
"function e([ ...undefined ]) {}"
];

// But it must never tolerate reassigning of undefined
TestRun(test)
.addError(1, "Expected an identifier and instead saw 'undefined' (a reserved word).")
.test(code1);
.addError(1, "Assigning a value to 'undefined'.")
.addError(2, "Assigning a value to 'undefined'.")
.addError(3, "Assigning a value to 'undefined'.")
.addError(4, "Assigning a value to 'undefined'.")
.addError(5, "Assigning a value to 'undefined'.")
.test(functions, { esnext: true });

test.done();
};
Expand Down

0 comments on commit eeee386

Please sign in to comment.