diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 91eee774ee4..fc067d63d59 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2287,8 +2287,16 @@ namespace ts { interface NodeBuilderContext { readonly enclosingDeclaration: Node | undefined; readonly flags: NodeBuilderFlags | undefined; + + // State encounteredError: boolean; inObjectTypeLiteral: boolean; + // TODO: needed for part of parens handling + InElementType: boolean; // Writing an array or union element type + // TODO: ??? + InFirstTypeArgument: boolean; // Writing first type argument of the instantiated type + // TODO: ??? + InTypeAlias: boolean; // Writing type in type alias declaration checkAlias: boolean; symbolStack: Symbol[] | undefined; } @@ -2299,18 +2307,28 @@ namespace ts { flags, encounteredError: false, inObjectTypeLiteral: false, + InElementType: false, + InFirstTypeArgument: false, + InTypeAlias: false, checkAlias: true, symbolStack: undefined }; } function typeToTypeNodeHelper(type: Type, context: NodeBuilderContext): TypeNode { + const InElementType = context.InElementType; + // TODO: why doesn't tts unset the flag? + context.InElementType = false; + + // TODO: should be assert? if (!type) { context.encounteredError = true; // TODO(aozgaa): should we return implict any (undefined) or explicit any (keywordtypenode)? return undefined; } + + if (type.flags & TypeFlags.Any) { return createKeywordTypeNode(SyntaxKind.AnyKeyword); } @@ -2390,16 +2408,17 @@ namespace ts { if (context.checkAlias && type.aliasSymbol) { const name = symbolToName(type.aliasSymbol, /*expectsIdentifier*/ false, context); - const typeArgumentNodes = type.aliasTypeArguments && mapToTypeNodeArray(type.aliasTypeArguments); + const typeArgumentNodes = type.aliasTypeArguments && mapToTypeNodeArray(type.aliasTypeArguments, /*addInElementTypeFlag*/ false); return createTypeReferenceNode(name, typeArgumentNodes); } context.checkAlias = false; - if (type.flags & TypeFlags.Union) { - const formattedUnionTypes = formatUnionTypes((type).types); - const unionTypeNodes = formattedUnionTypes && mapToTypeNodeArray(formattedUnionTypes); - if (unionTypeNodes && unionTypeNodes.length > 0) { - return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, unionTypeNodes); + if (type.flags & (TypeFlags.Union | TypeFlags.Intersection)) { + const types = type.flags & TypeFlags.Union ? formatUnionTypes((type).types) : (type).types; + const typeNodes = types && mapToTypeNodeArray(types, /*addInElementTypeFlag*/ true); + if (typeNodes && typeNodes.length > 0) { + const unionOrIntersectionTypeNode = createUnionOrIntersectionTypeNode(type.flags & TypeFlags.Union ? SyntaxKind.UnionType : SyntaxKind.IntersectionType, typeNodes); + return InElementType ? createParenthesizedTypeNode(unionOrIntersectionTypeNode) : unionOrIntersectionTypeNode; } else { if (!context.encounteredError && !(context.flags & NodeBuilderFlags.allowEmptyUnionOrIntersection)) { @@ -2409,10 +2428,6 @@ namespace ts { } } - if (type.flags & TypeFlags.Intersection) { - return createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, mapToTypeNodeArray((type as UnionType).types)); - } - if (objectFlags & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { Debug.assert(!!(type.flags & TypeFlags.Object)); // The type is an object literal type. @@ -2421,25 +2436,33 @@ namespace ts { if (type.flags & TypeFlags.Index) { const indexedType = (type).type; + context.InElementType = true; const indexTypeNode = typeToTypeNodeHelper(indexedType, context); + Debug.assert(context.InElementType === false); return createTypeOperatorNode(indexTypeNode); } + if (type.flags & TypeFlags.IndexedAccess) { + context.InElementType = true; const objectTypeNode = typeToTypeNodeHelper((type).objectType, context); + Debug.assert(context.InElementType === false); const indexTypeNode = typeToTypeNodeHelper((type).indexType, context); return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); } Debug.fail("Should be unreachable."); - function mapToTypeNodeArray(types: Type[]): TypeNode[] { + function mapToTypeNodeArray(types: Type[], addInElementTypeFlag: boolean): TypeNode[] { const result = []; + Debug.assert(context.InElementType === false, "should be unset at the beginning of the helper"); for (const type of types) { + context.InElementType = addInElementTypeFlag; const typeNode = typeToTypeNodeHelper(type, context); if (typeNode) { result.push(typeNode); } } + Debug.assert(context.InElementType === false, "should be unset at the beginning of the helper"); return result; } @@ -2523,6 +2546,7 @@ namespace ts { if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { const signature = resolved.callSignatures[0]; + shouldAddParenthesisAroundFunctionType(signature, context); return signatureToSignatureDeclarationHelper(signature, SyntaxKind.FunctionType, context); } if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { @@ -2538,6 +2562,20 @@ namespace ts { return createTypeLiteralNode(members); } + + function shouldAddParenthesisAroundFunctionType(callSignature: Signature, context: NodeBuilderContext) { + if (context.InElementType) { + return true; + } + else if (context.InFirstTypeArgument) { + // Add parenthesis around function type for the first type argument to avoid ambiguity + const typeParameters = callSignature.target && (context.flags & NodeBuilderFlags.WriteTypeArgumentsOfSignature) ? + callSignature.target.typeParameters : callSignature.typeParameters; + return typeParameters && typeParameters.length !== 0; + } + return false; + } + function createTypeQueryNodeFromSymbol(symbol: Symbol) { const entityName = symbolToName(symbol, /*expectsIdentifier*/ false, context); return createTypeQueryNode(entityName); @@ -2546,12 +2584,14 @@ namespace ts { function typeReferenceToTypeNode(type: TypeReference) { const typeArguments: Type[] = type.typeArguments || emptyArray; if (type.target === globalArrayType) { + context.InElementType = true; const elementType = typeToTypeNodeHelper(typeArguments[0], context); + context.InElementType = false; return createArrayTypeNode(elementType); } else if (type.target.objectFlags & ObjectFlags.Tuple) { if (typeArguments.length > 0) { - const tupleConstituentNodes = mapToTypeNodeArray(typeArguments.slice(0, getTypeReferenceArity(type))); + const tupleConstituentNodes = mapToTypeNodeArray(typeArguments.slice(0, getTypeReferenceArity(type)), /*addInElementTypeFlag*/ false); if (tupleConstituentNodes && tupleConstituentNodes.length > 0) { return createTupleTypeNode(tupleConstituentNodes); } @@ -2566,6 +2606,7 @@ namespace ts { let i = 0; let qualifiedName: QualifiedName | undefined = undefined; if (outerTypeParameters) { + let inFirstTypeArgument = true; const length = outerTypeParameters.length; while (i < length) { // Find group of type arguments for type parameters with the same declaring container. @@ -2577,6 +2618,7 @@ namespace ts { // When type parameters are their own type arguments for the whole group (i.e. we have // the default outer type arguments), we don't show the group. if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { + // inFirstTypeArgument??? const qualifiedNamePart = symbolToName(parent, /*expectsIdentifier*/ true, context); if (!qualifiedName) { qualifiedName = createQualifiedName(qualifiedNamePart, /*right*/ undefined); @@ -2587,6 +2629,7 @@ namespace ts { qualifiedName = createQualifiedName(qualifiedName, /*right*/ undefined); } } + inFirstTypeArgument = false; } } let entityName: EntityName = undefined; @@ -2600,7 +2643,7 @@ namespace ts { entityName = nameIdentifier; } const typeParameterCount = (type.target.typeParameters || emptyArray).length; - const typeArgumentNodes = some(typeArguments) ? mapToTypeNodeArray(typeArguments.slice(i, typeParameterCount - i)) : undefined; + const typeArgumentNodes = some(typeArguments) ? mapToTypeNodeArray(typeArguments.slice(i, typeParameterCount - i), /*addInElementTypeFlag*/ false) : undefined; return createTypeReferenceNode(entityName, typeArgumentNodes); } } @@ -2695,7 +2738,7 @@ namespace ts { const returnType = getReturnTypeOfSignature(signature); returnTypeNode = returnType && typeToTypeNodeHelper(returnType, context); } - if(context.flags & NodeBuilderFlags.suppressAnyReturnType) { + if(context.flags & NodeBuilderFlags.SuppressAnyReturnType) { if(returnTypeNode && returnTypeNode.kind === SyntaxKind.AnyKeyword) { returnTypeNode = undefined; } @@ -2733,6 +2776,8 @@ namespace ts { return parameterNode; } + // TODO: add meaning: SymbolFlags argument. + // TODO: add SymbolFormatFlags?? Yes to add outer type parameters. Defer UseOnlyExternalAliasing until a separate symbolbuilder PR. function symbolToName(symbol: Symbol, expectsIdentifier: true, context: NodeBuilderContext): Identifier; function symbolToName(symbol: Symbol, expectsIdentifier: false, context: NodeBuilderContext): EntityName; function symbolToName(symbol: Symbol, expectsIdentifier: boolean, context: NodeBuilderContext): EntityName { @@ -2826,6 +2871,7 @@ namespace ts { } } } + function getNameOfSymbol(symbol: Symbol, context: NodeBuilderContext): string { const declaration = firstOrUndefined(symbol.declarations); if (declaration) { diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index afafee51bf7..eebbe302898 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -2947,8 +2947,9 @@ namespace ts { // Precomputed Formats Modifiers = SingleLine | SpaceBetweenSiblings, HeritageClauses = SingleLine | SpaceBetweenSiblings, - SingleLineTypeLiteralMembers = SpaceBetweenBraces | SpaceBetweenSiblings | Indented, // MultiLine | Indented, + SingleLineTypeLiteralMembers = SingleLine | SpaceBetweenBraces | SpaceBetweenSiblings | Indented, // MultiLine | Indented, MultiLineTypeLiteralMembers = MultiLine | Indented, + TupleTypeElements = CommaDelimited | SpaceBetweenSiblings | SingleLine | Indented, UnionTypeConstituents = BarDelimited | SpaceBetweenSiblings | SingleLine, IntersectionTypeConstituents = AmpersandDelimited | SpaceBetweenSiblings | SingleLine, diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 9dfd38a0a5c..172710f39aa 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -393,6 +393,18 @@ namespace ts { : node; } + export function createParenthesizedTypeNode(type: TypeNode) { + const parenthesizedTypeNode = createSynthesizedNode(SyntaxKind.ParenthesizedType) as ParenthesizedTypeNode; + parenthesizedTypeNode.type = type; + return parenthesizedTypeNode; + } + + export function updateParenthesizedTypeNode(node: ParenthesizedTypeNode, type: TypeNode) { + return node.type !== type + ? updateNode(createParenthesizedTypeNode(type), node) + : node; + } + export function createTypeLiteralNode(members: TypeElement[]) { const typeLiteralNode = createSynthesizedNode(SyntaxKind.TypeLiteral) as TypeLiteralNode; typeLiteralNode.members = createNodeArray(members); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 53507e3aa38..17df0243a1b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2559,14 +2559,36 @@ namespace ts { export enum NodeBuilderFlags { None = 0, - allowThisInObjectLiteral = 1 << 0, - allowQualifedNameInPlaceOfIdentifier = 1 << 1, - allowTypeParameterInQualifiedName = 1 << 2, - allowAnonymousIdentifier = 1 << 3, - allowEmptyUnionOrIntersection = 1 << 4, - allowEmptyTuple = 1 << 5, - suppressAnyReturnType = 1 << 6, - ignoreErrors = allowThisInObjectLiteral | allowQualifedNameInPlaceOfIdentifier | allowTypeParameterInQualifiedName | allowAnonymousIdentifier | allowEmptyUnionOrIntersection | allowEmptyTuple + // Options + NoTruncation = 1 << 0, // Don't truncate result + // TODO: part of emit. + WriteArrayAsGenericType = 1 << 1, // Write Array instead T[] + // TODO: part of emit. + UseTypeOfFunction = 1 << 2, // Write typeof instead of function type literal + // TODO: part of emit. + WriteArrowStyleSignature = 1 << 3, // Write arrow style signature + // TODO: turn it into a failing type reference? + WriteOwnNameForAnyLike = 1 << 4, // Write symbol's own name instead of 'any' for any like types (eg. unknown, __resolving__ etc) + // TODO + WriteTypeArgumentsOfSignature = 1 << 5, // Write the type arguments instead of type parameters of the signature + // TODO + UseFullyQualifiedType = 1 << 6, // Write out the fully qualified type name (eg. Module.Type, instead of Type) + // TODO + UseTypeAliasValue = 1 << 7, // Serialize the type instead of using type-alias. This is needed when we emit declaration file. + SuppressAnyReturnType = 1 << 8, // If the return type is any-like, don't offer a return type. + // TODO + AddUndefined = 1 << 9, // Add undefined to types of initialized, non-optional parameters + + // Error handling + allowThisInObjectLiteral = 1 << 10, + allowQualifedNameInPlaceOfIdentifier = 1 << 11, + allowTypeParameterInQualifiedName = 1 << 12, + allowAnonymousIdentifier = 1 << 13, + allowEmptyUnionOrIntersection = 1 << 14, + allowEmptyTuple = 1 << 15, + + ignoreErrors = allowThisInObjectLiteral | allowQualifedNameInPlaceOfIdentifier | allowTypeParameterInQualifiedName | allowAnonymousIdentifier | allowEmptyUnionOrIntersection | allowEmptyTuple, + } export interface SymbolDisplayBuilder { diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 262520292e1..1e196a925f8 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -322,7 +322,8 @@ namespace ts { nodesVisitor((node).types, visitor, isTypeNode)); case SyntaxKind.ParenthesizedType: - throw Debug.fail("not implemented."); + return updateParenthesizedTypeNode(node, + visitNode((node).type, visitor, isTypeNode)); case SyntaxKind.TypeOperator: return updateTypeOperatorNode(node, visitNode((node).type, visitor, isTypeNode)); diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 0f636bb09ca..7c1a8c407fc 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -130,7 +130,7 @@ namespace ts.codefix { } function signatureToMethodDeclaration(signature: Signature, enclosingDeclaration: Node, body?: Block) { - const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration, NodeBuilderFlags.suppressAnyReturnType); + const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration, NodeBuilderFlags.SuppressAnyReturnType); if (signatureDeclaration) { signatureDeclaration.decorators = undefined; signatureDeclaration.modifiers = modifiers;