Detect chained call expressions and format them specifically

master
James Long 2017-01-05 15:26:45 -05:00
parent 9c72530589
commit d5ab6ab921
5 changed files with 157 additions and 69 deletions

View File

@ -317,7 +317,7 @@ function printTrailingComment(commentPath, print) {
// }
// }
parts.push(print(commentPath));
parts.push(print(commentPath), hardline);
return concat(parts);
}

View File

@ -294,19 +294,9 @@ function genericPrintNoParens(path, options, print) {
" = ",
path.call(print, "right")
]);
case "MemberExpression":
parts.push(path.call(print, "object"));
var property = path.call(print, "property");
if (n.computed) {
parts.push("[", property, "]");
} else {
parts.push(".", property);
}
return concat(parts);
case "MemberExpression": {
return concat([path.call(print, "object"), printMemberLookup(path, print)]);
}
case "MetaProperty":
return concat([
path.call(print, "meta"),
@ -620,11 +610,72 @@ function genericPrintNoParens(path, options, print) {
parts.push(";");
return concat(parts);
case "CallExpression":
case "CallExpression": {
const parent = path.getParentNode();
// We detect calls on member expressions specially to format a
// comman pattern better. The pattern we are looking for is this:
//
// arr
// .map(x => x + 1)
// .filter(x => x > 10)
// .some(x => x % 2)
//
// where there is a "chain" of function calls on the result of
// previous expressions. We want to format them like above so they
// are consistently aligned.
//
// The way we do is by eagerly traversing the AST tree and
// re-shape it into a list of calls on member expressions. This
// lets us implement a heuristic easily for when we want to format
// it: if there are more than 1 calls on a member expression that
// pass in a function, we treat it like the above.
if(n.callee.type === "MemberExpression") {
const nodes = [];
let curr = n;
// Traverse down and gather up all of the calls on member
// expressions. This flattens it out into a list that we can
// easily analyze.
while(curr.type === "CallExpression" && curr.callee.type === "MemberExpression") {
nodes.push({ property: curr.callee.property, call: curr });
curr = curr.callee.object;
}
// There are two kinds of formats we want to specialize: first,
// if there are multiple calls on lookups we want to group them
// together so they will all break at the same time. Second,
// it's a chain if there 2 or more calls pass in functions and
// we want to forcibly break all of the lookups onto new lines
// and indent them.
const hasMultipleLookups = nodes.length > 1;
const isChain = nodes.filter(n => {
return n.call.arguments.length > 0 &&
(n.call.arguments[0].type === "FunctionExpression" ||
n.call.arguments[0].type === "ArrowFunctionExpression");
}).length > 1;
if(hasMultipleLookups) {
return group(concat([
print(FastPath.from(curr)),
concat(nodes.reverse().map(node => {
const printed = concat([
isChain ? hardline : softline,
".",
print(FastPath.from(node.property)),
printArgumentsList(FastPath.from(node.call), options, print)
]);
return isChain ? indent(options.tabWidth, printed) : printed;
}))
]));
}
}
return concat([
path.call(print, "callee"),
printArgumentsList(path, options, print)
]);
}
case "ObjectExpression":
case "ObjectPattern":
case "ObjectTypeAnnotation":
@ -1914,6 +1965,16 @@ function printClass(path, print) {
return parts;
}
function printMemberLookup(path, print) {
const property = path.call(print, "property");
const n = path.getValue();
return concat(
n.computed ?
["[", property, "]"] :
[".", property]
);
}
function adjustClause(clause, options, forceSpace) {
if (isCurlyBracket(clause) || forceSpace) {
return concat([ " ", clause ]);

View File

@ -67,8 +67,9 @@ class MyWriteStream extends stream.Writable {}
class MyDuplex extends stream.Duplex {}
class MyTransform extends stream.Duplex {}
new MyReadStream().pipe(
new MyDuplex()
).pipe(new MyTransform()).pipe(new MyWriteStream());
new MyReadStream()
.pipe(new MyDuplex())
.pipe(new MyTransform())
.pipe(new MyWriteStream());
"
`;

View File

@ -529,59 +529,83 @@ Promise.reject(Promise.resolve(0)).then(function(num) {
var b: number = num;
});
Promise.resolve(0).then(function(num) {
return \"asdf\";
}).then(function(str) {
var a: string = str;
var b: number = str;
});
Promise
.resolve(0)
.then(function(num) {
return \"asdf\";
})
.then(function(str) {
var a: string = str;
var b: number = str;
});
Promise.resolve(0).then(function(num) {
return Promise.resolve(\"asdf\");
}).then(function(str) {
var a: string = str;
var b: number = str;
});
Promise
.resolve(0)
.then(function(num) {
return Promise.resolve(\"asdf\");
})
.then(function(str) {
var a: string = str;
var b: number = str;
});
Promise.resolve(0).then(function(num) {
return Promise.resolve(Promise.resolve(\"asdf\"));
}).then(function(str) {
var a: string = str;
var b: number = str;
});
Promise
.resolve(0)
.then(function(num) {
return Promise.resolve(Promise.resolve(\"asdf\"));
})
.then(function(str) {
var a: string = str;
var b: number = str;
});
Promise.resolve(0).then(function(num) {
throw \"str\";
}).catch(function(str) {
var a: string = str;
var b: number = str;
});
Promise
.resolve(0)
.then(function(num) {
throw \"str\";
})
.catch(function(str) {
var a: string = str;
var b: number = str;
});
Promise.reject(0).catch(function(num) {
return \"asdf\";
}).then(function(str) {
var a: string = str;
var b: number = str;
});
Promise
.reject(0)
.catch(function(num) {
return \"asdf\";
})
.then(function(str) {
var a: string = str;
var b: number = str;
});
Promise.reject(0).catch(function(num) {
return Promise.resolve(\"asdf\");
}).then(function(str) {
var a: string = str;
var b: number = str;
});
Promise
.reject(0)
.catch(function(num) {
return Promise.resolve(\"asdf\");
})
.then(function(str) {
var a: string = str;
var b: number = str;
});
Promise.reject(0).catch(function(num) {
return Promise.resolve(Promise.resolve(\"asdf\"));
}).then(function(str) {
var a: string = str;
var b: number = str;
});
Promise
.reject(0)
.catch(function(num) {
return Promise.resolve(Promise.resolve(\"asdf\"));
})
.then(function(str) {
var a: string = str;
var b: number = str;
});
Promise.resolve(0).catch(function(err) {}).then(function(num) {
var a: number = num;
var b: string = num;
});
Promise
.resolve(0)
.catch(function(err) {})
.then(function(num) {
var a: number = num;
var b: string = num;
});
"
`;

View File

@ -86,11 +86,13 @@ exports[`test issue-198.js 1`] = `
// This should fail because str is string, not number
var p = new Promise(function(resolve, reject) {
resolve(5);
}).then(function(num) {
return num.toFixed();
}).then(function(str) {
return str.toFixed();
});
})
.then(function(num) {
return num.toFixed();
})
.then(function(str) {
return str.toFixed();
});
"
`;