diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index acc9db3e629f8..6f2b1efb19c61 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -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; } @@ -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) { @@ -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) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2328b7e3d68a4..5c81e13d92cad 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -383,6 +383,7 @@ namespace ts { getReturnTypeOfSignature, getNullableType, getNonNullableType, + getNonOptionalType: removeOptionalTypeMarker, typeToTypeNode: nodeBuilder.typeToTypeNode, indexInfoToIndexSignatureDeclaration: nodeBuilder.indexInfoToIndexSignatureDeclaration, signatureToSignatureDeclaration: nodeBuilder.signatureToSignatureDeclaration, @@ -542,6 +543,7 @@ namespace ts { getNullType: () => nullType, getESSymbolType: () => esSymbolType, getNeverType: () => neverType, + getOptionalType: () => optionalType, isSymbolAccessible, getObjectFlags, isArrayType, @@ -644,6 +646,7 @@ namespace ts { const unknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined"); const undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType); + const optionalType = createIntrinsicType(TypeFlags.Undefined, "undefined"); const nullType = createIntrinsicType(TypeFlags.Null, "null"); const nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null, "null", ObjectFlags.ContainsWideningType); const stringType = createIntrinsicType(TypeFlags.String, "string"); @@ -7157,6 +7160,12 @@ namespace ts { return result; } + function createOptionalCallSignature(signature: Signature) { + const result = cloneSignature(signature); + result.isOptionalCall = true; + return result; + } + function getExpandedParameters(sig: Signature): ReadonlyArray { if (sig.hasRestParameter) { const restIndex = sig.parameters.length - 1; @@ -8779,6 +8788,9 @@ namespace ts { signature.unionSignatures ? getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature), UnionReduction.Subtype) : getReturnTypeFromAnnotation(signature.declaration!) || (nodeIsMissing((signature.declaration).body) ? anyType : getReturnTypeFromBody(signature.declaration)); + if (signature.isOptionalCall) { + type = propagateOptionalTypeMarker(type, /*wasOptional*/ true); + } if (!popTypeResolution()) { if (signature.declaration) { const typeNode = getEffectiveReturnTypeNode(signature.declaration); @@ -14034,7 +14046,7 @@ namespace ts { for (const sourceProp of excludeProperties(getPropertiesOfType(source), excludedProperties)) { if (!getPropertyOfObjectType(target, sourceProp.escapedName)) { const sourceType = getTypeOfSymbol(sourceProp); - if (!(sourceType === undefinedType || sourceType === undefinedWideningType)) { + if (!(sourceType === undefinedType || sourceType === undefinedWideningType || sourceType === optionalType)) { if (reportErrors) { reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(sourceProp), typeToString(target)); } @@ -14977,6 +14989,50 @@ namespace ts { return strictNullChecks ? getGlobalNonNullableTypeInstantiation(type) : type; } + function addOptionalTypeMarker(type: Type) { + return strictNullChecks ? getUnionType([type, optionalType]) : type; + } + + function removeOptionalTypeMarker(type: Type): Type { + return strictNullChecks ? filterType(type, t => t !== optionalType) : type; + } + + function propagateOptionalTypeMarker(type: Type, wasOptional: boolean) { + return wasOptional ? addOptionalTypeMarker(type) : type; + } + + function checkOptionalExpression( + parent: PropertyAccessExpression | QualifiedName | ElementAccessExpression | CallExpression, + expression: Expression | QualifiedName, + nullDiagnostic?: DiagnosticMessage, + undefinedDiagnostic?: DiagnosticMessage, + nullOrUndefinedDiagnostic?: DiagnosticMessage, + ) { + let isOptional = false; + let type = checkExpression(expression); + if (isOptionalChain(parent)) { + if (parent.questionDotToken) { + // If we have a questionDotToken then we are an OptionalExpression and should remove `null` and + // `undefined` from the type and add the optionalType to the result, if needed. + if (isNullableType(type)) { + isOptional = true; + type = getNonNullableType(type); + } + return { isOptional, type }; + } + + // If we do not have a questionDotToken, then we are an OptionalChain and we remove the optionalType and + // add it back into the result, if needed. + const nonOptionalType = removeOptionalTypeMarker(type); + if (nonOptionalType !== type) { + isOptional = true; + type = nonOptionalType; + } + } + + type = checkNonNullType(type, expression, nullDiagnostic, undefinedDiagnostic, nullOrUndefinedDiagnostic); + return { isOptional, type }; + } /** * Is source potentially coercible to target type under `==`. @@ -20668,12 +20724,12 @@ namespace ts { ); } + function isNullableType(type: Type) { + return !!((strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable); + } + function getNonNullableTypeIfNeeded(type: Type) { - const kind = (strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable; - if (kind) { - return getNonNullableType(type); - } - return type; + return isNullableType(type) ? getNonNullableType(type) : type; } function checkNonNullType( @@ -20724,8 +20780,7 @@ namespace ts { } function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) { - let propType: Type; - const leftType = checkNonNullExpression(left); + const { isOptional, type: leftType } = checkOptionalExpression(node, left); const parentSymbol = getNodeLinks(left).resolvedSymbol; const assignmentKind = getAssignmentTargetKind(node); const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(leftType) : leftType); @@ -20739,6 +20794,8 @@ namespace ts { if (isIdentifier(left) && parentSymbol && !(prop && isConstEnumOrConstEnumOnlyModule(prop))) { markAliasReferenced(parentSymbol, node); } + + let propType: Type; if (!prop) { const indexInfo = assignmentKind === AssignmentKind.None || !isGenericObjectType(leftType) || isThisTypeParameter(leftType) ? getIndexInfoOfType(apparentType, IndexKind.String) : undefined; if (!(indexInfo && indexInfo.type)) { @@ -20777,7 +20834,7 @@ namespace ts { } propType = getConstraintForLocation(getTypeOfSymbol(prop), node); } - return getFlowTypeOfAccessExpression(node, prop, propType, right); + return propagateOptionalTypeMarker(getFlowTypeOfAccessExpression(node, prop, propType, right), isOptional); } function getFlowTypeOfAccessExpression(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, propType: Type, errorNode: Node) { @@ -21134,9 +21191,8 @@ namespace ts { } function checkIndexedAccess(node: ElementAccessExpression): Type { - const exprType = checkNonNullExpression(node.expression); + const { isOptional, type: exprType } = checkOptionalExpression(node, node.expression); const objectType = getAssignmentTargetKind(node) !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(exprType) : exprType; - const indexExpression = node.argumentExpression; const indexType = checkExpression(indexExpression); @@ -21154,7 +21210,7 @@ namespace ts { AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) : AccessFlags.None; const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, node, accessFlags) || errorType; - return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node); + return propagateOptionalTypeMarker(checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node), isOptional); } function checkThatExpressionIsProperSymbolReference(expression: Expression, expressionType: Type, reportError: boolean): boolean { @@ -21237,7 +21293,7 @@ namespace ts { // interface B extends A { (x: 'foo'): string } // const b: B; // b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void] - function reorderCandidates(signatures: ReadonlyArray, result: Signature[]): void { + function reorderCandidates(signatures: ReadonlyArray, result: Signature[], isOptionalCall: boolean): void { let lastParent: Node | undefined; let lastSymbol: Symbol | undefined; let cutoffIndex = 0; @@ -21279,7 +21335,7 @@ namespace ts { spliceIndex = index; } - result.splice(spliceIndex, 0, signature); + result.splice(spliceIndex, 0, isOptionalCall ? createOptionalCallSignature(signature) : signature); } } @@ -21932,7 +21988,7 @@ namespace ts { return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, belowArgCount === -Infinity ? aboveArgCount : belowArgCount, argCount); } - function resolveCall(node: CallLikeExpression, signatures: ReadonlyArray, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, fallbackError?: DiagnosticMessage): Signature { + function resolveCall(node: CallLikeExpression, signatures: ReadonlyArray, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, isOptionalCall: boolean, fallbackError?: DiagnosticMessage): Signature { const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; const isDecorator = node.kind === SyntaxKind.Decorator; const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node); @@ -21951,7 +22007,7 @@ namespace ts { const candidates = candidatesOutArray || []; // reorderCandidates fills up the candidates array directly - reorderCandidates(signatures, candidates); + reorderCandidates(signatures, candidates, isOptionalCall); if (!candidates.length) { if (reportErrors) { diagnostics.add(getDiagnosticForCallNode(node, Diagnostics.Call_target_does_not_contain_any_signatures)); @@ -22335,13 +22391,14 @@ namespace ts { const baseTypeNode = getEffectiveBaseTypeNode(getContainingClass(node)!); if (baseTypeNode) { const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode); - return resolveCall(node, baseConstructors, candidatesOutArray, checkMode); + return resolveCall(node, baseConstructors, candidatesOutArray, checkMode, /*isOptional*/ false); } } return resolveUntypedCall(node); } - const funcType = checkNonNullExpression( + const { isOptional, type: funcType } = checkOptionalExpression( + node, node.expression, Diagnostics.Cannot_invoke_an_object_which_is_possibly_null, Diagnostics.Cannot_invoke_an_object_which_is_possibly_undefined, @@ -22351,8 +22408,8 @@ namespace ts { if (funcType === silentNeverType) { return silentNeverSignature; } - const apparentType = getApparentType(funcType); + const apparentType = getApparentType(funcType); if (apparentType === errorType) { // Another error has already been reported return resolveErrorCall(node); @@ -22416,7 +22473,8 @@ namespace ts { error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType)); return resolveErrorCall(node); } - return resolveCall(node, callSignatures, candidatesOutArray, checkMode); + + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, isOptional); } function isGenericFunctionReturningFunction(signature: Signature) { @@ -22487,7 +22545,7 @@ namespace ts { return resolveErrorCall(node); } - return resolveCall(node, constructSignatures, candidatesOutArray, checkMode); + return resolveCall(node, constructSignatures, candidatesOutArray, checkMode, /*isOptional*/ false); } // If expressionType's apparent type is an object type with no construct signatures but @@ -22496,7 +22554,7 @@ namespace ts { // operation is Any. It is an error to have a Void this type. const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call); if (callSignatures.length) { - const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode); + const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode, /*isOptional*/ false); if (!noImplicitAny) { if (signature.declaration && !isJSConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) { error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword); @@ -22711,7 +22769,7 @@ namespace ts { return resolveErrorCall(node); } - return resolveCall(node, callSignatures, candidatesOutArray, checkMode); + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, /*isOptional*/ false); } /** @@ -22774,7 +22832,7 @@ namespace ts { return resolveErrorCall(node); } - return resolveCall(node, callSignatures, candidatesOutArray, checkMode, headMessage); + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, /*isOptional*/ false, headMessage); } function createSignatureForJSXIntrinsic(node: JsxOpeningLikeElement, result: Type): Signature { @@ -22827,7 +22885,7 @@ namespace ts { return resolveErrorCall(node); } - return resolveCall(node, signatures, candidatesOutArray, checkMode); + return resolveCall(node, signatures, candidatesOutArray, checkMode, /*isOptional*/ false); } /** @@ -23004,18 +23062,20 @@ namespace ts { if (returnType.flags & TypeFlags.ESSymbolLike && isSymbolOrSymbolForCall(node)) { return getESSymbolLikeTypeForNode(walkUpParenthesizedExpressions(node.parent)); } - let jsAssignmentType: Type | undefined; + if (isInJSFile(node)) { const decl = getDeclarationOfExpando(node); if (decl) { const jsSymbol = getSymbolOfNode(decl); if (jsSymbol && hasEntries(jsSymbol.exports)) { - jsAssignmentType = createAnonymousType(jsSymbol, jsSymbol.exports, emptyArray, emptyArray, undefined, undefined); - (jsAssignmentType as ObjectType).objectFlags |= ObjectFlags.JSLiteral; + const jsAssignmentType = createAnonymousType(jsSymbol, jsSymbol.exports, emptyArray, emptyArray, undefined, undefined); + jsAssignmentType.objectFlags |= ObjectFlags.JSLiteral; + return getIntersectionType([returnType, jsAssignmentType]); } } } - return jsAssignmentType ? getIntersectionType([returnType, jsAssignmentType]) : returnType; + + return returnType; } function isSymbolOrSymbolForCall(node: Node) { @@ -25270,11 +25330,11 @@ namespace ts { const expr = skipParentheses(node); // Optimize for the common case of a call to a function with a single non-generic call // signature where we can just fetch the return type without checking the arguments. - if (expr.kind === SyntaxKind.CallExpression && (expr).expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(expr)) { - const funcType = checkNonNullExpression((expr).expression); + if (isCallExpression(expr) && expr.expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(expr)) { + const { isOptional, type: funcType } = checkOptionalExpression(expr, expr.expression); const signature = getSingleCallSignature(funcType); if (signature && !signature.typeParameters) { - return getReturnTypeOfSignature(signature); + return propagateOptionalTypeMarker(getReturnTypeOfSignature(signature), isOptional); } } else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) { diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts index c90f4d1fb01f5..933e649d72d64 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -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), diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 57cc47cf0e12a..611d4565f0fff 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -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(); @@ -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) { @@ -1076,6 +1077,8 @@ namespace ts { currentSourceFile = undefined!; currentLineMap = undefined!; detachedCommentsInfo = undefined; + lastNode = undefined; + lastSubstitution = undefined; setWriter(/*output*/ undefined, /*_sourceMapGenerator*/ undefined); } @@ -1083,24 +1086,47 @@ namespace ts { 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) { @@ -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)); @@ -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)) { @@ -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 { @@ -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) { @@ -2173,34 +2204,24 @@ 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); @@ -2208,28 +2229,27 @@ namespace ts { // 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(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); @@ -2237,6 +2257,7 @@ namespace ts { function emitCallExpression(node: CallExpression) { emitExpression(node.expression); + emit(node.questionDotToken); emitTypeArguments(node, node.typeArguments); emitExpressionList(node, node.arguments, ListFormat.CallExpressionArguments); } @@ -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); @@ -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); @@ -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); @@ -4693,6 +4718,7 @@ namespace ts { } } exitComment(); + Debug.assert(lastNode === node || lastSubstitution === node); } function emitLeadingSynthesizedComment(comment: SynthesizedComment) { @@ -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); @@ -4981,6 +5008,7 @@ namespace ts { emitSourcePos(source, end); } } + Debug.assert(lastNode === node || lastSubstitution === node); } /** diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index d0d617b349531..ea35b30eed44d 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1013,6 +1013,7 @@ namespace ts { } export function updatePropertyAccess(node: PropertyAccessExpression, expression: Expression, name: Identifier) { + Debug.assert(!(node.flags & NodeFlags.OptionalChain), "Cannot update a PropertyAccessChain using updatePropertyAccess. Use updatePropertyAccessChain instead."); // Because we are updating existed propertyAccess we want to inherit its emitFlags // instead of using the default from createPropertyAccess return node.expression !== expression @@ -1021,6 +1022,27 @@ namespace ts { : node; } + export function createPropertyAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, name: string | Identifier) { + const node = createSynthesizedNode(SyntaxKind.PropertyAccessExpression); + node.flags |= NodeFlags.OptionalChain; + node.expression = parenthesizeForAccess(expression); + node.questionDotToken = questionDotToken; + node.name = asName(name); + setEmitFlags(node, EmitFlags.NoIndentation); + return node; + } + + export function updatePropertyAccessChain(node: PropertyAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, name: Identifier) { + Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a PropertyAccessExpression using updatePropertyAccessChain. Use updatePropertyAccess instead."); + // Because we are updating an existing PropertyAccessChain we want to inherit its emitFlags + // instead of using the default from createPropertyAccess + return node.expression !== expression + || node.questionDotToken !== questionDotToken + || node.name !== name + ? updateNode(setEmitFlags(createPropertyAccessChain(expression, questionDotToken, name), getEmitFlags(node)), node) + : node; + } + export function createElementAccess(expression: Expression, index: number | Expression) { const node = createSynthesizedNode(SyntaxKind.ElementAccessExpression); node.expression = parenthesizeForAccess(expression); @@ -1029,12 +1051,31 @@ namespace ts { } export function updateElementAccess(node: ElementAccessExpression, expression: Expression, argumentExpression: Expression) { + Debug.assert(!(node.flags & NodeFlags.OptionalChain), "Cannot update an ElementAccessChain using updateElementAccess. Use updateElementAccessChain instead."); return node.expression !== expression || node.argumentExpression !== argumentExpression ? updateNode(createElementAccess(expression, argumentExpression), node) : node; } + export function createElementAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, index: number | Expression) { + const node = createSynthesizedNode(SyntaxKind.ElementAccessExpression); + node.flags |= NodeFlags.OptionalChain; + node.expression = parenthesizeForAccess(expression); + node.questionDotToken = questionDotToken; + node.argumentExpression = asExpression(index); + return node; + } + + export function updateElementAccessChain(node: ElementAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, argumentExpression: Expression) { + Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update an ElementAccessExpression using updateElementAccessChain. Use updateElementAccess instead."); + return node.expression !== expression + || node.questionDotToken !== questionDotToken + || node.argumentExpression !== argumentExpression + ? updateNode(createElementAccessChain(expression, questionDotToken, argumentExpression), node) + : node; + } + export function createCall(expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined) { const node = createSynthesizedNode(SyntaxKind.CallExpression); node.expression = parenthesizeForAccess(expression); @@ -1044,6 +1085,7 @@ namespace ts { } export function updateCall(node: CallExpression, expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray) { + Debug.assert(!(node.flags & NodeFlags.OptionalChain), "Cannot update a CallChain using updateCall. Use updateCallChain instead."); return node.expression !== expression || node.typeArguments !== typeArguments || node.arguments !== argumentsArray @@ -1051,6 +1093,26 @@ namespace ts { : node; } + export function createCallChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined) { + const node = createSynthesizedNode(SyntaxKind.CallExpression); + node.flags |= NodeFlags.OptionalChain; + node.expression = parenthesizeForAccess(expression); + node.questionDotToken = questionDotToken; + node.typeArguments = asNodeArray(typeArguments); + node.arguments = parenthesizeListElements(createNodeArray(argumentsArray)); + return node; + } + + export function updateCallChain(node: CallChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray) { + Debug.assert(!!(node.flags & NodeFlags.OptionalChain), "Cannot update a CallExpression using updateCallChain. Use updateCall instead."); + return node.expression !== expression + || node.questionDotToken !== questionDotToken + || node.typeArguments !== typeArguments + || node.arguments !== argumentsArray + ? updateNode(createCallChain(expression, questionDotToken, typeArguments, argumentsArray), node) + : node; + } + export function createNew(expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined) { const node = createSynthesizedNode(SyntaxKind.NewExpression); node.expression = parenthesizeForNew(expression); @@ -2721,6 +2783,22 @@ namespace ts { : node; } + /* @internal */ + export function createSyntheticReferenceExpression(expression: Expression, thisArg: Expression) { + const node = createSynthesizedNode(SyntaxKind.SyntheticReferenceExpression); + node.expression = expression; + node.thisArg = thisArg; + return node; + } + + /* @internal */ + export function updateSyntheticReferenceExpression(node: SyntheticReferenceExpression, expression: Expression, thisArg: Expression) { + return node.expression !== expression + || node.thisArg !== thisArg + ? updateNode(createSyntheticReferenceExpression(expression, thisArg), node) + : node; + } + export function createBundle(sourceFiles: ReadonlyArray, prepends: ReadonlyArray = emptyArray) { const node = createNode(SyntaxKind.Bundle); node.prepends = prepends; diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index f1221883bde8c..d427de81dd9f4 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -213,17 +213,21 @@ namespace ts { return visitNodes(cbNode, cbNodes, (node).properties); case SyntaxKind.PropertyAccessExpression: return visitNode(cbNode, (node).expression) || + visitNode(cbNode, (node).questionDotToken) || visitNode(cbNode, (node).name); case SyntaxKind.ElementAccessExpression: return visitNode(cbNode, (node).expression) || + visitNode(cbNode, (node).questionDotToken) || visitNode(cbNode, (node).argumentExpression); case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: return visitNode(cbNode, (node).expression) || + visitNode(cbNode, (node).questionDotToken) || visitNodes(cbNode, cbNodes, (node).typeArguments) || visitNodes(cbNode, cbNodes, (node).arguments); case SyntaxKind.TaggedTemplateExpression: return visitNode(cbNode, (node).tag) || + visitNode(cbNode, (node).questionDotToken) || visitNodes(cbNode, cbNodes, (node).typeArguments) || visitNode(cbNode, (node).template); case SyntaxKind.TypeAssertionExpression: @@ -4226,7 +4230,8 @@ namespace ts { } // Now, we *may* be complete. However, we might have consumed the start of a - // CallExpression. As such, we need to consume the rest of it here to be complete. + // CallExpression or OptionalExpression. As such, we need to consume the rest + // of it here to be complete. return parseCallExpressionRest(expression); } @@ -4279,7 +4284,7 @@ namespace ts { // Because CallExpression and MemberExpression are left recursive, we need to bottom out // of the recursion immediately. So we parse out a primary expression to start with. const expression = parsePrimaryExpression(); - return parseMemberExpressionRest(expression); + return parseMemberExpressionRest(expression, /*allowOptionalChain*/ true); } function parseSuperExpression(): MemberExpression { @@ -4573,18 +4578,70 @@ namespace ts { return finishNode(node); } - function parseMemberExpressionRest(expression: LeftHandSideExpression): MemberExpression { + function nextTokenIsIdentifierOrKeywordOrOpenBracketOrTemplate() { + nextToken(); + return tokenIsIdentifierOrKeyword(token()) + || token() === SyntaxKind.OpenBracketToken + || isTemplateStartOfTaggedTemplate(); + } + + function isStartOfOptionalPropertyOrElementAccessChain() { + return token() === SyntaxKind.QuestionDotToken + && lookAhead(nextTokenIsIdentifierOrKeywordOrOpenBracketOrTemplate); + } + + function parsePropertyAccessExpressionRest(expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) { + const propertyAccess = createNode(SyntaxKind.PropertyAccessExpression, expression.pos); + propertyAccess.expression = expression; + propertyAccess.questionDotToken = questionDotToken; + propertyAccess.name = parseRightSideOfDot(/*allowIdentifierNames*/ true); + if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { + propertyAccess.flags |= NodeFlags.OptionalChain; + } + return finishNode(propertyAccess); + } + + function parseElementAccessExpressionRest(expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) { + const indexedAccess = createNode(SyntaxKind.ElementAccessExpression, expression.pos); + indexedAccess.expression = expression; + indexedAccess.questionDotToken = questionDotToken; + + if (token() === SyntaxKind.CloseBracketToken) { + indexedAccess.argumentExpression = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.An_element_access_expression_should_take_an_argument); + } + else { + const argument = allowInAnd(parseExpression); + if (isStringOrNumericLiteralLike(argument)) { + argument.text = internIdentifier(argument.text); + } + indexedAccess.argumentExpression = argument; + } + + parseExpected(SyntaxKind.CloseBracketToken); + if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { + indexedAccess.flags |= NodeFlags.OptionalChain; + } + return finishNode(indexedAccess); + } + + function parseMemberExpressionRest(expression: LeftHandSideExpression, allowOptionalChain: boolean): MemberExpression { while (true) { - const dotToken = parseOptionalToken(SyntaxKind.DotToken); - if (dotToken) { - const propertyAccess = createNode(SyntaxKind.PropertyAccessExpression, expression.pos); - propertyAccess.expression = expression; - propertyAccess.name = parseRightSideOfDot(/*allowIdentifierNames*/ true); - expression = finishNode(propertyAccess); + let questionDotToken: QuestionDotToken | undefined; + let isPropertyAccess = false; + if (allowOptionalChain && isStartOfOptionalPropertyOrElementAccessChain()) { + questionDotToken = parseExpectedToken(SyntaxKind.QuestionDotToken); + isPropertyAccess = token() !== SyntaxKind.OpenBracketToken; + } + else { + isPropertyAccess = parseOptional(SyntaxKind.DotToken); + } + + if (isPropertyAccess) { + expression = parsePropertyAccessExpressionRest(expression, questionDotToken); continue; } - if (token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { + if (!questionDotToken && token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { nextToken(); const nonNullExpression = createNode(SyntaxKind.NonNullExpression, expression.pos); nonNullExpression.expression = expression; @@ -4593,28 +4650,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)) { - const indexedAccess = createNode(SyntaxKind.ElementAccessExpression, expression.pos); - indexedAccess.expression = expression; - - if (token() === SyntaxKind.CloseBracketToken) { - indexedAccess.argumentExpression = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.An_element_access_expression_should_take_an_argument); - } - else { - const argument = allowInAnd(parseExpression); - if (isStringOrNumericLiteralLike(argument)) { - argument.text = internIdentifier(argument.text); - } - indexedAccess.argumentExpression = argument; - } - - parseExpected(SyntaxKind.CloseBracketToken); - expression = finishNode(indexedAccess); + if ((questionDotToken || !inDecoratorContext()) && parseOptional(SyntaxKind.OpenBracketToken)) { + expression = parseElementAccessExpressionRest(expression, questionDotToken); continue; } if (isTemplateStartOfTaggedTemplate()) { - expression = parseTaggedTemplateRest(expression, /*typeArguments*/ undefined); + expression = parseTaggedTemplateRest(expression, questionDotToken, /*typeArguments*/ undefined); continue; } @@ -4626,19 +4668,25 @@ namespace ts { return token() === SyntaxKind.NoSubstitutionTemplateLiteral || token() === SyntaxKind.TemplateHead; } - function parseTaggedTemplateRest(tag: LeftHandSideExpression, typeArguments: NodeArray | undefined) { + function parseTaggedTemplateRest(tag: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined, typeArguments: NodeArray | undefined) { const tagExpression = createNode(SyntaxKind.TaggedTemplateExpression, tag.pos); tagExpression.tag = tag; + tagExpression.questionDotToken = questionDotToken; tagExpression.typeArguments = typeArguments; tagExpression.template = token() === SyntaxKind.NoSubstitutionTemplateLiteral ? parseLiteralNode() : parseTemplateExpression(); + if (questionDotToken || tag.flags & NodeFlags.OptionalChain) { + tagExpression.flags |= NodeFlags.OptionalChain; + } return finishNode(tagExpression); } function parseCallExpressionRest(expression: LeftHandSideExpression): LeftHandSideExpression { while (true) { - expression = parseMemberExpressionRest(expression); + expression = parseMemberExpressionRest(expression, /*allowOptionalChain*/ true); + const questionDotToken = parseOptionalToken(SyntaxKind.QuestionDotToken); + // handle 'foo<()' if (token() === SyntaxKind.LessThanToken || token() === SyntaxKind.LessThanLessThanToken) { // See if this is the start of a generic invocation. If so, consume it and @@ -4646,32 +4694,47 @@ namespace ts { // part of an arithmetic expression. Break out so we consume it higher in the // stack. const typeArguments = tryParse(parseTypeArgumentsInExpression); - if (!typeArguments) { - return expression; - } + if (typeArguments) { + if (isTemplateStartOfTaggedTemplate()) { + expression = parseTaggedTemplateRest(expression, questionDotToken, typeArguments); + continue; + } - if (isTemplateStartOfTaggedTemplate()) { - expression = parseTaggedTemplateRest(expression, typeArguments); + const callExpr = createNode(SyntaxKind.CallExpression, expression.pos); + callExpr.expression = expression; + callExpr.questionDotToken = questionDotToken; + callExpr.typeArguments = typeArguments; + callExpr.arguments = parseArgumentList(); + if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { + callExpr.flags |= NodeFlags.OptionalChain; + } + expression = finishNode(callExpr); continue; } - - const callExpr = 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 = createNode(SyntaxKind.CallExpression, expression.pos); callExpr.expression = expression; + callExpr.questionDotToken = questionDotToken; callExpr.arguments = parseArgumentList(); + if (questionDotToken || expression.flags & NodeFlags.OptionalChain) { + callExpr.flags |= NodeFlags.OptionalChain; + } expression = finishNode(callExpr); continue; } - - return expression; + if (questionDotToken) { + // We failed to parse anything, so report a missing identifier here. + const propertyAccess = createNode(SyntaxKind.PropertyAccessExpression, expression.pos) as PropertyAccessExpression; + propertyAccess.expression = expression; + propertyAccess.questionDotToken = questionDotToken; + propertyAccess.name = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false); + propertyAccess.flags |= NodeFlags.OptionalChain; + expression = finishNode(propertyAccess); + } + break; } + return expression; } function parseArgumentList() { @@ -4940,12 +5003,12 @@ namespace ts { let expression: MemberExpression = parsePrimaryExpression(); let typeArguments; while (true) { - expression = parseMemberExpressionRest(expression); + expression = parseMemberExpressionRest(expression, /*allowOptionalChain*/ false); typeArguments = tryParse(parseTypeArgumentsInExpression); if (isTemplateStartOfTaggedTemplate()) { Debug.assert(!!typeArguments, "Expected a type argument list; all plain tagged template starts should be consumed in 'parseMemberExpressionRest'"); - expression = parseTaggedTemplateRest(expression, typeArguments); + expression = parseTaggedTemplateRest(expression, /*optionalChain*/ undefined, typeArguments); typeArguments = undefined; } break; diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index af9f565fb52ff..9ea9353a66086 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -183,6 +183,7 @@ namespace ts { "&&": SyntaxKind.AmpersandAmpersandToken, "||": SyntaxKind.BarBarToken, "?": SyntaxKind.QuestionToken, + "?.": SyntaxKind.QuestionDotToken, ":": SyntaxKind.ColonToken, "=": SyntaxKind.EqualsToken, "+=": SyntaxKind.PlusEqualsToken, @@ -1826,6 +1827,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: diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 80debb46f8a04..c2693fb1a4aaf 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -1,6 +1,10 @@ /*@internal*/ namespace ts { export function transformESNext(context: TransformationContext) { + const { + hoistVariableDeclaration + } = context; + return chainBundle(transformSourceFile); function transformSourceFile(node: SourceFile) { @@ -16,9 +20,158 @@ namespace ts { return node; } switch (node.kind) { + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + case SyntaxKind.CallExpression: + case SyntaxKind.TaggedTemplateExpression: + if (node.flags & NodeFlags.OptionalChain) { + const updated = visitOptionalExpression(node as OptionalChain, /*captureThisArg*/ false); + Debug.assertNotNode(updated, isSyntheticReference); + return updated; + } + // falls through default: return visitEachChild(node, visitor, context); } } + + function flattenChain(chain: OptionalChain) { + const links: OptionalChain[] = [chain]; + while (!chain.questionDotToken && !isTaggedTemplateExpression(chain)) { + chain = cast(chain.expression, isOptionalChain); + links.unshift(chain); + } + return { expression: chain.expression, chain: links }; + } + + function visitNonOptionalParenthesizedExpression(node: ParenthesizedExpression, captureThisArg: boolean): Expression { + const expression = visitNonOptionalExpression(node.expression, captureThisArg); + if (isSyntheticReference(expression)) { + // `(a.b)` -> { expression `((_a = a).b)`, thisArg: `_a` } + // `(a[b])` -> { expression `((_a = a)[b])`, thisArg: `_a` } + return createSyntheticReferenceExpression(updateParen(node, expression.expression), expression.thisArg); + } + return updateParen(node, expression); + } + + function visitNonOptionalPropertyAccessExpression(node: PropertyAccessExpression, captureThisArg: boolean): Expression { + if (isOptionalChain(node)) { + // If `node` is an optional chain, then it is the outermost chain of an optional expression. + return visitOptionalExpression(node, captureThisArg); + } + + let expression = visitNode(node.expression, visitor, isExpression); + Debug.assertNotNode(expression, isSyntheticReference); + + let thisArg: Expression | undefined; + if (captureThisArg) { + // `a.b` -> { expression: `(_a = a).b`, thisArg: `_a` } + thisArg = createTempVariable(hoistVariableDeclaration); + expression = createParen(createAssignment(thisArg, expression)); + } + + expression = updatePropertyAccess(node, expression, visitNode(node.name, visitor, isIdentifier)); + return thisArg ? createSyntheticReferenceExpression(expression, thisArg) : expression; + } + + function visitNonOptionalElementAccessExpression(node: ElementAccessExpression, captureThisArg: boolean): Expression { + if (isOptionalChain(node)) { + // If `node` is an optional chain, then it is the outermost chain of an optional expression. + return visitOptionalExpression(node, captureThisArg); + } + + let expression = visitNode(node.expression, visitor, isExpression); + Debug.assertNotNode(expression, isSyntheticReference); + + let thisArg: Expression | undefined; + if (captureThisArg) { + // `a[b]` -> { expression: `(_a = a)[b]`, thisArg: `_a` } + thisArg = createTempVariable(hoistVariableDeclaration); + expression = createParen(createAssignment(thisArg, expression)); + } + + expression = updateElementAccess(node, expression, visitNode(node.argumentExpression, visitor, isExpression)); + return thisArg ? createSyntheticReferenceExpression(expression, thisArg) : expression; + } + + function visitNonOptionalCallExpression(node: CallExpression, captureThisArg: boolean): Expression { + if (isOptionalChain(node)) { + // If `node` is an optional chain, then it is the outermost chain of an optional expression. + return visitOptionalExpression(node, captureThisArg); + } + return visitEachChild(node, visitor, context); + } + + function visitNonOptionalExpression(node: Expression, captureThisArg: boolean): Expression { + switch (node.kind) { + case SyntaxKind.ParenthesizedExpression: return visitNonOptionalParenthesizedExpression(node as ParenthesizedExpression, captureThisArg); + case SyntaxKind.PropertyAccessExpression: return visitNonOptionalPropertyAccessExpression(node as PropertyAccessExpression, captureThisArg); + case SyntaxKind.ElementAccessExpression: return visitNonOptionalElementAccessExpression(node as ElementAccessExpression, captureThisArg); + case SyntaxKind.CallExpression: return visitNonOptionalCallExpression(node as CallExpression, captureThisArg); + default: return visitNode(node, visitor, isExpression); + } + } + + function visitOptionalExpression(node: OptionalChain, captureThisArg: boolean): Expression { + const { expression, chain } = flattenChain(node); + const left = visitNonOptionalExpression(expression, isCallChain(chain[0])); + const temp = createTempVariable(hoistVariableDeclaration); + const leftThisArg = isSyntheticReference(left) ? left.thisArg : undefined; + const leftExpression = isSyntheticReference(left) ? left.expression : left; + let rightExpression: Expression = temp; + let thisArg: Expression | undefined; + for (let i = 0; i < chain.length; i++) { + const segment = chain[i]; + switch (segment.kind) { + case SyntaxKind.PropertyAccessExpression: + if (i === chain.length - 1 && captureThisArg) { + thisArg = createTempVariable(hoistVariableDeclaration); + rightExpression = createParen(createAssignment(thisArg, rightExpression)); + } + rightExpression = createPropertyAccess( + rightExpression, + visitNode(segment.name, visitor, isIdentifier) + ); + break; + case SyntaxKind.ElementAccessExpression: + if (i === chain.length - 1 && captureThisArg) { + thisArg = createTempVariable(hoistVariableDeclaration); + rightExpression = createParen(createAssignment(thisArg, rightExpression)); + } + rightExpression = createElementAccess( + rightExpression, + visitNode(segment.argumentExpression, visitor, isExpression) + ); + break; + case SyntaxKind.CallExpression: + if (i === 0 && leftThisArg) { + rightExpression = createFunctionCall( + rightExpression, + leftThisArg, + visitNodes(segment.arguments, visitor, isExpression) + ); + } + else { + rightExpression = createCall( + rightExpression, + /*typeArguments*/ undefined, + visitNodes(segment.arguments, visitor, isExpression) + ); + } + break; + } + setOriginalNode(rightExpression, segment); + } + + const target = createConditional( + createLogicalOr( + createStrictEquality(createAssignment(temp, leftExpression), createNull()), + createStrictEquality(temp, createVoidZero()) + ), + createVoidZero(), + rightExpression + ); + return thisArg ? createSyntheticReferenceExpression(target, thisArg) : target; + } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 999a922041201..3408ca33bf4b3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -151,6 +151,7 @@ namespace ts { DotDotDotToken, SemicolonToken, CommaToken, + QuestionDotToken, LessThanToken, LessThanSlashToken, GreaterThanToken, @@ -483,6 +484,7 @@ namespace ts { CommaListExpression, MergeDeclarationMarker, EndOfDeclarationMarker, + SyntheticReferenceExpression, // Enum value count Count, @@ -528,20 +530,21 @@ namespace ts { 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 + OptionalChain = 1 << 5, // Chained MemberExpression rooted to a pseudo-OptionalExpression + 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 // These flags will be set when the parser encounters a dynamic import expression or 'import.meta' to avoid // walking the tree if the flags are not set. However, these flags are just a approximation @@ -552,13 +555,13 @@ namespace ts { // removal, it is likely that users will add the import anyway. // The advantage of this approach is its simplicity. For the case of batch compilation, // we guarantee that users won't have to pay the price of walking the tree if a dynamic import isn't used. - /* @internal */ PossiblyContainsDynamicImport = 1 << 19, - /* @internal */ PossiblyContainsImportMeta = 1 << 20, + /* @internal */ PossiblyContainsDynamicImport = 1 << 20, + /* @internal */ PossiblyContainsImportMeta = 1 << 21, - JSDoc = 1 << 21, // If node was parsed inside jsdoc - /* @internal */ Ambient = 1 << 22, // If node was inside an ambient context -- a declaration file, or inside something with the `declare` modifier. - /* @internal */ InWithStatement = 1 << 23, // If any ancestor of node was the `statement` of a WithStatement (not the `expression`) - JsonFile = 1 << 24, // If node was parsed in a Json + JSDoc = 1 << 22, // If node was parsed inside jsdoc + /* @internal */ Ambient = 1 << 23, // If node was inside an ambient context -- a declaration file, or inside something with the `declare` modifier. + /* @internal */ InWithStatement = 1 << 24, // If any ancestor of node was the `statement` of a WithStatement (not the `expression`) + JsonFile = 1 << 25, // If node was parsed in a Json BlockScoped = Let | Const, @@ -724,8 +727,10 @@ namespace ts { kind: TKind; } + export type DotToken = Token; export type DotDotDotToken = Token; export type QuestionToken = Token; + export type QuestionDotToken = Token; export type ExclamationToken = Token; export type ColonToken = Token; export type EqualsToken = Token; @@ -1776,9 +1781,15 @@ namespace ts { export interface PropertyAccessExpression extends MemberExpression, NamedDeclaration { kind: SyntaxKind.PropertyAccessExpression; expression: LeftHandSideExpression; + questionDotToken?: QuestionDotToken; name: Identifier; } + export interface PropertyAccessChain extends PropertyAccessExpression { + _optionalChainBrand: any; + kind: SyntaxKind.PropertyAccessExpression; + } + export interface SuperPropertyAccessExpression extends PropertyAccessExpression { expression: SuperExpression; } @@ -1792,9 +1803,15 @@ namespace ts { export interface ElementAccessExpression extends MemberExpression { kind: SyntaxKind.ElementAccessExpression; expression: LeftHandSideExpression; + questionDotToken?: QuestionDotToken; argumentExpression: Expression; } + export interface ElementAccessChain extends ElementAccessExpression { + _optionalChainBrand: any; + kind: SyntaxKind.ElementAccessExpression; + } + export interface SuperElementAccessExpression extends ElementAccessExpression { expression: SuperExpression; } @@ -1805,10 +1822,22 @@ namespace ts { export interface CallExpression extends LeftHandSideExpression, Declaration { kind: SyntaxKind.CallExpression; expression: LeftHandSideExpression; + questionDotToken?: QuestionDotToken; typeArguments?: NodeArray; arguments: NodeArray; } + export interface CallChain extends CallExpression { + _optionalChainBrand: any; + kind: SyntaxKind.CallExpression; + } + + export type OptionalChain = + | PropertyAccessChain + | ElementAccessChain + | CallChain + ; + /** @internal */ export type BindableObjectDefinePropertyCall = CallExpression & { arguments: { 0: EntityNameExpression, 1: StringLiteralLike | NumericLiteral, 2: ObjectLiteralExpression } }; @@ -1839,6 +1868,7 @@ namespace ts { tag: LeftHandSideExpression; typeArguments?: NodeArray; template: TemplateLiteral; + /*@internal*/ questionDotToken?: QuestionDotToken; // NOTE: Invalid syntax, only used to report a grammar error. } export type CallLikeExpression = CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement; @@ -2006,6 +2036,13 @@ namespace ts { kind: SyntaxKind.MergeDeclarationMarker; } + /* @internal */ + export interface SyntheticReferenceExpression extends LeftHandSideExpression { + kind: SyntaxKind.SyntheticReferenceExpression; + expression: Expression; + thisArg: Expression; + } + export interface EmptyStatement extends Statement { kind: SyntaxKind.EmptyStatement; } @@ -3162,6 +3199,7 @@ namespace ts { /* @internal */ getParameterType(signature: Signature, parameterIndex: number): Type; getNullableType(type: Type, flags: TypeFlags): Type; getNonNullableType(type: Type): Type; + /* @internal */ getNonOptionalType(type: Type): Type; // TODO: GH#18217 `xToDeclaration` calls are frequently asserted as defined. /** Note that the resulting nodes cannot be checked. */ @@ -3281,6 +3319,7 @@ namespace ts { /* @internal */ getNullType(): Type; /* @internal */ getESSymbolType(): Type; /* @internal */ getNeverType(): Type; + /* @internal */ getOptionalType(): Type; /* @internal */ getUnionType(types: Type[], subtypeReduction?: UnionReduction): Type; /* @internal */ createArrayType(elementType: Type): Type; /* @internal */ getElementTypeOfArrayType(arrayType: Type): Type | undefined; @@ -4477,6 +4516,8 @@ namespace ts { isolatedSignatureType?: ObjectType; // A manufactured type that just contains the signature for purposes of signature comparison /* @internal */ instantiations?: Map; // Generic signature instantiation cache + /* @internal */ + isOptionalCall?: boolean; } export const enum IndexKind { @@ -5886,6 +5927,8 @@ namespace ts { getColumn(): number; getIndent(): number; isAtStartOfLine(): boolean; + hasPrecedingComment(): boolean; + hasPrecedingWhitespace(): boolean; getTextPosWithWriteLine?(): number; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index facc13ada282f..95daabfabab2c 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -57,7 +57,6 @@ namespace ts { function createSingleLineStringWriter(): EmitTextWriter { let str = ""; - const writeText: (text: string) => void = text => str += text; return { getText: () => str, @@ -79,6 +78,8 @@ namespace ts { getColumn: () => 0, getIndent: () => 0, isAtStartOfLine: () => false, + hasPrecedingComment: () => false, + hasPrecedingWhitespace: () => !!str.length && isWhiteSpaceLike(str.charCodeAt(str.length - 1)), // Completely ignore indentation for string writers. And map newlines to // a single space. @@ -3282,6 +3283,7 @@ namespace ts { let lineStart: boolean; let lineCount: number; let linePos: number; + let hasPrecedingComment = false; function updateLineCountAndPosFor(s: string) { const lineStartsOfS = computeLineStarts(s); @@ -3295,7 +3297,7 @@ namespace ts { } } - function write(s: string) { + function writeText(s: string) { if (s && s.length) { if (lineStart) { s = getIndentString(indent) + s; @@ -3306,18 +3308,30 @@ namespace ts { } } + function write(s: string) { + if (s) hasPrecedingComment = false; + writeText(s); + } + + function writeComment(s: string) { + if (s) hasPrecedingComment = true; + writeText(s); + } + function reset(): void { output = ""; indent = 0; lineStart = true; lineCount = 0; linePos = 0; + hasPrecedingComment = false; } function rawWrite(s: string) { if (s !== undefined) { output += s; updateLineCountAndPosFor(s); + hasPrecedingComment = false; } } @@ -3333,6 +3347,7 @@ namespace ts { lineCount++; linePos = output.length; lineStart = true; + hasPrecedingComment = false; } } @@ -3355,6 +3370,8 @@ namespace ts { getColumn: () => lineStart ? indent * getIndentSize() : output.length - linePos, getText: () => output, isAtStartOfLine: () => lineStart, + hasPrecedingComment: () => hasPrecedingComment, + hasPrecedingWhitespace: () => !!output.length && isWhiteSpaceLike(output.charCodeAt(output.length - 1)), clear: reset, reportInaccessibleThisError: noop, reportPrivateInBaseOfClassExpression: noop, @@ -3369,7 +3386,7 @@ namespace ts { writeStringLiteral: write, writeSymbol: (s, _) => write(s), writeTrailingSemicolon: write, - writeComment: write, + writeComment, getTextPosWithWriteLine }; } @@ -4730,6 +4747,14 @@ namespace ts { return false; } } + + export function getDotOrQuestionDotToken(node: PropertyAccessExpression) { + if (node.questionDotToken) { + return node.questionDotToken; + } + + return createNode(SyntaxKind.DotToken, node.expression.end, node.name.pos) as DotToken; + } } namespace ts { @@ -5734,14 +5759,32 @@ namespace ts { return node.kind === SyntaxKind.PropertyAccessExpression; } + export function isPropertyAccessChain(node: Node): node is PropertyAccessChain { + return isPropertyAccessExpression(node) && !!(node.flags & NodeFlags.OptionalChain); + } + export function isElementAccessExpression(node: Node): node is ElementAccessExpression { return node.kind === SyntaxKind.ElementAccessExpression; } + export function isElementAccessChain(node: Node): node is ElementAccessChain { + return isElementAccessExpression(node) && !!(node.flags & NodeFlags.OptionalChain); + } + export function isCallExpression(node: Node): node is CallExpression { return node.kind === SyntaxKind.CallExpression; } + export function isCallChain(node: Node): node is CallChain { + return isCallExpression(node) && !!(node.flags & NodeFlags.OptionalChain); + } + + export function isOptionalChain(node: Node): node is PropertyAccessChain | ElementAccessChain | CallChain { + return isPropertyAccessChain(node) + || isElementAccessChain(node) + || isCallChain(node); + } + export function isNewExpression(node: Node): node is NewExpression { return node.kind === SyntaxKind.NewExpression; } @@ -6750,6 +6793,11 @@ namespace ts { return node.kind === SyntaxKind.NotEmittedStatement; } + /* @internal */ + export function isSyntheticReference(node: Node): node is SyntheticReferenceExpression { + return node.kind === SyntaxKind.SyntheticReferenceExpression; + } + /* @internal */ export function isNotEmittedOrPartiallyEmittedNode(node: Node): node is NotEmittedStatement | PartiallyEmittedExpression { return isNotEmittedStatement(node) diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 45b43c1d761ff..f6d766679ee4e 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -460,16 +460,35 @@ namespace ts { nodesVisitor((node).properties, visitor, isObjectLiteralElementLike)); case SyntaxKind.PropertyAccessExpression: + if (node.flags & NodeFlags.OptionalChain) { + return updatePropertyAccessChain(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).questionDotToken, visitor, isToken), + visitNode((node).name, visitor, isIdentifier)); + } return updatePropertyAccess(node, visitNode((node).expression, visitor, isExpression), visitNode((node).name, visitor, isIdentifier)); case SyntaxKind.ElementAccessExpression: + if (node.flags & NodeFlags.OptionalChain) { + return updateElementAccessChain(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).questionDotToken, visitor, isToken), + visitNode((node).argumentExpression, visitor, isExpression)); + } return updateElementAccess(node, visitNode((node).expression, visitor, isExpression), visitNode((node).argumentExpression, visitor, isExpression)); case SyntaxKind.CallExpression: + if (node.flags & NodeFlags.OptionalChain) { + return updateCallChain(node, + visitNode((node).expression, visitor, isExpression), + visitNode((node).questionDotToken, visitor, isToken), + nodesVisitor((node).typeArguments, visitor, isTypeNode), + nodesVisitor((node).arguments, visitor, isExpression)); + } return updateCall(node, visitNode((node).expression, visitor, isExpression), nodesVisitor((node).typeArguments, visitor, isTypeNode), diff --git a/src/services/completions.ts b/src/services/completions.ts index 3ee843c38da4c..9d5fef67f2900 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -767,6 +767,8 @@ namespace ts.Completions { let node = currentToken; let propertyAccessToConvert: PropertyAccessExpression | undefined; let isRightOfDot = false; + let isRightOfQuestionDot = false; + let isOptional = false; let isRightOfOpenTag = false; let isStartingCloseTag = false; let isJsxInitializer: IsJsxInitializer = false; @@ -780,10 +782,12 @@ namespace ts.Completions { } let parent = contextToken.parent; - if (contextToken.kind === SyntaxKind.DotToken) { - isRightOfDot = true; + if (contextToken.kind === SyntaxKind.DotToken || contextToken.kind === SyntaxKind.QuestionDotToken) { + isRightOfDot = contextToken.kind === SyntaxKind.DotToken; + isRightOfQuestionDot = contextToken.kind === SyntaxKind.QuestionDotToken; switch (parent.kind) { case SyntaxKind.PropertyAccessExpression: + isOptional = isOptionalChain(parent); propertyAccessToConvert = parent as PropertyAccessExpression; node = propertyAccessToConvert.expression; if (node.end === contextToken.pos && @@ -890,6 +894,10 @@ namespace ts.Completions { if (isRightOfDot) { getTypeScriptMemberSymbols(); } + else if (isRightOfQuestionDot) { + getTypeScriptMemberSymbols(); + isNewIdentifierLocation = true; + } else if (isRightOfOpenTag) { const tagSymbols = Debug.assertEachDefined(typeChecker.getJsxIntrinsicTagNamesAt(location), "getJsxIntrinsicTagNames() should all be defined"); tryGetGlobalSymbols(); @@ -995,7 +1003,7 @@ namespace ts.Completions { if (!isTypeLocation && symbol.declarations && symbol.declarations.some(d => d.kind !== SyntaxKind.SourceFile && d.kind !== SyntaxKind.ModuleDeclaration && d.kind !== SyntaxKind.EnumDeclaration)) { - addTypeProperties(typeChecker.getTypeOfSymbolAtLocation(symbol, node), !!(node.flags & NodeFlags.AwaitContext)); + addTypeProperties(removeOptionality(typeChecker.getTypeOfSymbolAtLocation(symbol, node), isRightOfQuestionDot, isOptional), !!(node.flags & NodeFlags.AwaitContext)); } return; @@ -1010,7 +1018,7 @@ namespace ts.Completions { } if (!isTypeLocation) { - addTypeProperties(typeChecker.getTypeAtLocation(node), !!(node.flags & NodeFlags.AwaitContext)); + addTypeProperties(removeOptionality(typeChecker.getTypeAtLocation(node), isRightOfQuestionDot, isOptional), !!(node.flags & NodeFlags.AwaitContext)); } } diff --git a/src/services/services.ts b/src/services/services.ts index ad8528b65af65..d6a26610d6925 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -414,6 +414,9 @@ namespace ts { getNonNullableType(): Type { return this.checker.getNonNullableType(this); } + getNonOptionalType(): Type { + return this.checker.getNonOptionalType(this); + } getConstraint(): Type | undefined { return this.checker.getBaseConstraintOfType(this); } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 31c9f9f26717b..cd073447a7d33 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -897,7 +897,6 @@ namespace ts.textChanges { function createWriter(newLine: string, omitTrailingSemicolon?: boolean): TextChangesWriter { let lastNonTriviaPosition = 0; - const writer = omitTrailingSemicolon ? getTrailingSemicolonOmittingWriter(createTextWriter(newLine)) : createTextWriter(newLine); const onEmitNode: PrintHandlers["onEmitNode"] = (hint, node, printCallback) => { if (node) { @@ -1052,6 +1051,8 @@ namespace ts.textChanges { getColumn, getIndent, isAtStartOfLine, + hasPrecedingComment: () => writer.hasPrecedingComment(), + hasPrecedingWhitespace: () => writer.hasPrecedingWhitespace(), clear }; } diff --git a/src/services/types.ts b/src/services/types.ts index 53d552394e0df..91e47a5f4ed27 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -50,6 +50,7 @@ namespace ts { getNumberIndexType(): Type | undefined; getBaseTypes(): BaseType[] | undefined; getNonNullableType(): Type; + /*@internal*/ getNonOptionalType(): Type; getConstraint(): Type | undefined; getDefault(): Type | undefined; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 2e6e6875a8159..540f4f87c08ba 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -950,6 +950,12 @@ namespace ts { } } + export function removeOptionality(type: Type, isOptionalExpression: boolean, isOptionalChain: boolean) { + return isOptionalExpression ? type.getNonNullableType() : + isOptionalChain ? type.getNonOptionalType() : + type; + } + export function isPossiblyTypeArgumentPosition(token: Node, sourceFile: SourceFile, checker: TypeChecker): boolean { const info = getPossibleTypeArgumentsInfo(token, sourceFile); return info !== undefined && (isPartOfTypeNode(info.called) || @@ -958,7 +964,11 @@ namespace ts { } export function getPossibleGenericSignatures(called: Expression, typeArgumentCount: number, checker: TypeChecker): ReadonlyArray { - const type = checker.getTypeAtLocation(called); + let type = checker.getTypeAtLocation(called); + if (isOptionalChain(called.parent)) { + type = removeOptionality(type, !!called.parent.questionDotToken, /*isOptionalChain*/ true); + } + const signatures = isNewExpression(called.parent) ? type.getConstructSignatures() : type.getCallSignatures(); return signatures.filter(candidate => !!candidate.typeParameters && candidate.typeParameters.length >= typeArgumentCount); } @@ -987,6 +997,9 @@ namespace ts { case SyntaxKind.LessThanToken: // Found the beginning of the generic argument expression token = findPrecedingToken(token.getFullStart(), sourceFile); + if (token && token.kind === SyntaxKind.QuestionDotToken) { + token = findPrecedingToken(token.getFullStart(), sourceFile); + } if (!token || !isIdentifier(token)) return undefined; if (!remainingLessThanTokens) { return isDeclarationName(token) ? undefined : { called: token, nTypeArguments }; @@ -1485,6 +1498,8 @@ namespace ts { getColumn: () => 0, getLine: () => 0, isAtStartOfLine: () => false, + hasPrecedingWhitespace: () => false, + hasPrecedingComment: () => false, rawWrite: notImplemented, getIndent: () => indent, increaseIndent: () => { indent++; }, diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 67d6a98e11ec5..4bfc0c5e1bb7b 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -104,329 +104,331 @@ declare namespace ts { DotDotDotToken = 25, SemicolonToken = 26, CommaToken = 27, - LessThanToken = 28, - LessThanSlashToken = 29, - GreaterThanToken = 30, - LessThanEqualsToken = 31, - GreaterThanEqualsToken = 32, - EqualsEqualsToken = 33, - ExclamationEqualsToken = 34, - EqualsEqualsEqualsToken = 35, - ExclamationEqualsEqualsToken = 36, - EqualsGreaterThanToken = 37, - PlusToken = 38, - MinusToken = 39, - AsteriskToken = 40, - AsteriskAsteriskToken = 41, - SlashToken = 42, - PercentToken = 43, - PlusPlusToken = 44, - MinusMinusToken = 45, - LessThanLessThanToken = 46, - GreaterThanGreaterThanToken = 47, - GreaterThanGreaterThanGreaterThanToken = 48, - AmpersandToken = 49, - BarToken = 50, - CaretToken = 51, - ExclamationToken = 52, - TildeToken = 53, - AmpersandAmpersandToken = 54, - BarBarToken = 55, - QuestionToken = 56, - ColonToken = 57, - AtToken = 58, + QuestionDotToken = 28, + LessThanToken = 29, + LessThanSlashToken = 30, + GreaterThanToken = 31, + LessThanEqualsToken = 32, + GreaterThanEqualsToken = 33, + EqualsEqualsToken = 34, + ExclamationEqualsToken = 35, + EqualsEqualsEqualsToken = 36, + ExclamationEqualsEqualsToken = 37, + EqualsGreaterThanToken = 38, + PlusToken = 39, + MinusToken = 40, + AsteriskToken = 41, + AsteriskAsteriskToken = 42, + SlashToken = 43, + PercentToken = 44, + PlusPlusToken = 45, + MinusMinusToken = 46, + LessThanLessThanToken = 47, + GreaterThanGreaterThanToken = 48, + GreaterThanGreaterThanGreaterThanToken = 49, + AmpersandToken = 50, + BarToken = 51, + CaretToken = 52, + ExclamationToken = 53, + TildeToken = 54, + AmpersandAmpersandToken = 55, + BarBarToken = 56, + QuestionToken = 57, + ColonToken = 58, + AtToken = 59, /** Only the JSDoc scanner produces BacktickToken. The normal scanner produces NoSubstitutionTemplateLiteral and related kinds. */ - BacktickToken = 59, - EqualsToken = 60, - PlusEqualsToken = 61, - MinusEqualsToken = 62, - AsteriskEqualsToken = 63, - AsteriskAsteriskEqualsToken = 64, - SlashEqualsToken = 65, - PercentEqualsToken = 66, - LessThanLessThanEqualsToken = 67, - GreaterThanGreaterThanEqualsToken = 68, - GreaterThanGreaterThanGreaterThanEqualsToken = 69, - AmpersandEqualsToken = 70, - BarEqualsToken = 71, - CaretEqualsToken = 72, - Identifier = 73, - BreakKeyword = 74, - CaseKeyword = 75, - CatchKeyword = 76, - ClassKeyword = 77, - ConstKeyword = 78, - ContinueKeyword = 79, - DebuggerKeyword = 80, - DefaultKeyword = 81, - DeleteKeyword = 82, - DoKeyword = 83, - ElseKeyword = 84, - EnumKeyword = 85, - ExportKeyword = 86, - ExtendsKeyword = 87, - FalseKeyword = 88, - FinallyKeyword = 89, - ForKeyword = 90, - FunctionKeyword = 91, - IfKeyword = 92, - ImportKeyword = 93, - InKeyword = 94, - InstanceOfKeyword = 95, - NewKeyword = 96, - NullKeyword = 97, - ReturnKeyword = 98, - SuperKeyword = 99, - SwitchKeyword = 100, - ThisKeyword = 101, - ThrowKeyword = 102, - TrueKeyword = 103, - TryKeyword = 104, - TypeOfKeyword = 105, - VarKeyword = 106, - VoidKeyword = 107, - WhileKeyword = 108, - WithKeyword = 109, - ImplementsKeyword = 110, - InterfaceKeyword = 111, - LetKeyword = 112, - PackageKeyword = 113, - PrivateKeyword = 114, - ProtectedKeyword = 115, - PublicKeyword = 116, - StaticKeyword = 117, - YieldKeyword = 118, - AbstractKeyword = 119, - AsKeyword = 120, - AnyKeyword = 121, - AsyncKeyword = 122, - AwaitKeyword = 123, - BooleanKeyword = 124, - ConstructorKeyword = 125, - DeclareKeyword = 126, - GetKeyword = 127, - InferKeyword = 128, - IsKeyword = 129, - KeyOfKeyword = 130, - ModuleKeyword = 131, - NamespaceKeyword = 132, - NeverKeyword = 133, - ReadonlyKeyword = 134, - RequireKeyword = 135, - NumberKeyword = 136, - ObjectKeyword = 137, - SetKeyword = 138, - StringKeyword = 139, - SymbolKeyword = 140, - TypeKeyword = 141, - UndefinedKeyword = 142, - UniqueKeyword = 143, - UnknownKeyword = 144, - FromKeyword = 145, - GlobalKeyword = 146, - BigIntKeyword = 147, - OfKeyword = 148, - QualifiedName = 149, - ComputedPropertyName = 150, - TypeParameter = 151, - Parameter = 152, - Decorator = 153, - PropertySignature = 154, - PropertyDeclaration = 155, - MethodSignature = 156, - MethodDeclaration = 157, - Constructor = 158, - GetAccessor = 159, - SetAccessor = 160, - CallSignature = 161, - ConstructSignature = 162, - IndexSignature = 163, - TypePredicate = 164, - TypeReference = 165, - FunctionType = 166, - ConstructorType = 167, - TypeQuery = 168, - TypeLiteral = 169, - ArrayType = 170, - TupleType = 171, - OptionalType = 172, - RestType = 173, - UnionType = 174, - IntersectionType = 175, - ConditionalType = 176, - InferType = 177, - ParenthesizedType = 178, - ThisType = 179, - TypeOperator = 180, - IndexedAccessType = 181, - MappedType = 182, - LiteralType = 183, - ImportType = 184, - ObjectBindingPattern = 185, - ArrayBindingPattern = 186, - BindingElement = 187, - ArrayLiteralExpression = 188, - ObjectLiteralExpression = 189, - PropertyAccessExpression = 190, - ElementAccessExpression = 191, - CallExpression = 192, - NewExpression = 193, - TaggedTemplateExpression = 194, - TypeAssertionExpression = 195, - ParenthesizedExpression = 196, - FunctionExpression = 197, - ArrowFunction = 198, - DeleteExpression = 199, - TypeOfExpression = 200, - VoidExpression = 201, - AwaitExpression = 202, - PrefixUnaryExpression = 203, - PostfixUnaryExpression = 204, - BinaryExpression = 205, - ConditionalExpression = 206, - TemplateExpression = 207, - YieldExpression = 208, - SpreadElement = 209, - ClassExpression = 210, - OmittedExpression = 211, - ExpressionWithTypeArguments = 212, - AsExpression = 213, - NonNullExpression = 214, - MetaProperty = 215, - SyntheticExpression = 216, - TemplateSpan = 217, - SemicolonClassElement = 218, - Block = 219, - VariableStatement = 220, - EmptyStatement = 221, - ExpressionStatement = 222, - IfStatement = 223, - DoStatement = 224, - WhileStatement = 225, - ForStatement = 226, - ForInStatement = 227, - ForOfStatement = 228, - ContinueStatement = 229, - BreakStatement = 230, - ReturnStatement = 231, - WithStatement = 232, - SwitchStatement = 233, - LabeledStatement = 234, - ThrowStatement = 235, - TryStatement = 236, - DebuggerStatement = 237, - VariableDeclaration = 238, - VariableDeclarationList = 239, - FunctionDeclaration = 240, - ClassDeclaration = 241, - InterfaceDeclaration = 242, - TypeAliasDeclaration = 243, - EnumDeclaration = 244, - ModuleDeclaration = 245, - ModuleBlock = 246, - CaseBlock = 247, - NamespaceExportDeclaration = 248, - ImportEqualsDeclaration = 249, - ImportDeclaration = 250, - ImportClause = 251, - NamespaceImport = 252, - NamedImports = 253, - ImportSpecifier = 254, - ExportAssignment = 255, - ExportDeclaration = 256, - NamedExports = 257, - ExportSpecifier = 258, - MissingDeclaration = 259, - ExternalModuleReference = 260, - JsxElement = 261, - JsxSelfClosingElement = 262, - JsxOpeningElement = 263, - JsxClosingElement = 264, - JsxFragment = 265, - JsxOpeningFragment = 266, - JsxClosingFragment = 267, - JsxAttribute = 268, - JsxAttributes = 269, - JsxSpreadAttribute = 270, - JsxExpression = 271, - CaseClause = 272, - DefaultClause = 273, - HeritageClause = 274, - CatchClause = 275, - PropertyAssignment = 276, - ShorthandPropertyAssignment = 277, - SpreadAssignment = 278, - EnumMember = 279, - UnparsedPrologue = 280, - UnparsedPrepend = 281, - UnparsedText = 282, - UnparsedInternalText = 283, - UnparsedSyntheticReference = 284, - SourceFile = 285, - Bundle = 286, - UnparsedSource = 287, - InputFiles = 288, - JSDocTypeExpression = 289, - JSDocAllType = 290, - JSDocUnknownType = 291, - JSDocNullableType = 292, - JSDocNonNullableType = 293, - JSDocOptionalType = 294, - JSDocFunctionType = 295, - JSDocVariadicType = 296, - JSDocNamepathType = 297, - JSDocComment = 298, - JSDocTypeLiteral = 299, - JSDocSignature = 300, - JSDocTag = 301, - JSDocAugmentsTag = 302, - JSDocAuthorTag = 303, - JSDocClassTag = 304, - JSDocCallbackTag = 305, - JSDocEnumTag = 306, - JSDocParameterTag = 307, - JSDocReturnTag = 308, - JSDocThisTag = 309, - JSDocTypeTag = 310, - JSDocTemplateTag = 311, - JSDocTypedefTag = 312, - JSDocPropertyTag = 313, - SyntaxList = 314, - NotEmittedStatement = 315, - PartiallyEmittedExpression = 316, - CommaListExpression = 317, - MergeDeclarationMarker = 318, - EndOfDeclarationMarker = 319, - Count = 320, - FirstAssignment = 60, - LastAssignment = 72, - FirstCompoundAssignment = 61, - LastCompoundAssignment = 72, - FirstReservedWord = 74, - LastReservedWord = 109, - FirstKeyword = 74, - LastKeyword = 148, - FirstFutureReservedWord = 110, - LastFutureReservedWord = 118, - FirstTypeNode = 164, - LastTypeNode = 184, + BacktickToken = 60, + EqualsToken = 61, + PlusEqualsToken = 62, + MinusEqualsToken = 63, + AsteriskEqualsToken = 64, + AsteriskAsteriskEqualsToken = 65, + SlashEqualsToken = 66, + PercentEqualsToken = 67, + LessThanLessThanEqualsToken = 68, + GreaterThanGreaterThanEqualsToken = 69, + GreaterThanGreaterThanGreaterThanEqualsToken = 70, + AmpersandEqualsToken = 71, + BarEqualsToken = 72, + CaretEqualsToken = 73, + Identifier = 74, + BreakKeyword = 75, + CaseKeyword = 76, + CatchKeyword = 77, + ClassKeyword = 78, + ConstKeyword = 79, + ContinueKeyword = 80, + DebuggerKeyword = 81, + DefaultKeyword = 82, + DeleteKeyword = 83, + DoKeyword = 84, + ElseKeyword = 85, + EnumKeyword = 86, + ExportKeyword = 87, + ExtendsKeyword = 88, + FalseKeyword = 89, + FinallyKeyword = 90, + ForKeyword = 91, + FunctionKeyword = 92, + IfKeyword = 93, + ImportKeyword = 94, + InKeyword = 95, + InstanceOfKeyword = 96, + NewKeyword = 97, + NullKeyword = 98, + ReturnKeyword = 99, + SuperKeyword = 100, + SwitchKeyword = 101, + ThisKeyword = 102, + ThrowKeyword = 103, + TrueKeyword = 104, + TryKeyword = 105, + TypeOfKeyword = 106, + VarKeyword = 107, + VoidKeyword = 108, + WhileKeyword = 109, + WithKeyword = 110, + ImplementsKeyword = 111, + InterfaceKeyword = 112, + LetKeyword = 113, + PackageKeyword = 114, + PrivateKeyword = 115, + ProtectedKeyword = 116, + PublicKeyword = 117, + StaticKeyword = 118, + YieldKeyword = 119, + AbstractKeyword = 120, + AsKeyword = 121, + AnyKeyword = 122, + AsyncKeyword = 123, + AwaitKeyword = 124, + BooleanKeyword = 125, + ConstructorKeyword = 126, + DeclareKeyword = 127, + GetKeyword = 128, + InferKeyword = 129, + IsKeyword = 130, + KeyOfKeyword = 131, + ModuleKeyword = 132, + NamespaceKeyword = 133, + NeverKeyword = 134, + ReadonlyKeyword = 135, + RequireKeyword = 136, + NumberKeyword = 137, + ObjectKeyword = 138, + SetKeyword = 139, + StringKeyword = 140, + SymbolKeyword = 141, + TypeKeyword = 142, + UndefinedKeyword = 143, + UniqueKeyword = 144, + UnknownKeyword = 145, + FromKeyword = 146, + GlobalKeyword = 147, + BigIntKeyword = 148, + OfKeyword = 149, + QualifiedName = 150, + ComputedPropertyName = 151, + TypeParameter = 152, + Parameter = 153, + Decorator = 154, + PropertySignature = 155, + PropertyDeclaration = 156, + MethodSignature = 157, + MethodDeclaration = 158, + Constructor = 159, + GetAccessor = 160, + SetAccessor = 161, + CallSignature = 162, + ConstructSignature = 163, + IndexSignature = 164, + TypePredicate = 165, + TypeReference = 166, + FunctionType = 167, + ConstructorType = 168, + TypeQuery = 169, + TypeLiteral = 170, + ArrayType = 171, + TupleType = 172, + OptionalType = 173, + RestType = 174, + UnionType = 175, + IntersectionType = 176, + ConditionalType = 177, + InferType = 178, + ParenthesizedType = 179, + ThisType = 180, + TypeOperator = 181, + IndexedAccessType = 182, + MappedType = 183, + LiteralType = 184, + ImportType = 185, + ObjectBindingPattern = 186, + ArrayBindingPattern = 187, + BindingElement = 188, + ArrayLiteralExpression = 189, + ObjectLiteralExpression = 190, + PropertyAccessExpression = 191, + ElementAccessExpression = 192, + CallExpression = 193, + NewExpression = 194, + TaggedTemplateExpression = 195, + TypeAssertionExpression = 196, + ParenthesizedExpression = 197, + FunctionExpression = 198, + ArrowFunction = 199, + DeleteExpression = 200, + TypeOfExpression = 201, + VoidExpression = 202, + AwaitExpression = 203, + PrefixUnaryExpression = 204, + PostfixUnaryExpression = 205, + BinaryExpression = 206, + ConditionalExpression = 207, + TemplateExpression = 208, + YieldExpression = 209, + SpreadElement = 210, + ClassExpression = 211, + OmittedExpression = 212, + ExpressionWithTypeArguments = 213, + AsExpression = 214, + NonNullExpression = 215, + MetaProperty = 216, + SyntheticExpression = 217, + TemplateSpan = 218, + SemicolonClassElement = 219, + Block = 220, + VariableStatement = 221, + EmptyStatement = 222, + ExpressionStatement = 223, + IfStatement = 224, + DoStatement = 225, + WhileStatement = 226, + ForStatement = 227, + ForInStatement = 228, + ForOfStatement = 229, + ContinueStatement = 230, + BreakStatement = 231, + ReturnStatement = 232, + WithStatement = 233, + SwitchStatement = 234, + LabeledStatement = 235, + ThrowStatement = 236, + TryStatement = 237, + DebuggerStatement = 238, + VariableDeclaration = 239, + VariableDeclarationList = 240, + FunctionDeclaration = 241, + ClassDeclaration = 242, + InterfaceDeclaration = 243, + TypeAliasDeclaration = 244, + EnumDeclaration = 245, + ModuleDeclaration = 246, + ModuleBlock = 247, + CaseBlock = 248, + NamespaceExportDeclaration = 249, + ImportEqualsDeclaration = 250, + ImportDeclaration = 251, + ImportClause = 252, + NamespaceImport = 253, + NamedImports = 254, + ImportSpecifier = 255, + ExportAssignment = 256, + ExportDeclaration = 257, + NamedExports = 258, + ExportSpecifier = 259, + MissingDeclaration = 260, + ExternalModuleReference = 261, + JsxElement = 262, + JsxSelfClosingElement = 263, + JsxOpeningElement = 264, + JsxClosingElement = 265, + JsxFragment = 266, + JsxOpeningFragment = 267, + JsxClosingFragment = 268, + JsxAttribute = 269, + JsxAttributes = 270, + JsxSpreadAttribute = 271, + JsxExpression = 272, + CaseClause = 273, + DefaultClause = 274, + HeritageClause = 275, + CatchClause = 276, + PropertyAssignment = 277, + ShorthandPropertyAssignment = 278, + SpreadAssignment = 279, + EnumMember = 280, + UnparsedPrologue = 281, + UnparsedPrepend = 282, + UnparsedText = 283, + UnparsedInternalText = 284, + UnparsedSyntheticReference = 285, + SourceFile = 286, + Bundle = 287, + UnparsedSource = 288, + InputFiles = 289, + JSDocTypeExpression = 290, + JSDocAllType = 291, + JSDocUnknownType = 292, + JSDocNullableType = 293, + JSDocNonNullableType = 294, + JSDocOptionalType = 295, + JSDocFunctionType = 296, + JSDocVariadicType = 297, + JSDocNamepathType = 298, + JSDocComment = 299, + JSDocTypeLiteral = 300, + JSDocSignature = 301, + JSDocTag = 302, + JSDocAugmentsTag = 303, + JSDocAuthorTag = 304, + JSDocClassTag = 305, + JSDocCallbackTag = 306, + JSDocEnumTag = 307, + JSDocParameterTag = 308, + JSDocReturnTag = 309, + JSDocThisTag = 310, + JSDocTypeTag = 311, + JSDocTemplateTag = 312, + JSDocTypedefTag = 313, + JSDocPropertyTag = 314, + SyntaxList = 315, + NotEmittedStatement = 316, + PartiallyEmittedExpression = 317, + CommaListExpression = 318, + MergeDeclarationMarker = 319, + EndOfDeclarationMarker = 320, + SyntheticReferenceExpression = 321, + Count = 322, + FirstAssignment = 61, + LastAssignment = 73, + FirstCompoundAssignment = 62, + LastCompoundAssignment = 73, + FirstReservedWord = 75, + LastReservedWord = 110, + FirstKeyword = 75, + LastKeyword = 149, + FirstFutureReservedWord = 111, + LastFutureReservedWord = 119, + FirstTypeNode = 165, + LastTypeNode = 185, FirstPunctuation = 18, - LastPunctuation = 72, + LastPunctuation = 73, FirstToken = 0, - LastToken = 148, + LastToken = 149, FirstTriviaToken = 2, LastTriviaToken = 7, FirstLiteralToken = 8, LastLiteralToken = 14, FirstTemplateToken = 14, LastTemplateToken = 17, - FirstBinaryOperator = 28, - LastBinaryOperator = 72, - FirstNode = 149, - FirstJSDocNode = 289, - LastJSDocNode = 313, - FirstJSDocTagNode = 301, - LastJSDocTagNode = 313, + FirstBinaryOperator = 29, + LastBinaryOperator = 73, + FirstNode = 150, + FirstJSDocNode = 290, + LastJSDocNode = 314, + FirstJSDocTagNode = 302, + LastJSDocTagNode = 314, } export enum NodeFlags { None = 0, @@ -435,27 +437,28 @@ declare namespace ts { NestedNamespace = 4, Synthesized = 8, Namespace = 16, - ExportContext = 32, - ContainsThis = 64, - HasImplicitReturn = 128, - HasExplicitReturn = 256, - GlobalAugmentation = 512, - HasAsyncFunctions = 1024, - DisallowInContext = 2048, - YieldContext = 4096, - DecoratorContext = 8192, - AwaitContext = 16384, - ThisNodeHasError = 32768, - JavaScriptFile = 65536, - ThisNodeOrAnySubNodesHasError = 131072, - HasAggregatedChildData = 262144, - JSDoc = 2097152, - JsonFile = 16777216, + OptionalChain = 32, + ExportContext = 64, + ContainsThis = 128, + HasImplicitReturn = 256, + HasExplicitReturn = 512, + GlobalAugmentation = 1024, + HasAsyncFunctions = 2048, + DisallowInContext = 4096, + YieldContext = 8192, + DecoratorContext = 16384, + AwaitContext = 32768, + ThisNodeHasError = 65536, + JavaScriptFile = 131072, + ThisNodeOrAnySubNodesHasError = 262144, + HasAggregatedChildData = 524288, + JSDoc = 4194304, + JsonFile = 33554432, BlockScoped = 3, - ReachabilityCheckFlags = 384, - ReachabilityAndEmitFlags = 1408, - ContextFlags = 12679168, - TypeExcludesFlags = 20480, + ReachabilityCheckFlags = 768, + ReachabilityAndEmitFlags = 2816, + ContextFlags = 25358336, + TypeExcludesFlags = 40960, } export enum ModifierFlags { None = 0, @@ -505,8 +508,10 @@ declare namespace ts { export interface Token extends Node { kind: TKind; } + export type DotToken = Token; export type DotDotDotToken = Token; export type QuestionToken = Token; + export type QuestionDotToken = Token; export type ExclamationToken = Token; export type ColonToken = Token; export type EqualsToken = Token; @@ -1077,8 +1082,13 @@ declare namespace ts { export interface PropertyAccessExpression extends MemberExpression, NamedDeclaration { kind: SyntaxKind.PropertyAccessExpression; expression: LeftHandSideExpression; + questionDotToken?: QuestionDotToken; name: Identifier; } + export interface PropertyAccessChain extends PropertyAccessExpression { + _optionalChainBrand: any; + kind: SyntaxKind.PropertyAccessExpression; + } export interface SuperPropertyAccessExpression extends PropertyAccessExpression { expression: SuperExpression; } @@ -1090,8 +1100,13 @@ declare namespace ts { export interface ElementAccessExpression extends MemberExpression { kind: SyntaxKind.ElementAccessExpression; expression: LeftHandSideExpression; + questionDotToken?: QuestionDotToken; argumentExpression: Expression; } + export interface ElementAccessChain extends ElementAccessExpression { + _optionalChainBrand: any; + kind: SyntaxKind.ElementAccessExpression; + } export interface SuperElementAccessExpression extends ElementAccessExpression { expression: SuperExpression; } @@ -1099,9 +1114,15 @@ declare namespace ts { export interface CallExpression extends LeftHandSideExpression, Declaration { kind: SyntaxKind.CallExpression; expression: LeftHandSideExpression; + questionDotToken?: QuestionDotToken; typeArguments?: NodeArray; arguments: NodeArray; } + export interface CallChain extends CallExpression { + _optionalChainBrand: any; + kind: SyntaxKind.CallExpression; + } + export type OptionalChain = PropertyAccessChain | ElementAccessChain | CallChain; export interface SuperCall extends CallExpression { expression: SuperExpression; } @@ -3445,8 +3466,12 @@ declare namespace ts { function isArrayLiteralExpression(node: Node): node is ArrayLiteralExpression; function isObjectLiteralExpression(node: Node): node is ObjectLiteralExpression; function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression; + function isPropertyAccessChain(node: Node): node is PropertyAccessChain; function isElementAccessExpression(node: Node): node is ElementAccessExpression; + function isElementAccessChain(node: Node): node is ElementAccessChain; function isCallExpression(node: Node): node is CallExpression; + function isCallChain(node: Node): node is CallChain; + function isOptionalChain(node: Node): node is PropertyAccessChain | ElementAccessChain | CallChain; function isNewExpression(node: Node): node is NewExpression; function isTaggedTemplateExpression(node: Node): node is TaggedTemplateExpression; function isTypeAssertion(node: Node): node is TypeAssertion; @@ -3890,10 +3915,16 @@ declare namespace ts { function updateObjectLiteral(node: ObjectLiteralExpression, properties: ReadonlyArray): ObjectLiteralExpression; function createPropertyAccess(expression: Expression, name: string | Identifier): PropertyAccessExpression; function updatePropertyAccess(node: PropertyAccessExpression, expression: Expression, name: Identifier): PropertyAccessExpression; + function createPropertyAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, name: string | Identifier): PropertyAccessChain; + function updatePropertyAccessChain(node: PropertyAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, name: Identifier): PropertyAccessChain; function createElementAccess(expression: Expression, index: number | Expression): ElementAccessExpression; function updateElementAccess(node: ElementAccessExpression, expression: Expression, argumentExpression: Expression): ElementAccessExpression; + function createElementAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, index: number | Expression): ElementAccessChain; + function updateElementAccessChain(node: ElementAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, argumentExpression: Expression): ElementAccessChain; function createCall(expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined): CallExpression; function updateCall(node: CallExpression, expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray): CallExpression; + function createCallChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined): CallChain; + function updateCallChain(node: CallChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray): CallChain; function createNew(expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined): NewExpression; function updateNew(node: NewExpression, expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined): NewExpression; /** @deprecated */ function createTaggedTemplate(tag: Expression, template: TemplateLiteral): TaggedTemplateExpression; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 279357eddf752..72ad9da1b90da 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -104,329 +104,331 @@ declare namespace ts { DotDotDotToken = 25, SemicolonToken = 26, CommaToken = 27, - LessThanToken = 28, - LessThanSlashToken = 29, - GreaterThanToken = 30, - LessThanEqualsToken = 31, - GreaterThanEqualsToken = 32, - EqualsEqualsToken = 33, - ExclamationEqualsToken = 34, - EqualsEqualsEqualsToken = 35, - ExclamationEqualsEqualsToken = 36, - EqualsGreaterThanToken = 37, - PlusToken = 38, - MinusToken = 39, - AsteriskToken = 40, - AsteriskAsteriskToken = 41, - SlashToken = 42, - PercentToken = 43, - PlusPlusToken = 44, - MinusMinusToken = 45, - LessThanLessThanToken = 46, - GreaterThanGreaterThanToken = 47, - GreaterThanGreaterThanGreaterThanToken = 48, - AmpersandToken = 49, - BarToken = 50, - CaretToken = 51, - ExclamationToken = 52, - TildeToken = 53, - AmpersandAmpersandToken = 54, - BarBarToken = 55, - QuestionToken = 56, - ColonToken = 57, - AtToken = 58, + QuestionDotToken = 28, + LessThanToken = 29, + LessThanSlashToken = 30, + GreaterThanToken = 31, + LessThanEqualsToken = 32, + GreaterThanEqualsToken = 33, + EqualsEqualsToken = 34, + ExclamationEqualsToken = 35, + EqualsEqualsEqualsToken = 36, + ExclamationEqualsEqualsToken = 37, + EqualsGreaterThanToken = 38, + PlusToken = 39, + MinusToken = 40, + AsteriskToken = 41, + AsteriskAsteriskToken = 42, + SlashToken = 43, + PercentToken = 44, + PlusPlusToken = 45, + MinusMinusToken = 46, + LessThanLessThanToken = 47, + GreaterThanGreaterThanToken = 48, + GreaterThanGreaterThanGreaterThanToken = 49, + AmpersandToken = 50, + BarToken = 51, + CaretToken = 52, + ExclamationToken = 53, + TildeToken = 54, + AmpersandAmpersandToken = 55, + BarBarToken = 56, + QuestionToken = 57, + ColonToken = 58, + AtToken = 59, /** Only the JSDoc scanner produces BacktickToken. The normal scanner produces NoSubstitutionTemplateLiteral and related kinds. */ - BacktickToken = 59, - EqualsToken = 60, - PlusEqualsToken = 61, - MinusEqualsToken = 62, - AsteriskEqualsToken = 63, - AsteriskAsteriskEqualsToken = 64, - SlashEqualsToken = 65, - PercentEqualsToken = 66, - LessThanLessThanEqualsToken = 67, - GreaterThanGreaterThanEqualsToken = 68, - GreaterThanGreaterThanGreaterThanEqualsToken = 69, - AmpersandEqualsToken = 70, - BarEqualsToken = 71, - CaretEqualsToken = 72, - Identifier = 73, - BreakKeyword = 74, - CaseKeyword = 75, - CatchKeyword = 76, - ClassKeyword = 77, - ConstKeyword = 78, - ContinueKeyword = 79, - DebuggerKeyword = 80, - DefaultKeyword = 81, - DeleteKeyword = 82, - DoKeyword = 83, - ElseKeyword = 84, - EnumKeyword = 85, - ExportKeyword = 86, - ExtendsKeyword = 87, - FalseKeyword = 88, - FinallyKeyword = 89, - ForKeyword = 90, - FunctionKeyword = 91, - IfKeyword = 92, - ImportKeyword = 93, - InKeyword = 94, - InstanceOfKeyword = 95, - NewKeyword = 96, - NullKeyword = 97, - ReturnKeyword = 98, - SuperKeyword = 99, - SwitchKeyword = 100, - ThisKeyword = 101, - ThrowKeyword = 102, - TrueKeyword = 103, - TryKeyword = 104, - TypeOfKeyword = 105, - VarKeyword = 106, - VoidKeyword = 107, - WhileKeyword = 108, - WithKeyword = 109, - ImplementsKeyword = 110, - InterfaceKeyword = 111, - LetKeyword = 112, - PackageKeyword = 113, - PrivateKeyword = 114, - ProtectedKeyword = 115, - PublicKeyword = 116, - StaticKeyword = 117, - YieldKeyword = 118, - AbstractKeyword = 119, - AsKeyword = 120, - AnyKeyword = 121, - AsyncKeyword = 122, - AwaitKeyword = 123, - BooleanKeyword = 124, - ConstructorKeyword = 125, - DeclareKeyword = 126, - GetKeyword = 127, - InferKeyword = 128, - IsKeyword = 129, - KeyOfKeyword = 130, - ModuleKeyword = 131, - NamespaceKeyword = 132, - NeverKeyword = 133, - ReadonlyKeyword = 134, - RequireKeyword = 135, - NumberKeyword = 136, - ObjectKeyword = 137, - SetKeyword = 138, - StringKeyword = 139, - SymbolKeyword = 140, - TypeKeyword = 141, - UndefinedKeyword = 142, - UniqueKeyword = 143, - UnknownKeyword = 144, - FromKeyword = 145, - GlobalKeyword = 146, - BigIntKeyword = 147, - OfKeyword = 148, - QualifiedName = 149, - ComputedPropertyName = 150, - TypeParameter = 151, - Parameter = 152, - Decorator = 153, - PropertySignature = 154, - PropertyDeclaration = 155, - MethodSignature = 156, - MethodDeclaration = 157, - Constructor = 158, - GetAccessor = 159, - SetAccessor = 160, - CallSignature = 161, - ConstructSignature = 162, - IndexSignature = 163, - TypePredicate = 164, - TypeReference = 165, - FunctionType = 166, - ConstructorType = 167, - TypeQuery = 168, - TypeLiteral = 169, - ArrayType = 170, - TupleType = 171, - OptionalType = 172, - RestType = 173, - UnionType = 174, - IntersectionType = 175, - ConditionalType = 176, - InferType = 177, - ParenthesizedType = 178, - ThisType = 179, - TypeOperator = 180, - IndexedAccessType = 181, - MappedType = 182, - LiteralType = 183, - ImportType = 184, - ObjectBindingPattern = 185, - ArrayBindingPattern = 186, - BindingElement = 187, - ArrayLiteralExpression = 188, - ObjectLiteralExpression = 189, - PropertyAccessExpression = 190, - ElementAccessExpression = 191, - CallExpression = 192, - NewExpression = 193, - TaggedTemplateExpression = 194, - TypeAssertionExpression = 195, - ParenthesizedExpression = 196, - FunctionExpression = 197, - ArrowFunction = 198, - DeleteExpression = 199, - TypeOfExpression = 200, - VoidExpression = 201, - AwaitExpression = 202, - PrefixUnaryExpression = 203, - PostfixUnaryExpression = 204, - BinaryExpression = 205, - ConditionalExpression = 206, - TemplateExpression = 207, - YieldExpression = 208, - SpreadElement = 209, - ClassExpression = 210, - OmittedExpression = 211, - ExpressionWithTypeArguments = 212, - AsExpression = 213, - NonNullExpression = 214, - MetaProperty = 215, - SyntheticExpression = 216, - TemplateSpan = 217, - SemicolonClassElement = 218, - Block = 219, - VariableStatement = 220, - EmptyStatement = 221, - ExpressionStatement = 222, - IfStatement = 223, - DoStatement = 224, - WhileStatement = 225, - ForStatement = 226, - ForInStatement = 227, - ForOfStatement = 228, - ContinueStatement = 229, - BreakStatement = 230, - ReturnStatement = 231, - WithStatement = 232, - SwitchStatement = 233, - LabeledStatement = 234, - ThrowStatement = 235, - TryStatement = 236, - DebuggerStatement = 237, - VariableDeclaration = 238, - VariableDeclarationList = 239, - FunctionDeclaration = 240, - ClassDeclaration = 241, - InterfaceDeclaration = 242, - TypeAliasDeclaration = 243, - EnumDeclaration = 244, - ModuleDeclaration = 245, - ModuleBlock = 246, - CaseBlock = 247, - NamespaceExportDeclaration = 248, - ImportEqualsDeclaration = 249, - ImportDeclaration = 250, - ImportClause = 251, - NamespaceImport = 252, - NamedImports = 253, - ImportSpecifier = 254, - ExportAssignment = 255, - ExportDeclaration = 256, - NamedExports = 257, - ExportSpecifier = 258, - MissingDeclaration = 259, - ExternalModuleReference = 260, - JsxElement = 261, - JsxSelfClosingElement = 262, - JsxOpeningElement = 263, - JsxClosingElement = 264, - JsxFragment = 265, - JsxOpeningFragment = 266, - JsxClosingFragment = 267, - JsxAttribute = 268, - JsxAttributes = 269, - JsxSpreadAttribute = 270, - JsxExpression = 271, - CaseClause = 272, - DefaultClause = 273, - HeritageClause = 274, - CatchClause = 275, - PropertyAssignment = 276, - ShorthandPropertyAssignment = 277, - SpreadAssignment = 278, - EnumMember = 279, - UnparsedPrologue = 280, - UnparsedPrepend = 281, - UnparsedText = 282, - UnparsedInternalText = 283, - UnparsedSyntheticReference = 284, - SourceFile = 285, - Bundle = 286, - UnparsedSource = 287, - InputFiles = 288, - JSDocTypeExpression = 289, - JSDocAllType = 290, - JSDocUnknownType = 291, - JSDocNullableType = 292, - JSDocNonNullableType = 293, - JSDocOptionalType = 294, - JSDocFunctionType = 295, - JSDocVariadicType = 296, - JSDocNamepathType = 297, - JSDocComment = 298, - JSDocTypeLiteral = 299, - JSDocSignature = 300, - JSDocTag = 301, - JSDocAugmentsTag = 302, - JSDocAuthorTag = 303, - JSDocClassTag = 304, - JSDocCallbackTag = 305, - JSDocEnumTag = 306, - JSDocParameterTag = 307, - JSDocReturnTag = 308, - JSDocThisTag = 309, - JSDocTypeTag = 310, - JSDocTemplateTag = 311, - JSDocTypedefTag = 312, - JSDocPropertyTag = 313, - SyntaxList = 314, - NotEmittedStatement = 315, - PartiallyEmittedExpression = 316, - CommaListExpression = 317, - MergeDeclarationMarker = 318, - EndOfDeclarationMarker = 319, - Count = 320, - FirstAssignment = 60, - LastAssignment = 72, - FirstCompoundAssignment = 61, - LastCompoundAssignment = 72, - FirstReservedWord = 74, - LastReservedWord = 109, - FirstKeyword = 74, - LastKeyword = 148, - FirstFutureReservedWord = 110, - LastFutureReservedWord = 118, - FirstTypeNode = 164, - LastTypeNode = 184, + BacktickToken = 60, + EqualsToken = 61, + PlusEqualsToken = 62, + MinusEqualsToken = 63, + AsteriskEqualsToken = 64, + AsteriskAsteriskEqualsToken = 65, + SlashEqualsToken = 66, + PercentEqualsToken = 67, + LessThanLessThanEqualsToken = 68, + GreaterThanGreaterThanEqualsToken = 69, + GreaterThanGreaterThanGreaterThanEqualsToken = 70, + AmpersandEqualsToken = 71, + BarEqualsToken = 72, + CaretEqualsToken = 73, + Identifier = 74, + BreakKeyword = 75, + CaseKeyword = 76, + CatchKeyword = 77, + ClassKeyword = 78, + ConstKeyword = 79, + ContinueKeyword = 80, + DebuggerKeyword = 81, + DefaultKeyword = 82, + DeleteKeyword = 83, + DoKeyword = 84, + ElseKeyword = 85, + EnumKeyword = 86, + ExportKeyword = 87, + ExtendsKeyword = 88, + FalseKeyword = 89, + FinallyKeyword = 90, + ForKeyword = 91, + FunctionKeyword = 92, + IfKeyword = 93, + ImportKeyword = 94, + InKeyword = 95, + InstanceOfKeyword = 96, + NewKeyword = 97, + NullKeyword = 98, + ReturnKeyword = 99, + SuperKeyword = 100, + SwitchKeyword = 101, + ThisKeyword = 102, + ThrowKeyword = 103, + TrueKeyword = 104, + TryKeyword = 105, + TypeOfKeyword = 106, + VarKeyword = 107, + VoidKeyword = 108, + WhileKeyword = 109, + WithKeyword = 110, + ImplementsKeyword = 111, + InterfaceKeyword = 112, + LetKeyword = 113, + PackageKeyword = 114, + PrivateKeyword = 115, + ProtectedKeyword = 116, + PublicKeyword = 117, + StaticKeyword = 118, + YieldKeyword = 119, + AbstractKeyword = 120, + AsKeyword = 121, + AnyKeyword = 122, + AsyncKeyword = 123, + AwaitKeyword = 124, + BooleanKeyword = 125, + ConstructorKeyword = 126, + DeclareKeyword = 127, + GetKeyword = 128, + InferKeyword = 129, + IsKeyword = 130, + KeyOfKeyword = 131, + ModuleKeyword = 132, + NamespaceKeyword = 133, + NeverKeyword = 134, + ReadonlyKeyword = 135, + RequireKeyword = 136, + NumberKeyword = 137, + ObjectKeyword = 138, + SetKeyword = 139, + StringKeyword = 140, + SymbolKeyword = 141, + TypeKeyword = 142, + UndefinedKeyword = 143, + UniqueKeyword = 144, + UnknownKeyword = 145, + FromKeyword = 146, + GlobalKeyword = 147, + BigIntKeyword = 148, + OfKeyword = 149, + QualifiedName = 150, + ComputedPropertyName = 151, + TypeParameter = 152, + Parameter = 153, + Decorator = 154, + PropertySignature = 155, + PropertyDeclaration = 156, + MethodSignature = 157, + MethodDeclaration = 158, + Constructor = 159, + GetAccessor = 160, + SetAccessor = 161, + CallSignature = 162, + ConstructSignature = 163, + IndexSignature = 164, + TypePredicate = 165, + TypeReference = 166, + FunctionType = 167, + ConstructorType = 168, + TypeQuery = 169, + TypeLiteral = 170, + ArrayType = 171, + TupleType = 172, + OptionalType = 173, + RestType = 174, + UnionType = 175, + IntersectionType = 176, + ConditionalType = 177, + InferType = 178, + ParenthesizedType = 179, + ThisType = 180, + TypeOperator = 181, + IndexedAccessType = 182, + MappedType = 183, + LiteralType = 184, + ImportType = 185, + ObjectBindingPattern = 186, + ArrayBindingPattern = 187, + BindingElement = 188, + ArrayLiteralExpression = 189, + ObjectLiteralExpression = 190, + PropertyAccessExpression = 191, + ElementAccessExpression = 192, + CallExpression = 193, + NewExpression = 194, + TaggedTemplateExpression = 195, + TypeAssertionExpression = 196, + ParenthesizedExpression = 197, + FunctionExpression = 198, + ArrowFunction = 199, + DeleteExpression = 200, + TypeOfExpression = 201, + VoidExpression = 202, + AwaitExpression = 203, + PrefixUnaryExpression = 204, + PostfixUnaryExpression = 205, + BinaryExpression = 206, + ConditionalExpression = 207, + TemplateExpression = 208, + YieldExpression = 209, + SpreadElement = 210, + ClassExpression = 211, + OmittedExpression = 212, + ExpressionWithTypeArguments = 213, + AsExpression = 214, + NonNullExpression = 215, + MetaProperty = 216, + SyntheticExpression = 217, + TemplateSpan = 218, + SemicolonClassElement = 219, + Block = 220, + VariableStatement = 221, + EmptyStatement = 222, + ExpressionStatement = 223, + IfStatement = 224, + DoStatement = 225, + WhileStatement = 226, + ForStatement = 227, + ForInStatement = 228, + ForOfStatement = 229, + ContinueStatement = 230, + BreakStatement = 231, + ReturnStatement = 232, + WithStatement = 233, + SwitchStatement = 234, + LabeledStatement = 235, + ThrowStatement = 236, + TryStatement = 237, + DebuggerStatement = 238, + VariableDeclaration = 239, + VariableDeclarationList = 240, + FunctionDeclaration = 241, + ClassDeclaration = 242, + InterfaceDeclaration = 243, + TypeAliasDeclaration = 244, + EnumDeclaration = 245, + ModuleDeclaration = 246, + ModuleBlock = 247, + CaseBlock = 248, + NamespaceExportDeclaration = 249, + ImportEqualsDeclaration = 250, + ImportDeclaration = 251, + ImportClause = 252, + NamespaceImport = 253, + NamedImports = 254, + ImportSpecifier = 255, + ExportAssignment = 256, + ExportDeclaration = 257, + NamedExports = 258, + ExportSpecifier = 259, + MissingDeclaration = 260, + ExternalModuleReference = 261, + JsxElement = 262, + JsxSelfClosingElement = 263, + JsxOpeningElement = 264, + JsxClosingElement = 265, + JsxFragment = 266, + JsxOpeningFragment = 267, + JsxClosingFragment = 268, + JsxAttribute = 269, + JsxAttributes = 270, + JsxSpreadAttribute = 271, + JsxExpression = 272, + CaseClause = 273, + DefaultClause = 274, + HeritageClause = 275, + CatchClause = 276, + PropertyAssignment = 277, + ShorthandPropertyAssignment = 278, + SpreadAssignment = 279, + EnumMember = 280, + UnparsedPrologue = 281, + UnparsedPrepend = 282, + UnparsedText = 283, + UnparsedInternalText = 284, + UnparsedSyntheticReference = 285, + SourceFile = 286, + Bundle = 287, + UnparsedSource = 288, + InputFiles = 289, + JSDocTypeExpression = 290, + JSDocAllType = 291, + JSDocUnknownType = 292, + JSDocNullableType = 293, + JSDocNonNullableType = 294, + JSDocOptionalType = 295, + JSDocFunctionType = 296, + JSDocVariadicType = 297, + JSDocNamepathType = 298, + JSDocComment = 299, + JSDocTypeLiteral = 300, + JSDocSignature = 301, + JSDocTag = 302, + JSDocAugmentsTag = 303, + JSDocAuthorTag = 304, + JSDocClassTag = 305, + JSDocCallbackTag = 306, + JSDocEnumTag = 307, + JSDocParameterTag = 308, + JSDocReturnTag = 309, + JSDocThisTag = 310, + JSDocTypeTag = 311, + JSDocTemplateTag = 312, + JSDocTypedefTag = 313, + JSDocPropertyTag = 314, + SyntaxList = 315, + NotEmittedStatement = 316, + PartiallyEmittedExpression = 317, + CommaListExpression = 318, + MergeDeclarationMarker = 319, + EndOfDeclarationMarker = 320, + SyntheticReferenceExpression = 321, + Count = 322, + FirstAssignment = 61, + LastAssignment = 73, + FirstCompoundAssignment = 62, + LastCompoundAssignment = 73, + FirstReservedWord = 75, + LastReservedWord = 110, + FirstKeyword = 75, + LastKeyword = 149, + FirstFutureReservedWord = 111, + LastFutureReservedWord = 119, + FirstTypeNode = 165, + LastTypeNode = 185, FirstPunctuation = 18, - LastPunctuation = 72, + LastPunctuation = 73, FirstToken = 0, - LastToken = 148, + LastToken = 149, FirstTriviaToken = 2, LastTriviaToken = 7, FirstLiteralToken = 8, LastLiteralToken = 14, FirstTemplateToken = 14, LastTemplateToken = 17, - FirstBinaryOperator = 28, - LastBinaryOperator = 72, - FirstNode = 149, - FirstJSDocNode = 289, - LastJSDocNode = 313, - FirstJSDocTagNode = 301, - LastJSDocTagNode = 313, + FirstBinaryOperator = 29, + LastBinaryOperator = 73, + FirstNode = 150, + FirstJSDocNode = 290, + LastJSDocNode = 314, + FirstJSDocTagNode = 302, + LastJSDocTagNode = 314, } export enum NodeFlags { None = 0, @@ -435,27 +437,28 @@ declare namespace ts { NestedNamespace = 4, Synthesized = 8, Namespace = 16, - ExportContext = 32, - ContainsThis = 64, - HasImplicitReturn = 128, - HasExplicitReturn = 256, - GlobalAugmentation = 512, - HasAsyncFunctions = 1024, - DisallowInContext = 2048, - YieldContext = 4096, - DecoratorContext = 8192, - AwaitContext = 16384, - ThisNodeHasError = 32768, - JavaScriptFile = 65536, - ThisNodeOrAnySubNodesHasError = 131072, - HasAggregatedChildData = 262144, - JSDoc = 2097152, - JsonFile = 16777216, + OptionalChain = 32, + ExportContext = 64, + ContainsThis = 128, + HasImplicitReturn = 256, + HasExplicitReturn = 512, + GlobalAugmentation = 1024, + HasAsyncFunctions = 2048, + DisallowInContext = 4096, + YieldContext = 8192, + DecoratorContext = 16384, + AwaitContext = 32768, + ThisNodeHasError = 65536, + JavaScriptFile = 131072, + ThisNodeOrAnySubNodesHasError = 262144, + HasAggregatedChildData = 524288, + JSDoc = 4194304, + JsonFile = 33554432, BlockScoped = 3, - ReachabilityCheckFlags = 384, - ReachabilityAndEmitFlags = 1408, - ContextFlags = 12679168, - TypeExcludesFlags = 20480, + ReachabilityCheckFlags = 768, + ReachabilityAndEmitFlags = 2816, + ContextFlags = 25358336, + TypeExcludesFlags = 40960, } export enum ModifierFlags { None = 0, @@ -505,8 +508,10 @@ declare namespace ts { export interface Token extends Node { kind: TKind; } + export type DotToken = Token; export type DotDotDotToken = Token; export type QuestionToken = Token; + export type QuestionDotToken = Token; export type ExclamationToken = Token; export type ColonToken = Token; export type EqualsToken = Token; @@ -1077,8 +1082,13 @@ declare namespace ts { export interface PropertyAccessExpression extends MemberExpression, NamedDeclaration { kind: SyntaxKind.PropertyAccessExpression; expression: LeftHandSideExpression; + questionDotToken?: QuestionDotToken; name: Identifier; } + export interface PropertyAccessChain extends PropertyAccessExpression { + _optionalChainBrand: any; + kind: SyntaxKind.PropertyAccessExpression; + } export interface SuperPropertyAccessExpression extends PropertyAccessExpression { expression: SuperExpression; } @@ -1090,8 +1100,13 @@ declare namespace ts { export interface ElementAccessExpression extends MemberExpression { kind: SyntaxKind.ElementAccessExpression; expression: LeftHandSideExpression; + questionDotToken?: QuestionDotToken; argumentExpression: Expression; } + export interface ElementAccessChain extends ElementAccessExpression { + _optionalChainBrand: any; + kind: SyntaxKind.ElementAccessExpression; + } export interface SuperElementAccessExpression extends ElementAccessExpression { expression: SuperExpression; } @@ -1099,9 +1114,15 @@ declare namespace ts { export interface CallExpression extends LeftHandSideExpression, Declaration { kind: SyntaxKind.CallExpression; expression: LeftHandSideExpression; + questionDotToken?: QuestionDotToken; typeArguments?: NodeArray; arguments: NodeArray; } + export interface CallChain extends CallExpression { + _optionalChainBrand: any; + kind: SyntaxKind.CallExpression; + } + export type OptionalChain = PropertyAccessChain | ElementAccessChain | CallChain; export interface SuperCall extends CallExpression { expression: SuperExpression; } @@ -3445,8 +3466,12 @@ declare namespace ts { function isArrayLiteralExpression(node: Node): node is ArrayLiteralExpression; function isObjectLiteralExpression(node: Node): node is ObjectLiteralExpression; function isPropertyAccessExpression(node: Node): node is PropertyAccessExpression; + function isPropertyAccessChain(node: Node): node is PropertyAccessChain; function isElementAccessExpression(node: Node): node is ElementAccessExpression; + function isElementAccessChain(node: Node): node is ElementAccessChain; function isCallExpression(node: Node): node is CallExpression; + function isCallChain(node: Node): node is CallChain; + function isOptionalChain(node: Node): node is PropertyAccessChain | ElementAccessChain | CallChain; function isNewExpression(node: Node): node is NewExpression; function isTaggedTemplateExpression(node: Node): node is TaggedTemplateExpression; function isTypeAssertion(node: Node): node is TypeAssertion; @@ -3890,10 +3915,16 @@ declare namespace ts { function updateObjectLiteral(node: ObjectLiteralExpression, properties: ReadonlyArray): ObjectLiteralExpression; function createPropertyAccess(expression: Expression, name: string | Identifier): PropertyAccessExpression; function updatePropertyAccess(node: PropertyAccessExpression, expression: Expression, name: Identifier): PropertyAccessExpression; + function createPropertyAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, name: string | Identifier): PropertyAccessChain; + function updatePropertyAccessChain(node: PropertyAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, name: Identifier): PropertyAccessChain; function createElementAccess(expression: Expression, index: number | Expression): ElementAccessExpression; function updateElementAccess(node: ElementAccessExpression, expression: Expression, argumentExpression: Expression): ElementAccessExpression; + function createElementAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, index: number | Expression): ElementAccessChain; + function updateElementAccessChain(node: ElementAccessChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, argumentExpression: Expression): ElementAccessChain; function createCall(expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined): CallExpression; function updateCall(node: CallExpression, expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray): CallExpression; + function createCallChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined): CallChain; + function updateCallChain(node: CallChain, expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray): CallChain; function createNew(expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined): NewExpression; function updateNew(node: NewExpression, expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray | undefined): NewExpression; /** @deprecated */ function createTaggedTemplate(tag: Expression, template: TemplateLiteral): TaggedTemplateExpression; diff --git a/tests/baselines/reference/callChain.js b/tests/baselines/reference/callChain.js new file mode 100644 index 0000000000000..dde51814f9b8b --- /dev/null +++ b/tests/baselines/reference/callChain.js @@ -0,0 +1,17 @@ +//// [callChain.ts] +declare const o1: undefined | (() => number); +o1?.(); + +declare const o2: undefined | { b: () => number }; +o2?.b(); + +declare const o3: { b: (() => { c: string }) | undefined }; +o3.b?.().c; + + +//// [callChain.js] +"use strict"; +var _a, _b, _c, _d; +(_a = o1) === null || _a === void 0 ? void 0 : _a(); +(_b = o2) === null || _b === void 0 ? void 0 : _b.b(); +(_d = (_c = o3).b) === null || _d === void 0 ? void 0 : _d.call(_c).c; diff --git a/tests/baselines/reference/callChain.symbols b/tests/baselines/reference/callChain.symbols new file mode 100644 index 0000000000000..b8d537402e7ac --- /dev/null +++ b/tests/baselines/reference/callChain.symbols @@ -0,0 +1,28 @@ +=== tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts === +declare const o1: undefined | (() => number); +>o1 : Symbol(o1, Decl(callChain.ts, 0, 13)) + +o1?.(); +>o1 : Symbol(o1, Decl(callChain.ts, 0, 13)) + +declare const o2: undefined | { b: () => number }; +>o2 : Symbol(o2, Decl(callChain.ts, 3, 13)) +>b : Symbol(b, Decl(callChain.ts, 3, 31)) + +o2?.b(); +>o2?.b : Symbol(b, Decl(callChain.ts, 3, 31)) +>o2 : Symbol(o2, Decl(callChain.ts, 3, 13)) +>b : Symbol(b, Decl(callChain.ts, 3, 31)) + +declare const o3: { b: (() => { c: string }) | undefined }; +>o3 : Symbol(o3, Decl(callChain.ts, 6, 13)) +>b : Symbol(b, Decl(callChain.ts, 6, 19)) +>c : Symbol(c, Decl(callChain.ts, 6, 31)) + +o3.b?.().c; +>o3.b?.().c : Symbol(c, Decl(callChain.ts, 6, 31)) +>o3.b : Symbol(b, Decl(callChain.ts, 6, 19)) +>o3 : Symbol(o3, Decl(callChain.ts, 6, 13)) +>b : Symbol(b, Decl(callChain.ts, 6, 19)) +>c : Symbol(c, Decl(callChain.ts, 6, 31)) + diff --git a/tests/baselines/reference/callChain.types b/tests/baselines/reference/callChain.types new file mode 100644 index 0000000000000..ba476114311c7 --- /dev/null +++ b/tests/baselines/reference/callChain.types @@ -0,0 +1,31 @@ +=== tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts === +declare const o1: undefined | (() => number); +>o1 : (() => number) | undefined + +o1?.(); +>o1?.() : number | undefined +>o1 : (() => number) | undefined + +declare const o2: undefined | { b: () => number }; +>o2 : { b: () => number; } | undefined +>b : () => number + +o2?.b(); +>o2?.b() : number | undefined +>o2?.b : (() => number) | undefined +>o2 : { b: () => number; } | undefined +>b : (() => number) | undefined + +declare const o3: { b: (() => { c: string }) | undefined }; +>o3 : { b: (() => { c: string; }) | undefined; } +>b : (() => { c: string; }) | undefined +>c : string + +o3.b?.().c; +>o3.b?.().c : string | undefined +>o3.b?.() : { c: string; } | undefined +>o3.b : (() => { c: string; }) | undefined +>o3 : { b: (() => { c: string; }) | undefined; } +>b : (() => { c: string; }) | undefined +>c : string | undefined + diff --git a/tests/baselines/reference/elementAccessChain.js b/tests/baselines/reference/elementAccessChain.js new file mode 100644 index 0000000000000..c6b4de76afa07 --- /dev/null +++ b/tests/baselines/reference/elementAccessChain.js @@ -0,0 +1,21 @@ +//// [elementAccessChain.ts] +declare const o1: undefined | { b: string }; +o1?.["b"]; + +declare const o2: undefined | { b: { c: string } }; +o2?.["b"].c; +o2?.b["c"]; + +declare const o3: { b: undefined | { c: string } }; +o3["b"]?.c; +o3.b?.["c"]; + + +//// [elementAccessChain.js] +"use strict"; +var _a, _b, _c, _d, _e; +(_a = o1) === null || _a === void 0 ? void 0 : _a["b"]; +(_b = o2) === null || _b === void 0 ? void 0 : _b["b"].c; +(_c = o2) === null || _c === void 0 ? void 0 : _c.b["c"]; +(_d = o3["b"]) === null || _d === void 0 ? void 0 : _d.c; +(_e = o3.b) === null || _e === void 0 ? void 0 : _e["c"]; diff --git a/tests/baselines/reference/elementAccessChain.symbols b/tests/baselines/reference/elementAccessChain.symbols new file mode 100644 index 0000000000000..1a5d7cf975312 --- /dev/null +++ b/tests/baselines/reference/elementAccessChain.symbols @@ -0,0 +1,39 @@ +=== tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts === +declare const o1: undefined | { b: string }; +>o1 : Symbol(o1, Decl(elementAccessChain.ts, 0, 13)) +>b : Symbol(b, Decl(elementAccessChain.ts, 0, 31)) + +o1?.["b"]; +>o1 : Symbol(o1, Decl(elementAccessChain.ts, 0, 13)) + +declare const o2: undefined | { b: { c: string } }; +>o2 : Symbol(o2, Decl(elementAccessChain.ts, 3, 13)) +>b : Symbol(b, Decl(elementAccessChain.ts, 3, 31)) +>c : Symbol(c, Decl(elementAccessChain.ts, 3, 36)) + +o2?.["b"].c; +>o2?.["b"].c : Symbol(c, Decl(elementAccessChain.ts, 3, 36)) +>o2 : Symbol(o2, Decl(elementAccessChain.ts, 3, 13)) +>c : Symbol(c, Decl(elementAccessChain.ts, 3, 36)) + +o2?.b["c"]; +>o2?.b : Symbol(b, Decl(elementAccessChain.ts, 3, 31)) +>o2 : Symbol(o2, Decl(elementAccessChain.ts, 3, 13)) +>b : Symbol(b, Decl(elementAccessChain.ts, 3, 31)) + +declare const o3: { b: undefined | { c: string } }; +>o3 : Symbol(o3, Decl(elementAccessChain.ts, 7, 13)) +>b : Symbol(b, Decl(elementAccessChain.ts, 7, 19)) +>c : Symbol(c, Decl(elementAccessChain.ts, 7, 36)) + +o3["b"]?.c; +>o3["b"]?.c : Symbol(c, Decl(elementAccessChain.ts, 7, 36)) +>o3 : Symbol(o3, Decl(elementAccessChain.ts, 7, 13)) +>"b" : Symbol(b, Decl(elementAccessChain.ts, 7, 19)) +>c : Symbol(c, Decl(elementAccessChain.ts, 7, 36)) + +o3.b?.["c"]; +>o3.b : Symbol(b, Decl(elementAccessChain.ts, 7, 19)) +>o3 : Symbol(o3, Decl(elementAccessChain.ts, 7, 13)) +>b : Symbol(b, Decl(elementAccessChain.ts, 7, 19)) + diff --git a/tests/baselines/reference/elementAccessChain.types b/tests/baselines/reference/elementAccessChain.types new file mode 100644 index 0000000000000..b93246f71c01e --- /dev/null +++ b/tests/baselines/reference/elementAccessChain.types @@ -0,0 +1,48 @@ +=== tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts === +declare const o1: undefined | { b: string }; +>o1 : { b: string; } | undefined +>b : string + +o1?.["b"]; +>o1?.["b"] : string | undefined +>o1 : { b: string; } | undefined +>"b" : "b" + +declare const o2: undefined | { b: { c: string } }; +>o2 : { b: { c: string; }; } | undefined +>b : { c: string; } +>c : string + +o2?.["b"].c; +>o2?.["b"].c : string | undefined +>o2?.["b"] : { c: string; } | undefined +>o2 : { b: { c: string; }; } | undefined +>"b" : "b" +>c : string | undefined + +o2?.b["c"]; +>o2?.b["c"] : string | undefined +>o2?.b : { c: string; } | undefined +>o2 : { b: { c: string; }; } | undefined +>b : { c: string; } | undefined +>"c" : "c" + +declare const o3: { b: undefined | { c: string } }; +>o3 : { b: { c: string; } | undefined; } +>b : { c: string; } | undefined +>c : string + +o3["b"]?.c; +>o3["b"]?.c : string | undefined +>o3["b"] : { c: string; } | undefined +>o3 : { b: { c: string; } | undefined; } +>"b" : "b" +>c : string | undefined + +o3.b?.["c"]; +>o3.b?.["c"] : string | undefined +>o3.b : { c: string; } | undefined +>o3 : { b: { c: string; } | undefined; } +>b : { c: string; } | undefined +>"c" : "c" + diff --git a/tests/baselines/reference/propertyAccessChain.js b/tests/baselines/reference/propertyAccessChain.js new file mode 100644 index 0000000000000..f9ea8a78f26ba --- /dev/null +++ b/tests/baselines/reference/propertyAccessChain.js @@ -0,0 +1,17 @@ +//// [propertyAccessChain.ts] +declare const o1: undefined | { b: string }; +o1?.b; + +declare const o2: undefined | { b: { c: string } }; +o2?.b.c; + +declare const o3: { b: undefined | { c: string } }; +o3.b?.c; + + +//// [propertyAccessChain.js] +"use strict"; +var _a, _b, _c; +(_a = o1) === null || _a === void 0 ? void 0 : _a.b; +(_b = o2) === null || _b === void 0 ? void 0 : _b.b.c; +(_c = o3.b) === null || _c === void 0 ? void 0 : _c.c; diff --git a/tests/baselines/reference/propertyAccessChain.symbols b/tests/baselines/reference/propertyAccessChain.symbols new file mode 100644 index 0000000000000..384ac9d9f05b9 --- /dev/null +++ b/tests/baselines/reference/propertyAccessChain.symbols @@ -0,0 +1,34 @@ +=== tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts === +declare const o1: undefined | { b: string }; +>o1 : Symbol(o1, Decl(propertyAccessChain.ts, 0, 13)) +>b : Symbol(b, Decl(propertyAccessChain.ts, 0, 31)) + +o1?.b; +>o1?.b : Symbol(b, Decl(propertyAccessChain.ts, 0, 31)) +>o1 : Symbol(o1, Decl(propertyAccessChain.ts, 0, 13)) +>b : Symbol(b, Decl(propertyAccessChain.ts, 0, 31)) + +declare const o2: undefined | { b: { c: string } }; +>o2 : Symbol(o2, Decl(propertyAccessChain.ts, 3, 13)) +>b : Symbol(b, Decl(propertyAccessChain.ts, 3, 31)) +>c : Symbol(c, Decl(propertyAccessChain.ts, 3, 36)) + +o2?.b.c; +>o2?.b.c : Symbol(c, Decl(propertyAccessChain.ts, 3, 36)) +>o2?.b : Symbol(b, Decl(propertyAccessChain.ts, 3, 31)) +>o2 : Symbol(o2, Decl(propertyAccessChain.ts, 3, 13)) +>b : Symbol(b, Decl(propertyAccessChain.ts, 3, 31)) +>c : Symbol(c, Decl(propertyAccessChain.ts, 3, 36)) + +declare const o3: { b: undefined | { c: string } }; +>o3 : Symbol(o3, Decl(propertyAccessChain.ts, 6, 13)) +>b : Symbol(b, Decl(propertyAccessChain.ts, 6, 19)) +>c : Symbol(c, Decl(propertyAccessChain.ts, 6, 36)) + +o3.b?.c; +>o3.b?.c : Symbol(c, Decl(propertyAccessChain.ts, 6, 36)) +>o3.b : Symbol(b, Decl(propertyAccessChain.ts, 6, 19)) +>o3 : Symbol(o3, Decl(propertyAccessChain.ts, 6, 13)) +>b : Symbol(b, Decl(propertyAccessChain.ts, 6, 19)) +>c : Symbol(c, Decl(propertyAccessChain.ts, 6, 36)) + diff --git a/tests/baselines/reference/propertyAccessChain.types b/tests/baselines/reference/propertyAccessChain.types new file mode 100644 index 0000000000000..42122303839de --- /dev/null +++ b/tests/baselines/reference/propertyAccessChain.types @@ -0,0 +1,34 @@ +=== tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts === +declare const o1: undefined | { b: string }; +>o1 : { b: string; } | undefined +>b : string + +o1?.b; +>o1?.b : string | undefined +>o1 : { b: string; } | undefined +>b : string | undefined + +declare const o2: undefined | { b: { c: string } }; +>o2 : { b: { c: string; }; } | undefined +>b : { c: string; } +>c : string + +o2?.b.c; +>o2?.b.c : string | undefined +>o2?.b : { c: string; } | undefined +>o2 : { b: { c: string; }; } | undefined +>b : { c: string; } | undefined +>c : string | undefined + +declare const o3: { b: undefined | { c: string } }; +>o3 : { b: { c: string; } | undefined; } +>b : { c: string; } | undefined +>c : string + +o3.b?.c; +>o3.b?.c : string | undefined +>o3.b : { c: string; } | undefined +>o3 : { b: { c: string; } | undefined; } +>b : { c: string; } | undefined +>c : string | undefined + diff --git a/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts b/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts new file mode 100644 index 0000000000000..1e239f70628cd --- /dev/null +++ b/tests/cases/conformance/expressions/optionalChaining/callChain/callChain.ts @@ -0,0 +1,10 @@ +// @strict: true + +declare const o1: undefined | (() => number); +o1?.(); + +declare const o2: undefined | { b: () => number }; +o2?.b(); + +declare const o3: { b: (() => { c: string }) | undefined }; +o3.b?.().c; diff --git a/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts b/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts new file mode 100644 index 0000000000000..b1a9efe746d4a --- /dev/null +++ b/tests/cases/conformance/expressions/optionalChaining/elementAccessChain/elementAccessChain.ts @@ -0,0 +1,12 @@ +// @strict: true + +declare const o1: undefined | { b: string }; +o1?.["b"]; + +declare const o2: undefined | { b: { c: string } }; +o2?.["b"].c; +o2?.b["c"]; + +declare const o3: { b: undefined | { c: string } }; +o3["b"]?.c; +o3.b?.["c"]; diff --git a/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts b/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts new file mode 100644 index 0000000000000..0514a8e1880c9 --- /dev/null +++ b/tests/cases/conformance/expressions/optionalChaining/propertyAccessChain/propertyAccessChain.ts @@ -0,0 +1,10 @@ +// @strict: true + +declare const o1: undefined | { b: string }; +o1?.b; + +declare const o2: undefined | { b: { c: string } }; +o2?.b.c; + +declare const o3: { b: undefined | { c: string } }; +o3.b?.c; diff --git a/tests/cases/fourslash/signatureHelpOptionalCall.ts b/tests/cases/fourslash/signatureHelpOptionalCall.ts new file mode 100644 index 0000000000000..fea90d7ee1f2a --- /dev/null +++ b/tests/cases/fourslash/signatureHelpOptionalCall.ts @@ -0,0 +1,14 @@ +/// + +////function fnTest(str: string, num: number) { } +////fnTest?.(/*1*/); + +verify.signatureHelp( + { + marker: "1", + text: 'fnTest(str: string, num: number): void', + parameterCount: 2, + parameterName: "str", + parameterSpan: "str: string", + }, +);