Skip to content

Commit

Permalink
Add support for Optional Chaining
Browse files Browse the repository at this point in the history
  • Loading branch information
rbuckton committed Sep 7, 2019
1 parent fb453f8 commit c95daab
Show file tree
Hide file tree
Showing 31 changed files with 1,737 additions and 816 deletions.
12 changes: 12 additions & 0 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3160,6 +3160,10 @@ namespace ts {
const callee = skipOuterExpressions(node.expression);
const expression = node.expression;

if (node.flags & NodeFlags.OptionalChain) {
transformFlags |= TransformFlags.ContainsESNext;
}

if (node.typeArguments) {
transformFlags |= TransformFlags.AssertTypeScript;
}
Expand Down Expand Up @@ -3555,6 +3559,10 @@ namespace ts {
function computePropertyAccess(node: PropertyAccessExpression, subtreeFlags: TransformFlags) {
let transformFlags = subtreeFlags;

if (node.flags & NodeFlags.OptionalChain) {
transformFlags |= TransformFlags.ContainsESNext;
}

// If a PropertyAccessExpression starts with a super keyword, then it is
// ES6 syntax, and requires a lexical `this` binding.
if (node.expression.kind === SyntaxKind.SuperKeyword) {
Expand All @@ -3570,6 +3578,10 @@ namespace ts {
function computeElementAccess(node: ElementAccessExpression, subtreeFlags: TransformFlags) {
let transformFlags = subtreeFlags;

if (node.flags & NodeFlags.OptionalChain) {
transformFlags |= TransformFlags.ContainsESNext;
}

// If an ElementAccessExpression starts with a super keyword, then it is
// ES6 syntax, and requires a lexical `this` binding.
if (node.expression.kind === SyntaxKind.SuperKeyword) {
Expand Down
124 changes: 92 additions & 32 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions src/compiler/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,14 @@ namespace ts {
assertNode)
: noop;

export const assertNotNode = shouldAssert(AssertionLevel.Normal)
? (node: Node | undefined, test: ((node: Node | undefined) => boolean) | undefined, message?: string): void => assert(
test === undefined || !test(node),
message || "Unexpected node.",
() => `Node ${formatSyntaxKind(node!.kind)} should not have passed test '${getFunctionName(test!)}'.`,
assertNode)
: noop;

export const assertOptionalNode = shouldAssert(AssertionLevel.Normal)
? (node: Node, test: (node: Node) => boolean, message?: string): void => assert(
test === undefined || node === undefined || test(node),
Expand Down
110 changes: 69 additions & 41 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,8 @@ namespace ts {
let detachedCommentsInfo: { nodePos: number, detachedCommentEndPos: number}[] | undefined;
let hasWrittenComment = false;
let commentsDisabled = !!printerOptions.removeComments;
let lastNode: Node | undefined;
let lastSubstitution: Node | undefined;
const { enter: enterComment, exit: exitComment } = performance.createTimerIf(extendedDiagnostics, "commentTime", "beforeComment", "afterComment");

reset();
Expand Down Expand Up @@ -1043,8 +1045,7 @@ namespace ts {
setSourceFile(sourceFile);
}

const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node);
pipelinePhase(hint, node);
pipelineEmit(hint, node);
}

function setSourceFile(sourceFile: SourceFile | undefined) {
Expand Down Expand Up @@ -1076,31 +1077,56 @@ namespace ts {
currentSourceFile = undefined!;
currentLineMap = undefined!;
detachedCommentsInfo = undefined;
lastNode = undefined;
lastSubstitution = undefined;
setWriter(/*output*/ undefined, /*_sourceMapGenerator*/ undefined);
}

function getCurrentLineMap() {
return currentLineMap || (currentLineMap = getLineStarts(currentSourceFile!));
}

function emit(node: Node): Node;
function emit(node: Node | undefined): Node | undefined;
function emit(node: Node | undefined) {
if (node === undefined) return;

const prevSourceFileTextKind = recordBundleFileInternalSectionStart(node);
const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node);
pipelinePhase(EmitHint.Unspecified, node);
const substitute = pipelineEmit(EmitHint.Unspecified, node);
recordBundleFileInternalSectionEnd(prevSourceFileTextKind);
return substitute;
}

function emitIdentifierName(node: Identifier | undefined) {
function emitIdentifierName(node: Identifier): Node;
function emitIdentifierName(node: Identifier | undefined): Node | undefined;
function emitIdentifierName(node: Identifier | undefined): Node | undefined {
if (node === undefined) return;
const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node);
pipelinePhase(EmitHint.IdentifierName, node);
return pipelineEmit(EmitHint.IdentifierName, node);
}

function emitExpression(node: Expression | undefined) {
function emitExpression(node: Expression): Node;
function emitExpression(node: Expression | undefined): Node | undefined;
function emitExpression(node: Expression | undefined): Node | undefined {
if (node === undefined) return;
return pipelineEmit(EmitHint.Expression, node);
}

function pipelineEmit(emitHint: EmitHint, node: Node) {
const savedLastNode = lastNode;
const savedLastSubstitution = lastSubstitution;
lastNode = node;
lastSubstitution = undefined;

const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node);
pipelinePhase(EmitHint.Expression, node);
pipelinePhase(emitHint, node);

Debug.assert(lastNode === node);

const substitute = lastSubstitution;
lastNode = savedLastNode;
lastSubstitution = savedLastSubstitution;

return substitute || node;
}

function getPipelinePhase(phase: PipelinePhase, node: Node) {
Expand Down Expand Up @@ -1142,11 +1168,14 @@ namespace ts {
}

function pipelineEmitWithNotification(hint: EmitHint, node: Node) {
Debug.assert(lastNode === node);
const pipelinePhase = getNextPipelinePhase(PipelinePhase.Notification, node);
onEmitNode(hint, node, pipelinePhase);
Debug.assert(lastNode === node);
}

function pipelineEmitWithHint(hint: EmitHint, node: Node): void {
Debug.assert(lastNode === node || lastSubstitution === node);
if (hint === EmitHint.SourceFile) return emitSourceFile(cast(node, isSourceFile));
if (hint === EmitHint.IdentifierName) return emitIdentifier(cast(node, isIdentifier));
if (hint === EmitHint.MappedTypeParameter) return emitMappedTypeParameter(cast(node, isTypeParameterDeclaration));
Expand Down Expand Up @@ -1459,7 +1488,7 @@ namespace ts {
if (isExpression(node)) {
hint = EmitHint.Expression;
if (substituteNode !== noEmitSubstitution) {
node = substituteNode(hint, node);
lastSubstitution = node = substituteNode(hint, node);
}
}
else if (isToken(node)) {
Expand Down Expand Up @@ -1575,8 +1604,11 @@ namespace ts {
}

function pipelineEmitWithSubstitution(hint: EmitHint, node: Node) {
Debug.assert(lastNode === node || lastSubstitution === node);
const pipelinePhase = getNextPipelinePhase(PipelinePhase.Substitution, node);
pipelinePhase(hint, substituteNode(hint, node));
lastSubstitution = substituteNode(hint, node);
pipelinePhase(hint, lastSubstitution);
Debug.assert(lastNode === node || lastSubstitution === node);
}

function getHelpersFromBundledSourceFiles(bundle: Bundle): string[] | undefined {
Expand Down Expand Up @@ -2074,8 +2106,7 @@ namespace ts {
}
writePunctuation("[");

const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node.typeParameter);
pipelinePhase(EmitHint.MappedTypeParameter, node.typeParameter);
pipelineEmit(EmitHint.MappedTypeParameter, node.typeParameter);

writePunctuation("]");
if (node.questionToken) {
Expand Down Expand Up @@ -2173,70 +2204,60 @@ namespace ts {
}

function emitPropertyAccessExpression(node: PropertyAccessExpression) {
let indentBeforeDot = false;
let indentAfterDot = false;
const dotRangeFirstCommentStart = skipTrivia(
currentSourceFile!.text,
node.expression.end,
/*stopAfterLineBreak*/ false,
/*stopAtComments*/ true
);
const dotRangeStart = skipTrivia(currentSourceFile!.text, dotRangeFirstCommentStart);
const dotRangeEnd = dotRangeStart + 1;
if (!(getEmitFlags(node) & EmitFlags.NoIndentation)) {
const dotToken = createToken(SyntaxKind.DotToken);
dotToken.pos = node.expression.end;
dotToken.end = dotRangeEnd;
indentBeforeDot = needsIndentation(node, node.expression, dotToken);
indentAfterDot = needsIndentation(node, dotToken, node.name);
}
const expression = cast(emitExpression(node.expression), isExpression);
const token = getDotOrQuestionDotToken(node);
const indentBeforeDot = needsIndentation(node, node.expression, token);
const indentAfterDot = needsIndentation(node, token, node.name);

emitExpression(node.expression);
increaseIndentIf(indentBeforeDot, /*writeSpaceIfNotIndenting*/ false);

const dotHasCommentTrivia = dotRangeFirstCommentStart !== dotRangeStart;
const shouldEmitDotDot = !indentBeforeDot && needsDotDotForPropertyAccess(node.expression, dotHasCommentTrivia);
const shouldEmitDotDot =
token.kind !== SyntaxKind.QuestionDotToken &&
mayNeedDotDotForPropertyAccess(expression) &&
!writer.hasPrecedingComment() &&
!writer.hasPrecedingWhitespace();

if (shouldEmitDotDot) {
writePunctuation(".");
}
emitTokenWithComment(SyntaxKind.DotToken, node.expression.end, writePunctuation, node);

emitTokenWithComment(token.kind, node.expression.end, writePunctuation, node);
increaseIndentIf(indentAfterDot, /*writeSpaceIfNotIndenting*/ false);
emit(node.name);
decreaseIndentIf(indentBeforeDot, indentAfterDot);
}

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

function emitElementAccessExpression(node: ElementAccessExpression) {
emitExpression(node.expression);
emit(node.questionDotToken);
emitTokenWithComment(SyntaxKind.OpenBracketToken, node.expression.end, writePunctuation, node);
emitExpression(node.argumentExpression);
emitTokenWithComment(SyntaxKind.CloseBracketToken, node.argumentExpression.end, writePunctuation, node);
}

function emitCallExpression(node: CallExpression) {
emitExpression(node.expression);
emit(node.questionDotToken);
emitTypeArguments(node, node.typeArguments);
emitExpressionList(node, node.arguments, ListFormat.CallExpressionArguments);
}
Expand Down Expand Up @@ -3700,8 +3721,7 @@ namespace ts {
writeLine();
increaseIndent();
if (isEmptyStatement(node)) {
const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node);
pipelinePhase(EmitHint.EmbeddedStatement, node);
pipelineEmit(EmitHint.EmbeddedStatement, node);
}
else {
emit(node);
Expand Down Expand Up @@ -4172,6 +4192,10 @@ namespace ts {
}

function needsIndentation(parent: Node, node1: Node, node2: Node): boolean {
if (getEmitFlags(parent) & EmitFlags.NoIndentation) {
return false;
}

parent = skipSynthesizedParentheses(parent);
node1 = skipSynthesizedParentheses(node1);
node2 = skipSynthesizedParentheses(node2);
Expand Down Expand Up @@ -4627,6 +4651,7 @@ namespace ts {
// Comments

function pipelineEmitWithComments(hint: EmitHint, node: Node) {
Debug.assert(lastNode === node || lastSubstitution === node);
enterComment();
hasWrittenComment = false;
const emitFlags = getEmitFlags(node);
Expand Down Expand Up @@ -4693,6 +4718,7 @@ namespace ts {
}
}
exitComment();
Debug.assert(lastNode === node || lastSubstitution === node);
}

function emitLeadingSynthesizedComment(comment: SynthesizedComment) {
Expand Down Expand Up @@ -4939,6 +4965,7 @@ namespace ts {
}

function pipelineEmitWithSourceMap(hint: EmitHint, node: Node) {
Debug.assert(lastNode === node || lastSubstitution === node);
const pipelinePhase = getNextPipelinePhase(PipelinePhase.SourceMaps, node);
if (isUnparsedSource(node) || isUnparsedPrepend(node)) {
pipelinePhase(hint, node);
Expand Down Expand Up @@ -4981,6 +5008,7 @@ namespace ts {
emitSourcePos(source, end);
}
}
Debug.assert(lastNode === node || lastSubstitution === node);
}

/**
Expand Down
Loading

0 comments on commit c95daab

Please sign in to comment.