From fb09626e571dcb176565a2d8dbb7adac2de728fc Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 6 Jul 2018 12:58:25 -0700 Subject: [PATCH] Fancy truncation + higher hard limit --- src/compiler/checker.ts | 160 +++++++++++------- src/compiler/types.ts | 1 - src/compiler/utilities.ts | 2 +- src/services/utilities.ts | 13 +- .../reference/api/tsserverlibrary.d.ts | 2 +- .../errorWithTruncatedType.errors.txt | 4 +- .../fourslash/quickInfoCanBeTruncated.ts | 66 +++++++- 7 files changed, 173 insertions(+), 75 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a85e159537477..0405d14b63c3b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2993,9 +2993,6 @@ namespace ts { } function typeToString(type: Type, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.AllowUniqueESSymbolType | TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer: EmitTextWriter = createTextWriter("")): string { - if (writer.maximumApproximateLength === undefined) { - writer.maximumApproximateLength = defaultMaximumTruncationLength; - } const noTruncation = compilerOptions.noErrorTruncation || flags & TypeFormatFlags.NoTruncation; const typeNode = nodeBuilder.typeToTypeNode(type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | (noTruncation ? NodeBuilderFlags.NoTruncation : 0), writer); if (typeNode === undefined) return Debug.fail("should always get typenode"); @@ -3005,7 +3002,7 @@ namespace ts { printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ sourceFile, writer); const result = writer.getText(); - const maxLength = noTruncation ? undefined : writer.maximumApproximateLength; + const maxLength = noTruncation ? undefined : defaultMaximumTruncationLength * 2; if (maxLength && result && result.length >= maxLength) { return result.substr(0, maxLength - "...".length) + "..."; } @@ -3051,6 +3048,11 @@ namespace ts { return context.encounteredError ? undefined : resultingNode; } + function checkTruncationLength(context: NodeBuilderContext): boolean { + if (context.truncating) return context.truncating; + return context.truncating = !(context.flags & NodeBuilderFlags.NoTruncation) && context.approximateLength > defaultMaximumTruncationLength; + } + function typeToTypeNodeHelper(type: Type, context: NodeBuilderContext): TypeNode { if (cancellationToken && cancellationToken.throwIfCancellationRequested) { cancellationToken.throwIfCancellationRequested(); @@ -3063,7 +3065,7 @@ namespace ts { return undefined!; // TODO: GH#18217 } - if (type.flags & TypeFlags.Any || (!(context.flags & NodeBuilderFlags.NoTruncation) && context.approximateLength > (context.tracker.maximumApproximateLength || 2000))) { + if (type.flags & TypeFlags.Any) { context.approximateLength += 3; return createKeywordTypeNode(SyntaxKind.AnyKeyword); } @@ -3189,10 +3191,9 @@ namespace ts { } if (type.flags & (TypeFlags.Union | TypeFlags.Intersection)) { const types = type.flags & TypeFlags.Union ? formatUnionTypes((type).types) : (type).types; - const typeNodes = mapToTypeNodes(types, context); + const typeNodes = mapToTypeNodes(types, context, /*isBareList*/ true); if (typeNodes && typeNodes.length > 0) { const unionOrIntersectionTypeNode = createUnionOrIntersectionTypeNode(type.flags & TypeFlags.Union ? SyntaxKind.UnionType : SyntaxKind.IntersectionType, typeNodes); - context.approximateLength += (3 * (types.length - 1)); return unionOrIntersectionTypeNode; } else { @@ -3471,6 +3472,9 @@ namespace ts { } function createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] | undefined { + if (checkTruncationLength(context)) { + return [createPropertySignature(/*modifiers*/ undefined, "...", /*questionToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined)]; + } const typeElements: TypeElement[] = []; for (const signature of resolvedType.callSignatures) { typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.CallSignature, context)); @@ -3493,7 +3497,9 @@ namespace ts { return typeElements; } + let i = 0; for (const propertySymbol of properties) { + i++; if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) { if (propertySymbol.flags & SymbolFlags.Prototype) { continue; @@ -3502,69 +3508,102 @@ namespace ts { context.tracker.reportPrivateInBaseOfClassExpression(unescapeLeadingUnderscores(propertySymbol.escapedName)); } } - const propertyType = getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped && context.flags & NodeBuilderFlags.InReverseMappedType ? - anyType : getTypeOfSymbol(propertySymbol); - const saveEnclosingDeclaration = context.enclosingDeclaration; - context.enclosingDeclaration = undefined; - if (getCheckFlags(propertySymbol) & CheckFlags.Late) { - const decl = first(propertySymbol.declarations); - if (context.tracker.trackSymbol && hasLateBindableName(decl)) { - // get symbol of the first identifier of the entityName - const firstIdentifier = getFirstIdentifier(decl.name.expression); - const name = resolveName(firstIdentifier, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); - if (name) { - context.tracker.trackSymbol(name, saveEnclosingDeclaration, SymbolFlags.Value); - } - } + if (checkTruncationLength(context) && (i + 2 < properties.length - 1)) { + typeElements.push(createPropertySignature(/*modifiers*/ undefined, `... ${properties.length - i} more ...`, /*questionToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined)) + addPropertyToElementList(properties[properties.length - 1], context, typeElements); + break; } - const propertyName = symbolToName(propertySymbol, context, SymbolFlags.Value, /*expectsIdentifier*/ true); - context.approximateLength += (symbolName(propertySymbol).length + 1); - context.enclosingDeclaration = saveEnclosingDeclaration; - const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? createToken(SyntaxKind.QuestionToken) : undefined; - if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length) { - const signatures = getSignaturesOfType(propertyType, SignatureKind.Call); - for (const signature of signatures) { - const methodDeclaration = signatureToSignatureDeclarationHelper(signature, SyntaxKind.MethodSignature, context); - methodDeclaration.name = propertyName; - methodDeclaration.questionToken = optionalToken; - if (propertySymbol.valueDeclaration) { - // Copy comments to node for declaration emit - setCommentRange(methodDeclaration, propertySymbol.valueDeclaration); - } - typeElements.push(methodDeclaration); - } + addPropertyToElementList(propertySymbol, context, typeElements); + + } + return typeElements.length ? typeElements : undefined; + } + } + + function addPropertyToElementList(propertySymbol: Symbol, context: NodeBuilderContext, typeElements: TypeElement[]) { + const propertyType = getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped && context.flags & NodeBuilderFlags.InReverseMappedType ? + anyType : getTypeOfSymbol(propertySymbol); + const saveEnclosingDeclaration = context.enclosingDeclaration; + context.enclosingDeclaration = undefined; + if (getCheckFlags(propertySymbol) & CheckFlags.Late) { + const decl = first(propertySymbol.declarations); + if (context.tracker.trackSymbol && hasLateBindableName(decl)) { + // get symbol of the first identifier of the entityName + const firstIdentifier = getFirstIdentifier(decl.name.expression); + const name = resolveName(firstIdentifier, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); + if (name) { + context.tracker.trackSymbol(name, saveEnclosingDeclaration, SymbolFlags.Value); } - else { - const savedFlags = context.flags; - context.flags |= !!(getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped) ? NodeBuilderFlags.InReverseMappedType : 0; - const propertyTypeNode = propertyType ? typeToTypeNodeHelper(propertyType, context) : createKeywordTypeNode(SyntaxKind.AnyKeyword); - context.flags = savedFlags; - - const modifiers = isReadonlySymbol(propertySymbol) ? [createToken(SyntaxKind.ReadonlyKeyword)] : undefined; - if (modifiers) { - context.approximateLength += 9; - } - const propertySignature = createPropertySignature( - modifiers, - propertyName, - optionalToken, - propertyTypeNode, - /*initializer*/ undefined); - if (propertySymbol.valueDeclaration) { - // Copy comments to node for declaration emit - setCommentRange(propertySignature, propertySymbol.valueDeclaration); - } - typeElements.push(propertySignature); + } + } + const propertyName = symbolToName(propertySymbol, context, SymbolFlags.Value, /*expectsIdentifier*/ true); + context.approximateLength += (symbolName(propertySymbol).length + 1); + context.enclosingDeclaration = saveEnclosingDeclaration; + const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? createToken(SyntaxKind.QuestionToken) : undefined; + if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length) { + const signatures = getSignaturesOfType(propertyType, SignatureKind.Call); + for (const signature of signatures) { + const methodDeclaration = signatureToSignatureDeclarationHelper(signature, SyntaxKind.MethodSignature, context); + methodDeclaration.name = propertyName; + methodDeclaration.questionToken = optionalToken; + if (propertySymbol.valueDeclaration) { + // Copy comments to node for declaration emit + setCommentRange(methodDeclaration, propertySymbol.valueDeclaration); } + typeElements.push(methodDeclaration); } - return typeElements.length ? typeElements : undefined; + } + else { + const savedFlags = context.flags; + context.flags |= !!(getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped) ? NodeBuilderFlags.InReverseMappedType : 0; + const propertyTypeNode = propertyType ? typeToTypeNodeHelper(propertyType, context) : createKeywordTypeNode(SyntaxKind.AnyKeyword); + context.flags = savedFlags; + + const modifiers = isReadonlySymbol(propertySymbol) ? [createToken(SyntaxKind.ReadonlyKeyword)] : undefined; + if (modifiers) { + context.approximateLength += 9; + } + const propertySignature = createPropertySignature( + modifiers, + propertyName, + optionalToken, + propertyTypeNode, + /*initializer*/ undefined); + if (propertySymbol.valueDeclaration) { + // Copy comments to node for declaration emit + setCommentRange(propertySignature, propertySymbol.valueDeclaration); + } + typeElements.push(propertySignature); } } - function mapToTypeNodes(types: ReadonlyArray | undefined, context: NodeBuilderContext): TypeNode[] | undefined { + function mapToTypeNodes(types: ReadonlyArray | undefined, context: NodeBuilderContext, isBareList?: boolean): TypeNode[] | undefined { if (some(types)) { + if (checkTruncationLength(context)) { + if (!isBareList) { + return [createTypeReferenceNode("...", /*typeArguments*/ undefined)]; + } + else if (types.length > 2) { + return [ + typeToTypeNodeHelper(types[0], context), + createTypeReferenceNode(`... ${types.length - 2} more ...`, /*typeArguments*/ undefined), + typeToTypeNodeHelper(types[types.length - 1], context) + ]; + } + } const result = []; + let i = 0; for (const type of types) { + i++; + if (checkTruncationLength(context) && (i + 2 < types.length - 1)) { + result.push(createTypeReferenceNode(`... ${types.length - i} more ...`, /*typeArguments*/ undefined)); + const typeNode = typeToTypeNodeHelper(types[types.length - 1], context); + if (typeNode) { + result.push(typeNode); + } + break; + } + context.approximateLength += 2; // Account for whitespace + separator const typeNode = typeToTypeNodeHelper(type, context); if (typeNode) { result.push(typeNode); @@ -4078,6 +4117,7 @@ namespace ts { visitedSymbols: Map | undefined; inferTypeParameters: TypeParameter[] | undefined; approximateLength: number; + truncating?: boolean; } function isDefaultBindingContext(location: Node) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1ccbc48033f5f..d096cba6c5589 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5324,7 +5324,6 @@ namespace ts { reportInaccessibleUniqueSymbolError?(): void; moduleResolverHost?: ModuleSpecifierResolutionHost; trackReferencedAmbientModule?(decl: ModuleDeclaration, symbol: Symbol): void; - maximumApproximateLength?: number; } export interface TextSpan { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index c52945a0765b1..9cedb7d659e30 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -21,7 +21,7 @@ namespace ts { export const externalHelpersModuleNameText = "tslib"; - export const defaultMaximumTruncationLength = 240; + export const defaultMaximumTruncationLength = 160; export function getDeclarationOfKind(symbol: Symbol, kind: T["kind"]): T | undefined { const declarations = symbol.declarations; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 13fa3d884a333..deccf19b8eae1 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1435,7 +1435,7 @@ namespace ts { const displayPartWriter = getDisplayPartWriter(); function getDisplayPartWriter(): DisplayPartsSymbolWriter { - const maximumApproximateLength = defaultMaximumTruncationLength; + const absoluteMaximumLength = defaultMaximumTruncationLength * 10; // A hard cutoff to avoid overloading the messaging channel in worst-case scenarios let displayParts: SymbolDisplayPart[]; let lineStart: boolean; let indent: number; @@ -1446,7 +1446,7 @@ namespace ts { return { displayParts: () => { const finalText = displayParts.length && displayParts[displayParts.length - 1].text; - if (length > maximumApproximateLength && finalText && finalText !== "...") { + if (length > absoluteMaximumLength && finalText && finalText !== "...") { if (!isWhiteSpaceLike(finalText.charCodeAt(finalText.length - 1))) { displayParts.push(displayPart(" ", SymbolDisplayPartKind.space)); } @@ -1480,11 +1480,10 @@ namespace ts { reportInaccessibleThisError: noop, reportInaccessibleUniqueSymbolError: noop, reportPrivateInBaseOfClassExpression: noop, - maximumApproximateLength, // Limit output to about 2000 characters }; function writeIndent() { - if (length > maximumApproximateLength) return; + if (length > absoluteMaximumLength) return; if (lineStart) { const indentString = getIndentString(indent); if (indentString) { @@ -1496,21 +1495,21 @@ namespace ts { } function writeKind(text: string, kind: SymbolDisplayPartKind) { - if (length > maximumApproximateLength) return; + if (length > absoluteMaximumLength) return; writeIndent(); length += text.length; displayParts.push(displayPart(text, kind)); } function writeSymbol(text: string, symbol: Symbol) { - if (length > maximumApproximateLength) return; + if (length > absoluteMaximumLength) return; writeIndent(); length += text.length; displayParts.push(symbolPart(text, symbol)); } function writeLine() { - if (length > maximumApproximateLength) return; + if (length > absoluteMaximumLength) return; length += 1; displayParts.push(lineBreakPart()); lineStart = true; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 383d9478fb8db..50459ff4389aa 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4505,7 +4505,6 @@ declare namespace ts { reportInaccessibleUniqueSymbolError?(): void; moduleResolverHost?: ModuleSpecifierResolutionHost; trackReferencedAmbientModule?(decl: ModuleDeclaration, symbol: Symbol): void; - maximumApproximateLength?: number; } interface TextSpan { start: number; @@ -6032,6 +6031,7 @@ declare namespace ts { const emptyMap: ReadonlyMap; const emptyUnderscoreEscapedMap: ReadonlyUnderscoreEscapedMap; const externalHelpersModuleNameText = "tslib"; + const defaultMaximumTruncationLength = 160; function getDeclarationOfKind(symbol: Symbol, kind: T["kind"]): T | undefined; /** Create a new escaped identifier map. */ function createUnderscoreEscapedMap(): UnderscoreEscapedMap; diff --git a/tests/baselines/reference/errorWithTruncatedType.errors.txt b/tests/baselines/reference/errorWithTruncatedType.errors.txt index 33714b8626280..2f6e095a042ae 100644 --- a/tests/baselines/reference/errorWithTruncatedType.errors.txt +++ b/tests/baselines/reference/errorWithTruncatedType.errors.txt @@ -1,4 +1,4 @@ -tests/cases/compiler/errorWithTruncatedType.ts(10,5): error TS2322: Type '{ propertyWithAnExceedinglyLongName1: string; propertyWithAnExceedinglyLongName2: string; propert...' is not assignable to type 'string'. +tests/cases/compiler/errorWithTruncatedType.ts(10,5): error TS2322: Type '{ propertyWithAnExceedinglyLongName1: string; propertyWithAnExceedinglyLongName2: string; propertyWithAnExceedinglyLongName3: string; propertyWithAnExceedinglyLongName4: string; propertyWithAnExceedinglyLongName5: string; }' is not assignable to type 'string'. ==== tests/cases/compiler/errorWithTruncatedType.ts (1 errors) ==== @@ -13,5 +13,5 @@ tests/cases/compiler/errorWithTruncatedType.ts(10,5): error TS2322: Type '{ prop // String representation of type of 'x' should be truncated in error message var s: string = x; ~ -!!! error TS2322: Type '{ propertyWithAnExceedinglyLongName1: string; propertyWithAnExceedinglyLongName2: string; propert...' is not assignable to type 'string'. +!!! error TS2322: Type '{ propertyWithAnExceedinglyLongName1: string; propertyWithAnExceedinglyLongName2: string; propertyWithAnExceedinglyLongName3: string; propertyWithAnExceedinglyLongName4: string; propertyWithAnExceedinglyLongName5: string; }' is not assignable to type 'string'. \ No newline at end of file diff --git a/tests/cases/fourslash/quickInfoCanBeTruncated.ts b/tests/cases/fourslash/quickInfoCanBeTruncated.ts index 8867714308d3e..9c8965641d9d4 100644 --- a/tests/cases/fourslash/quickInfoCanBeTruncated.ts +++ b/tests/cases/fourslash/quickInfoCanBeTruncated.ts @@ -508,13 +508,73 @@ //// type Less/*2*/ = Exclude; //// function f(s: T, x: Exclude, y: string) {} //// f("_499", /*3*/); +//// type Decomposed/*4*/ = {[K in A]: Foo[K]} +//// type LongTuple/*5*/ = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17.18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70]; +//// type DeeplyMapped/*6*/ = {[K in keyof Foo]: {[K2 in keyof Foo]: [K, K2, Foo[K], Foo[K2]]}} goTo.marker("1"); -verify.quickInfoIs(`type A = "_0" | "_1" | "_2" | "_3" | "_4" | "_5" | "_6" | "_7" | "_8" | "_9" | "_10" | "_11" | "_12" | "_13" | "_14" | "_15" | "_16" | "_17" | "_18" | "_19" | "_20" | "_21" | "_22" | "_23" | "_24" | "_25" | "_26" | "_27" | "_28" | "_29" | "_30" | "_31" ...`); +verify.quickInfoIs(`type A = "_0" | "_1" | "_2" | "_3" | "_4" | "_5" | "_6" | "_7" | "_8" | "_9" | "_10" | "_11" | "_12" | "_13" | "_14" | "_15" | "_16" | "_17" | "_18" | "_19" | "_20" | "_21" | "_22" | "_23" | "_24" | ... 474 more ... | "_499"`); goTo.marker("2"); -verify.quickInfoIs(`type Less = "_1" | "_2" | "_3" | "_4" | "_5" | "_6" | "_7" | "_8" | "_9" | "_10" | "_11" | "_12" | "_13" | "_14" | "_15" | "_16" | "_17" | "_18" | "_19" | "_20" | "_21" | "_22" | "_23" | "_24" | "_25" | "_26" | "_27" | "_28" | "_29" | "_30" | "_31" | "_32" ...`); +verify.quickInfoIs(`type Less = "_1" | "_2" | "_3" | "_4" | "_5" | "_6" | "_7" | "_8" | "_9" | "_10" | "_11" | "_12" | "_13" | "_14" | "_15" | "_16" | "_17" | "_18" | "_19" | "_20" | "_21" | "_22" | "_23" | "_24" | "_25" | ... 473 more ... | "_499"`); goTo.marker("3"); verify.signatureHelp({ marker: "3", - text: `f | Exclude<"_1", T> | Exclude<"_2", T> | Exclude<"_3", T> | Exclude<"_4", T> | Exclude<"_5", T> | Exclude<"_6", T> | Exclude<"_7", T> | Exclude<"_8", T> | Exclude<"_9", T> | Exclude<"_10", T> | Exclude<"_11", T> | Exclude< ..., y: string): void` + text: `f(s: T, x: Exclude<"_0", T> | Exclude<"_1", T> | Exclude<"_2", T> | Exclude<"_3", T> | Exclude<"_4", T> | Exclude<"_5", T> | Exclude<"_6", T> | Exclude<"_7", T> | Exclude<...> | ... 490 more ... | Exclude<...>, y: string): void` }); +goTo.marker("4"); +verify.quickInfoIs(`type Decomposed = { + _0: 0; + _1: 1; + _2: 2; + _3: 3; + _4: 4; + _5: 5; + _6: 6; + _7: 7; + _8: 8; + _9: 9; + _10: 10; + _11: 11; + _12: 12; + _13: 13; + _14: 14; + _15: 15; + _16: 16; + _17: 17; + _18: 18; + _19: 19; + _20: 20; + _21: 21; + _22: 22; + _23: 23; + _24: 24; + _25: 25; + _26: 26; + _27: 27; + _28: 28; + _29: 29; + _30: 30; + ... 468 more ...; + _499: 499; +}`); +goTo.marker("5"); +verify.quickInfoIs(`type LongTuple = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17.18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, ... 27 more ..., 70]`); +goTo.marker("6"); +verify.quickInfoIs(`type DeeplyMapped = { + _0: { + _0: ["_0", "_0", 0, 0]; + _1: ["_0", "_1", 0, 1]; + _2: ["_0", "_2", 0, 2]; + _3: ["_0", "_3", 0, 3]; + _4: ["_0", "_4", 0, 4]; + _5: ["_0", "_5", 0, 5]; + _6: ["_0", "_6", 0, 6]; + _7: ["_0", "_7", 0, 7]; + ... 491 more ...; + _499: [...]; + }; + ... 498 more ...; + _499: { + ...; + }; +}`);