Implement proposal-optional-chaining (#2572)

* Implement proposal-optional-chaining

Closes #2568

* Refactor optional token printing
master
Lucas Azzola 2017-08-03 17:38:02 +10:00 committed by GitHub
parent fea408ff5c
commit e378f5e9ba
6 changed files with 97 additions and 17 deletions

View File

@ -220,6 +220,14 @@ FastPath.prototype.needsParens = function(options) {
return false;
}
case "MemberExpression": {
return (
parent.type === "MemberExpression" &&
parent.object === node &&
node.optional
);
}
case "SpreadElement":
case "SpreadProperty":
return (

View File

@ -24,7 +24,8 @@ function parse(text, parsers, opts) {
"dynamicImport",
"numericSeparator",
"importMeta",
"optionalCatchBinding"
"optionalCatchBinding",
"optionalChaining"
]
};

View File

@ -422,7 +422,7 @@ function genericPrintNoParens(path, options, print, args) {
return concat([
n.name,
n.optional ? "?" : "",
printOptionalToken(path),
n.typeAnnotation && !isFunctionDeclarationIdentifier ? ": " : "",
path.call(print, "typeAnnotation")
]);
@ -809,6 +809,7 @@ function genericPrintNoParens(path, options, print, args) {
case "NewExpression":
case "CallExpression": {
const isNew = n.type === "NewExpression";
const optional = printOptionalToken(path);
if (
// We want to keep require calls as a unit
(!isNew &&
@ -837,6 +838,7 @@ function genericPrintNoParens(path, options, print, args) {
return concat([
isNew ? "new " : "",
path.call(print, "callee"),
optional,
path.call(print, "typeParameters"),
concat(["(", join(", ", path.map(print, "arguments")), ")"])
]);
@ -851,6 +853,7 @@ function genericPrintNoParens(path, options, print, args) {
return concat([
isNew ? "new " : "",
path.call(print, "callee"),
optional,
printFunctionTypeParameters(path, options, print),
printArgumentsList(path, options, print)
]);
@ -969,7 +972,7 @@ function genericPrintNoParens(path, options, print, args) {
comments.printDanglingComments(path, options),
softline,
rightBrace,
n.optional ? "?" : ""
printOptionalToken(path)
])
);
} else {
@ -985,7 +988,7 @@ function genericPrintNoParens(path, options, print, args) {
: ""
),
concat([options.bracketSpacing ? line : softline, rightBrace]),
n.optional ? "?" : "",
printOptionalToken(path),
n.typeAnnotation ? ": " : "",
path.call(print, "typeAnnotation")
]);
@ -1117,9 +1120,7 @@ function genericPrintNoParens(path, options, print, args) {
);
}
if (n.optional) {
parts.push("?");
}
parts.push(printOptionalToken(path));
if (n.typeAnnotation) {
parts.push(": ", path.call(print, "typeAnnotation"));
@ -2072,7 +2073,7 @@ function genericPrintNoParens(path, options, print, args) {
case "FunctionTypeParam":
return concat([
path.call(print, "name"),
n.optional ? "?" : "",
printOptionalToken(path),
n.name ? ": " : "",
path.call(print, "typeAnnotation")
]);
@ -2221,7 +2222,7 @@ function genericPrintNoParens(path, options, print, args) {
isGetterOrSetter(n) ? n.kind + " " : "",
variance || "",
path.call(print, "key"),
n.optional ? "?" : "",
printOptionalToken(path),
isFunctionNotation(n) ? "" : ": ",
path.call(print, "value")
]);
@ -2387,9 +2388,9 @@ function genericPrintNoParens(path, options, print, args) {
if (n.computed) {
parts.push("]");
}
if (n.optional) {
parts.push("?");
}
parts.push(printOptionalToken(path));
if (n.typeAnnotation) {
parts.push(": ");
parts.push(path.call(print, "typeAnnotation"));
@ -2531,7 +2532,7 @@ function genericPrintNoParens(path, options, print, args) {
n.computed ? "[" : "",
path.call(print, "key"),
n.computed ? "]" : "",
n.optional ? "?" : "",
printOptionalToken(path),
printFunctionParams(
path,
print,
@ -3400,12 +3401,27 @@ function printClass(path, options, print) {
return parts;
}
function printOptionalToken(path) {
const node = path.getValue();
if (!node.optional) {
return "";
}
if (
node.type === "CallExpression" ||
(node.type === "MemberExpression" && node.computed)
) {
return "?.";
}
return "?";
}
function printMemberLookup(path, options, print) {
const property = path.call(print, "property");
const n = path.getValue();
const optional = printOptionalToken(path);
if (!n.computed) {
return concat([".", property]);
return concat([optional, ".", property]);
}
if (
@ -3413,11 +3429,11 @@ function printMemberLookup(path, options, print) {
(n.property.type === "Literal" && typeof n.property.value === "number") ||
n.property.type === "NumericLiteral"
) {
return concat(["[", property, "]"]);
return concat([optional, "[", property, "]"]);
}
return group(
concat(["[", indent(concat([softline, property])), softline, "]"])
concat([optional, "[", indent(concat([softline, property])), softline, "]"])
);
}
@ -3455,6 +3471,7 @@ function printMemberChain(path, options, print) {
path,
() =>
concat([
printOptionalToken(path),
printFunctionTypeParameters(path, options, print),
printArgumentsList(path, options, print)
]),
@ -3485,9 +3502,11 @@ function printMemberChain(path, options, print) {
// Note: the comments of the root node have already been printed, so we
// need to extract this first call without printing them as they would
// if handled inside of the recursive call.
const node = path.getValue();
printedNodes.unshift({
node: path.getValue(),
node,
printed: concat([
printOptionalToken(path),
printFunctionTypeParameters(path, options, print),
printArgumentsList(path, options, print)
])

View File

@ -0,0 +1,36 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`chaining.js 1`] = `
var street = user.address?.street
var fooValue = myForm.querySelector('input[name=foo]')?.value
obj?.prop;
obj?.[expr];
func?.(...args);
a?.();
a?.[++x];
a?.b.c(++x).d;
a?.b[3].c?.(x).d;
(a?.b).c;
delete a?.b;
a?.b[3].c?.(x).d.e?.f[3].g?.(y).h;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
var street = user.address?.street;
var fooValue = myForm.querySelector("input[name=foo]")?.value;
obj?.prop;
obj?.[expr];
func?.(...args);
a?.();
a?.[++x];
a?.b.c(++x).d;
a?.b[3].c?.(x).d;
(a?.b).c;
delete a?.b;
a?.b[3].c?.(x).d.e?.f[3].g?.(y).h;
`;

View File

@ -0,0 +1,15 @@
var street = user.address?.street
var fooValue = myForm.querySelector('input[name=foo]')?.value
obj?.prop;
obj?.[expr];
func?.(...args);
a?.();
a?.[++x];
a?.b.c(++x).d;
a?.b[3].c?.(x).d;
(a?.b).c;
delete a?.b;
a?.b[3].c?.(x).d.e?.f[3].g?.(y).h;

View File

@ -0,0 +1 @@
run_spec(__dirname, { parser: "babylon" });