Add support for Optional Chaining (#33294)

* Add support for Optional Chaining

* Add grammar error for invalid tagged template, more tests

* Prototype

* PR feedback

* Add errors for invalid assignments and a trailing '?.'

* Add additional signature help test, fix lint warnings

* Fix to insert text for completions

* Add initial control-flow analysis for optional chains

* PR Feedback and more tests

* Update to control flow

* Remove mangled smart quotes in comments

* Fix lint, PR feedback

* Updates to control flow

* Switch to FlowCondition for CFA of optional chains

* Fix ?. insertion for completions on type variables

* Accept API baseline change

* Clean up types

* improve control-flow debug output

* Revert Debug.formatControlFlowGraph helper
This commit is contained in:
Ron Buckton
2019-09-30 12:33:28 -07:00
committed by GitHub
parent 7ce793c5b8
commit fcd9334f57
76 changed files with 6422 additions and 885 deletions
+121 -24
View File
@@ -797,6 +797,10 @@ namespace ts {
case SyntaxKind.VariableDeclaration:
bindVariableDeclarationFlow(<VariableDeclaration>node);
break;
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
bindAccessExpressionFlow(<AccessExpression>node);
break;
case SyntaxKind.CallExpression:
bindCallExpressionFlow(<CallExpression>node);
break;
@@ -941,7 +945,9 @@ namespace ts {
}
if (expression.kind === SyntaxKind.TrueKeyword && flags & FlowFlags.FalseCondition ||
expression.kind === SyntaxKind.FalseKeyword && flags & FlowFlags.TrueCondition) {
return unreachableFlow;
if (!isOptionalChainRoot(expression.parent)) {
return unreachableFlow;
}
}
if (!isNarrowingExpression(expression)) {
return antecedent;
@@ -1015,23 +1021,28 @@ namespace ts {
}
function isTopLevelLogicalExpression(node: Node): boolean {
while (node.parent.kind === SyntaxKind.ParenthesizedExpression ||
node.parent.kind === SyntaxKind.PrefixUnaryExpression &&
(<PrefixUnaryExpression>node.parent).operator === SyntaxKind.ExclamationToken) {
while (isParenthesizedExpression(node.parent) ||
isPrefixUnaryExpression(node.parent) && node.parent.operator === SyntaxKind.ExclamationToken) {
node = node.parent;
}
return !isStatementCondition(node) && !isLogicalExpression(node.parent);
return !isStatementCondition(node) &&
!isLogicalExpression(node.parent) &&
!(isOptionalChain(node.parent) && node.parent.expression === node);
}
function doWithConditionalBranches<T>(action: (value: T) => void, value: T, trueTarget: FlowLabel, falseTarget: FlowLabel) {
const savedTrueTarget = currentTrueTarget;
const savedFalseTarget = currentFalseTarget;
currentTrueTarget = trueTarget;
currentFalseTarget = falseTarget;
action(value);
currentTrueTarget = savedTrueTarget;
currentFalseTarget = savedFalseTarget;
}
function bindCondition(node: Expression | undefined, trueTarget: FlowLabel, falseTarget: FlowLabel) {
const saveTrueTarget = currentTrueTarget;
const saveFalseTarget = currentFalseTarget;
currentTrueTarget = trueTarget;
currentFalseTarget = falseTarget;
bind(node);
currentTrueTarget = saveTrueTarget;
currentFalseTarget = saveFalseTarget;
if (!node || !isLogicalExpression(node)) {
doWithConditionalBranches(bind, node, trueTarget, falseTarget);
if (!node || !isLogicalExpression(node) && !(isOptionalChain(node) && isOutermostOptionalChain(node))) {
addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node));
addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node));
}
@@ -1536,22 +1547,96 @@ namespace ts {
}
}
function bindCallExpressionFlow(node: CallExpression) {
// If the target of the call expression is a function expression or arrow function we have
// an immediately invoked function expression (IIFE). Initialize the flowNode property to
// the current control flow (which includes evaluation of the IIFE arguments).
let expr: Expression = node.expression;
while (expr.kind === SyntaxKind.ParenthesizedExpression) {
expr = (<ParenthesizedExpression>expr).expression;
function isOutermostOptionalChain(node: OptionalChain) {
return !isOptionalChain(node.parent) || isOptionalChainRoot(node.parent) || node !== node.parent.expression;
}
function bindOptionalExpression(node: Expression, trueTarget: FlowLabel, falseTarget: FlowLabel) {
doWithConditionalBranches(bind, node, trueTarget, falseTarget);
if (!isOptionalChain(node) || isOutermostOptionalChain(node)) {
addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node));
addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node));
}
if (expr.kind === SyntaxKind.FunctionExpression || expr.kind === SyntaxKind.ArrowFunction) {
bindEach(node.typeArguments);
bindEach(node.arguments);
bind(node.expression);
}
function bindOptionalChainRest(node: OptionalChain) {
bind(node.questionDotToken);
switch (node.kind) {
case SyntaxKind.PropertyAccessExpression:
bind(node.name);
break;
case SyntaxKind.ElementAccessExpression:
bind(node.argumentExpression);
break;
case SyntaxKind.CallExpression:
bindEach(node.typeArguments);
bindEach(node.arguments);
break;
}
}
function bindOptionalChain(node: OptionalChain, trueTarget: FlowLabel, falseTarget: FlowLabel) {
// For an optional chain, we emulate the behavior of a logical expression:
//
// a?.b -> a && a.b
// a?.b.c -> a && a.b.c
// a?.b?.c -> a && a.b && a.b.c
// a?.[x = 1] -> a && a[x = 1]
//
// To do this we descend through the chain until we reach the root of a chain (the expression with a `?.`)
// and build it's CFA graph as if it were the first condition (`a && ...`). Then we bind the rest
// of the node as part of the "true" branch, and continue to do so as we ascend back up to the outermost
// chain node. We then treat the entire node as the right side of the expression.
const preChainLabel = node.questionDotToken ? createBranchLabel() : undefined;
bindOptionalExpression(node.expression, preChainLabel || trueTarget, falseTarget);
if (preChainLabel) {
currentFlow = finishFlowLabel(preChainLabel);
}
doWithConditionalBranches(bindOptionalChainRest, node, trueTarget, falseTarget);
if (isOutermostOptionalChain(node)) {
addAntecedent(trueTarget, createFlowCondition(FlowFlags.TrueCondition, currentFlow, node));
addAntecedent(falseTarget, createFlowCondition(FlowFlags.FalseCondition, currentFlow, node));
}
}
function bindOptionalChainFlow(node: OptionalChain) {
if (isTopLevelLogicalExpression(node)) {
const postExpressionLabel = createBranchLabel();
bindOptionalChain(node, postExpressionLabel, postExpressionLabel);
currentFlow = finishFlowLabel(postExpressionLabel);
}
else {
bindOptionalChain(node, currentTrueTarget!, currentFalseTarget!);
}
}
function bindAccessExpressionFlow(node: AccessExpression) {
if (isOptionalChain(node)) {
bindOptionalChainFlow(node);
}
else {
bindEachChild(node);
}
}
function bindCallExpressionFlow(node: CallExpression) {
if (isOptionalChain(node)) {
bindOptionalChainFlow(node);
}
else {
// If the target of the call expression is a function expression or arrow function we have
// an immediately invoked function expression (IIFE). Initialize the flowNode property to
// the current control flow (which includes evaluation of the IIFE arguments).
const expr = skipParentheses(node.expression);
if (expr.kind === SyntaxKind.FunctionExpression || expr.kind === SyntaxKind.ArrowFunction) {
bindEach(node.typeArguments);
bindEach(node.arguments);
bind(node.expression);
}
else {
bindEachChild(node);
}
}
if (node.expression.kind === SyntaxKind.PropertyAccessExpression) {
const propertyAccess = <PropertyAccessExpression>node.expression;
if (isNarrowableOperand(propertyAccess.expression) && isPushOrUnshiftIdentifier(propertyAccess.name)) {
@@ -3297,6 +3382,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;
}
@@ -3692,6 +3781,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) {
@@ -3707,6 +3800,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) {
+159 -46
View File
@@ -333,6 +333,10 @@ namespace ts {
/** This will be set during calls to `getResolvedSignature` where services determines an apparent number of arguments greater than what is actually provided. */
let apparentArgumentCount: number | undefined;
// This object is reused for `checkOptionalExpression` return values to avoid frequent GC due to nursery object allocations.
// This object represents a pool-size of 1.
const pooledOptionalTypeResult: { isOptional: boolean, type: Type } = { isOptional: false, type: undefined! };
// for public members that accept a Node or one of its subtypes, we must guard against
// synthetic nodes created during transformations by calling `getParseTreeNode`.
// for most of these, we perform the guard only on `checker` to avoid any possible
@@ -380,8 +384,10 @@ namespace ts {
getParameterType: getTypeAtPosition,
getPromisedTypeOfPromise,
getReturnTypeOfSignature,
isNullableType,
getNullableType,
getNonNullableType,
getNonOptionalType: removeOptionalTypeMarker,
getTypeArguments,
typeToTypeNode: nodeBuilder.typeToTypeNode,
indexInfoToIndexSignatureDeclaration: nodeBuilder.indexInfoToIndexSignatureDeclaration,
@@ -546,6 +552,7 @@ namespace ts {
getNullType: () => nullType,
getESSymbolType: () => esSymbolType,
getNeverType: () => neverType,
getOptionalType: () => optionalType,
isSymbolAccessible,
getObjectFlags,
isArrayType,
@@ -648,6 +655,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");
@@ -1171,7 +1179,7 @@ namespace ts {
// obtain item referenced by 'export='
mainModule = resolveExternalModuleSymbol(mainModule);
if (mainModule.flags & SymbolFlags.Namespace) {
// If were merging an augmentation to a pattern ambient module, we want to
// If we're merging an augmentation to a pattern ambient module, we want to
// perform the merge unidirectionally from the augmentation ('a.foo') to
// the pattern ('*.foo'), so that 'getMergedSymbol()' on a.foo gives you
// all the exports both from the pattern and from the augmentation, but
@@ -2657,7 +2665,7 @@ namespace ts {
if (patternAmbientModules) {
const pattern = findBestPatternMatch(patternAmbientModules, _ => _.pattern, moduleReference);
if (pattern) {
// If the module reference matched a pattern ambient module ('*.foo') but theres also a
// If the module reference matched a pattern ambient module ('*.foo') but there's also a
// module augmentation by the specific name requested ('a.foo'), we store the merged symbol
// by the augmentation name ('a.foo'), because asking for *.foo should not give you exports
// from a.foo.
@@ -8599,6 +8607,12 @@ namespace ts {
return result;
}
function createOptionalCallSignature(signature: Signature) {
const result = cloneSignature(signature);
result.isOptionalCall = true;
return result;
}
function getExpandedParameters(sig: Signature): readonly Symbol[] {
if (sig.hasRestParameter) {
const restIndex = sig.parameters.length - 1;
@@ -10211,6 +10225,9 @@ namespace ts {
signature.unionSignatures ? getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature), UnionReduction.Subtype) :
getReturnTypeFromAnnotation(signature.declaration!) ||
(nodeIsMissing((<FunctionLikeDeclaration>signature.declaration).body) ? anyType : getReturnTypeFromBody(<FunctionLikeDeclaration>signature.declaration));
if (signature.isOptionalCall) {
type = propagateOptionalTypeMarker(type, /*wasOptional*/ true);
}
if (!popTypeResolution()) {
if (signature.declaration) {
const typeNode = getEffectiveReturnTypeNode(signature.declaration);
@@ -15653,7 +15670,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));
}
@@ -16620,6 +16637,53 @@ 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 createPooledOptionalTypeResult(isOptional: boolean, type: Type) {
pooledOptionalTypeResult.isOptional = isOptional;
pooledOptionalTypeResult.type = type;
return pooledOptionalTypeResult;
}
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.
isOptional = isNullableType(type);
return createPooledOptionalTypeResult(isOptional, isOptional ? getNonNullableType(type) : type);
}
// If we do not have a questionDotToken, then we are an OptionalChain and we remove the optionalType and
// indicate whether we need to add optionalType back into the result.
const nonOptionalType = removeOptionalTypeMarker(type);
if (nonOptionalType !== type) {
isOptional = true;
type = nonOptionalType;
}
}
type = checkNonNullType(type, expression, nullDiagnostic, undefinedDiagnostic, nullOrUndefinedDiagnostic);
return createPooledOptionalTypeResult(isOptional, type);
}
/**
* Is source potentially coercible to target type under `==`.
@@ -18105,7 +18169,7 @@ namespace ts {
}
function getFlowNodeId(flow: FlowNode): number {
if (!flow.id) {
if (!flow.id || flow.id < 0) {
flow.id = nextFlowId;
nextFlowId++;
}
@@ -18653,7 +18717,7 @@ namespace ts {
// circularities in control flow analysis, we use getTypeOfDottedName when resolving the call
// target expression of an assertion.
const funcType = node.parent.kind === SyntaxKind.ExpressionStatement ? getTypeOfDottedName(node.expression, /*diagnostic*/ undefined) :
node.expression.kind !== SyntaxKind.SuperKeyword ? checkNonNullExpression(node.expression) :
node.expression.kind !== SyntaxKind.SuperKeyword ? checkOptionalExpression(node, node.expression).type :
undefined;
const signatures = getSignaturesOfType(funcType && getApparentType(funcType) || unknownType, SignatureKind.Call);
const candidate = signatures.length === 1 && !signatures[0].typeParameters ? signatures[0] :
@@ -19571,7 +19635,7 @@ namespace ts {
function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {
if (hasMatchingArgument(callExpression, reference)) {
const signature = getEffectsSignature(callExpression);
const signature = assumeTrue || !isCallChain(callExpression) ? getEffectsSignature(callExpression) : undefined;
const predicate = signature && getTypePredicateOfSignature(signature);
if (predicate && (predicate.kind === TypePredicateKind.This || predicate.kind === TypePredicateKind.Identifier)) {
return narrowTypeByTypePredicate(type, predicate, callExpression, assumeTrue);
@@ -19614,6 +19678,10 @@ namespace ts {
// Narrow the given type based on the given expression having the assumed boolean value. The returned type
// will be a subtype or the same type as the argument.
function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type {
// for `a?.b`, we emulate a synthetic `a !== null && a !== undefined` condition for `a`
if (isOptionalChainRoot(expr.parent)) {
return narrowTypeByOptionality(type, expr, assumeTrue);
}
switch (expr.kind) {
case SyntaxKind.Identifier:
case SyntaxKind.ThisKeyword:
@@ -19635,6 +19703,19 @@ namespace ts {
}
return type;
}
function narrowTypeByOptionality(type: Type, expr: Expression, assumePresent: boolean): Type {
if (isMatchingReference(reference, expr)) {
return getTypeWithFacts(type, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull);
}
if (isMatchingReferenceDiscriminant(expr, declaredType)) {
return narrowTypeByDiscriminant(type, <AccessExpression>expr, t => getTypeWithFacts(t, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull));
}
if (containsMatchingReferenceDiscriminant(reference, expr)) {
return declaredType;
}
return type;
}
}
function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) {
@@ -22505,12 +22586,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(
@@ -22561,8 +22642,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);
@@ -22576,6 +22656,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)) {
@@ -22614,7 +22696,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) {
@@ -22971,9 +23053,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);
@@ -22991,7 +23072,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 {
@@ -23074,7 +23155,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: readonly Signature[], result: Signature[]): void {
function reorderCandidates(signatures: readonly Signature[], result: Signature[], isOptionalCall: boolean): void {
let lastParent: Node | undefined;
let lastSymbol: Symbol | undefined;
let cutoffIndex = 0;
@@ -23116,7 +23197,7 @@ namespace ts {
spliceIndex = index;
}
result.splice(spliceIndex, 0, signature);
result.splice(spliceIndex, 0, isOptionalCall ? createOptionalCallSignature(signature) : signature);
}
}
@@ -23769,7 +23850,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: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, fallbackError?: DiagnosticMessage): Signature {
function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], 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);
@@ -23788,7 +23869,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));
@@ -24172,13 +24253,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,
@@ -24188,8 +24270,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);
@@ -24253,7 +24335,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) {
@@ -24324,7 +24407,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
@@ -24333,7 +24416,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);
@@ -24548,7 +24631,7 @@ namespace ts {
return resolveErrorCall(node);
}
return resolveCall(node, callSignatures, candidatesOutArray, checkMode);
return resolveCall(node, callSignatures, candidatesOutArray, checkMode, /*isOptional*/ false);
}
/**
@@ -24611,7 +24694,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 {
@@ -24664,7 +24747,7 @@ namespace ts {
return resolveErrorCall(node);
}
return resolveCall(node, signatures, candidatesOutArray, checkMode);
return resolveCall(node, signatures, candidatesOutArray, checkMode, /*isOptional*/ false);
}
/**
@@ -24851,18 +24934,20 @@ namespace ts {
getTypeOfDottedName(node.expression, diagnostic);
}
}
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) {
@@ -24969,7 +25054,7 @@ namespace ts {
}
function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type {
checkGrammarTypeArguments(node, node.typeArguments);
if (!checkGrammarTaggedTemplateChain(node)) checkGrammarTypeArguments(node, node.typeArguments);
if (languageVersion < ScriptTarget.ES2015) {
checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject);
}
@@ -25886,13 +25971,17 @@ namespace ts {
return false;
}
function checkReferenceExpression(expr: Expression, invalidReferenceMessage: DiagnosticMessage): boolean {
function checkReferenceExpression(expr: Expression, invalidReferenceMessage: DiagnosticMessage, invalidOptionalChainMessage: DiagnosticMessage): boolean {
// References are combinations of identifiers, parentheses, and property accesses.
const node = skipOuterExpressions(expr, OuterExpressionKinds.Assertions | OuterExpressionKinds.Parentheses);
if (node.kind !== SyntaxKind.Identifier && node.kind !== SyntaxKind.PropertyAccessExpression && node.kind !== SyntaxKind.ElementAccessExpression) {
error(expr, invalidReferenceMessage);
return false;
}
if (node.flags & NodeFlags.OptionalChain) {
error(expr, invalidOptionalChainMessage);
return false;
}
return true;
}
@@ -26002,7 +26091,10 @@ namespace ts {
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type);
if (ok) {
// run check only if former checks succeeded to avoid reporting cascading errors
checkReferenceExpression(node.operand, Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access);
checkReferenceExpression(
node.operand,
Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access,
Diagnostics.The_operand_of_an_increment_or_decrement_operator_may_not_be_an_optional_property_access);
}
return getUnaryResultType(operandType);
}
@@ -26020,7 +26112,10 @@ namespace ts {
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type);
if (ok) {
// run check only if former checks succeeded to avoid reporting cascading errors
checkReferenceExpression(node.operand, Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access);
checkReferenceExpression(
node.operand,
Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access,
Diagnostics.The_operand_of_an_increment_or_decrement_operator_may_not_be_an_optional_property_access);
}
return getUnaryResultType(operandType);
}
@@ -26270,7 +26365,10 @@ namespace ts {
const error = target.parent.kind === SyntaxKind.SpreadAssignment ?
Diagnostics.The_target_of_an_object_rest_assignment_must_be_a_variable_or_a_property_access :
Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access;
if (checkReferenceExpression(target, error)) {
const optionalError = target.parent.kind === SyntaxKind.SpreadAssignment ?
Diagnostics.The_target_of_an_object_rest_assignment_may_not_be_an_optional_property_access :
Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access;
if (checkReferenceExpression(target, error, optionalError)) {
checkTypeAssignableToAndOptionallyElaborate(sourceType, targetType, target, target);
}
return sourceType;
@@ -26476,7 +26574,7 @@ namespace ts {
if (!resultType) {
// Types that have a reasonably good chance of being a valid operand type.
// If both types have an awaited type of one of these, well assume the user
// If both types have an awaited type of one of these, we'll assume the user
// might be missing an await without doing an exhaustive check that inserting
// await(s) will actually be a completely valid binary expression.
const closeEnoughKind = TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.AnyOrUnknown;
@@ -26614,7 +26712,9 @@ namespace ts {
// A compound assignment furthermore requires VarExpr to be classified as a reference (section 4.1)
// and the type of the non-compound operation to be assignable to the type of VarExpr.
if (checkReferenceExpression(left, Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access)
if (checkReferenceExpression(left,
Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access,
Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access)
&& (!isIdentifier(left) || unescapeLeadingUnderscores(left.escapedText) !== "exports")) {
// to avoid cascading errors check assignability only if 'isReference' check succeeded and no errors were reported
checkTypeAssignableToAndOptionallyElaborate(valueType, leftType, left, right);
@@ -27113,11 +27213,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 && (<CallExpression>expr).expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(expr)) {
const funcType = checkNonNullExpression((<CallExpression>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)) {
@@ -29958,7 +30058,10 @@ namespace ts {
}
else {
const leftType = checkExpression(varExpr);
checkReferenceExpression(varExpr, Diagnostics.The_left_hand_side_of_a_for_of_statement_must_be_a_variable_or_a_property_access);
checkReferenceExpression(
varExpr,
Diagnostics.The_left_hand_side_of_a_for_of_statement_must_be_a_variable_or_a_property_access,
Diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_an_optional_property_access);
// iteratedType will be undefined if the rightType was missing properties/signatures
// required to get its iteratedType (like [Symbol.iterator] or next). This may be
@@ -30008,7 +30111,10 @@ namespace ts {
}
else {
// run check only former check succeeded to avoid cascading errors
checkReferenceExpression(varExpr, Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_a_variable_or_a_property_access);
checkReferenceExpression(
varExpr,
Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_a_variable_or_a_property_access,
Diagnostics.The_left_hand_side_of_a_for_in_statement_may_not_be_an_optional_property_access);
}
}
@@ -34799,6 +34905,13 @@ namespace ts {
checkGrammarForAtLeastOneTypeArgument(node, typeArguments);
}
function checkGrammarTaggedTemplateChain(node: TaggedTemplateExpression): boolean {
if (node.questionDotToken || node.flags & NodeFlags.OptionalChain) {
return grammarErrorOnNode(node.template, Diagnostics.Tagged_template_expressions_are_not_permitted_in_an_optional_chain);
}
return false;
}
function checkGrammarForOmittedArgument(args: NodeArray<Expression> | undefined): boolean {
if (args) {
for (const arg of args) {
+9
View File
@@ -187,6 +187,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),
@@ -260,5 +268,6 @@ namespace ts {
isDebugInfoEnabled = true;
}
}
}
+25
View File
@@ -1039,6 +1039,10 @@
"category": "Error",
"code": 1357
},
"Tagged template expressions are not permitted in an optional chain.": {
"category": "Error",
"code": 1358
},
"The types of '{0}' are incompatible between these types.": {
"category": "Error",
@@ -2747,6 +2751,27 @@
"category": "Error",
"code": 2776
},
"The operand of an increment or decrement operator may not be an optional property access.": {
"category": "Error",
"code": 2777
},
"The target of an object rest assignment may not be an optional property access.": {
"category": "Error",
"code": 2778
},
"The left-hand side of an assignment expression may not be an optional property access.": {
"category": "Error",
"code": 2779
},
"The left-hand side of a 'for...in' statement may not be an optional property access.": {
"category": "Error",
"code": 2780
},
"The left-hand side of a 'for...of' statement may not be an optional property access.": {
"category": "Error",
"code": 2781
},
"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
"code": 4000
+69 -41
View File
@@ -858,6 +858,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();
@@ -1080,8 +1082,7 @@ namespace ts {
setSourceFile(sourceFile);
}
const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node);
pipelinePhase(hint, node);
pipelineEmit(hint, node);
}
function setSourceFile(sourceFile: SourceFile | undefined) {
@@ -1113,6 +1114,8 @@ namespace ts {
currentSourceFile = undefined!;
currentLineMap = undefined!;
detachedCommentsInfo = undefined;
lastNode = undefined;
lastSubstitution = undefined;
setWriter(/*output*/ undefined, /*_sourceMapGenerator*/ undefined);
}
@@ -1120,24 +1123,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) {
@@ -1179,11 +1205,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));
@@ -1495,7 +1524,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)) {
@@ -1611,8 +1640,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 {
@@ -2116,8 +2148,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) {
@@ -2215,34 +2246,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.hasTrailingComment() &&
!writer.hasTrailingWhitespace();
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);
@@ -2250,28 +2271,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(<LiteralExpression>expression, /*neverAsciiEscape*/ true);
// If he number will be printed verbatim and it doesn't already contain a dot, add one
// if the expression doesn't have any comments that will be emitted.
return !expression.numericLiteralFlags && !stringContains(text, tokenToString(SyntaxKind.DotToken)!) &&
(!dotHasTrivia || printerOptions.removeComments);
return !expression.numericLiteralFlags && !stringContains(text, tokenToString(SyntaxKind.DotToken)!);
}
else if (isPropertyAccessExpression(expression) || isElementAccessExpression(expression)) {
// check if constant enum value is integer
const constantValue = getConstantValue(expression);
// isFinite handles cases when constantValue is undefined
return typeof constantValue === "number" && isFinite(constantValue)
&& Math.floor(constantValue) === constantValue
&& printerOptions.removeComments;
&& Math.floor(constantValue) === constantValue;
}
}
function emitElementAccessExpression(node: ElementAccessExpression) {
emitExpression(node.expression);
emit(node.questionDotToken);
emitTokenWithComment(SyntaxKind.OpenBracketToken, node.expression.end, writePunctuation, node);
emitExpression(node.argumentExpression);
emitTokenWithComment(SyntaxKind.CloseBracketToken, node.argumentExpression.end, writePunctuation, node);
@@ -2279,6 +2299,7 @@ namespace ts {
function emitCallExpression(node: CallExpression) {
emitExpression(node.expression);
emit(node.questionDotToken);
emitTypeArguments(node, node.typeArguments);
emitExpressionList(node, node.arguments, ListFormat.CallExpressionArguments);
}
@@ -3742,8 +3763,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);
@@ -4214,6 +4234,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);
@@ -4669,6 +4693,7 @@ namespace ts {
// Comments
function pipelineEmitWithComments(hint: EmitHint, node: Node) {
Debug.assert(lastNode === node || lastSubstitution === node);
enterComment();
hasWrittenComment = false;
const emitFlags = getEmitFlags(node);
@@ -4735,6 +4760,7 @@ namespace ts {
}
}
exitComment();
Debug.assert(lastNode === node || lastSubstitution === node);
}
function emitLeadingSynthesizedComment(comment: SynthesizedComment) {
@@ -4981,6 +5007,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);
@@ -5023,6 +5050,7 @@ namespace ts {
emitSourcePos(source, end);
}
}
Debug.assert(lastNode === node || lastSubstitution === node);
}
/**
+78
View File
@@ -1065,6 +1065,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
@@ -1073,6 +1074,27 @@ namespace ts {
: node;
}
export function createPropertyAccessChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, name: string | Identifier) {
const node = <PropertyAccessChain>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 = <ElementAccessExpression>createSynthesizedNode(SyntaxKind.ElementAccessExpression);
node.expression = parenthesizeForAccess(expression);
@@ -1081,12 +1103,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 = <ElementAccessChain>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: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) {
const node = <CallExpression>createSynthesizedNode(SyntaxKind.CallExpression);
node.expression = parenthesizeForAccess(expression);
@@ -1096,6 +1137,7 @@ namespace ts {
}
export function updateCall(node: CallExpression, expression: Expression, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[]) {
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
@@ -1103,6 +1145,26 @@ namespace ts {
: node;
}
export function createCallChain(expression: Expression, questionDotToken: QuestionDotToken | undefined, typeArguments: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) {
const node = <CallChain>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: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[]) {
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: readonly TypeNode[] | undefined, argumentsArray: readonly Expression[] | undefined) {
const node = <NewExpression>createSynthesizedNode(SyntaxKind.NewExpression);
node.expression = parenthesizeForNew(expression);
@@ -2776,6 +2838,22 @@ namespace ts {
: node;
}
/* @internal */
export function createSyntheticReferenceExpression(expression: Expression, thisArg: Expression) {
const node = <SyntheticReferenceExpression>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: readonly SourceFile[], prepends: readonly (UnparsedSource | InputFiles)[] = emptyArray) {
const node = <Bundle>createNode(SyntaxKind.Bundle);
node.prepends = prepends;
+109 -46
View File
@@ -212,17 +212,21 @@ namespace ts {
return visitNodes(cbNode, cbNodes, (<ObjectLiteralExpression>node).properties);
case SyntaxKind.PropertyAccessExpression:
return visitNode(cbNode, (<PropertyAccessExpression>node).expression) ||
visitNode(cbNode, (<PropertyAccessExpression>node).questionDotToken) ||
visitNode(cbNode, (<PropertyAccessExpression>node).name);
case SyntaxKind.ElementAccessExpression:
return visitNode(cbNode, (<ElementAccessExpression>node).expression) ||
visitNode(cbNode, (<ElementAccessExpression>node).questionDotToken) ||
visitNode(cbNode, (<ElementAccessExpression>node).argumentExpression);
case SyntaxKind.CallExpression:
case SyntaxKind.NewExpression:
return visitNode(cbNode, (<CallExpression>node).expression) ||
visitNode(cbNode, (<CallExpression>node).questionDotToken) ||
visitNodes(cbNode, cbNodes, (<CallExpression>node).typeArguments) ||
visitNodes(cbNode, cbNodes, (<CallExpression>node).arguments);
case SyntaxKind.TaggedTemplateExpression:
return visitNode(cbNode, (<TaggedTemplateExpression>node).tag) ||
visitNode(cbNode, (<TaggedTemplateExpression>node).questionDotToken) ||
visitNodes(cbNode, cbNodes, (<TaggedTemplateExpression>node).typeArguments) ||
visitNode(cbNode, (<TaggedTemplateExpression>node).template);
case SyntaxKind.TypeAssertionExpression:
@@ -4243,7 +4247,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);
}
@@ -4296,7 +4301,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 {
@@ -4590,18 +4595,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 = <PropertyAccessExpression>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 = <ElementAccessExpression>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 = <PropertyAccessExpression>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 = tokenIsIdentifierOrKeyword(token());
}
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 = <NonNullExpression>createNode(SyntaxKind.NonNullExpression, expression.pos);
nonNullExpression.expression = expression;
@@ -4610,28 +4667,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 = <ElementAccessExpression>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;
}
@@ -4643,19 +4685,25 @@ namespace ts {
return token() === SyntaxKind.NoSubstitutionTemplateLiteral || token() === SyntaxKind.TemplateHead;
}
function parseTaggedTemplateRest(tag: LeftHandSideExpression, typeArguments: NodeArray<TypeNode> | undefined) {
function parseTaggedTemplateRest(tag: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined, typeArguments: NodeArray<TypeNode> | undefined) {
const tagExpression = <TaggedTemplateExpression>createNode(SyntaxKind.TaggedTemplateExpression, tag.pos);
tagExpression.tag = tag;
tagExpression.questionDotToken = questionDotToken;
tagExpression.typeArguments = typeArguments;
tagExpression.template = token() === SyntaxKind.NoSubstitutionTemplateLiteral
? <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<<T>()'
if (token() === SyntaxKind.LessThanToken || token() === SyntaxKind.LessThanLessThanToken) {
// See if this is the start of a generic invocation. If so, consume it and
@@ -4663,32 +4711,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 = <CallExpression>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 = <CallExpression>createNode(SyntaxKind.CallExpression, expression.pos);
callExpr.expression = expression;
callExpr.typeArguments = typeArguments;
callExpr.arguments = parseArgumentList();
expression = finishNode(callExpr);
continue;
}
else if (token() === SyntaxKind.OpenParenToken) {
const callExpr = <CallExpression>createNode(SyntaxKind.CallExpression, expression.pos);
callExpr.expression = expression;
callExpr.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, Diagnostics.Identifier_expected);
propertyAccess.flags |= NodeFlags.OptionalChain;
expression = finishNode(propertyAccess);
}
break;
}
return expression;
}
function parseArgumentList() {
@@ -4957,12 +5020,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;
+5
View File
@@ -184,6 +184,7 @@ namespace ts {
"&&": SyntaxKind.AmpersandAmpersandToken,
"||": SyntaxKind.BarBarToken,
"?": SyntaxKind.QuestionToken,
"?.": SyntaxKind.QuestionDotToken,
":": SyntaxKind.ColonToken,
"=": SyntaxKind.EqualsToken,
"+=": SyntaxKind.PlusEqualsToken,
@@ -1829,6 +1830,10 @@ namespace ts {
return token = SyntaxKind.GreaterThanToken;
case CharacterCodes.question:
pos++;
if (text.charCodeAt(pos) === CharacterCodes.dot && !isDigit(text.charCodeAt(pos + 1))) {
pos++;
return token = SyntaxKind.QuestionDotToken;
}
return token = SyntaxKind.QuestionToken;
case CharacterCodes.openBracket:
pos++;
+152
View File
@@ -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,157 @@ namespace ts {
return node;
}
switch (node.kind) {
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
case SyntaxKind.CallExpression:
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;
}
}
}
+85 -21
View File
@@ -152,6 +152,7 @@ namespace ts {
DotDotDotToken,
SemicolonToken,
CommaToken,
QuestionDotToken,
LessThanToken,
LessThanSlashToken,
GreaterThanToken,
@@ -485,6 +486,7 @@ namespace ts {
CommaListExpression,
MergeDeclarationMarker,
EndOfDeclarationMarker,
SyntheticReferenceExpression,
// Enum value count
Count,
@@ -532,20 +534,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
@@ -556,13 +559,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,
@@ -732,8 +735,10 @@ namespace ts {
kind: TKind;
}
export type DotToken = Token<SyntaxKind.DotToken>;
export type DotDotDotToken = Token<SyntaxKind.DotDotDotToken>;
export type QuestionToken = Token<SyntaxKind.QuestionToken>;
export type QuestionDotToken = Token<SyntaxKind.QuestionDotToken>;
export type ExclamationToken = Token<SyntaxKind.ExclamationToken>;
export type ColonToken = Token<SyntaxKind.ColonToken>;
export type EqualsToken = Token<SyntaxKind.EqualsToken>;
@@ -1803,9 +1808,19 @@ namespace ts {
export interface PropertyAccessExpression extends MemberExpression, NamedDeclaration {
kind: SyntaxKind.PropertyAccessExpression;
expression: LeftHandSideExpression;
questionDotToken?: QuestionDotToken;
name: Identifier;
}
export interface PropertyAccessChain extends PropertyAccessExpression {
_optionalChainBrand: any;
}
/* @internal */
export interface PropertyAccessChainRoot extends PropertyAccessChain {
questionDotToken: QuestionDotToken;
}
export interface SuperPropertyAccessExpression extends PropertyAccessExpression {
expression: SuperExpression;
}
@@ -1819,9 +1834,19 @@ namespace ts {
export interface ElementAccessExpression extends MemberExpression {
kind: SyntaxKind.ElementAccessExpression;
expression: LeftHandSideExpression;
questionDotToken?: QuestionDotToken;
argumentExpression: Expression;
}
export interface ElementAccessChain extends ElementAccessExpression {
_optionalChainBrand: any;
}
/* @internal */
export interface ElementAccessChainRoot extends ElementAccessChain {
questionDotToken: QuestionDotToken;
}
export interface SuperElementAccessExpression extends ElementAccessExpression {
expression: SuperExpression;
}
@@ -1832,10 +1857,33 @@ namespace ts {
export interface CallExpression extends LeftHandSideExpression, Declaration {
kind: SyntaxKind.CallExpression;
expression: LeftHandSideExpression;
questionDotToken?: QuestionDotToken;
typeArguments?: NodeArray<TypeNode>;
arguments: NodeArray<Expression>;
}
export interface CallChain extends CallExpression {
_optionalChainBrand: any;
}
/* @internal */
export interface CallChainRoot extends CallChain {
questionDotToken: QuestionDotToken;
}
export type OptionalChain =
| PropertyAccessChain
| ElementAccessChain
| CallChain
;
/* @internal */
export type OptionalChainRoot =
| PropertyAccessChainRoot
| ElementAccessChainRoot
| CallChainRoot
;
/** @internal */
export type BindableObjectDefinePropertyCall = CallExpression & { arguments: { 0: EntityNameExpression, 1: StringLiteralLike | NumericLiteral, 2: ObjectLiteralExpression } };
@@ -1866,6 +1914,7 @@ namespace ts {
tag: LeftHandSideExpression;
typeArguments?: NodeArray<TypeNode>;
template: TemplateLiteral;
/*@internal*/ questionDotToken?: QuestionDotToken; // NOTE: Invalid syntax, only used to report a grammar error.
}
export type CallLikeExpression = CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement;
@@ -2033,6 +2082,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;
}
@@ -2603,8 +2659,9 @@ namespace ts {
AfterFinally = 1 << 13, // Injected edge that links post-finally flow with the rest of the graph
/** @internal */
Cached = 1 << 14, // Indicates that at least one cross-call cache entry exists for this node, even if not a loop participant
Label = BranchLabel | LoopLabel,
Condition = TrueCondition | FalseCondition
Condition = TrueCondition | FalseCondition,
}
export type FlowNode =
@@ -3209,6 +3266,8 @@ namespace ts {
/* @internal */ getParameterType(signature: Signature, parameterIndex: number): Type;
getNullableType(type: Type, flags: TypeFlags): Type;
getNonNullableType(type: Type): Type;
/* @internal */ getNonOptionalType(type: Type): Type;
/* @internal */ isNullableType(type: Type): boolean;
getTypeArguments(type: TypeReference): readonly Type[];
// TODO: GH#18217 `xToDeclaration` calls are frequently asserted as defined.
@@ -3329,6 +3388,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;
@@ -4590,6 +4650,8 @@ namespace ts {
isolatedSignatureType?: ObjectType; // A manufactured type that just contains the signature for purposes of signature comparison
/* @internal */
instantiations?: Map<Signature>; // Generic signature instantiation cache
/* @internal */
isOptionalCall?: boolean;
}
export const enum IndexKind {
@@ -6032,6 +6094,8 @@ namespace ts {
getColumn(): number;
getIndent(): number;
isAtStartOfLine(): boolean;
hasTrailingComment(): boolean;
hasTrailingWhitespace(): boolean;
getTextPosWithWriteLine?(): number;
}
+52 -3
View File
@@ -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,
hasTrailingComment: () => false,
hasTrailingWhitespace: () => !!str.length && isWhiteSpaceLike(str.charCodeAt(str.length - 1)),
// Completely ignore indentation for string writers. And map newlines to
// a single space.
@@ -3349,6 +3350,7 @@ namespace ts {
let lineStart: boolean;
let lineCount: number;
let linePos: number;
let hasTrailingComment = false;
function updateLineCountAndPosFor(s: string) {
const lineStartsOfS = computeLineStarts(s);
@@ -3362,7 +3364,7 @@ namespace ts {
}
}
function write(s: string) {
function writeText(s: string) {
if (s && s.length) {
if (lineStart) {
s = getIndentString(indent) + s;
@@ -3373,18 +3375,30 @@ namespace ts {
}
}
function write(s: string) {
if (s) hasTrailingComment = false;
writeText(s);
}
function writeComment(s: string) {
if (s) hasTrailingComment = true;
writeText(s);
}
function reset(): void {
output = "";
indent = 0;
lineStart = true;
lineCount = 0;
linePos = 0;
hasTrailingComment = false;
}
function rawWrite(s: string) {
if (s !== undefined) {
output += s;
updateLineCountAndPosFor(s);
hasTrailingComment = false;
}
}
@@ -3400,6 +3414,7 @@ namespace ts {
lineCount++;
linePos = output.length;
lineStart = true;
hasTrailingComment = false;
}
}
@@ -3422,6 +3437,8 @@ namespace ts {
getColumn: () => lineStart ? indent * getIndentSize() : output.length - linePos,
getText: () => output,
isAtStartOfLine: () => lineStart,
hasTrailingComment: () => hasTrailingComment,
hasTrailingWhitespace: () => !!output.length && isWhiteSpaceLike(output.charCodeAt(output.length - 1)),
clear: reset,
reportInaccessibleThisError: noop,
reportPrivateInBaseOfClassExpression: noop,
@@ -3436,7 +3453,7 @@ namespace ts {
writeStringLiteral: write,
writeSymbol: (s, _) => write(s),
writeTrailingSemicolon: write,
writeComment: write,
writeComment,
getTextPosWithWriteLine
};
}
@@ -4792,6 +4809,10 @@ namespace ts {
return false;
}
}
export function getDotOrQuestionDotToken(node: PropertyAccessExpression) {
return node.questionDotToken || createNode(SyntaxKind.DotToken, node.expression.end, node.name.pos) as DotToken;
}
}
namespace ts {
@@ -5807,14 +5828,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;
}
@@ -6823,6 +6862,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)
@@ -7127,6 +7171,11 @@ namespace ts {
return node.kind === SyntaxKind.GetAccessor;
}
/* @internal */
export function isOptionalChainRoot(node: Node): node is OptionalChainRoot {
return isOptionalChain(node) && !!node.questionDotToken;
}
/** True if has jsdoc nodes attached to it. */
/* @internal */
// TODO: GH#19856 Would like to return `node is Node & { jsDoc: JSDoc[] }` but it causes long compile times
+19
View File
@@ -456,16 +456,35 @@ namespace ts {
nodesVisitor((<ObjectLiteralExpression>node).properties, visitor, isObjectLiteralElementLike));
case SyntaxKind.PropertyAccessExpression:
if (node.flags & NodeFlags.OptionalChain) {
return updatePropertyAccessChain(<PropertyAccessChain>node,
visitNode((<PropertyAccessChain>node).expression, visitor, isExpression),
visitNode((<PropertyAccessChain>node).questionDotToken, visitor, isToken),
visitNode((<PropertyAccessChain>node).name, visitor, isIdentifier));
}
return updatePropertyAccess(<PropertyAccessExpression>node,
visitNode((<PropertyAccessExpression>node).expression, visitor, isExpression),
visitNode((<PropertyAccessExpression>node).name, visitor, isIdentifier));
case SyntaxKind.ElementAccessExpression:
if (node.flags & NodeFlags.OptionalChain) {
return updateElementAccessChain(<ElementAccessChain>node,
visitNode((<ElementAccessChain>node).expression, visitor, isExpression),
visitNode((<ElementAccessChain>node).questionDotToken, visitor, isToken),
visitNode((<ElementAccessChain>node).argumentExpression, visitor, isExpression));
}
return updateElementAccess(<ElementAccessExpression>node,
visitNode((<ElementAccessExpression>node).expression, visitor, isExpression),
visitNode((<ElementAccessExpression>node).argumentExpression, visitor, isExpression));
case SyntaxKind.CallExpression:
if (node.flags & NodeFlags.OptionalChain) {
return updateCallChain(<CallChain>node,
visitNode((<CallChain>node).expression, visitor, isExpression),
visitNode((<CallChain>node).questionDotToken, visitor, isToken),
nodesVisitor((<CallChain>node).typeArguments, visitor, isTypeNode),
nodesVisitor((<CallChain>node).arguments, visitor, isExpression));
}
return updateCall(<CallExpression>node,
visitNode((<CallExpression>node).expression, visitor, isExpression),
nodesVisitor((<CallExpression>node).typeArguments, visitor, isTypeNode),
+93 -29
View File
@@ -11,28 +11,52 @@ namespace ts.Completions {
}
export type Log = (message: string) => void;
const enum SymbolOriginInfoKind { ThisType, SymbolMemberNoExport, SymbolMemberExport, Export, Promise }
type SymbolOriginInfo = { kind: SymbolOriginInfoKind.ThisType } | { kind: SymbolOriginInfoKind.Promise } | { kind: SymbolOriginInfoKind.SymbolMemberNoExport } | SymbolOriginInfoExport;
interface SymbolOriginInfoExport {
kind: SymbolOriginInfoKind.SymbolMemberExport | SymbolOriginInfoKind.Export;
const enum SymbolOriginInfoKind {
ThisType = 1 << 0,
SymbolMember = 1 << 1,
Export = 1 << 2,
Promise = 1 << 3,
Nullable = 1 << 4,
SymbolMemberNoExport = SymbolMember,
SymbolMemberExport = SymbolMember | Export,
}
interface SymbolOriginInfo {
kind: SymbolOriginInfoKind;
}
interface SymbolOriginInfoExport extends SymbolOriginInfo {
kind: SymbolOriginInfoKind;
moduleSymbol: Symbol;
isDefaultExport: boolean;
}
function originIsThisType(origin: SymbolOriginInfo): boolean {
return !!(origin.kind & SymbolOriginInfoKind.ThisType);
}
function originIsSymbolMember(origin: SymbolOriginInfo): boolean {
return origin.kind === SymbolOriginInfoKind.SymbolMemberExport || origin.kind === SymbolOriginInfoKind.SymbolMemberNoExport;
return !!(origin.kind & SymbolOriginInfoKind.SymbolMember);
}
function originIsExport(origin: SymbolOriginInfo): origin is SymbolOriginInfoExport {
return origin.kind === SymbolOriginInfoKind.SymbolMemberExport || origin.kind === SymbolOriginInfoKind.Export;
return !!(origin.kind & SymbolOriginInfoKind.Export);
}
function originIsPromise(origin: SymbolOriginInfo): boolean {
return origin.kind === SymbolOriginInfoKind.Promise;
return !!(origin.kind & SymbolOriginInfoKind.Promise);
}
function originIsNullableMember(origin: SymbolOriginInfo): boolean {
return !!(origin.kind & SymbolOriginInfoKind.Nullable);
}
/**
* Map from symbol id -> SymbolOriginInfo.
* Only populated for symbols that come from other modules.
*/
type SymbolOriginInfoMap = (SymbolOriginInfo | undefined)[];
type SymbolOriginInfoMap = (SymbolOriginInfo | SymbolOriginInfoExport | undefined)[];
type SymbolSortTextMap = (SortText | undefined)[];
@@ -314,14 +338,26 @@ namespace ts.Completions {
): CompletionEntry | undefined {
let insertText: string | undefined;
let replacementSpan: TextSpan | undefined;
if (origin && origin.kind === SymbolOriginInfoKind.ThisType) {
insertText = needsConvertPropertyAccess ? `this[${quote(name, preferences)}]` : `this.${name}`;
const insertQuestionDot = origin && originIsNullableMember(origin);
const useBraces = origin && originIsSymbolMember(origin) || needsConvertPropertyAccess;
if (origin && originIsThisType(origin)) {
insertText = needsConvertPropertyAccess
? `this${insertQuestionDot ? "?." : ""}[${quote(name, preferences)}]`
: `this${insertQuestionDot ? "?." : "."}${name}`;
}
// We should only have needsConvertPropertyAccess if there's a property access to convert. But see #21790.
// Somehow there was a global with a non-identifier name. Hopefully someone will complain about getting a "foo bar" global completion and provide a repro.
else if ((origin && originIsSymbolMember(origin) || needsConvertPropertyAccess) && propertyAccessToConvert) {
insertText = needsConvertPropertyAccess ? `[${quote(name, preferences)}]` : `[${name}]`;
const dot = findChildOfKind(propertyAccessToConvert, SyntaxKind.DotToken, sourceFile)!;
else if ((useBraces || insertQuestionDot) && propertyAccessToConvert) {
insertText = useBraces ? needsConvertPropertyAccess ? `[${quote(name, preferences)}]` : `[${name}]` : name;
if (insertQuestionDot || propertyAccessToConvert.questionDotToken) {
insertText = `?.${insertText}`;
}
const dot = findChildOfKind(propertyAccessToConvert, SyntaxKind.DotToken, sourceFile) ||
findChildOfKind(propertyAccessToConvert, SyntaxKind.QuestionDotToken, sourceFile);
if (!dot) {
return undefined;
}
// If the text after the '.' starts with this name, write over it. Else, add new text.
const end = startsWith(name, propertyAccessToConvert.name.text) ? propertyAccessToConvert.name.end : dot.end;
replacementSpan = createTextSpanFromBounds(dot.getStart(sourceFile), end);
@@ -337,7 +373,7 @@ namespace ts.Completions {
if (origin && originIsPromise(origin) && propertyAccessToConvert) {
if (insertText === undefined) insertText = name;
const awaitText = `(await ${propertyAccessToConvert.expression.getText()})`;
insertText = needsConvertPropertyAccess ? `${awaitText}${insertText}` : `${awaitText}.${insertText}`;
insertText = needsConvertPropertyAccess ? `${awaitText}${insertText}` : `${awaitText}${insertQuestionDot ? "?." : "."}${insertText}`;
replacementSpan = createTextSpanFromBounds(propertyAccessToConvert.getStart(sourceFile), propertyAccessToConvert.end);
}
@@ -846,6 +882,7 @@ namespace ts.Completions {
let node = currentToken;
let propertyAccessToConvert: PropertyAccessExpression | undefined;
let isRightOfDot = false;
let isRightOfQuestionDot = false;
let isRightOfOpenTag = false;
let isStartingCloseTag = false;
let isJsxInitializer: IsJsxInitializer = false;
@@ -859,8 +896,9 @@ 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:
propertyAccessToConvert = parent as PropertyAccessExpression;
@@ -967,7 +1005,7 @@ namespace ts.Completions {
const symbolToSortTextMap: SymbolSortTextMap = [];
const importSuggestionsCache = host.getImportSuggestionsCache && host.getImportSuggestionsCache();
if (isRightOfDot) {
if (isRightOfDot || isRightOfQuestionDot) {
getTypeScriptMemberSymbols();
}
else if (isRightOfOpenTag) {
@@ -1074,7 +1112,13 @@ 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));
let type = typeChecker.getTypeOfSymbolAtLocation(symbol, node).getNonOptionalType();
let insertQuestionDot = false;
if (type.isNullableType()) {
insertQuestionDot = isRightOfDot && !isRightOfQuestionDot;
type = type.getNonNullableType();
}
addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot);
}
return;
@@ -1089,12 +1133,21 @@ namespace ts.Completions {
}
if (!isTypeLocation) {
addTypeProperties(typeChecker.getTypeAtLocation(node), !!(node.flags & NodeFlags.AwaitContext));
let type = typeChecker.getTypeAtLocation(node).getNonOptionalType();
let insertQuestionDot = false;
if (type.isNullableType()) {
insertQuestionDot = isRightOfDot && !isRightOfQuestionDot;
type = type.getNonNullableType();
}
addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot);
}
}
function addTypeProperties(type: Type, insertAwait?: boolean): void {
function addTypeProperties(type: Type, insertAwait: boolean, insertQuestionDot: boolean): void {
isNewIdentifierLocation = !!type.getStringIndexType();
if (isRightOfQuestionDot && some(type.getCallSignatures())) {
isNewIdentifierLocation = true;
}
const propertyAccess = node.kind === SyntaxKind.ImportType ? <ImportTypeNode>node : <PropertyAccessExpression | QualifiedName>node.parent;
if (isUncheckedFile) {
@@ -1108,7 +1161,7 @@ namespace ts.Completions {
else {
for (const symbol of type.getApparentProperties()) {
if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, type, symbol)) {
addPropertySymbol(symbol);
addPropertySymbol(symbol, /*insertAwait*/ false, insertQuestionDot);
}
}
}
@@ -1118,14 +1171,14 @@ namespace ts.Completions {
if (promiseType) {
for (const symbol of promiseType.getApparentProperties()) {
if (typeChecker.isValidPropertyAccessForCompletions(propertyAccess, promiseType, symbol)) {
addPropertySymbol(symbol, /* insertAwait */ true);
addPropertySymbol(symbol, /* insertAwait */ true, insertQuestionDot);
}
}
}
}
}
function addPropertySymbol(symbol: Symbol, insertAwait?: boolean) {
function addPropertySymbol(symbol: Symbol, insertAwait: boolean, insertQuestionDot: boolean) {
// For a computed property with an accessible name like `Symbol.iterator`,
// we'll add a completion for the *name* `Symbol` instead of for the property.
// If this is e.g. [Symbol.iterator], add a completion for `Symbol`.
@@ -1139,23 +1192,34 @@ namespace ts.Completions {
symbols.push(firstAccessibleSymbol);
const moduleSymbol = firstAccessibleSymbol.parent;
symbolToOriginInfoMap[getSymbolId(firstAccessibleSymbol)] =
!moduleSymbol || !isExternalModuleSymbol(moduleSymbol) ? { kind: SymbolOriginInfoKind.SymbolMemberNoExport } : { kind: SymbolOriginInfoKind.SymbolMemberExport, moduleSymbol, isDefaultExport: false };
!moduleSymbol || !isExternalModuleSymbol(moduleSymbol)
? { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberNoExport) }
: { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberExport), moduleSymbol, isDefaultExport: false };
}
else if (preferences.includeCompletionsWithInsertText) {
addPromiseSymbolOriginInfo(symbol);
addSymbolOriginInfo(symbol);
symbols.push(symbol);
}
}
else {
addPromiseSymbolOriginInfo(symbol);
addSymbolOriginInfo(symbol);
symbols.push(symbol);
}
function addPromiseSymbolOriginInfo (symbol: Symbol) {
if (insertAwait && preferences.includeCompletionsWithInsertText && !symbolToOriginInfoMap[getSymbolId(symbol)]) {
symbolToOriginInfoMap[getSymbolId(symbol)] = { kind: SymbolOriginInfoKind.Promise };
function addSymbolOriginInfo(symbol: Symbol) {
if (preferences.includeCompletionsWithInsertText) {
if (insertAwait && !symbolToOriginInfoMap[getSymbolId(symbol)]) {
symbolToOriginInfoMap[getSymbolId(symbol)] = { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.Promise) };
}
else if (insertQuestionDot) {
symbolToOriginInfoMap[getSymbolId(symbol)] = { kind: SymbolOriginInfoKind.Nullable };
}
}
}
function getNullableSymbolOriginInfoKind(kind: SymbolOriginInfoKind) {
return insertQuestionDot ? kind | SymbolOriginInfoKind.Nullable : kind;
}
}
/** Given 'a.b.c', returns 'a'. */
+6
View File
@@ -409,9 +409,15 @@ namespace ts {
getBaseTypes(): BaseType[] | undefined {
return this.isClassOrInterface() ? this.checker.getBaseTypes(this) : undefined;
}
isNullableType(): boolean {
return this.checker.isNullableType(this);
}
getNonNullableType(): Type {
return this.checker.getNonNullableType(this);
}
getNonOptionalType(): Type {
return this.checker.getNonOptionalType(this);
}
getConstraint(): Type | undefined {
return this.checker.getBaseConstraintOfType(this);
}
+2
View File
@@ -1061,6 +1061,8 @@ namespace ts.textChanges {
getColumn,
getIndent,
isAtStartOfLine,
hasTrailingComment: () => writer.hasTrailingComment(),
hasTrailingWhitespace: () => writer.hasTrailingWhitespace(),
clear
};
}
+2
View File
@@ -52,6 +52,8 @@ namespace ts {
getNumberIndexType(): Type | undefined;
getBaseTypes(): BaseType[] | undefined;
getNonNullableType(): Type;
/*@internal*/ getNonOptionalType(): Type;
/*@internal*/ isNullableType(): boolean;
getConstraint(): Type | undefined;
getDefault(): Type | undefined;
+16 -1
View File
@@ -956,6 +956,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) ||
@@ -964,7 +970,11 @@ namespace ts {
}
export function getPossibleGenericSignatures(called: Expression, typeArgumentCount: number, checker: TypeChecker): readonly Signature[] {
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);
}
@@ -993,6 +1003,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 };
@@ -1493,6 +1506,8 @@ namespace ts {
getColumn: () => 0,
getLine: () => 0,
isAtStartOfLine: () => false,
hasTrailingWhitespace: () => false,
hasTrailingComment: () => false,
rawWrite: notImplemented,
getIndent: () => indent,
increaseIndent: () => { indent++; },
+1
View File
@@ -76,6 +76,7 @@
"unittests/evaluation/awaiter.ts",
"unittests/evaluation/forAwaitOf.ts",
"unittests/evaluation/forOf.ts",
"unittests/evaluation/optionalCall.ts",
"unittests/evaluation/objectRest.ts",
"unittests/services/cancellableLanguageServiceOperations.ts",
"unittests/services/colorization.ts",
@@ -0,0 +1,191 @@
describe("unittests:: evaluation:: optionalCall", () => {
it("f?.()", async () => {
const result = evaluator.evaluateTypeScript(`
function f(a) {
output.push(a);
output.push(this);
}
export const output: any[] = [];
f?.(1);
`);
assert.strictEqual(result.output[0], 1);
assert.isUndefined(result.output[1]);
});
it("o.f?.()", async () => {
const result = evaluator.evaluateTypeScript(`
export const o = {
f(a) {
output.push(a);
output.push(this);
}
};
export const output: any[] = [];
o.f?.(1);
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o);
});
it("o.x.f?.()", async () => {
const result = evaluator.evaluateTypeScript(`
export const o = {
x: {
f(a) {
output.push(a);
output.push(this);
}
}
};
export const output: any[] = [];
o.x.f?.(1);
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o.x);
});
it("o?.f()", async () => {
const result = evaluator.evaluateTypeScript(`
export const o = {
f(a) {
output.push(a);
output.push(this);
}
};
export const output: any[] = [];
o?.f(1);
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o);
});
it("o?.f?.()", async () => {
const result = evaluator.evaluateTypeScript(`
export const o = {
f(a) {
output.push(a);
output.push(this);
}
};
export const output: any[] = [];
o?.f?.(1);
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o);
});
it("o.x?.f()", async () => {
const result = evaluator.evaluateTypeScript(`
export const o = {
x: {
f(a) {
output.push(a);
output.push(this);
}
}
};
export const output: any[] = [];
o.x?.f(1);
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o.x);
});
it("o?.x.f()", async () => {
const result = evaluator.evaluateTypeScript(`
export const o = {
x: {
f(a) {
output.push(a);
output.push(this);
}
}
};
export const output: any[] = [];
o?.x.f(1);
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o.x);
});
it("o?.x?.f()", async () => {
const result = evaluator.evaluateTypeScript(`
export const o = {
x: {
f(a) {
output.push(a);
output.push(this);
}
}
};
export const output: any[] = [];
o?.x?.f(1);
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o.x);
});
it("o?.x?.f?.()", async () => {
const result = evaluator.evaluateTypeScript(`
export const o = {
x: {
f(a) {
output.push(a);
output.push(this);
}
}
};
export const output: any[] = [];
o?.x?.f?.(1);
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], result.o.x);
});
it("f?.()?.()", async () => {
const result = evaluator.evaluateTypeScript(`
function g(a) {
output.push(a);
output.push(this);
}
function f(a) {
output.push(a);
return g;
}
export const output: any[] = [];
f?.(1)?.(2)
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], 2);
assert.isUndefined(result.output[2]);
});
it("f?.().f?.()", async () => {
const result = evaluator.evaluateTypeScript(`
export const o = {
f(a) {
output.push(a);
output.push(this);
}
};
function f(a) {
output.push(a);
return o;
}
export const output: any[] = [];
f?.(1).f?.(2)
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], 2);
assert.strictEqual(result.output[2], result.o);
});
it("f?.()?.f?.()", async () => {
const result = evaluator.evaluateTypeScript(`
export const o = {
f(a) {
output.push(a);
output.push(this);
}
};
function f(a) {
output.push(a);
return o;
}
export const output: any[] = [];
f?.(1)?.f?.(2)
`);
assert.strictEqual(result.output[0], 1);
assert.strictEqual(result.output[1], 2);
assert.strictEqual(result.output[2], result.o);
});
});