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 Nov 5, 2015
1 parent 0d87919 commit 7ddc0ea
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 39 deletions.
39 changes: 29 additions & 10 deletions src/jshint.js
Original file line number Diff line number Diff line change
Expand Up @@ -1260,7 +1260,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 @@ -1521,10 +1521,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 @@ -2058,7 +2054,6 @@ var JSHINT = (function() {
}
});
reservevar("true");
reservevar("undefined");

assignop("=", "assign", 20);
assignop("+=", "assignadd", 20);
Expand Down Expand Up @@ -2884,6 +2879,9 @@ var JSHINT = (function() {
} else {
if (checkPunctuator(state.tokens.next, "...")) pastRest = true;
ident = identifier(true);
if (pastRest && state.tokens.curr.value === "undefined") {
warning("W139", state.tokens.curr);
}
if (ident) {
paramsIds.push(ident);
currentParams.push([ident, state.tokens.curr]);
Expand All @@ -2905,6 +2903,9 @@ var JSHINT = (function() {
if (!state.inES6()) {
warning("W119", state.tokens.next, "default parameters", "6");
}
if (state.tokens.curr.value === "undefined") {
warning("W139", state.tokens.curr);
}
advance("=");
pastDefault = true;
expression(10);
Expand Down Expand Up @@ -3420,6 +3421,9 @@ var JSHINT = (function() {
if (ident) {
identifiers.push({ id: ident, token: state.tokens.curr });
}
if (is_rest && state.tokens.curr.value === "undefined") {
warning("W139", state.tokens.curr);
}
return is_rest;
}
return false;
Expand Down Expand Up @@ -3469,14 +3473,17 @@ var JSHINT = (function() {
element_after_rest = true;
}
if (checkPunctuator(state.tokens.next, "=")) {
if (state.tokens.curr.value === "undefined") {
warning("W139", state.tokens.curr);
}
if (checkPunctuator(state.tokens.prev, "...")) {
advance("]");
} else {
advance("=");
}
id = state.tokens.prev;
value = expression(10);
if (value && value.type === "undefined") {
if (value && value.value === "undefined") {
warning("W080", id, id.value);
}
}
Expand All @@ -3496,10 +3503,13 @@ var JSHINT = (function() {
while (!checkPunctuator(state.tokens.next, "}")) {
assignmentProperty();
if (checkPunctuator(state.tokens.next, "=")) {
if (state.tokens.curr.value === "undefined") {
warning("W139", state.tokens.curr);
}
advance("=");
id = state.tokens.prev;
value = expression(10);
if (value && value.type === "undefined") {
if (value && value.value === "undefined") {
warning("W080", id, id.value);
}
}
Expand Down Expand Up @@ -3595,14 +3605,18 @@ var JSHINT = (function() {
}

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

advance("=");
if (!prefix && peek(0).id === "=" && state.tokens.next.identifier) {
warning("W120", state.tokens.next, state.tokens.next.value);
}
var id = state.tokens.prev;
// don't accept `in` in expression if prefix is used for ForIn/Of loop.
value = expression(prefix ? 120 : 10);
if (!prefix && value && value.type === "undefined") {
if (!prefix && value && value.value === "undefined") {
warning("W080", id, id.value);
}
if (lone) {
Expand Down Expand Up @@ -3702,6 +3716,10 @@ var JSHINT = (function() {
if (state.tokens.next.id === "=") {
state.nameStack.set(state.tokens.curr);

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

advance("=");
if (peek(0).id === "=" && state.tokens.next.identifier) {
if (!prefix && report &&
Expand All @@ -3713,7 +3731,8 @@ var JSHINT = (function() {
var id = state.tokens.prev;
// don't accept `in` in expression if prefix is used for ForIn/Of loop.
value = expression(prefix ? 120 : 10);
if (value && !prefix && report && !state.funct["(loopage)"] && value.type === "undefined") {
if (value && !prefix && report && !state.funct["(loopage)"] &&
value.value === "undefined") {
warning("W080", id, id.value);
}
if (lone) {
Expand Down
3 changes: 2 additions & 1 deletion src/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,8 @@ var warnings = {
W135: "{a} may not be supported by non-browser environments.",
W136: "'{a}' must be in function scope.",
W137: "Empty destructuring.",
W138: "Regular parameters should not come after default parameters."
W138: "Regular parameters should not come after default parameters.",
W139: "Assigning a value to 'undefined'."
};

var info = {
Expand Down
30 changes: 16 additions & 14 deletions src/scope-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ var scopeManager = function(state, predefined, exported, declared) {
continue;
}

if (isUnstackingFunctionOuter) {
if (isUnstackingFunctionOuter && usedLabelName !== "undefined") {
state.funct["(isCapturing)"] = true;
}

Expand Down Expand Up @@ -346,26 +346,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 (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 (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 @@ -218,24 +218,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 7ddc0ea

Please sign in to comment.