diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f06afe4425d..82567bc170d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2449,13 +2449,13 @@ namespace ts { if (!inTypeAlias && type.aliasSymbol && isSymbolAccessible(type.aliasSymbol, context.enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false).accessibility === SymbolAccessibility.Accessible) { const name = symbolToName(type.aliasSymbol, /*expectsIdentifier*/ false, SymbolFlags.Type, context); - const typeArgumentNodes = type.aliasTypeArguments && mapToTypeNodeArray(type.aliasTypeArguments, /*addInElementTypeFlag*/ false); + const typeArgumentNodes = type.aliasTypeArguments && mapToTypeNodeArray(type.aliasTypeArguments, context, /*addInElementTypeFlag*/ false, /*addInFirstTypeArgumentFlag*/ true); return createTypeReferenceNode(name, typeArgumentNodes); } 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); + const typeNodes = types && mapToTypeNodeArray(types, context, /*addInElementTypeFlag*/ true, /*addInFirstTypeArgumentFlag*/ false); if (typeNodes && typeNodes.length > 0) { const unionOrIntersectionTypeNode = createUnionOrIntersectionTypeNode(type.flags & TypeFlags.Union ? SyntaxKind.UnionType : SyntaxKind.IntersectionType, typeNodes); return InElementType ? createParenthesizedTypeNode(unionOrIntersectionTypeNode) : unionOrIntersectionTypeNode; @@ -2492,20 +2492,6 @@ namespace ts { Debug.fail("Should be unreachable."); - 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; - } - function createMappedTypeNodeFromType(type: MappedType) { Debug.assert(!!(type.flags & TypeFlags.Object)); const typeParameter = getTypeParameterFromMappedType(type); @@ -2642,7 +2628,7 @@ namespace ts { } else if (type.target.objectFlags & ObjectFlags.Tuple) { if (typeArguments.length > 0) { - const tupleConstituentNodes = mapToTypeNodeArray(typeArguments.slice(0, getTypeReferenceArity(type)), /*addInElementTypeFlag*/ false); + const tupleConstituentNodes = mapToTypeNodeArray(typeArguments.slice(0, getTypeReferenceArity(type)), context, /*addInElementTypeFlag*/ false, /*addInFirstTypeArgumentFlag*/ false); if (tupleConstituentNodes && tupleConstituentNodes.length > 0) { return createTupleTypeNode(tupleConstituentNodes); } @@ -2669,8 +2655,11 @@ 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 qualifiedNamePartTypeArguments = typeArguments.slice(start,i); + const qualifiedNamePartTypeArgumentNodes = qualifiedNamePartTypeArguments && createNodeArray(mapToTypeNodeArray(qualifiedNamePartTypeArguments, context, /*addInElementTypeFlag*/ false, /*addInFirstTypeArgumentFlag*/ true)); const qualifiedNamePart = symbolToName(parent, /*expectsIdentifier*/ true, SymbolFlags.Type, context); + qualifiedNamePart.typeArguments = qualifiedNamePartTypeArgumentNodes; + if (!qualifiedName) { qualifiedName = createQualifiedName(qualifiedNamePart, /*right*/ undefined); } @@ -2699,7 +2688,7 @@ namespace ts { if (some(typeArguments)) { const slice = typeArguments.slice(i, typeParameterCount - i); context.InFirstTypeArgument = true; - typeArgumentNodes = mapToTypeNodeArray(slice, /*addInElementTypeFlag*/ false); + typeArgumentNodes = mapToTypeNodeArray(slice, context, /*addInElementTypeFlag*/ false, /*addInFirstTypeArgumentFlag*/ true); } return createTypeReferenceNode(entityName, typeArgumentNodes); @@ -2761,6 +2750,24 @@ namespace ts { } } + function mapToTypeNodeArray(types: Type[], context: NodeBuilderContext, addInElementTypeFlag: boolean, addInFirstTypeArgumentFlag: boolean): TypeNode[] { + const result = []; + Debug.assert(context.InElementType === false, "should be unset at the beginning of the helper"); + for (let i = 0; i < types.length; ++i) { + let type = types[i] + context.InElementType = addInElementTypeFlag; + if (i === 0) { + context.InFirstTypeArgument = addInFirstTypeArgumentFlag; + } + 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; + } + function indexInfoToIndexSignatureDeclarationHelper(indexInfo: IndexInfo, kind: IndexKind, context: NodeBuilderContext): IndexSignatureDeclaration { const indexerTypeNode = createKeywordTypeNode(kind === IndexKind.String ? SyntaxKind.StringKeyword : SyntaxKind.NumberKeyword); const name = getNameFromIndexInfo(indexInfo) || "x"; @@ -2889,9 +2896,8 @@ namespace ts { Debug.assert(chain && 0 <= index && index < chain.length); // const parentIndex = index - 1; const symbol = chain[index]; - let typeParameterString = ""; + let typeParameterNodes: TypeNode[] | undefined; if (index > 0) { - const parentSymbol = chain[index - 1]; let typeParameters: TypeParameter[]; if (getCheckFlags(symbol) & CheckFlags.Instantiated) { @@ -2907,17 +2913,12 @@ namespace ts { if (!context.encounteredError && !(context.flags & NodeBuilderFlags.allowTypeParameterInQualifiedName)) { context.encounteredError = true; } - const writer = getSingleLineStringWriter(); - const displayBuilder = getSymbolDisplayBuilder(); - displayBuilder.buildDisplayForTypeParametersAndDelimiters(typeParameters, writer, context.enclosingDeclaration, 0); - typeParameterString = writer.string(); - releaseStringWriter(writer); - + typeParameterNodes = typeParameters && mapToTypeNodeArray(typeParameters, context, /*addInElementTypeFlag*/ false, /*addInFirstTypeArgumentFlag*/ true); } } + const symbolName = getNameOfSymbol(symbol, context); - const symbolNameWithTypeParameters = typeParameterString.length > 0 ? `${symbolName}<${typeParameterString}>` : symbolName; - const identifier = createIdentifier(symbolNameWithTypeParameters); + const identifier = createIdentifier(symbolName, typeParameterNodes); return index > 0 ? createQualifiedName(createEntityNameFromSymbolChain(chain, index - 1), identifier) : identifier; } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 582bc6ed7ab..b96e87ec55c 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -780,6 +780,7 @@ namespace ts { function emitIdentifier(node: Identifier) { write(getTextOfNode(node, /*includeTrivia*/ false)); + emitTypeArguments(node, node.typeArguments); } // diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 45b09892bb0..510e4ec03de 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -136,15 +136,22 @@ namespace ts { // Identifiers - export function createIdentifier(text: string): Identifier { + export function createIdentifier(text: string, typeArguments?: TypeNode[]): Identifier { const node = createSynthesizedNode(SyntaxKind.Identifier); node.text = escapeIdentifier(text); node.originalKeywordKind = text ? stringToToken(text) : SyntaxKind.Unknown; node.autoGenerateKind = GeneratedIdentifierKind.None; node.autoGenerateId = 0; + node.typeArguments = asNodeArray(typeArguments); return node; } + export function updateIdentifier(node: Identifier, typeArguments: NodeArray | undefined): Identifier { + return node.typeArguments !== typeArguments + ? updateNode(createIdentifier(node.text, typeArguments), node) + : node; + } + let nextAutoGenerateId = 0; /** Create a unique temporary variable. */ diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 67b73cdbda5..ad89069c286 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -575,6 +575,7 @@ namespace ts { /*@internal*/ autoGenerateKind?: GeneratedIdentifierKind; // Specifies whether to auto-generate the text for an identifier. /*@internal*/ autoGenerateId?: number; // Ensures unique generated identifiers get unique names, but clones get the same name. isInJSDocNamespace?: boolean; // if the node is a member in a JSDoc namespace + /*@internal*/ typeArguments: NodeArray; // Only defined on synthesized nodes.Though not syntactically valid, used in emitting diagnostics. } // Transient identifier node (marked by id === -1) diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 1e196a925f8..fce43b622bb 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -219,6 +219,9 @@ namespace ts { } switch (node.kind) { + case SyntaxKind.Identifier: + return updateIdentifier(node, nodesVisitor((node).typeArguments, visitor, isTypeNode)); + case SyntaxKind.SemicolonClassElement: case SyntaxKind.EmptyStatement: case SyntaxKind.OmittedExpression: diff --git a/src/services/services.ts b/src/services/services.ts index 8f2cbb22379..faf27f4f2b0 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -357,6 +357,7 @@ namespace ts { _incrementExpressionBrand: any; _unaryExpressionBrand: any; _expressionBrand: any; + typeArguments: any; constructor(_kind: SyntaxKind.Identifier, pos: number, end: number) { super(pos, end); }