Skip to content

Commit c95daab

Browse files
committed
Add support for Optional Chaining
1 parent fb453f8 commit c95daab

31 files changed

+1737
-816
lines changed

src/compiler/binder.ts

+12
Original file line numberDiff line numberDiff line change
@@ -3160,6 +3160,10 @@ namespace ts {
31603160
const callee = skipOuterExpressions(node.expression);
31613161
const expression = node.expression;
31623162

3163+
if (node.flags & NodeFlags.OptionalChain) {
3164+
transformFlags |= TransformFlags.ContainsESNext;
3165+
}
3166+
31633167
if (node.typeArguments) {
31643168
transformFlags |= TransformFlags.AssertTypeScript;
31653169
}
@@ -3555,6 +3559,10 @@ namespace ts {
35553559
function computePropertyAccess(node: PropertyAccessExpression, subtreeFlags: TransformFlags) {
35563560
let transformFlags = subtreeFlags;
35573561

3562+
if (node.flags & NodeFlags.OptionalChain) {
3563+
transformFlags |= TransformFlags.ContainsESNext;
3564+
}
3565+
35583566
// If a PropertyAccessExpression starts with a super keyword, then it is
35593567
// ES6 syntax, and requires a lexical `this` binding.
35603568
if (node.expression.kind === SyntaxKind.SuperKeyword) {
@@ -3570,6 +3578,10 @@ namespace ts {
35703578
function computeElementAccess(node: ElementAccessExpression, subtreeFlags: TransformFlags) {
35713579
let transformFlags = subtreeFlags;
35723580

3581+
if (node.flags & NodeFlags.OptionalChain) {
3582+
transformFlags |= TransformFlags.ContainsESNext;
3583+
}
3584+
35733585
// If an ElementAccessExpression starts with a super keyword, then it is
35743586
// ES6 syntax, and requires a lexical `this` binding.
35753587
if (node.expression.kind === SyntaxKind.SuperKeyword) {

src/compiler/checker.ts

+92-32
Large diffs are not rendered by default.

src/compiler/debug.ts

+8
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,14 @@ namespace ts {
184184
assertNode)
185185
: noop;
186186

187+
export const assertNotNode = shouldAssert(AssertionLevel.Normal)
188+
? (node: Node | undefined, test: ((node: Node | undefined) => boolean) | undefined, message?: string): void => assert(
189+
test === undefined || !test(node),
190+
message || "Unexpected node.",
191+
() => `Node ${formatSyntaxKind(node!.kind)} should not have passed test '${getFunctionName(test!)}'.`,
192+
assertNode)
193+
: noop;
194+
187195
export const assertOptionalNode = shouldAssert(AssertionLevel.Normal)
188196
? (node: Node, test: (node: Node) => boolean, message?: string): void => assert(
189197
test === undefined || node === undefined || test(node),

src/compiler/emitter.ts

+69-41
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,8 @@ namespace ts {
821821
let detachedCommentsInfo: { nodePos: number, detachedCommentEndPos: number}[] | undefined;
822822
let hasWrittenComment = false;
823823
let commentsDisabled = !!printerOptions.removeComments;
824+
let lastNode: Node | undefined;
825+
let lastSubstitution: Node | undefined;
824826
const { enter: enterComment, exit: exitComment } = performance.createTimerIf(extendedDiagnostics, "commentTime", "beforeComment", "afterComment");
825827

826828
reset();
@@ -1043,8 +1045,7 @@ namespace ts {
10431045
setSourceFile(sourceFile);
10441046
}
10451047

1046-
const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node);
1047-
pipelinePhase(hint, node);
1048+
pipelineEmit(hint, node);
10481049
}
10491050

10501051
function setSourceFile(sourceFile: SourceFile | undefined) {
@@ -1076,31 +1077,56 @@ namespace ts {
10761077
currentSourceFile = undefined!;
10771078
currentLineMap = undefined!;
10781079
detachedCommentsInfo = undefined;
1080+
lastNode = undefined;
1081+
lastSubstitution = undefined;
10791082
setWriter(/*output*/ undefined, /*_sourceMapGenerator*/ undefined);
10801083
}
10811084

10821085
function getCurrentLineMap() {
10831086
return currentLineMap || (currentLineMap = getLineStarts(currentSourceFile!));
10841087
}
10851088

1089+
function emit(node: Node): Node;
1090+
function emit(node: Node | undefined): Node | undefined;
10861091
function emit(node: Node | undefined) {
10871092
if (node === undefined) return;
1093+
10881094
const prevSourceFileTextKind = recordBundleFileInternalSectionStart(node);
1089-
const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node);
1090-
pipelinePhase(EmitHint.Unspecified, node);
1095+
const substitute = pipelineEmit(EmitHint.Unspecified, node);
10911096
recordBundleFileInternalSectionEnd(prevSourceFileTextKind);
1097+
return substitute;
10921098
}
10931099

1094-
function emitIdentifierName(node: Identifier | undefined) {
1100+
function emitIdentifierName(node: Identifier): Node;
1101+
function emitIdentifierName(node: Identifier | undefined): Node | undefined;
1102+
function emitIdentifierName(node: Identifier | undefined): Node | undefined {
10951103
if (node === undefined) return;
1096-
const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node);
1097-
pipelinePhase(EmitHint.IdentifierName, node);
1104+
return pipelineEmit(EmitHint.IdentifierName, node);
10981105
}
10991106

1100-
function emitExpression(node: Expression | undefined) {
1107+
function emitExpression(node: Expression): Node;
1108+
function emitExpression(node: Expression | undefined): Node | undefined;
1109+
function emitExpression(node: Expression | undefined): Node | undefined {
11011110
if (node === undefined) return;
1111+
return pipelineEmit(EmitHint.Expression, node);
1112+
}
1113+
1114+
function pipelineEmit(emitHint: EmitHint, node: Node) {
1115+
const savedLastNode = lastNode;
1116+
const savedLastSubstitution = lastSubstitution;
1117+
lastNode = node;
1118+
lastSubstitution = undefined;
1119+
11021120
const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node);
1103-
pipelinePhase(EmitHint.Expression, node);
1121+
pipelinePhase(emitHint, node);
1122+
1123+
Debug.assert(lastNode === node);
1124+
1125+
const substitute = lastSubstitution;
1126+
lastNode = savedLastNode;
1127+
lastSubstitution = savedLastSubstitution;
1128+
1129+
return substitute || node;
11041130
}
11051131

11061132
function getPipelinePhase(phase: PipelinePhase, node: Node) {
@@ -1142,11 +1168,14 @@ namespace ts {
11421168
}
11431169

11441170
function pipelineEmitWithNotification(hint: EmitHint, node: Node) {
1171+
Debug.assert(lastNode === node);
11451172
const pipelinePhase = getNextPipelinePhase(PipelinePhase.Notification, node);
11461173
onEmitNode(hint, node, pipelinePhase);
1174+
Debug.assert(lastNode === node);
11471175
}
11481176

11491177
function pipelineEmitWithHint(hint: EmitHint, node: Node): void {
1178+
Debug.assert(lastNode === node || lastSubstitution === node);
11501179
if (hint === EmitHint.SourceFile) return emitSourceFile(cast(node, isSourceFile));
11511180
if (hint === EmitHint.IdentifierName) return emitIdentifier(cast(node, isIdentifier));
11521181
if (hint === EmitHint.MappedTypeParameter) return emitMappedTypeParameter(cast(node, isTypeParameterDeclaration));
@@ -1459,7 +1488,7 @@ namespace ts {
14591488
if (isExpression(node)) {
14601489
hint = EmitHint.Expression;
14611490
if (substituteNode !== noEmitSubstitution) {
1462-
node = substituteNode(hint, node);
1491+
lastSubstitution = node = substituteNode(hint, node);
14631492
}
14641493
}
14651494
else if (isToken(node)) {
@@ -1575,8 +1604,11 @@ namespace ts {
15751604
}
15761605

15771606
function pipelineEmitWithSubstitution(hint: EmitHint, node: Node) {
1607+
Debug.assert(lastNode === node || lastSubstitution === node);
15781608
const pipelinePhase = getNextPipelinePhase(PipelinePhase.Substitution, node);
1579-
pipelinePhase(hint, substituteNode(hint, node));
1609+
lastSubstitution = substituteNode(hint, node);
1610+
pipelinePhase(hint, lastSubstitution);
1611+
Debug.assert(lastNode === node || lastSubstitution === node);
15801612
}
15811613

15821614
function getHelpersFromBundledSourceFiles(bundle: Bundle): string[] | undefined {
@@ -2074,8 +2106,7 @@ namespace ts {
20742106
}
20752107
writePunctuation("[");
20762108

2077-
const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node.typeParameter);
2078-
pipelinePhase(EmitHint.MappedTypeParameter, node.typeParameter);
2109+
pipelineEmit(EmitHint.MappedTypeParameter, node.typeParameter);
20792110

20802111
writePunctuation("]");
20812112
if (node.questionToken) {
@@ -2173,70 +2204,60 @@ namespace ts {
21732204
}
21742205

21752206
function emitPropertyAccessExpression(node: PropertyAccessExpression) {
2176-
let indentBeforeDot = false;
2177-
let indentAfterDot = false;
2178-
const dotRangeFirstCommentStart = skipTrivia(
2179-
currentSourceFile!.text,
2180-
node.expression.end,
2181-
/*stopAfterLineBreak*/ false,
2182-
/*stopAtComments*/ true
2183-
);
2184-
const dotRangeStart = skipTrivia(currentSourceFile!.text, dotRangeFirstCommentStart);
2185-
const dotRangeEnd = dotRangeStart + 1;
2186-
if (!(getEmitFlags(node) & EmitFlags.NoIndentation)) {
2187-
const dotToken = createToken(SyntaxKind.DotToken);
2188-
dotToken.pos = node.expression.end;
2189-
dotToken.end = dotRangeEnd;
2190-
indentBeforeDot = needsIndentation(node, node.expression, dotToken);
2191-
indentAfterDot = needsIndentation(node, dotToken, node.name);
2192-
}
2207+
const expression = cast(emitExpression(node.expression), isExpression);
2208+
const token = getDotOrQuestionDotToken(node);
2209+
const indentBeforeDot = needsIndentation(node, node.expression, token);
2210+
const indentAfterDot = needsIndentation(node, token, node.name);
21932211

2194-
emitExpression(node.expression);
21952212
increaseIndentIf(indentBeforeDot, /*writeSpaceIfNotIndenting*/ false);
21962213

2197-
const dotHasCommentTrivia = dotRangeFirstCommentStart !== dotRangeStart;
2198-
const shouldEmitDotDot = !indentBeforeDot && needsDotDotForPropertyAccess(node.expression, dotHasCommentTrivia);
2214+
const shouldEmitDotDot =
2215+
token.kind !== SyntaxKind.QuestionDotToken &&
2216+
mayNeedDotDotForPropertyAccess(expression) &&
2217+
!writer.hasPrecedingComment() &&
2218+
!writer.hasPrecedingWhitespace();
2219+
21992220
if (shouldEmitDotDot) {
22002221
writePunctuation(".");
22012222
}
2202-
emitTokenWithComment(SyntaxKind.DotToken, node.expression.end, writePunctuation, node);
22032223

2224+
emitTokenWithComment(token.kind, node.expression.end, writePunctuation, node);
22042225
increaseIndentIf(indentAfterDot, /*writeSpaceIfNotIndenting*/ false);
22052226
emit(node.name);
22062227
decreaseIndentIf(indentBeforeDot, indentAfterDot);
22072228
}
22082229

22092230
// 1..toString is a valid property access, emit a dot after the literal
22102231
// Also emit a dot if expression is a integer const enum value - it will appear in generated code as numeric literal
2211-
function needsDotDotForPropertyAccess(expression: Expression, dotHasTrivia: boolean) {
2232+
function mayNeedDotDotForPropertyAccess(expression: Expression) {
22122233
expression = skipPartiallyEmittedExpressions(expression);
22132234
if (isNumericLiteral(expression)) {
22142235
// check if numeric literal is a decimal literal that was originally written with a dot
22152236
const text = getLiteralTextOfNode(<LiteralExpression>expression, /*neverAsciiEscape*/ true);
22162237
// If he number will be printed verbatim and it doesn't already contain a dot, add one
22172238
// if the expression doesn't have any comments that will be emitted.
2218-
return !expression.numericLiteralFlags && !stringContains(text, tokenToString(SyntaxKind.DotToken)!) &&
2219-
(!dotHasTrivia || printerOptions.removeComments);
2239+
return !expression.numericLiteralFlags && !stringContains(text, tokenToString(SyntaxKind.DotToken)!);
22202240
}
22212241
else if (isPropertyAccessExpression(expression) || isElementAccessExpression(expression)) {
22222242
// check if constant enum value is integer
22232243
const constantValue = getConstantValue(expression);
22242244
// isFinite handles cases when constantValue is undefined
22252245
return typeof constantValue === "number" && isFinite(constantValue)
2226-
&& Math.floor(constantValue) === constantValue
2227-
&& printerOptions.removeComments;
2246+
&& Math.floor(constantValue) === constantValue;
22282247
}
22292248
}
22302249

22312250
function emitElementAccessExpression(node: ElementAccessExpression) {
22322251
emitExpression(node.expression);
2252+
emit(node.questionDotToken);
22332253
emitTokenWithComment(SyntaxKind.OpenBracketToken, node.expression.end, writePunctuation, node);
22342254
emitExpression(node.argumentExpression);
22352255
emitTokenWithComment(SyntaxKind.CloseBracketToken, node.argumentExpression.end, writePunctuation, node);
22362256
}
22372257

22382258
function emitCallExpression(node: CallExpression) {
22392259
emitExpression(node.expression);
2260+
emit(node.questionDotToken);
22402261
emitTypeArguments(node, node.typeArguments);
22412262
emitExpressionList(node, node.arguments, ListFormat.CallExpressionArguments);
22422263
}
@@ -3700,8 +3721,7 @@ namespace ts {
37003721
writeLine();
37013722
increaseIndent();
37023723
if (isEmptyStatement(node)) {
3703-
const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node);
3704-
pipelinePhase(EmitHint.EmbeddedStatement, node);
3724+
pipelineEmit(EmitHint.EmbeddedStatement, node);
37053725
}
37063726
else {
37073727
emit(node);
@@ -4172,6 +4192,10 @@ namespace ts {
41724192
}
41734193

41744194
function needsIndentation(parent: Node, node1: Node, node2: Node): boolean {
4195+
if (getEmitFlags(parent) & EmitFlags.NoIndentation) {
4196+
return false;
4197+
}
4198+
41754199
parent = skipSynthesizedParentheses(parent);
41764200
node1 = skipSynthesizedParentheses(node1);
41774201
node2 = skipSynthesizedParentheses(node2);
@@ -4627,6 +4651,7 @@ namespace ts {
46274651
// Comments
46284652

46294653
function pipelineEmitWithComments(hint: EmitHint, node: Node) {
4654+
Debug.assert(lastNode === node || lastSubstitution === node);
46304655
enterComment();
46314656
hasWrittenComment = false;
46324657
const emitFlags = getEmitFlags(node);
@@ -4693,6 +4718,7 @@ namespace ts {
46934718
}
46944719
}
46954720
exitComment();
4721+
Debug.assert(lastNode === node || lastSubstitution === node);
46964722
}
46974723

46984724
function emitLeadingSynthesizedComment(comment: SynthesizedComment) {
@@ -4939,6 +4965,7 @@ namespace ts {
49394965
}
49404966

49414967
function pipelineEmitWithSourceMap(hint: EmitHint, node: Node) {
4968+
Debug.assert(lastNode === node || lastSubstitution === node);
49424969
const pipelinePhase = getNextPipelinePhase(PipelinePhase.SourceMaps, node);
49434970
if (isUnparsedSource(node) || isUnparsedPrepend(node)) {
49444971
pipelinePhase(hint, node);
@@ -4981,6 +5008,7 @@ namespace ts {
49815008
emitSourcePos(source, end);
49825009
}
49835010
}
5011+
Debug.assert(lastNode === node || lastSubstitution === node);
49845012
}
49855013

49865014
/**

0 commit comments

Comments
 (0)