Skip to content

Commit

Permalink
[[FIX]] Support property assignment when destructure assigning
Browse files Browse the repository at this point in the history
Fixes #2659 and fixes #2660
  • Loading branch information
lukeapage authored and jugglinmike committed Sep 6, 2015
1 parent ceca549 commit b6df1f2
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 9 deletions.
41 changes: 35 additions & 6 deletions src/jshint.js
Original file line number Diff line number Diff line change
Expand Up @@ -1342,7 +1342,7 @@ var JSHINT = (function() {
/**
* Checks the left hand side of an assignment for issues, returns if ok
* @param {token} left - the left hand side of the assignment
* @param {token} assignToken - the token for the assignment
* @param {token=} assignToken - the token for the assignment, used for reporting
* @param {object=} options - optional object
* @param {boolean} options.allowDestructuring - whether to allow destructuting binding
* @returns {boolean} Whether the left hand side is OK
Expand All @@ -1351,6 +1351,8 @@ var JSHINT = (function() {

var allowDestructuring = options && options.allowDestructuring;

assignToken = assignToken || left;

if (state.option.freeze) {
var nativeObject = findNativePrototype(left);
if (nativeObject)
Expand All @@ -1374,7 +1376,9 @@ var JSHINT = (function() {
} else if (left.id === "{" || left.id === "[") {
if (allowDestructuring && state.tokens.curr.left.destructAssign) {
state.tokens.curr.left.destructAssign.forEach(function(t) {
state.funct["(scope)"].block.modify(t.id, t.token);
if (t.id) {
state.funct["(scope)"].block.modify(t.id, t.token);
}
});
} else {
if (left.id === "{" || !left.left) {
Expand Down Expand Up @@ -3360,12 +3364,14 @@ var JSHINT = (function() {
var ids;
var identifiers = [];
var openingParsed = options && options.openingParsed;
var isAssignment = options && options.assignment;
var recursiveOptions = isAssignment ? { assignment: isAssignment } : null;
var firstToken = openingParsed ? state.tokens.curr : state.tokens.next;

var nextInnerDE = function() {
var ident;
if (checkPunctuators(state.tokens.next, ["[", "{"])) {
ids = destructuringPatternRecursive();
ids = destructuringPatternRecursive(recursiveOptions);
for (var id in ids) {
id = ids[id];
identifiers.push({ id: id.id, token: id.token });
Expand All @@ -3378,9 +3384,27 @@ var JSHINT = (function() {
advance(")");
} else {
var is_rest = checkPunctuator(state.tokens.next, "...");
ident = identifier();
if (ident)

if (isAssignment) {
var identifierToken = is_rest ? peek(0) : state.tokens.next;
if (!identifierToken.identifier) {
warning("E030", identifierToken, identifierToken.value);
}
var assignTarget = expression(155);
if (assignTarget) {
checkLeftSideAssign(assignTarget);

// if the target was a simple identifier, add it to the list to return
if (assignTarget.identifier) {
ident = assignTarget.value;
}
}
} else {
ident = identifier();
}
if (ident) {
identifiers.push({ id: ident, token: state.tokens.curr });
}
return is_rest;
}
return false;
Expand All @@ -3399,11 +3423,16 @@ var JSHINT = (function() {
advance(":");
nextInnerDE();
} else {
// this id will either be the property name or the property name and the assigning identifier
id = identifier();
if (checkPunctuator(state.tokens.next, ":")) {
advance(":");
nextInnerDE();
} else {
} else if (id) {
// in this case we are assigning (not declaring), so check assignment
if (isAssignment) {
checkLeftSideAssign(state.tokens.curr);
}
identifiers.push({ id: id, token: state.tokens.curr });
}
}
Expand Down
102 changes: 99 additions & 3 deletions tests/unit/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -1514,7 +1514,9 @@ exports["destructuring const as esnext"] = function (test) {
"const [ f, [ [ [ g ], h ], i ] ] = [ 1, [ [ [ 2 ], 3], 4 ] ];",
"const { foo : bar } = { foo : 1 };",
"const [ j, { foo : foobar } ] = [ 2, { foo : 1 } ];",
"[j] = [1];"
"[j] = [1];",
"[j.a] = [1];",
"[j['a']] = [1];",
];

TestRun(test)
Expand All @@ -1533,6 +1535,7 @@ exports["destructuring const as esnext"] = function (test) {
.addError(7, "'bar' is defined but never used.")
.addError(8, "'foobar' is defined but never used.")
.addError(9, "Attempting to override 'j' which is a constant.")
.addError(11, "['a'] is better written in dot notation.")
.addError(3, "'z' is not defined.")
.test(code, {esnext: true, unused: true, undef: true});

Expand Down Expand Up @@ -1649,7 +1652,10 @@ exports["destructuring const errors"] = function (test) {
"const [ a, b, c ] = [ 1, 2, 3 ];",
"const [ 1 ] = [ a ];",
"const [ k, l; m ] = [ 1, 2, 3 ];",
"const [ n, o, p ] = [ 1, 2; 3 ];"
"const [ n, o, p ] = [ 1, 2; 3 ];",
"const q = {};",
"[q.a] = [1];",
"({a:q.a} = {a:1});"
];

TestRun(test)
Expand Down Expand Up @@ -1688,29 +1694,49 @@ exports["destructuring globals as moz"] = function (test) {
"[ o ] = [ { o : 1 } ];",
"[ a, [ [ [ b ], c ], d ] ] = [ 1, [ [ [ 2 ], 3], 4 ] ];",
"[ a, { foo : b } ] = [ 2, { foo : 1 } ];",
"[ a.b ] = [1];",
"[ { a: a.b } ] = [{a:1}];",
"[ { a: a['b'] } ] = [{a:1}];",
"[a['b']] = [1];",
"[,...a.b] = [1];"
];

TestRun(test)
.addError(4, "'z' is not defined.")
.addError(11, "['b'] is better written in dot notation.")
.addError(12, "['b'] is better written in dot notation.")
.addError(13, "'spread/rest operator' is only available in ES6 (use 'esversion: 6').")
.test(code, {moz: true, unused: true, undef: true});

test.done();
};

exports["destructuring globals as esnext"] = function (test) {
var code = [
"var a, b, c, d, h, w, o;",
"var a, b, c, d, h, i, w, o;",
"[ a, b, c ] = [ 1, 2, 3 ];",
"[ a ] = [ 1 ];",
"[ a ] = [ z ];",
"[ h, w ] = [ 'hello', 'world' ]; ",
"[ o ] = [ { o : 1 } ];",
"[ a, [ [ [ b ], c ], d ] ] = [ 1, [ [ [ 2 ], 3], 4 ] ];",
"[ a, { foo : b } ] = [ 2, { foo : 1 } ];",
"[ a.b ] = [1];",
"[ { a: a.b } ] = [{a:1}];",
"[ { a: a['b'] } ] = [{a:1}];",
"[a['b']] = [1];",
"[,...a.b] = [1];",
"[...i] = [1];",
"[notDefined1] = [];",
"[...notDefined2] = [];",
];

TestRun(test)
.addError(4, "'z' is not defined.")
.addError(11, "['b'] is better written in dot notation.")
.addError(12, "['b'] is better written in dot notation.")
.addError(15, "'notDefined1' is not defined.")
.addError(16, "'notDefined2' is not defined.")
.test(code, {esnext: true, unused: true, undef: true});

test.done();
Expand All @@ -1726,6 +1752,11 @@ exports["destructuring globals as es5"] = function (test) {
"[ o ] = [ { o : 1 } ];",
"[ a, [ [ [ b ], c ], d ] ] = [ 1, [ [ [ 2 ], 3], 4 ] ];",
"[ a, { foo : b } ] = [ 2, { foo : 1 } ];",
"[ a.b ] = [1];",
"[ { a: a.b } ] = [{a:1}];",
"[ { a: a['b'] } ] = [{a:1}];",
"[a['b']] = [1];",
"[,...a.b] = [1];"
];

TestRun(test)
Expand All @@ -1737,6 +1768,14 @@ exports["destructuring globals as es5"] = function (test) {
.addError(6, "'destructuring assignment' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).")
.addError(7, "'destructuring assignment' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).")
.addError(8, "'destructuring assignment' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).")
.addError(9, "'destructuring assignment' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).")
.addError(10, "'destructuring assignment' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).")
.addError(11, "['b'] is better written in dot notation.")
.addError(11, "'destructuring assignment' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).")
.addError(12, "['b'] is better written in dot notation.")
.addError(12, "'destructuring assignment' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).")
.addError(13, "'destructuring assignment' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).")
.addError(13, "'spread/rest operator' is only available in ES6 (use 'esversion: 6').")
.test(code, {unused: true, undef: true}); // es5

test.done();
Expand All @@ -1752,6 +1791,11 @@ exports["destructuring globals as legacy JS"] = function (test) {
"[ o ] = [ { o : 1 } ];",
"[ a, [ [ [ b ], c ], d ] ] = [ 1, [ [ [ 2 ], 3], 4 ] ];",
"[ a, { foo : b } ] = [ 2, { foo : 1 } ];",
"[ a.b ] = [1];",
"[ { a: a.b } ] = [{a:1}];",
"[ { a: a['b'] } ] = [{a:1}];",
"[a['b']] = [1];",
"[,...a.b] = [1];"
];

TestRun(test)
Expand All @@ -1763,6 +1807,14 @@ exports["destructuring globals as legacy JS"] = function (test) {
.addError(6, "'destructuring assignment' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).")
.addError(7, "'destructuring assignment' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).")
.addError(8, "'destructuring assignment' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).")
.addError(9, "'destructuring assignment' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).")
.addError(10, "'destructuring assignment' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).")
.addError(11, "['b'] is better written in dot notation.")
.addError(11, "'destructuring assignment' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).")
.addError(12, "['b'] is better written in dot notation.")
.addError(12, "'destructuring assignment' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).")
.addError(13, "'destructuring assignment' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).")
.addError(13, "'spread/rest operator' is only available in ES6 (use 'esversion: 6').")
.test(code, {es3: true, unused: true, undef: true});

test.done();
Expand All @@ -1776,6 +1828,9 @@ exports["destructuring globals with syntax error"] = function (test) {
"[ a, b; c ] = [ 1, 2, 3 ];",
"[ a, b, c ] = [ 1, 2; 3 ];",
"[ a ] += [ 1 ];",
"[ { a.b } ] = [{a:1}];",
"[ a() ] = [];",
"[ { a: a() } ] = [];"
];

TestRun(test)
Expand All @@ -1788,9 +1843,50 @@ exports["destructuring globals with syntax error"] = function (test) {
.addError(5, "Expected an identifier and instead saw ']'.")
.addError(5, "Expected an assignment or function call and instead saw an expression.")
.addError(6, "Bad assignment.")
.addError(7, "Expected ',' and instead saw '.'.")
.addError(8, "Expected ',' and instead saw '('.")
.addError(8, "Expected an identifier and instead saw ')'.")
.addError(8, "Expected an identifier and instead saw ')'.")
.addError(9, "Expected ',' and instead saw '('.")
.addError(9, "Expected an identifier and instead saw ')'.")
.addError(2, "'z' is not defined.")
.test(code, {esnext: true, unused: true, undef: true});

TestRun(test)
.addError(1, "Expected ',' and instead saw '['.")
.addError(1, "Expected ':' and instead saw ']'.")
.addError(1, "Expected an identifier and instead saw '}'.")
.addError(1, "Expected ',' and instead saw ']'.")
.addError(1, "Expected an identifier and instead saw '{'.")
.addError(1, "Expected ',' and instead saw 'a'.")
.addError(1, "Expected an identifier and instead saw ':'.")
.addError(1, "Expected ',' and instead saw '1'.")
.addError(1, "Expected an assignment or function call and instead saw an expression.")
.addError(1, "Expected an identifier and instead saw '='.")
.test("[ { a['b'] } ] = [{a:1}];", {esnext: true, unused: true, undef: true});

TestRun(test)
.addError(1, "Expected ',' and instead saw '('.")
.addError(1, "Expected an identifier and instead saw ')'.")
.test("[ { a() } ] = [];", {esnext: true, unused: true, undef: true});

TestRun(test)
.addError(1, "Extending prototype of native object: 'Number'.")
.addError(3, "Bad assignment.")
.addError(4, "Bad assignment.")
.addError(6, "Do not assign to the exception parameter.")
.addError(7, "Do not assign to the exception parameter.")
.test([
"[ Number.prototype.toString ] = [function(){}];",
"function a() {",
" [ new.target ] = [];",
" [ arguments.anything ] = [];",
" try{} catch(e) {",
" ({e} = {e});",
" [e] = [];",
" }",
"}"], {esnext: true, freeze: true});

test.done();
};

Expand Down

0 comments on commit b6df1f2

Please sign in to comment.