mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-07 14:34:35 -06:00
Parse and Emit for safe navigation
This commit is contained in:
parent
359823b4be
commit
d9fceab72d
@ -2610,6 +2610,10 @@ namespace ts {
|
||||
transformFlags |= TransformFlags.AssertTypeScript;
|
||||
}
|
||||
|
||||
if (node.flags & NodeFlags.PropagateNull) {
|
||||
transformFlags |= TransformFlags.AssertESNext;
|
||||
}
|
||||
|
||||
if (subtreeFlags & TransformFlags.ContainsSpread
|
||||
|| isSuperOrSuperProperty(expression, expressionKind)) {
|
||||
// If the this node contains a SpreadExpression, or is a super call, then it is an ES6
|
||||
@ -2641,11 +2645,17 @@ namespace ts {
|
||||
if (node.typeArguments) {
|
||||
transformFlags |= TransformFlags.AssertTypeScript;
|
||||
}
|
||||
|
||||
if (node.flags & NodeFlags.PropagateNull) {
|
||||
transformFlags |= TransformFlags.AssertESNext;
|
||||
}
|
||||
|
||||
if (subtreeFlags & TransformFlags.ContainsSpread) {
|
||||
// If the this node contains a SpreadElementExpression then it is an ES6
|
||||
// node.
|
||||
transformFlags |= TransformFlags.AssertES2015;
|
||||
}
|
||||
|
||||
node.transformFlags = transformFlags | TransformFlags.HasComputedFlags;
|
||||
return transformFlags & ~TransformFlags.ArrayLiteralOrCallOrNewExcludes;
|
||||
}
|
||||
@ -3049,6 +3059,10 @@ namespace ts {
|
||||
const expression = node.expression;
|
||||
const expressionKind = expression.kind;
|
||||
|
||||
if (node.flags & NodeFlags.PropagateNull) {
|
||||
transformFlags |= TransformFlags.AssertESNext;
|
||||
}
|
||||
|
||||
// If a PropertyAccessExpression starts with a super keyword, then it is
|
||||
// ES6 syntax, and requires a lexical `this` binding.
|
||||
if (expressionKind === SyntaxKind.SuperKeyword) {
|
||||
@ -3354,7 +3368,6 @@ namespace ts {
|
||||
break;
|
||||
|
||||
case SyntaxKind.ArrayLiteralExpression:
|
||||
case SyntaxKind.NewExpression:
|
||||
excludeFlags = TransformFlags.ArrayLiteralOrCallOrNewExcludes;
|
||||
if (subtreeFlags & TransformFlags.ContainsSpread) {
|
||||
// If the this node contains a SpreadExpression, then it is an ES6
|
||||
@ -3364,6 +3377,13 @@ namespace ts {
|
||||
|
||||
break;
|
||||
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
if (node.flags & NodeFlags.PropagateNull) {
|
||||
transformFlags |= TransformFlags.AssertESNext;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SyntaxKind.DoStatement:
|
||||
case SyntaxKind.WhileStatement:
|
||||
case SyntaxKind.ForStatement:
|
||||
|
||||
@ -13109,6 +13109,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
function checkPropertyAccessExpression(node: PropertyAccessExpression) {
|
||||
checkGrammarNullPropagation(node);
|
||||
return checkPropertyAccessExpressionOrQualifiedName(node, node.expression, node.name);
|
||||
}
|
||||
|
||||
@ -13276,6 +13277,8 @@ namespace ts {
|
||||
}
|
||||
|
||||
function checkIndexedAccess(node: ElementAccessExpression): Type {
|
||||
checkGrammarNullPropagation(node);
|
||||
|
||||
const objectType = checkNonNullExpression(node.expression);
|
||||
|
||||
const indexExpression = node.argumentExpression;
|
||||
@ -14678,7 +14681,7 @@ namespace ts {
|
||||
*/
|
||||
function checkCallExpression(node: CallExpression | NewExpression): Type {
|
||||
// Grammar checking; stop grammar-checking if checkGrammarTypeArguments return true
|
||||
checkGrammarTypeArguments(node, node.typeArguments) || checkGrammarArguments(node, node.arguments);
|
||||
checkGrammarNullPropagation(node) || checkGrammarTypeArguments(node, node.typeArguments) || checkGrammarArguments(node, node.arguments);
|
||||
|
||||
const signature = getResolvedSignature(node);
|
||||
|
||||
@ -22386,6 +22389,18 @@ namespace ts {
|
||||
return checkGrammarDecorators(node) || checkGrammarModifiers(node) || checkGrammarIndexSignatureParameters(node);
|
||||
}
|
||||
|
||||
function checkGrammarNullPropagation(node: CallExpression | NewExpression | PropertyAccessExpression | ElementAccessExpression) {
|
||||
if (node.flags & NodeFlags.PropagateNull && node.expression.kind === SyntaxKind.SuperKeyword) {
|
||||
const sourceFile = getSourceFileOfNode(node);
|
||||
const start = skipTrivia(sourceFile.text, node.expression.end);
|
||||
if (node.kind === SyntaxKind.PropertyAccessExpression) {
|
||||
return grammarErrorAtPos(sourceFile, start, 2, Diagnostics._0_expected, tokenToString(SyntaxKind.DotToken));
|
||||
}
|
||||
|
||||
return grammarErrorAtPos(sourceFile, start, 2, Diagnostics.Unexpected_token);
|
||||
}
|
||||
}
|
||||
|
||||
function checkGrammarForAtLeastOneTypeArgument(node: Node, typeArguments: NodeArray<TypeNode>): boolean {
|
||||
if (typeArguments && typeArguments.length === 0) {
|
||||
const sourceFile = getSourceFileOfNode(node);
|
||||
|
||||
@ -1092,12 +1092,13 @@ namespace ts {
|
||||
}
|
||||
|
||||
function emitPropertyAccessExpression(node: PropertyAccessExpression) {
|
||||
const propagatesNull = node.flags & NodeFlags.PropagateNull;
|
||||
let indentBeforeDot = false;
|
||||
let indentAfterDot = false;
|
||||
if (!(getEmitFlags(node) & EmitFlags.NoIndentation)) {
|
||||
const dotRangeStart = node.expression.end;
|
||||
const dotRangeEnd = skipTrivia(currentSourceFile.text, node.expression.end) + 1;
|
||||
const dotToken = <Node>{ kind: SyntaxKind.DotToken, pos: dotRangeStart, end: dotRangeEnd };
|
||||
const dotRangeEnd = skipTrivia(currentSourceFile.text, node.expression.end) + (propagatesNull ? 2 : 1);
|
||||
const dotToken = <Node>{ kind: propagatesNull ? SyntaxKind.QuestionDotToken : SyntaxKind.DotToken, pos: dotRangeStart, end: dotRangeEnd };
|
||||
indentBeforeDot = needsIndentation(node, node.expression, dotToken);
|
||||
indentAfterDot = needsIndentation(node, dotToken, node.name);
|
||||
}
|
||||
@ -1105,8 +1106,8 @@ namespace ts {
|
||||
emitExpression(node.expression);
|
||||
increaseIndentIf(indentBeforeDot);
|
||||
|
||||
const shouldEmitDotDot = !indentBeforeDot && needsDotDotForPropertyAccess(node.expression);
|
||||
write(shouldEmitDotDot ? ".." : ".");
|
||||
const shouldEmitDotDot = !propagatesNull && !indentBeforeDot && needsDotDotForPropertyAccess(node.expression);
|
||||
write(shouldEmitDotDot ? ".." : propagatesNull ? "?." : ".");
|
||||
|
||||
increaseIndentIf(indentAfterDot);
|
||||
emit(node.name);
|
||||
@ -1135,13 +1136,16 @@ namespace ts {
|
||||
|
||||
function emitElementAccessExpression(node: ElementAccessExpression) {
|
||||
emitExpression(node.expression);
|
||||
write("[");
|
||||
write(node.flags & NodeFlags.PropagateNull ? "?.[" : "[");
|
||||
emitExpression(node.argumentExpression);
|
||||
write("]");
|
||||
}
|
||||
|
||||
function emitCallExpression(node: CallExpression) {
|
||||
emitExpression(node.expression);
|
||||
if (node.flags & NodeFlags.PropagateNull) {
|
||||
write("?.");
|
||||
}
|
||||
emitTypeArguments(node, node.typeArguments);
|
||||
emitExpressionList(node, node.arguments, ListFormat.CallExpressionArguments);
|
||||
}
|
||||
@ -1149,6 +1153,9 @@ namespace ts {
|
||||
function emitNewExpression(node: NewExpression) {
|
||||
write("new ");
|
||||
emitExpression(node.expression);
|
||||
if (node.flags & NodeFlags.PropagateNull) {
|
||||
write("?.");
|
||||
}
|
||||
emitTypeArguments(node, node.typeArguments);
|
||||
emitExpressionList(node, node.arguments, ListFormat.NewExpressionArguments);
|
||||
}
|
||||
|
||||
@ -440,8 +440,9 @@ namespace ts {
|
||||
: node;
|
||||
}
|
||||
|
||||
export function createPropertyAccess(expression: Expression, name: string | Identifier) {
|
||||
export function createPropertyAccess(expression: Expression, name: string | Identifier, flags?: NodeFlags) {
|
||||
const node = <PropertyAccessExpression>createSynthesizedNode(SyntaxKind.PropertyAccessExpression);
|
||||
node.flags |= flags;
|
||||
node.expression = parenthesizeForAccess(expression);
|
||||
node.name = asName(name);
|
||||
setEmitFlags(node, EmitFlags.NoIndentation);
|
||||
@ -453,12 +454,13 @@ namespace ts {
|
||||
// instead of using the default from createPropertyAccess
|
||||
return node.expression !== expression
|
||||
|| node.name !== name
|
||||
? updateNode(setEmitFlags(createPropertyAccess(expression, name), getEmitFlags(node)), node)
|
||||
? updateNode(setEmitFlags(createPropertyAccess(expression, name, node.flags), getEmitFlags(node)), node)
|
||||
: node;
|
||||
}
|
||||
|
||||
export function createElementAccess(expression: Expression, index: number | Expression) {
|
||||
export function createElementAccess(expression: Expression, index: number | Expression, flags?: NodeFlags) {
|
||||
const node = <ElementAccessExpression>createSynthesizedNode(SyntaxKind.ElementAccessExpression);
|
||||
node.flags |= flags;
|
||||
node.expression = parenthesizeForAccess(expression);
|
||||
node.argumentExpression = asExpression(index);
|
||||
return node;
|
||||
@ -467,12 +469,13 @@ namespace ts {
|
||||
export function updateElementAccess(node: ElementAccessExpression, expression: Expression, argumentExpression: Expression) {
|
||||
return node.expression !== expression
|
||||
|| node.argumentExpression !== argumentExpression
|
||||
? updateNode(createElementAccess(expression, argumentExpression), node)
|
||||
? updateNode(createElementAccess(expression, argumentExpression, node.flags), node)
|
||||
: node;
|
||||
}
|
||||
|
||||
export function createCall(expression: Expression, typeArguments: TypeNode[] | undefined, argumentsArray: Expression[]) {
|
||||
export function createCall(expression: Expression, typeArguments: TypeNode[] | undefined, argumentsArray: Expression[], flags?: NodeFlags) {
|
||||
const node = <CallExpression>createSynthesizedNode(SyntaxKind.CallExpression);
|
||||
node.flags |= flags;
|
||||
node.expression = parenthesizeForAccess(expression);
|
||||
node.typeArguments = asNodeArray(typeArguments);
|
||||
node.arguments = parenthesizeListElements(createNodeArray(argumentsArray));
|
||||
@ -483,12 +486,13 @@ namespace ts {
|
||||
return expression !== node.expression
|
||||
|| typeArguments !== node.typeArguments
|
||||
|| argumentsArray !== node.arguments
|
||||
? updateNode(createCall(expression, typeArguments, argumentsArray), node)
|
||||
? updateNode(createCall(expression, typeArguments, argumentsArray, node.flags), node)
|
||||
: node;
|
||||
}
|
||||
|
||||
export function createNew(expression: Expression, typeArguments: TypeNode[] | undefined, argumentsArray: Expression[] | undefined) {
|
||||
export function createNew(expression: Expression, typeArguments: TypeNode[] | undefined, argumentsArray: Expression[] | undefined, flags?: NodeFlags) {
|
||||
const node = <NewExpression>createSynthesizedNode(SyntaxKind.NewExpression);
|
||||
node.flags |= flags;
|
||||
node.expression = parenthesizeForNew(expression);
|
||||
node.typeArguments = asNodeArray(typeArguments);
|
||||
node.arguments = argumentsArray ? parenthesizeListElements(createNodeArray(argumentsArray)) : undefined;
|
||||
@ -499,7 +503,7 @@ namespace ts {
|
||||
return node.expression !== expression
|
||||
|| node.typeArguments !== typeArguments
|
||||
|| node.arguments !== argumentsArray
|
||||
? updateNode(createNew(expression, typeArguments, argumentsArray), node)
|
||||
? updateNode(createNew(expression, typeArguments, argumentsArray, node.flags), node)
|
||||
: node;
|
||||
}
|
||||
|
||||
@ -1740,6 +1744,10 @@ namespace ts {
|
||||
return createBinary(left, SyntaxKind.EqualsToken, right);
|
||||
}
|
||||
|
||||
export function createEquality(left: Expression, right: Expression) {
|
||||
return createBinary(left, SyntaxKind.EqualsEqualsToken, right);
|
||||
}
|
||||
|
||||
export function createStrictEquality(left: Expression, right: Expression) {
|
||||
return createBinary(left, SyntaxKind.EqualsEqualsEqualsToken, right);
|
||||
}
|
||||
@ -2449,7 +2457,8 @@ namespace ts {
|
||||
),
|
||||
(<PropertyAccessExpression>callee).expression
|
||||
),
|
||||
(<PropertyAccessExpression>callee).name
|
||||
(<PropertyAccessExpression>callee).name,
|
||||
callee.flags & NodeFlags.PropagateNull
|
||||
);
|
||||
setTextRange(target, callee);
|
||||
}
|
||||
@ -2472,7 +2481,8 @@ namespace ts {
|
||||
),
|
||||
(<ElementAccessExpression>callee).expression
|
||||
),
|
||||
(<ElementAccessExpression>callee).argumentExpression
|
||||
(<ElementAccessExpression>callee).argumentExpression,
|
||||
callee.flags & NodeFlags.PropagateNull
|
||||
);
|
||||
setTextRange(target, callee);
|
||||
}
|
||||
|
||||
@ -3748,7 +3748,10 @@ namespace ts {
|
||||
|
||||
function parseSuperExpression(): MemberExpression {
|
||||
const expression = parseTokenNode<PrimaryExpression>();
|
||||
if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.DotToken || token() === SyntaxKind.OpenBracketToken) {
|
||||
if (token() === SyntaxKind.OpenParenToken ||
|
||||
token() === SyntaxKind.DotToken ||
|
||||
token() === SyntaxKind.OpenBracketToken ||
|
||||
token() === SyntaxKind.QuestionDotToken) {
|
||||
return expression;
|
||||
}
|
||||
|
||||
@ -4004,11 +4007,32 @@ namespace ts {
|
||||
return finishNode(node);
|
||||
}
|
||||
|
||||
function isNullPropagatingCallOrNewExpression() {
|
||||
return token() === SyntaxKind.QuestionDotToken
|
||||
&& lookAhead(nextTokenIsOpenParenOrLessThanToken);
|
||||
}
|
||||
|
||||
function nextTokenIsOpenParenOrLessThanToken() {
|
||||
nextToken();
|
||||
return token() === SyntaxKind.OpenParenToken
|
||||
|| token() === SyntaxKind.LessThanToken;
|
||||
}
|
||||
|
||||
function parseMemberExpressionRest(expression: LeftHandSideExpression): MemberExpression {
|
||||
while (true) {
|
||||
if (isNullPropagatingCallOrNewExpression()) {
|
||||
// In a null-propagating call or new expression, we defer parsing `.?` to parseCallExpressionRest.
|
||||
return <MemberExpression>expression;
|
||||
}
|
||||
|
||||
const dotToken = parseOptionalToken(SyntaxKind.DotToken);
|
||||
if (dotToken) {
|
||||
const questionDotToken = !dotToken && parseOptionalToken(SyntaxKind.QuestionDotToken);
|
||||
if (dotToken || (questionDotToken && token() !== SyntaxKind.OpenBracketToken)) {
|
||||
const propertyAccess = <PropertyAccessExpression>createNode(SyntaxKind.PropertyAccessExpression, expression.pos);
|
||||
if (questionDotToken) {
|
||||
propertyAccess.flags |= NodeFlags.PropagateNull;
|
||||
}
|
||||
|
||||
propertyAccess.expression = expression;
|
||||
propertyAccess.name = parseRightSideOfDot(/*allowIdentifierNames*/ true);
|
||||
expression = finishNode(propertyAccess);
|
||||
@ -4024,8 +4048,13 @@ namespace ts {
|
||||
}
|
||||
|
||||
// when in the [Decorator] context, we do not parse ElementAccess as it could be part of a ComputedPropertyName
|
||||
if (!inDecoratorContext() && parseOptional(SyntaxKind.OpenBracketToken)) {
|
||||
// however, `?.[` is unambiguously *not* a ComputedPropertyName.
|
||||
if ((questionDotToken || !inDecoratorContext()) && parseOptional(SyntaxKind.OpenBracketToken)) {
|
||||
const indexedAccess = <ElementAccessExpression>createNode(SyntaxKind.ElementAccessExpression, expression.pos);
|
||||
if (questionDotToken) {
|
||||
indexedAccess.flags |= NodeFlags.PropagateNull;
|
||||
}
|
||||
|
||||
indexedAccess.expression = expression;
|
||||
|
||||
// It's not uncommon for a user to write: "new Type[]".
|
||||
@ -4060,32 +4089,34 @@ namespace ts {
|
||||
function parseCallExpressionRest(expression: LeftHandSideExpression): LeftHandSideExpression {
|
||||
while (true) {
|
||||
expression = parseMemberExpressionRest(expression);
|
||||
const questionDot = parseOptionalToken(SyntaxKind.QuestionDotToken);
|
||||
let typeArguments: NodeArray<TypeNode>;
|
||||
if (token() === SyntaxKind.LessThanToken) {
|
||||
// See if this is the start of a generic invocation. If so, consume it and
|
||||
// keep checking for postfix expressions. Otherwise, it's just a '<' that's
|
||||
// part of an arithmetic expression. Break out so we consume it higher in the
|
||||
// stack.
|
||||
const typeArguments = tryParse(parseTypeArgumentsInExpression);
|
||||
// If we have seen `?.<` then this is definately a call expression.
|
||||
typeArguments = questionDot
|
||||
? parseTypeArgumentsInExpression()
|
||||
: tryParse(parseTypeArgumentsInExpression);
|
||||
if (!typeArguments) {
|
||||
return expression;
|
||||
}
|
||||
|
||||
const callExpr = <CallExpression>createNode(SyntaxKind.CallExpression, expression.pos);
|
||||
callExpr.expression = expression;
|
||||
callExpr.typeArguments = typeArguments;
|
||||
callExpr.arguments = parseArgumentList();
|
||||
expression = finishNode(callExpr);
|
||||
continue;
|
||||
}
|
||||
else if (token() === SyntaxKind.OpenParenToken) {
|
||||
const callExpr = <CallExpression>createNode(SyntaxKind.CallExpression, expression.pos);
|
||||
callExpr.expression = expression;
|
||||
callExpr.arguments = parseArgumentList();
|
||||
expression = finishNode(callExpr);
|
||||
continue;
|
||||
else if (token() !== SyntaxKind.OpenParenToken) {
|
||||
return expression;
|
||||
}
|
||||
|
||||
return expression;
|
||||
const callExpr = <CallExpression>createNode(SyntaxKind.CallExpression, expression.pos);
|
||||
if (questionDot) {
|
||||
callExpr.flags |= NodeFlags.PropagateNull;
|
||||
}
|
||||
|
||||
callExpr.expression = expression;
|
||||
callExpr.typeArguments = typeArguments;
|
||||
callExpr.arguments = parseArgumentList();
|
||||
expression = finishNode(callExpr);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4121,6 +4152,7 @@ namespace ts {
|
||||
// list. So we definitely want to treat this as a type arg list.
|
||||
|
||||
case SyntaxKind.DotToken: // foo<x>.
|
||||
case SyntaxKind.QuestionDotToken: // foo<x>?.
|
||||
case SyntaxKind.CloseParenToken: // foo<x>)
|
||||
case SyntaxKind.CloseBracketToken: // foo<x>]
|
||||
case SyntaxKind.ColonToken: // foo<x>:
|
||||
@ -4365,6 +4397,10 @@ namespace ts {
|
||||
|
||||
const node = <NewExpression>createNode(SyntaxKind.NewExpression, fullStart);
|
||||
node.expression = parseMemberExpressionOrHigher();
|
||||
if (parseOptional(SyntaxKind.QuestionDotToken)) {
|
||||
node.flags |= NodeFlags.PropagateNull;
|
||||
}
|
||||
|
||||
node.typeArguments = tryParse(parseTypeArgumentsInExpression);
|
||||
if (node.typeArguments || token() === SyntaxKind.OpenParenToken) {
|
||||
node.arguments = parseArgumentList();
|
||||
|
||||
@ -167,6 +167,7 @@ namespace ts {
|
||||
"&&": SyntaxKind.AmpersandAmpersandToken,
|
||||
"||": SyntaxKind.BarBarToken,
|
||||
"?": SyntaxKind.QuestionToken,
|
||||
"?.": SyntaxKind.QuestionDotToken,
|
||||
":": SyntaxKind.ColonToken,
|
||||
"=": SyntaxKind.EqualsToken,
|
||||
"+=": SyntaxKind.PlusEqualsToken,
|
||||
@ -1529,6 +1530,9 @@ namespace ts {
|
||||
pos++;
|
||||
return token = SyntaxKind.GreaterThanToken;
|
||||
case CharacterCodes.question:
|
||||
if (text.charCodeAt(pos + 1) === CharacterCodes.dot && !isDigit(text.charCodeAt(pos + 2))) {
|
||||
return pos += 2, token = SyntaxKind.QuestionDotToken;
|
||||
}
|
||||
pos++;
|
||||
return token = SyntaxKind.QuestionToken;
|
||||
case CharacterCodes.openBracket:
|
||||
|
||||
@ -26,6 +26,7 @@ namespace ts {
|
||||
const previousOnSubstituteNode = context.onSubstituteNode;
|
||||
context.onSubstituteNode = onSubstituteNode;
|
||||
|
||||
const nullPropagatingExpressions = createMap<boolean>();
|
||||
let enabledSubstitutions: ESNextSubstitutionFlags;
|
||||
let enclosingFunctionFlags: FunctionFlags;
|
||||
let enclosingSuperContainerFlags: NodeCheckFlags = 0;
|
||||
@ -39,6 +40,7 @@ namespace ts {
|
||||
|
||||
const visited = visitEachChild(node, visitor, context);
|
||||
addEmitHelpers(visited, context.readEmitHelpers());
|
||||
nullPropagatingExpressions.clear();
|
||||
return visited;
|
||||
}
|
||||
|
||||
@ -101,6 +103,20 @@ namespace ts {
|
||||
return visitExpressionStatement(node as ExpressionStatement);
|
||||
case SyntaxKind.ParenthesizedExpression:
|
||||
return visitParenthesizedExpression(node as ParenthesizedExpression, noDestructuringValue);
|
||||
case SyntaxKind.CallExpression:
|
||||
return visitCallExpression(node as CallExpression);
|
||||
case SyntaxKind.NewExpression:
|
||||
return visitNewExpression(node as NewExpression);
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
return visitPropertyAccess(node as PropertyAccessExpression);
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
return visitElementAccess(node as ElementAccessExpression);
|
||||
case SyntaxKind.DeleteExpression:
|
||||
return visitDelete(node as DeleteExpression);
|
||||
case SyntaxKind.PrefixUnaryExpression:
|
||||
return visitPrefix(node as PrefixUnaryExpression);
|
||||
case SyntaxKind.PostfixUnaryExpression:
|
||||
return visitPostfix(node as PostfixUnaryExpression);
|
||||
default:
|
||||
return visitEachChild(node, visitor, context);
|
||||
}
|
||||
@ -230,7 +246,20 @@ namespace ts {
|
||||
visitNode(node.right, noDestructuringValue ? visitorNoDestructuringValue : visitor, isExpression)
|
||||
);
|
||||
}
|
||||
return visitEachChild(node, visitor, context);
|
||||
|
||||
const left = visitNode(node.left, visitor, isExpression);
|
||||
const right = visitNode(node.right, visitor, isExpression);
|
||||
if (isAssignmentExpression(node)) {
|
||||
const nilReference = getNilReference(left);
|
||||
if (nilReference) {
|
||||
return updateNilReference(
|
||||
nilReference,
|
||||
updateBinary(node, nilReference.whenFalse, right)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return updateBinary(node, left, right);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -699,6 +728,310 @@ namespace ts {
|
||||
return statements;
|
||||
}
|
||||
|
||||
function isNullPropagatingExpression(node: CallExpression | NewExpression | PropertyAccessExpression | ElementAccessExpression) {
|
||||
return (node.flags & NodeFlags.PropagateNull) !== 0
|
||||
&& node.expression.kind !== SyntaxKind.SuperKeyword;
|
||||
}
|
||||
|
||||
function getNilReference(expression: Expression): ConditionalExpression {
|
||||
expression = skipOuterExpressions(expression);
|
||||
return isConditionalExpression(expression)
|
||||
&& isBinaryExpression(expression.condition)
|
||||
&& expression.condition.operatorToken.kind === SyntaxKind.EqualsEqualsToken
|
||||
&& expression.condition.right.kind === SyntaxKind.NullKeyword
|
||||
&& isVoidZero(expression.whenTrue)
|
||||
? expression
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function updateNilReference(expression: ConditionalExpression, whenNotNil: Expression) {
|
||||
return setTextRange(
|
||||
createConditional(
|
||||
expression.condition,
|
||||
expression.whenTrue,
|
||||
whenNotNil
|
||||
),
|
||||
whenNotNil
|
||||
);
|
||||
}
|
||||
|
||||
function propagateNull<T extends Node>(finishExpression: (node: T, nullableExpression: Expression) => Expression, node: T, nullableExpression: Expression): Expression;
|
||||
function propagateNull<T extends Node, U>(finishExpression: (node: T, nullableExpression: Expression, data: U) => Expression, node: T, nullableExpression: Expression, data: U): Expression;
|
||||
function propagateNull<T extends Node, U>(finishExpression: (node: T, nullableExpression: Expression, data: U) => Expression, node: T, nullableExpression: Expression, data?: U): Expression {
|
||||
if (node.flags & NodeFlags.PropagateNull) {
|
||||
if (isIdentifier(nullableExpression)) {
|
||||
return setTextRange(
|
||||
createConditional(
|
||||
createEquality(nullableExpression, createNull()),
|
||||
createVoidZero(),
|
||||
finishExpression(node, nullableExpression, data)
|
||||
),
|
||||
node
|
||||
);
|
||||
}
|
||||
else {
|
||||
const temp = createTempVariable(hoistVariableDeclaration);
|
||||
return setTextRange(
|
||||
createConditional(
|
||||
createEquality(
|
||||
createAssignment(temp, nullableExpression),
|
||||
createNull()
|
||||
),
|
||||
createVoidZero(),
|
||||
finishExpression(node, temp, data)
|
||||
),
|
||||
node
|
||||
);
|
||||
}
|
||||
}
|
||||
return finishExpression(node, nullableExpression, data);
|
||||
}
|
||||
|
||||
function visitCallExpression(node: CallExpression): Expression {
|
||||
if (isNullPropagatingExpression(node)) {
|
||||
// null propagation in call:
|
||||
// x?.() -> x == null ? void 0 : x()
|
||||
// (x)?.() -> (_a = (x)) == null ? void 0 : _a()
|
||||
// x.y?.() -> (_a = x.y) == null ? void 0 : _a.call(x);
|
||||
// x[y]?.() -> (_a = x[y]) == null ? void 0 : _a.call(x);
|
||||
// (x.y)?.() -> (_a = x.y) == null ? void 0 : _a.call(x);
|
||||
// (x[y])?.() -> (_a = x[y]) == null ? void 0 : _a.call(x);
|
||||
const { target, thisArg } = createCallBinding(node.expression, hoistVariableDeclaration);
|
||||
return propagateNull(finishNullableCallExpression, node, visitNode(target, visitor, isExpression), visitNode(thisArg, visitor, isExpression));
|
||||
}
|
||||
|
||||
const expression = visitNode(node.expression, visitor, isExpression);
|
||||
const argumentsArray = visitNodes(node.arguments, visitor, isExpression);
|
||||
const nilReference = getNilReference(expression);
|
||||
if (nilReference) {
|
||||
// NilReference shortcut in expression
|
||||
// x?.y() -> x == null ? void 0 : x.y();
|
||||
// x?.[y]() -> x == null ? void 0 : x[y]();
|
||||
return updateNilReference(
|
||||
nilReference,
|
||||
updateCall(
|
||||
node,
|
||||
nilReference.whenFalse,
|
||||
/*typeArguments*/ undefined,
|
||||
argumentsArray
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return updateCall(
|
||||
node,
|
||||
expression,
|
||||
/*typeArguments*/ undefined,
|
||||
argumentsArray
|
||||
);
|
||||
}
|
||||
|
||||
function finishNullableCallExpression(node: CallExpression, target: Expression, thisArg: Expression) {
|
||||
if (!isVoidZero(thisArg)) {
|
||||
return setOriginalNode(
|
||||
createFunctionCall(
|
||||
target,
|
||||
thisArg,
|
||||
visitNodes(node.arguments, visitor, isExpression),
|
||||
node
|
||||
),
|
||||
node
|
||||
);
|
||||
}
|
||||
return setOriginalNode(
|
||||
setTextRange(
|
||||
createCall(
|
||||
target,
|
||||
/*typeArguments*/ undefined,
|
||||
visitNodes(node.arguments, visitor, isExpression)
|
||||
),
|
||||
node
|
||||
),
|
||||
node
|
||||
);
|
||||
}
|
||||
|
||||
function visitNewExpression(node: NewExpression): Expression {
|
||||
const expression = visitNode(node.expression, visitor, isExpression);
|
||||
if (isNullPropagatingExpression(node)) {
|
||||
// null propagation in new:
|
||||
// new x?.() -> x == null ? void 0 : new x();
|
||||
// new (x)?.() -> (_a = (x)) == null ? void 0 : new _a();
|
||||
// new x.y?.() -> (_a = x.y) == null ? void 0 : new _a();
|
||||
// new x[y]?.() -> (_a = x[y]) == null ? void 0 : new _a();
|
||||
// new (x.y)?.() -> (_a = x.y) == null ? void 0 : new _a();
|
||||
// new (x[y])?.() -> (_a = x[y]) == null ? void 0 : new _a();
|
||||
return propagateNull(finishNullableNewExpression, node, expression);
|
||||
}
|
||||
|
||||
const argumentsArray = visitNodes(node.arguments, visitor, isExpression);
|
||||
const nilReference = getNilReference(expression);
|
||||
if (nilReference) {
|
||||
// NilReference shortcut in expression
|
||||
// new x?.y() -> x == null ? void 0 : new x.y();
|
||||
// new x?.[y]() -> x == null ? void 0 : new x[y]();
|
||||
return updateNilReference(
|
||||
nilReference,
|
||||
updateNew(
|
||||
node,
|
||||
nilReference.whenTrue,
|
||||
/*typeArguments*/ undefined,
|
||||
argumentsArray
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return updateNew(
|
||||
node,
|
||||
expression,
|
||||
/*typeArguments*/ undefined,
|
||||
argumentsArray);
|
||||
}
|
||||
|
||||
function finishNullableNewExpression(node: NewExpression, expression: Expression) {
|
||||
return setOriginalNode(
|
||||
setTextRange(
|
||||
createNew(
|
||||
expression,
|
||||
/*typeArguments*/ undefined,
|
||||
visitNodes(node.arguments, visitor, isExpression)
|
||||
),
|
||||
node
|
||||
),
|
||||
node
|
||||
);
|
||||
}
|
||||
|
||||
function visitPropertyAccess(node: PropertyAccessExpression): Expression {
|
||||
const expression = visitNode(node.expression, visitor, isExpression);
|
||||
if (isNullPropagatingExpression(node)) {
|
||||
// null propagation in property access
|
||||
// x?.y -> x == null ? void 0 : x.y;
|
||||
// x.y?.z -> (_a = x.y) == null ? void 0 : _a.z;
|
||||
return propagateNull(finishNullablePropertyAccess, node, expression);
|
||||
}
|
||||
|
||||
const name = visitNode(node.name, visitor, isIdentifier);
|
||||
const nilReference = getNilReference(expression);
|
||||
if (nilReference) {
|
||||
// NilReference shortcut in expression
|
||||
// x?.y.z -> x == null ? void 0 : x.y.z;
|
||||
// x.y?.z.a -> (_a = x.y) == null ? void 0 : _a.z.a;
|
||||
return updateNilReference(
|
||||
nilReference,
|
||||
updatePropertyAccess(
|
||||
node,
|
||||
nilReference.whenFalse,
|
||||
name
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return updatePropertyAccess(
|
||||
node,
|
||||
expression,
|
||||
name
|
||||
);
|
||||
}
|
||||
|
||||
function finishNullablePropertyAccess(node: PropertyAccessExpression, expression: Expression) {
|
||||
return setOriginalNode(
|
||||
setTextRange(
|
||||
createPropertyAccess(
|
||||
expression,
|
||||
node.name
|
||||
),
|
||||
node
|
||||
),
|
||||
node
|
||||
);
|
||||
}
|
||||
|
||||
function visitElementAccess(node: ElementAccessExpression): Expression {
|
||||
const expression = visitNode(node.expression, visitor, isExpression);
|
||||
if (isNullPropagatingExpression(node)) {
|
||||
// null propagation in element access
|
||||
// x?.[y] -> x == null ? void 0 : x.[y];
|
||||
// x.y?.[z] -> (_a = x.y) == null ? void 0 : _a.[z];
|
||||
return propagateNull(finishNullableElementAccess, node, expression);
|
||||
}
|
||||
|
||||
const argumentExpression = visitNode(node.argumentExpression, visitor, isExpression);
|
||||
const nilReference = getNilReference(expression);
|
||||
if (nilReference) {
|
||||
// NilReference shortcut in expression
|
||||
// x?.y.[z] -> x == null ? void 0 : x.y.[z];
|
||||
// x.y?.z.[a] -> (_a = x.y) == null ? void 0 : _a.z.[a];
|
||||
return updateNilReference(
|
||||
nilReference,
|
||||
updateElementAccess(
|
||||
node,
|
||||
nilReference.whenFalse,
|
||||
argumentExpression
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return updateElementAccess(
|
||||
node,
|
||||
expression,
|
||||
argumentExpression
|
||||
);
|
||||
}
|
||||
|
||||
function finishNullableElementAccess(node: ElementAccessExpression, expression: Expression) {
|
||||
return setOriginalNode(
|
||||
setTextRange(
|
||||
createElementAccess(
|
||||
expression,
|
||||
visitNode(node.argumentExpression, visitor, isExpression)
|
||||
),
|
||||
node
|
||||
),
|
||||
node
|
||||
);
|
||||
}
|
||||
|
||||
function visitDelete(node: DeleteExpression) {
|
||||
const expression = visitNode(node.expression, visitor, isExpression);
|
||||
const nilReference = getNilReference(expression);
|
||||
if (nilReference) {
|
||||
return setTextRange(
|
||||
createConditional(
|
||||
nilReference.condition,
|
||||
createTrue(),
|
||||
updateDelete(node, nilReference.whenFalse)
|
||||
),
|
||||
node
|
||||
);
|
||||
}
|
||||
return updateDelete(node, expression);
|
||||
}
|
||||
|
||||
function visitPrefix(node: PrefixUnaryExpression) {
|
||||
const operand = visitNode(node.operand, visitor, isExpression);
|
||||
const nilReference = getNilReference(operand);
|
||||
if (nilReference) {
|
||||
return updateNilReference(
|
||||
nilReference,
|
||||
updatePrefix(node, nilReference.whenFalse)
|
||||
);
|
||||
}
|
||||
return updatePrefix(node, operand);
|
||||
}
|
||||
|
||||
function visitPostfix(node: PostfixUnaryExpression) {
|
||||
const operand = visitNode(node.operand, visitor, isExpression);
|
||||
const nilReference = getNilReference(operand);
|
||||
if (nilReference) {
|
||||
return updateNilReference(
|
||||
nilReference,
|
||||
updatePostfix(node, nilReference.whenFalse)
|
||||
);
|
||||
}
|
||||
return updatePostfix(node, operand);
|
||||
}
|
||||
|
||||
function enableSubstitutionForAsyncMethodsWithSuper() {
|
||||
if ((enabledSubstitutions & ESNextSubstitutionFlags.AsyncMethodsWithSuper) === 0) {
|
||||
enabledSubstitutions |= ESNextSubstitutionFlags.AsyncMethodsWithSuper;
|
||||
|
||||
@ -82,6 +82,7 @@
|
||||
DotDotDotToken,
|
||||
SemicolonToken,
|
||||
CommaToken,
|
||||
QuestionDotToken,
|
||||
LessThanToken,
|
||||
LessThanSlashToken,
|
||||
GreaterThanToken,
|
||||
@ -435,20 +436,21 @@
|
||||
NestedNamespace = 1 << 2, // Namespace declaration
|
||||
Synthesized = 1 << 3, // Node was synthesized during transformation
|
||||
Namespace = 1 << 4, // Namespace declaration
|
||||
ExportContext = 1 << 5, // Export context (initialized by binding)
|
||||
ContainsThis = 1 << 6, // Interface contains references to "this"
|
||||
HasImplicitReturn = 1 << 7, // If function implicitly returns on one of codepaths (initialized by binding)
|
||||
HasExplicitReturn = 1 << 8, // If function has explicit reachable return on one of codepaths (initialized by binding)
|
||||
GlobalAugmentation = 1 << 9, // Set if module declaration is an augmentation for the global scope
|
||||
HasAsyncFunctions = 1 << 10, // If the file has async functions (initialized by binding)
|
||||
DisallowInContext = 1 << 11, // If node was parsed in a context where 'in-expressions' are not allowed
|
||||
YieldContext = 1 << 12, // If node was parsed in the 'yield' context created when parsing a generator
|
||||
DecoratorContext = 1 << 13, // If node was parsed as part of a decorator
|
||||
AwaitContext = 1 << 14, // If node was parsed in the 'await' context created when parsing an async function
|
||||
ThisNodeHasError = 1 << 15, // If the parser encountered an error when parsing the code that created this node
|
||||
JavaScriptFile = 1 << 16, // If node was parsed in a JavaScript
|
||||
ThisNodeOrAnySubNodesHasError = 1 << 17, // If this node or any of its children had an error
|
||||
HasAggregatedChildData = 1 << 18, // If we've computed data from children and cached it in this node
|
||||
PropagateNull = 1 << 5, // Expression
|
||||
ExportContext = 1 << 6, // Export context (initialized by binding)
|
||||
ContainsThis = 1 << 7, // Interface contains references to "this"
|
||||
HasImplicitReturn = 1 << 8, // If function implicitly returns on one of codepaths (initialized by binding)
|
||||
HasExplicitReturn = 1 << 9, // If function has explicit reachable return on one of codepaths (initialized by binding)
|
||||
GlobalAugmentation = 1 << 10, // Set if module declaration is an augmentation for the global scope
|
||||
HasAsyncFunctions = 1 << 11, // If the file has async functions (initialized by binding)
|
||||
DisallowInContext = 1 << 12, // If node was parsed in a context where 'in-expressions' are not allowed
|
||||
YieldContext = 1 << 13, // If node was parsed in the 'yield' context created when parsing a generator
|
||||
DecoratorContext = 1 << 14, // If node was parsed as part of a decorator
|
||||
AwaitContext = 1 << 15, // If node was parsed in the 'await' context created when parsing an async function
|
||||
ThisNodeHasError = 1 << 16, // If the parser encountered an error when parsing the code that created this node
|
||||
JavaScriptFile = 1 << 17, // If node was parsed in a JavaScript
|
||||
ThisNodeOrAnySubNodesHasError = 1 << 18, // If this node or any of its children had an error
|
||||
HasAggregatedChildData = 1 << 19, // If we've computed data from children and cached it in this node
|
||||
|
||||
BlockScoped = Let | Const,
|
||||
|
||||
|
||||
@ -3583,10 +3583,6 @@ namespace ts {
|
||||
return node.kind === SyntaxKind.Identifier;
|
||||
}
|
||||
|
||||
export function isVoidExpression(node: Node): node is VoidExpression {
|
||||
return node.kind === SyntaxKind.VoidExpression;
|
||||
}
|
||||
|
||||
export function isGeneratedIdentifier(node: Node): node is GeneratedIdentifier {
|
||||
// Using `>` here catches both `GeneratedIdentifierKind.None` and `undefined`.
|
||||
return isIdentifier(node) && node.autoGenerateKind > GeneratedIdentifierKind.None;
|
||||
@ -3897,6 +3893,16 @@ namespace ts {
|
||||
return isExpressionKind(skipPartiallyEmittedExpressions(node).kind);
|
||||
}
|
||||
|
||||
export function isVoidExpression(node: Node): node is VoidExpression {
|
||||
return node.kind === SyntaxKind.VoidExpression;
|
||||
}
|
||||
|
||||
export function isVoidZero(node: Node): boolean {
|
||||
return isVoidExpression(node)
|
||||
&& node.expression.kind === SyntaxKind.NumericLiteral
|
||||
&& (<NumericLiteral>node.expression).text === "0";
|
||||
}
|
||||
|
||||
export function isAssertionExpression(node: Node): node is AssertionExpression {
|
||||
const kind = node.kind;
|
||||
return kind === SyntaxKind.TypeAssertionExpression
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user