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; return false;
} }
case "MemberExpression": {
return (
parent.type === "MemberExpression" &&
parent.object === node &&
node.optional
);
}
case "SpreadElement": case "SpreadElement":
case "SpreadProperty": case "SpreadProperty":
return ( return (

View File

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

View File

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