From 705514c12c2241eabf1871f9f55d24bf604ea97a Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Thu, 3 Dec 2015 14:30:55 -0800 Subject: [PATCH] Expose ObjectFormatter as a public API ...and a host object property on InteractiveScriptGlobals. This is a first cut and my main goal at this point is forward compatibility. The existing unit tests all pass. TODO: Make some improvements to the existing behavior. TODO: Update the existing test baselines. TODO: Introduce new tests. This branch should be buddy testable - tweak the existing formatter by subtyping (e.g.) `CSharpObjectFormatter` and assigning an instance of your subtype to the global `ObjectFormatter` property. It should be consumed by subsequent submissions. --- src/Interactive/CsiCore/Csi.cs | 2 +- .../Interactive/CSharpReplServiceProvider.cs | 2 +- .../VisualBasicReplServiceProvider.vb | 6 +- .../Core/InteractiveHost.Service.cs | 11 - .../HostTest/InteractiveHostTests.cs | 2 - src/Interactive/VbiCore/Vbi.vb | 2 +- src/Scripting/CSharp/CSharpScripting.csproj | 2 + .../ObjectFormatter/CSharpObjectFormatter.cs | 266 +----- .../CSharpPrimitiveFormatter.cs | 117 +++ .../CSharpTypeNameFormatter.cs | 59 ++ .../ObjectFormatterTests.cs | 15 +- .../CSharpTest/CommandLineRunnerTests.cs | 31 +- .../CSharpTest/ObjectFormatterTests.cs | 149 ++-- .../Hosting/CommandLine/CommandLineRunner.cs | 57 +- .../CommandLine/CommandLineScriptGlobals.cs | 11 +- .../Core/Hosting/InteractiveScriptGlobals.cs | 8 +- .../CommonObjectFormatter.Builder.cs | 177 ++++ .../CommonObjectFormatter.BuilderOptions.cs | 54 ++ ...ObjectFormatter.Visitor.FormattedMember.cs | 81 ++ ...er.cs => CommonObjectFormatter.Visitor.cs} | 374 ++++---- .../ObjectFormatter/CommonObjectFormatter.cs | 154 ++++ .../CommonPrimitiveFormatter.Options.cs | 21 + .../CommonPrimitiveFormatter.cs | 114 +++ .../CommonTypeNameFormatter.cs | 272 ++++++ .../ObjectFormatter/MemberDisplayFormat.cs | 25 +- .../ObjectFormatter/ObjectFormatter.cs | 817 +----------------- .../ObjectFormatter/ObjectFormatterHelpers.cs | 449 ++++++++++ .../ObjectFormattingOptions.cs | 71 -- src/Scripting/Core/Scripting.csproj | 13 +- .../CoreTest/ObjectFormatterTestBase.cs | 4 - src/Scripting/CoreTest/ScriptingTest.csproj | 6 + .../CoreTest/TestCSharpObjectFormatter.cs | 35 + .../TestVisualBasicObjectFormatter.cs | 36 + .../VisualBasic/BasicScripting.vbproj | 2 + .../VisualBasicObjectFormatter.vb | 242 +----- .../VisualBasicPrimitiveFormatter.vb | 96 ++ .../VisualBasicTypeNameFormatter.vb | 88 ++ .../ObjectFormatterTests.vb | 14 +- .../VisualBasicTest/ObjectFormatterTests.vb | 34 +- 39 files changed, 2117 insertions(+), 1802 deletions(-) create mode 100644 src/Scripting/CSharp/Hosting/ObjectFormatter/CSharpPrimitiveFormatter.cs create mode 100644 src/Scripting/CSharp/Hosting/ObjectFormatter/CSharpTypeNameFormatter.cs create mode 100644 src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Builder.cs create mode 100644 src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.BuilderOptions.cs create mode 100644 src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.FormattedMember.cs rename src/Scripting/Core/Hosting/ObjectFormatter/{ObjectFormatter.Formatter.cs => CommonObjectFormatter.Visitor.cs} (71%) create mode 100644 src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.cs create mode 100644 src/Scripting/Core/Hosting/ObjectFormatter/CommonPrimitiveFormatter.Options.cs create mode 100644 src/Scripting/Core/Hosting/ObjectFormatter/CommonPrimitiveFormatter.cs create mode 100644 src/Scripting/Core/Hosting/ObjectFormatter/CommonTypeNameFormatter.cs create mode 100644 src/Scripting/Core/Hosting/ObjectFormatter/ObjectFormatterHelpers.cs delete mode 100644 src/Scripting/Core/Hosting/ObjectFormatter/ObjectFormattingOptions.cs create mode 100644 src/Scripting/CoreTest/TestCSharpObjectFormatter.cs create mode 100644 src/Scripting/CoreTest/TestVisualBasicObjectFormatter.cs create mode 100644 src/Scripting/VisualBasic/Hosting/ObjectFormatter/VisualBasicPrimitiveFormatter.vb create mode 100644 src/Scripting/VisualBasic/Hosting/ObjectFormatter/VisualBasicTypeNameFormatter.vb diff --git a/src/Interactive/CsiCore/Csi.cs b/src/Interactive/CsiCore/Csi.cs index 9d383690563..d29ed034bf4 100644 --- a/src/Interactive/CsiCore/Csi.cs +++ b/src/Interactive/CsiCore/Csi.cs @@ -30,7 +30,7 @@ internal static int Main(string[] args) ConsoleIO.Default, compiler, CSharpScriptCompiler.Instance, - CSharpObjectFormatter.Instance); + new CSharpObjectFormatter()); return runner.RunInteractive(); } diff --git a/src/Interactive/EditorFeatures/CSharp/Interactive/CSharpReplServiceProvider.cs b/src/Interactive/EditorFeatures/CSharp/Interactive/CSharpReplServiceProvider.cs index d8f61aac84f..7c1bfd50ada 100644 --- a/src/Interactive/EditorFeatures/CSharp/Interactive/CSharpReplServiceProvider.cs +++ b/src/Interactive/EditorFeatures/CSharp/Interactive/CSharpReplServiceProvider.cs @@ -17,7 +17,7 @@ public CSharpReplServiceProvider() { } - public override ObjectFormatter ObjectFormatter => CSharpObjectFormatter.Instance; + public override ObjectFormatter ObjectFormatter { get; } = new CSharpObjectFormatter(); public override CommandLineParser CommandLineParser => CSharpCommandLineParser.ScriptRunner; public override DiagnosticFormatter DiagnosticFormatter => CSharpDiagnosticFormatter.Instance; diff --git a/src/Interactive/EditorFeatures/VisualBasic/Interactive/VisualBasicReplServiceProvider.vb b/src/Interactive/EditorFeatures/VisualBasic/Interactive/VisualBasicReplServiceProvider.vb index 07b65004aa8..7312ed743f2 100644 --- a/src/Interactive/EditorFeatures/VisualBasic/Interactive/VisualBasicReplServiceProvider.vb +++ b/src/Interactive/EditorFeatures/VisualBasic/Interactive/VisualBasicReplServiceProvider.vb @@ -33,11 +33,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.Interactive End Get End Property - Public Overrides ReadOnly Property ObjectFormatter As ObjectFormatter - Get - Return VisualBasicObjectFormatter.Instance - End Get - End Property + Public Overrides ReadOnly Property ObjectFormatter As ObjectFormatter = New VisualBasicObjectFormatter() Public Overrides Function CreateScript(Of T)(code As String, options As ScriptOptions, globalsTypeOpt As Type, assemblyLoader As InteractiveAssemblyLoader) As Script(Of T) Return VisualBasicScript.Create(Of T)(code, options, globalsTypeOpt, assemblyLoader) diff --git a/src/Interactive/Features/Interactive/Core/InteractiveHost.Service.cs b/src/Interactive/Features/Interactive/Core/InteractiveHost.Service.cs index e9dfca6a58a..1110944e103 100644 --- a/src/Interactive/Features/Interactive/Core/InteractiveHost.Service.cs +++ b/src/Interactive/Features/Interactive/Core/InteractiveHost.Service.cs @@ -552,17 +552,6 @@ private static void ReportUnhandledException(Exception e) #region Operations - // TODO (tomat): testing only - public void SetTestObjectFormattingOptions() - { - _globals.PrintOptions = new ObjectFormattingOptions( - memberFormat: MemberDisplayFormat.Inline, - quoteStrings: true, - useHexadecimalNumbers: false, - maxOutputLength: int.MaxValue, - memberIndentation: " "); - } - /// /// Loads references, set options and execute files specified in the initialization file. /// Also prints logo unless is true. diff --git a/src/Interactive/HostTest/InteractiveHostTests.cs b/src/Interactive/HostTest/InteractiveHostTests.cs index 38384c71dea..b036201e487 100644 --- a/src/Interactive/HostTest/InteractiveHostTests.cs +++ b/src/Interactive/HostTest/InteractiveHostTests.cs @@ -48,8 +48,6 @@ public InteractiveHostTests() var remoteService = Host.TryGetService(); Assert.NotNull(remoteService); - remoteService.SetTestObjectFormattingOptions(); - Host.SetPathsAsync(new[] { s_fxDir }, new[] { s_homeDir }, s_homeDir).Wait(); // assert and remove logo: diff --git a/src/Interactive/VbiCore/Vbi.vb b/src/Interactive/VbiCore/Vbi.vb index 9849112ccea..fad1f7a9624 100644 --- a/src/Interactive/VbiCore/Vbi.vb +++ b/src/Interactive/VbiCore/Vbi.vb @@ -24,7 +24,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Scripting.Hosting ConsoleIO.Default, compiler, VisualBasicScriptCompiler.Instance, - VisualBasicObjectFormatter.Instance) + New VisualBasicObjectFormatter()) Return runner.RunInteractive() Catch ex As Exception diff --git a/src/Scripting/CSharp/CSharpScripting.csproj b/src/Scripting/CSharp/CSharpScripting.csproj index df958f41470..cbb7f74ba52 100644 --- a/src/Scripting/CSharp/CSharpScripting.csproj +++ b/src/Scripting/CSharp/CSharpScripting.csproj @@ -47,6 +47,8 @@ + + diff --git a/src/Scripting/CSharp/Hosting/ObjectFormatter/CSharpObjectFormatter.cs b/src/Scripting/CSharp/Hosting/ObjectFormatter/CSharpObjectFormatter.cs index 4e6dc6fdcec..5c02d1f1551 100644 --- a/src/Scripting/CSharp/Hosting/ObjectFormatter/CSharpObjectFormatter.cs +++ b/src/Scripting/CSharp/Hosting/ObjectFormatter/CSharpObjectFormatter.cs @@ -1,268 +1,42 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Text; -using Microsoft.CodeAnalysis.CSharp; +using System.Reflection; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.Scripting.Hosting; -using System.Globalization; namespace Microsoft.CodeAnalysis.CSharp.Scripting.Hosting { - public sealed class CSharpObjectFormatter : ObjectFormatter + public class CSharpObjectFormatter : CommonObjectFormatter { - public static CSharpObjectFormatter Instance { get; } = new CSharpObjectFormatter(); + protected override CommonTypeNameFormatter TypeNameFormatter { get; } + protected override CommonPrimitiveFormatter PrimitiveFormatter { get; } - private CSharpObjectFormatter() + public CSharpObjectFormatter() { + PrimitiveFormatter = new CSharpPrimitiveFormatter(); + TypeNameFormatter = new CSharpTypeNameFormatter(PrimitiveFormatter); } - internal override object VoidDisplayString => ""; - internal override string NullLiteral => ObjectDisplay.NullLiteral; - internal override string GenericParameterOpening => "<"; - internal override string GenericParameterClosing => ">"; - - internal override string FormatLiteral(bool value) - { - return ObjectDisplay.FormatLiteral(value); - } - - internal override string FormatLiteral(string value, bool quote, bool useHexadecimalNumbers = false) - { - var options = ObjectDisplayOptions.EscapeNonPrintableCharacters; - if (quote) - { - options |= ObjectDisplayOptions.UseQuotes; - } - - if (useHexadecimalNumbers) - { - options |= ObjectDisplayOptions.UseHexadecimalNumbers; - } - - return ObjectDisplay.FormatLiteral(value, options); - } - - internal override string FormatLiteral(char c, bool quote, bool includeCodePoints = false, bool useHexadecimalNumbers = false) - { - var options = ObjectDisplayOptions.EscapeNonPrintableCharacters; - if (quote) - { - options |= ObjectDisplayOptions.UseQuotes; - } - - if (includeCodePoints) - { - options |= ObjectDisplayOptions.IncludeCodePoints; - } - - if (useHexadecimalNumbers) - { - options |= ObjectDisplayOptions.UseHexadecimalNumbers; - } - - return ObjectDisplay.FormatLiteral(c, options); - } - - internal override string FormatLiteral(sbyte value, bool useHexadecimalNumbers = false) - { - return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers), UIFormatCulture); - } - - internal override string FormatLiteral(byte value, bool useHexadecimalNumbers = false) - { - return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers), UIFormatCulture); - } - - internal override string FormatLiteral(short value, bool useHexadecimalNumbers = false) - { - return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers), UIFormatCulture); - } - - internal override string FormatLiteral(ushort value, bool useHexadecimalNumbers = false) - { - return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers), UIFormatCulture); - } - - internal override string FormatLiteral(int value, bool useHexadecimalNumbers = false) - { - return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers), UIFormatCulture); - } - - internal override string FormatLiteral(uint value, bool useHexadecimalNumbers = false) - { - return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers), UIFormatCulture); - } - - internal override string FormatLiteral(long value, bool useHexadecimalNumbers = false) - { - return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers), UIFormatCulture); - } - - internal override string FormatLiteral(ulong value, bool useHexadecimalNumbers = false) - { - return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers), UIFormatCulture); - } - - internal override string FormatLiteral(double value) - { - return ObjectDisplay.FormatLiteral(value, ObjectDisplayOptions.None, UIFormatCulture); - } - - internal override string FormatLiteral(float value) - { - return ObjectDisplay.FormatLiteral(value, ObjectDisplayOptions.None, UIFormatCulture); - } - - internal override string FormatLiteral(decimal value) - { - return ObjectDisplay.FormatLiteral(value, ObjectDisplayOptions.None, UIFormatCulture); - } - - internal override string FormatLiteral(DateTime value) - { - // DateTime is not primitive in C# - return null; - } - - internal override string GetPrimitiveTypeName(SpecialType type) - { - switch (type) - { - case SpecialType.System_Boolean: return "bool"; - case SpecialType.System_Byte: return "byte"; - case SpecialType.System_Char: return "char"; - case SpecialType.System_Decimal: return "decimal"; - case SpecialType.System_Double: return "double"; - case SpecialType.System_Int16: return "short"; - case SpecialType.System_Int32: return "int"; - case SpecialType.System_Int64: return "long"; - case SpecialType.System_SByte: return "sbyte"; - case SpecialType.System_Single: return "float"; - case SpecialType.System_String: return "string"; - case SpecialType.System_UInt16: return "ushort"; - case SpecialType.System_UInt32: return "uint"; - case SpecialType.System_UInt64: return "ulong"; - case SpecialType.System_Object: return "object"; - - default: - return null; - } - } - - internal override string FormatGeneratedTypeName(Type type) - { - string stateMachineName; - if (GeneratedNames.TryParseSourceMethodNameFromGeneratedName(type.Name, GeneratedNameKind.StateMachineType, out stateMachineName)) - { - return stateMachineName; - } - - return null; - } - - internal override string FormatArrayTypeName(Type arrayType, Array arrayOpt, ObjectFormattingOptions options) - { - StringBuilder sb = new StringBuilder(); - - // print the inner-most element type first: - Type elementType = arrayType.GetElementType(); - while (elementType.IsArray) - { - elementType = elementType.GetElementType(); - } - - sb.Append(FormatTypeName(elementType, options)); - - // print all components of a jagged array: - Type type = arrayType; - do - { - if (arrayOpt != null) - { - sb.Append('['); - - int rank = type.GetArrayRank(); - - bool anyNonzeroLowerBound = false; - for (int i = 0; i < rank; i++) - { - if (arrayOpt.GetLowerBound(i) > 0) - { - anyNonzeroLowerBound = true; - break; - } - } - - for (int i = 0; i < rank; i++) - { - int lowerBound = arrayOpt.GetLowerBound(i); - int length = arrayOpt.GetLength(i); - - if (i > 0) - { - sb.Append(", "); - } - - if (anyNonzeroLowerBound) - { - AppendArrayBound(sb, lowerBound, options.UseHexadecimalNumbers); - sb.Append(".."); - AppendArrayBound(sb, length + lowerBound, options.UseHexadecimalNumbers); - } - else - { - AppendArrayBound(sb, length, options.UseHexadecimalNumbers); - } - } - - sb.Append(']'); - arrayOpt = null; - } - else - { - AppendArrayRank(sb, type); - } - - type = type.GetElementType(); - } - while (type.IsArray); - - return sb.ToString(); - } - - private void AppendArrayBound(StringBuilder sb, long bound, bool useHexadecimalNumbers) - { - if (bound <= int.MaxValue) - { - sb.Append(FormatLiteral((int)bound, useHexadecimalNumbers)); - } - else - { - sb.Append(FormatLiteral(bound, useHexadecimalNumbers)); - } - } - - private static void AppendArrayRank(StringBuilder sb, Type arrayType) + protected override bool IsHiddenMember(MemberInfo member) { - sb.Append('['); - int rank = arrayType.GetArrayRank(); - if (rank > 1) - { - sb.Append(',', rank - 1); - } - sb.Append(']'); + // Generated fields, e.g. "k__BackingField" + return GeneratedNames.IsGeneratedMemberName(member.Name); } - internal override string FormatMemberName(System.Reflection.MemberInfo member) + protected override string FormatRefKind(ParameterInfo parameter) { - return member.Name; + return parameter.IsOut + ? parameter.IsIn + ? "ref" + : "out" + : ""; } - internal override bool IsHiddenMember(System.Reflection.MemberInfo member) + protected override bool TryFormatCompositeObject(object obj, out string value, out bool suppressMembers) { - // Generated fields, e.g. "k__BackingField" - return GeneratedNames.IsGeneratedMemberName(member.Name); + value = null; + suppressMembers = false; + return false; } } } diff --git a/src/Scripting/CSharp/Hosting/ObjectFormatter/CSharpPrimitiveFormatter.cs b/src/Scripting/CSharp/Hosting/ObjectFormatter/CSharpPrimitiveFormatter.cs new file mode 100644 index 00000000000..8931c232b80 --- /dev/null +++ b/src/Scripting/CSharp/Hosting/ObjectFormatter/CSharpPrimitiveFormatter.cs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.CodeAnalysis.Scripting.Hosting; + +namespace Microsoft.CodeAnalysis.CSharp.Scripting.Hosting +{ + using static ObjectFormatterHelpers; + + public class CSharpPrimitiveFormatter : CommonPrimitiveFormatter + { + protected override string NullLiteral => ObjectDisplay.NullLiteral; + + protected override string FormatLiteral(bool value) + { + return ObjectDisplay.FormatLiteral(value); + } + + protected override string FormatLiteral(string value, bool quote, bool useHexadecimalNumbers = false) + { + var options = ObjectDisplayOptions.None; + if (quote) + { + options |= ObjectDisplayOptions.UseQuotes; + } + + if (useHexadecimalNumbers) + { + options |= ObjectDisplayOptions.UseHexadecimalNumbers; + } + + return ObjectDisplay.FormatLiteral(value, options); + } + + protected override string FormatLiteral(char c, bool quote, bool includeCodePoints = false, bool useHexadecimalNumbers = false) + { + var options = ObjectDisplayOptions.None; + if (quote) + { + options |= ObjectDisplayOptions.UseQuotes; + } + + if (includeCodePoints) + { + options |= ObjectDisplayOptions.IncludeCodePoints; + } + + if (useHexadecimalNumbers) + { + options |= ObjectDisplayOptions.UseHexadecimalNumbers; + } + + return ObjectDisplay.FormatLiteral(c, options); + } + + protected override string FormatLiteral(sbyte value, bool useHexadecimalNumbers = false) + { + return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers)); + } + + protected override string FormatLiteral(byte value, bool useHexadecimalNumbers = false) + { + return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers)); + } + + protected override string FormatLiteral(short value, bool useHexadecimalNumbers = false) + { + return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers)); + } + + protected override string FormatLiteral(ushort value, bool useHexadecimalNumbers = false) + { + return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers)); + } + + protected override string FormatLiteral(int value, bool useHexadecimalNumbers = false) + { + return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers)); + } + + protected override string FormatLiteral(uint value, bool useHexadecimalNumbers = false) + { + return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers)); + } + + protected override string FormatLiteral(long value, bool useHexadecimalNumbers = false) + { + return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers)); + } + + protected override string FormatLiteral(ulong value, bool useHexadecimalNumbers = false) + { + return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers)); + } + + protected override string FormatLiteral(double value) + { + return ObjectDisplay.FormatLiteral(value, ObjectDisplayOptions.None); + } + + protected override string FormatLiteral(float value) + { + return ObjectDisplay.FormatLiteral(value, ObjectDisplayOptions.None); + } + + protected override string FormatLiteral(decimal value) + { + return ObjectDisplay.FormatLiteral(value, ObjectDisplayOptions.None); + } + + protected override string FormatLiteral(DateTime value) + { + // DateTime is not primitive in C# + return null; + } + } +} diff --git a/src/Scripting/CSharp/Hosting/ObjectFormatter/CSharpTypeNameFormatter.cs b/src/Scripting/CSharp/Hosting/ObjectFormatter/CSharpTypeNameFormatter.cs new file mode 100644 index 00000000000..5f0e40f433c --- /dev/null +++ b/src/Scripting/CSharp/Hosting/ObjectFormatter/CSharpTypeNameFormatter.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.Scripting.Hosting; + +namespace Microsoft.CodeAnalysis.CSharp.Scripting.Hosting +{ + public class CSharpTypeNameFormatter : CommonTypeNameFormatter + { + protected override CommonPrimitiveFormatter PrimitiveFormatter { get; } + + public CSharpTypeNameFormatter(CommonPrimitiveFormatter primitiveFormatter) + { + PrimitiveFormatter = primitiveFormatter; + } + + protected override string GenericParameterOpening => "<"; + protected override string GenericParameterClosing => ">"; + protected override string ArrayOpening => "["; + protected override string ArrayClosing => "]"; + + protected override string GetPrimitiveTypeName(SpecialType type) + { + switch (type) + { + case SpecialType.System_Boolean: return "bool"; + case SpecialType.System_Byte: return "byte"; + case SpecialType.System_Char: return "char"; + case SpecialType.System_Decimal: return "decimal"; + case SpecialType.System_Double: return "double"; + case SpecialType.System_Int16: return "short"; + case SpecialType.System_Int32: return "int"; + case SpecialType.System_Int64: return "long"; + case SpecialType.System_SByte: return "sbyte"; + case SpecialType.System_Single: return "float"; + case SpecialType.System_String: return "string"; + case SpecialType.System_UInt16: return "ushort"; + case SpecialType.System_UInt32: return "uint"; + case SpecialType.System_UInt64: return "ulong"; + case SpecialType.System_Object: return "object"; + + default: + return null; + } + } + + public override string FormatTypeName(Type type, bool useHexadecimalArrayBounds) + { + string stateMachineName; + if (GeneratedNames.TryParseSourceMethodNameFromGeneratedName(type.Name, GeneratedNameKind.StateMachineType, out stateMachineName)) + { + return stateMachineName; + } + + return base.FormatTypeName(type, useHexadecimalArrayBounds); + } + } +} diff --git a/src/Scripting/CSharpTest.Desktop/ObjectFormatterTests.cs b/src/Scripting/CSharpTest.Desktop/ObjectFormatterTests.cs index 639121c7018..6cffc7602ac 100644 --- a/src/Scripting/CSharpTest.Desktop/ObjectFormatterTests.cs +++ b/src/Scripting/CSharpTest.Desktop/ObjectFormatterTests.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.Scripting.Hosting.UnitTests; using Xunit; using VB = Microsoft.CodeAnalysis.VisualBasic; +using Formatter = Microsoft.CodeAnalysis.Scripting.Hosting.UnitTests.TestCSharpObjectFormatter; namespace Microsoft.CodeAnalysis.CSharp.Scripting.Hosting.UnitTests { @@ -17,7 +18,7 @@ public class ObjectFormatterTests : ObjectFormatterTestBase public void DebuggerProxy_FrameworkTypes_ArrayList() { var obj = new ArrayList { 1, 2, true, "foo" }; - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("ArrayList(4) { 1, 2, true, \"foo\" }", str); } @@ -30,7 +31,7 @@ public void DebuggerProxy_FrameworkTypes_Hashtable() { new byte[] { 1, 2 }, new[] { 1,2,3 } }, }; - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_memberList); + var str = Formatter.SeparateLines.FormatObject(obj); AssertMembers(str, "Hashtable(1)", "{ byte[2] { 1, 2 }, int[3] { 1, 2, 3 } }" @@ -45,7 +46,7 @@ public void DebuggerProxy_FrameworkTypes_Queue() obj.Enqueue(2); obj.Enqueue(3); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("Queue(3) { 1, 2, 3 }", str); } @@ -57,7 +58,7 @@ public void DebuggerProxy_FrameworkTypes_Stack() obj.Push(2); obj.Push(3); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("Stack(3) { 3, 2, 1 }", str); } @@ -69,13 +70,13 @@ public void DebuggerProxy_FrameworkTypes_SortedList() obj.Add(1, 5); obj.Add(2, 6); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("SortedList(3) { { 1, 5 }, { 2, 6 }, { 3, 4 } }", str); obj = new SortedList(); obj.Add(new[] { 3 }, new int[] { 4 }); - str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("SortedList(1) { { int[1] { 3 }, int[1] { 4 } } }", str); } @@ -108,7 +109,7 @@ End Class var c = a.GetType("C"); var obj = Activator.CreateInstance(c); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_memberList); + var str = Formatter.SeparateLines.FormatObject(obj); AssertMembers(str, "C", "A: 0", "WE: null" diff --git a/src/Scripting/CSharpTest/CommandLineRunnerTests.cs b/src/Scripting/CSharpTest/CommandLineRunnerTests.cs index 3e5c3de25a1..8810f998189 100644 --- a/src/Scripting/CSharpTest/CommandLineRunnerTests.cs +++ b/src/Scripting/CSharpTest/CommandLineRunnerTests.cs @@ -45,7 +45,7 @@ public class CommandLineRunnerTests : TestBase args ?? DefaultArgs, new NotImplementedAnalyzerLoader()); - return new CommandLineRunner(io, compiler, CSharpScriptCompiler.Instance, CSharpObjectFormatter.Instance); + return new CommandLineRunner(io, compiler, CSharpScriptCompiler.Instance, new CSharpObjectFormatter()); } [Fact] @@ -182,8 +182,33 @@ > div(10, 2) > div(10, 0) «Red» {new System.DivideByZeroException().Message} -«DarkRed» - + Submission#0.div(Int32 a, Int32 b) + + Submission#0.div(int, int) +«Gray» +> ", runner.Console.Out.ToString()); + } + + [Fact] + public void ExceptionInGeneric() + { + var runner = CreateRunner(input: +@"static class C { public static int div(int a, int b) => a/b; } +C.div(10, 2) +C.div(10, 0) +"); + Assert.Equal(0, runner.RunInteractive()); + + Assert.Equal( +$@"Microsoft (R) Visual C# Interactive Compiler version {CompilerVersion} +Copyright (C) Microsoft Corporation. All rights reserved. + +Type ""#help"" for more information. +> static class C {{ public static int div(int a, int b) => a/b; }} +> C.div(10, 2) +5 +> C.div(10, 0) +«Red» +{new System.DivideByZeroException().Message} + + Submission#0.C.div(int, int) «Gray» > ", runner.Console.Out.ToString()); } diff --git a/src/Scripting/CSharpTest/ObjectFormatterTests.cs b/src/Scripting/CSharpTest/ObjectFormatterTests.cs index 12d5e1f883e..e8c5729626a 100644 --- a/src/Scripting/CSharpTest/ObjectFormatterTests.cs +++ b/src/Scripting/CSharpTest/ObjectFormatterTests.cs @@ -10,6 +10,7 @@ using ObjectFormatterFixtures; using Roslyn.Test.Utilities; using Xunit; +using Formatter = Microsoft.CodeAnalysis.Scripting.Hosting.UnitTests.TestCSharpObjectFormatter; namespace Microsoft.CodeAnalysis.CSharp.Scripting.Hosting.UnitTests { @@ -21,50 +22,49 @@ public void Objects() string str; object nested = new Outer.Nested(); - str = CSharpObjectFormatter.Instance.FormatObject(nested, s_inline); + str = Formatter.SingleLine.FormatObject(nested); Assert.Equal(@"Outer.Nested { A=1, B=2 }", str); - str = CSharpObjectFormatter.Instance.FormatObject(nested, new ObjectFormattingOptions(memberFormat: MemberDisplayFormat.NoMembers)); + str = Formatter.Hidden.FormatObject(nested); Assert.Equal(@"Outer.Nested", str); - str = CSharpObjectFormatter.Instance.FormatObject(A.X, new ObjectFormattingOptions(memberFormat: MemberDisplayFormat.NoMembers)); + str = Formatter.Hidden.FormatObject(A.X); Assert.Equal(@"A.B", str); object obj = new A.B.C.D.E(); - str = CSharpObjectFormatter.Instance.FormatObject(obj, new ObjectFormattingOptions(memberFormat: MemberDisplayFormat.NoMembers)); + str = Formatter.Hidden.FormatObject(obj); Assert.Equal(@"A.B.C.D.E", str); var sort = new Sort(); - str = CSharpObjectFormatter.Instance.FormatObject(sort, new ObjectFormattingOptions(maxLineLength: 51, memberFormat: MemberDisplayFormat.Inline)); + str = new Formatter(MemberDisplayFormat.SingleLine, lineLengthLimit: 51).FormatObject(sort); Assert.Equal(@"Sort { aB=-1, ab=1, Ac=-1, Ad=1, ad=-1, aE=1, a ...", str); Assert.Equal(51, str.Length); - str = CSharpObjectFormatter.Instance.FormatObject(sort, new ObjectFormattingOptions(maxLineLength: 5, memberFormat: MemberDisplayFormat.Inline)); + str = new Formatter(MemberDisplayFormat.SingleLine, lineLengthLimit: 5).FormatObject(sort); Assert.Equal(@"S ...", str); Assert.Equal(5, str.Length); - str = CSharpObjectFormatter.Instance.FormatObject(sort, new ObjectFormattingOptions(maxLineLength: 4, memberFormat: MemberDisplayFormat.Inline)); + str = new Formatter(MemberDisplayFormat.SingleLine, lineLengthLimit: 4).FormatObject(sort); Assert.Equal(@"...", str); - str = CSharpObjectFormatter.Instance.FormatObject(sort, new ObjectFormattingOptions(maxLineLength: 3, memberFormat: MemberDisplayFormat.Inline)); + str = new Formatter(MemberDisplayFormat.SingleLine, lineLengthLimit: 3).FormatObject(sort); Assert.Equal(@"...", str); - str = CSharpObjectFormatter.Instance.FormatObject(sort, new ObjectFormattingOptions(maxLineLength: 2, memberFormat: MemberDisplayFormat.Inline)); + str = new Formatter(MemberDisplayFormat.SingleLine, lineLengthLimit: 2).FormatObject(sort); Assert.Equal(@"...", str); - str = CSharpObjectFormatter.Instance.FormatObject(sort, new ObjectFormattingOptions(maxLineLength: 1, memberFormat: MemberDisplayFormat.Inline)); + str = new Formatter(MemberDisplayFormat.SingleLine, lineLengthLimit: 1).FormatObject(sort); Assert.Equal(@"...", str); - str = CSharpObjectFormatter.Instance.FormatObject(sort, new ObjectFormattingOptions(maxLineLength: 80, memberFormat: MemberDisplayFormat.Inline)); + str = new Formatter(MemberDisplayFormat.SingleLine, lineLengthLimit: 80).FormatObject(sort); Assert.Equal(@"Sort { aB=-1, ab=1, Ac=-1, Ad=1, ad=-1, aE=1, aF=-1, AG=1 }", str); } [Fact] - public void ArrayOtInt32_NoMembers() + public void ArrayOfInt32_NoMembers() { - CSharpObjectFormatter formatter = CSharpObjectFormatter.Instance; object o = new int[4] { 3, 4, 5, 6 }; - var str = formatter.FormatObject(o); + var str = Formatter.Hidden.FormatObject(o); Assert.Equal("int[4] { 3, 4, 5, 6 }", str); } @@ -76,7 +76,7 @@ public void RecursiveRootHidden() var DO_NOT_ADD_TO_WATCH_WINDOW = new RecursiveRootHidden(); DO_NOT_ADD_TO_WATCH_WINDOW.C = DO_NOT_ADD_TO_WATCH_WINDOW; - string str = CSharpObjectFormatter.Instance.FormatObject(DO_NOT_ADD_TO_WATCH_WINDOW, s_inline); + string str = Formatter.SingleLine.FormatObject(DO_NOT_ADD_TO_WATCH_WINDOW); Assert.Equal(@"RecursiveRootHidden { A=0, B=0 }", str); } @@ -113,12 +113,12 @@ public void DebuggerDisplay_ParseSimpleMemberName() private void Test_ParseSimpleMemberName(string value, string name, bool callable, bool nq) { bool actualNoQuotes, actualIsCallable; - string actualName = CSharpObjectFormatter.Formatter.ParseSimpleMemberName(value, 0, value.Length, out actualNoQuotes, out actualIsCallable); + string actualName = ObjectFormatterHelpers.ParseSimpleMemberName(value, 0, value.Length, out actualNoQuotes, out actualIsCallable); Assert.Equal(name, actualName); Assert.Equal(nq, actualNoQuotes); Assert.Equal(callable, actualIsCallable); - actualName = CSharpObjectFormatter.Formatter.ParseSimpleMemberName("---" + value + "-", 3, 3 + value.Length, out actualNoQuotes, out actualIsCallable); + actualName = ObjectFormatterHelpers.ParseSimpleMemberName("---" + value + "-", 3, 3 + value.Length, out actualNoQuotes, out actualIsCallable); Assert.Equal(name, actualName); Assert.Equal(nq, actualNoQuotes); Assert.Equal(callable, actualIsCallable); @@ -130,7 +130,7 @@ public void DebuggerDisplay() string str; var a = new ComplexProxy(); - str = CSharpObjectFormatter.Instance.FormatObject(a, s_memberList); + str = Formatter.SeparateLines.FormatObject(a); AssertMembers(str, @"[AStr]", @"_02_public_property_dd: *1", @@ -182,7 +182,7 @@ public void DebuggerDisplay() ); var b = new TypeWithComplexProxy(); - str = CSharpObjectFormatter.Instance.FormatObject(b, s_memberList); + str = Formatter.SeparateLines.FormatObject(b); AssertMembers(str, @"[BStr]", @"_02_public_property_dd: *1", @@ -231,7 +231,7 @@ public void DebuggerDisplay_Inherited() { var obj = new InheritedDebuggerDisplay(); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("InheritedDebuggerDisplay(DebuggerDisplayValue)", str); } @@ -240,10 +240,10 @@ public void DebuggerProxy_DebuggerDisplayAndProxy() { var obj = new TypeWithDebuggerDisplayAndProxy(); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("TypeWithDebuggerDisplayAndProxy(DD) { A=0, B=0 }", str); - str = CSharpObjectFormatter.Instance.FormatObject(obj, s_memberList); + str = Formatter.SeparateLines.FormatObject(obj); AssertMembers(str, "TypeWithDebuggerDisplayAndProxy(DD)", "A: 0", "B: 0" @@ -256,7 +256,7 @@ public void DebuggerProxy_Recursive() string str; object obj = new RecursiveProxy.Node(0); - str = CSharpObjectFormatter.Instance.FormatObject(obj, s_memberList); + str = Formatter.SeparateLines.FormatObject(obj); AssertMembers(str, "Node", "x: 0", @@ -264,7 +264,7 @@ public void DebuggerProxy_Recursive() ); obj = new InvalidRecursiveProxy.Node(); - str = CSharpObjectFormatter.Instance.FormatObject(obj, s_memberList); + str = Formatter.SeparateLines.FormatObject(obj); // TODO: better overflow handling Assert.Equal("!", str); @@ -286,7 +286,7 @@ public void Array_Recursive() n1.next = n2; n1.data = new object[] { 7, n2, 8, obj }; - str = CSharpObjectFormatter.Instance.FormatObject(obj, s_memberList); + str = Formatter.SeparateLines.FormatObject(obj); AssertMembers(str, "object[5]", "1", @@ -296,7 +296,7 @@ public void Array_Recursive() "3" ); - str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + str = Formatter.SingleLine.FormatObject(obj); Assert.Equal(str, "object[5] { 1, { ... }, ListNode { data={ ... }, next=ListNode { data=object[4] { 7, ListNode { ... }, 8, { ... } }, next=ListNode { ... } } }, object[5] { 4, 5, { ... }, 6, ListNode { data=null, next=null } }, 3 }"); } @@ -317,8 +317,7 @@ public void LargeGraph() for (int i = 100; i > 4; i--) { - var options = new ObjectFormattingOptions(maxOutputLength: i, memberFormat: MemberDisplayFormat.Inline); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, options); + var str = new Formatter(MemberDisplayFormat.SingleLine, totalLengthLimit: i).FormatObject(obj); var expected = output.Substring(0, i - " ...".Length); if (!expected.EndsWith(" ", StringComparison.Ordinal)) @@ -337,12 +336,10 @@ public void LongMembers() { object obj = new LongMembers(); - var options = new ObjectFormattingOptions(maxLineLength: 20, memberFormat: MemberDisplayFormat.Inline); - //str = ObjectFormatter.Instance.FormatObject(obj, options); - //Assert.Equal("LongMembers { Lo ...", str); + var str = new Formatter(MemberDisplayFormat.SingleLine, lineLengthLimit: 20).FormatObject(obj); + Assert.Equal("LongMembers { Lo ...", str); - options = new ObjectFormattingOptions(maxLineLength: 20, memberFormat: MemberDisplayFormat.List); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, options); + str = new Formatter(MemberDisplayFormat.SeparateLines, lineLengthLimit: 20).FormatObject(obj); Assert.Equal("LongMembers {\r\n LongName012345 ...\r\n LongValue: \"01 ...\r\n}\r\n", str); } @@ -350,7 +347,7 @@ public void LongMembers() public void DebuggerProxy_FrameworkTypes_Array() { var obj = new Object[] { new C(), 1, "str", 'c', true, null, new bool[] { true, false, true, false } }; - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_memberList); + var str = Formatter.SeparateLines.FormatObject(obj); AssertMembers(str, "object[7]", "[CStr]", @@ -382,10 +379,10 @@ public void DebuggerProxy_FrameworkTypes_MdArray() } }; - str = CSharpObjectFormatter.Instance.FormatObject(a, s_inline); + str = Formatter.SingleLine.FormatObject(a); Assert.Equal("int[2, 3, 4] { { { 0, 1, 2, 3 }, { 10, 11, 12, 13 }, { 20, 21, 22, 23 } }, { { 100, 101, 102, 103 }, { 110, 111, 112, 113 }, { 120, 121, 122, 123 } } }", str); - str = CSharpObjectFormatter.Instance.FormatObject(a, s_memberList); + str = Formatter.SeparateLines.FormatObject(a); AssertMembers(str, "int[2, 3, 4]", "{ { 0, 1, 2, 3 }, { 10, 11, 12, 13 }, { 20, 21, 22, 23 } }", "{ { 100, 101, 102, 103 }, { 110, 111, 112, 113 }, { 120, 121, 122, 123 } }" @@ -395,19 +392,19 @@ public void DebuggerProxy_FrameworkTypes_MdArray() obj[0] = new int[1, 2][,,,]; obj[0][0, 0] = new int[1, 2, 3, 4]; - str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("int[2][,][,,,] { int[1, 2][,,,] { { int[1, 2, 3, 4] { { { { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } }, { { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } } } }, null } }, null }", str); Array x = Array.CreateInstance(typeof(Object), lengths: new int[] { 2, 3 }, lowerBounds: new int[] { 2, 9 }); - str = CSharpObjectFormatter.Instance.FormatObject(x, s_inline); + str = Formatter.SingleLine.FormatObject(x); Assert.Equal("object[2..4, 9..12] { { null, null, null }, { null, null, null } }", str); Array y = Array.CreateInstance(typeof(Object), lengths: new int[] { 1, 1 }, lowerBounds: new int[] { 0, 0 }); - str = CSharpObjectFormatter.Instance.FormatObject(y, s_inline); + str = Formatter.SingleLine.FormatObject(y); Assert.Equal("object[1, 1] { { null } }", str); Array z = Array.CreateInstance(typeof(Object), lengths: new int[] { 0, 0 }, lowerBounds: new int[] { 0, 0 }); - str = CSharpObjectFormatter.Instance.FormatObject(z, s_inline); + str = Formatter.SingleLine.FormatObject(z); Assert.Equal("object[0, 0] { }", str); } @@ -418,7 +415,7 @@ public void DebuggerProxy_FrameworkTypes_IEnumerable() object obj; obj = Enumerable.Range(0, 10); - str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("RangeIterator { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }", str); } @@ -429,7 +426,7 @@ public void DebuggerProxy_FrameworkTypes_IEnumerable_Exception() object obj; obj = Enumerable.Range(0, 10).Where(i => { if (i == 5) throw new Exception("xxx"); return i < 7; }); - str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("Enumerable.WhereEnumerableIterator { 0, 1, 2, 3, 4, ! ... }", str); } @@ -440,10 +437,10 @@ public void DebuggerProxy_FrameworkTypes_IDictionary() object obj; obj = new ThrowingDictionary(throwAt: -1); - str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("ThrowingDictionary(10) { { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 } }", str); - str = CSharpObjectFormatter.Instance.FormatObject(obj, s_memberList); + str = Formatter.SeparateLines.FormatObject(obj); AssertMembers(str, "ThrowingDictionary(10)", "{ 1, 1 }", "{ 2, 2 }", @@ -459,7 +456,7 @@ public void DebuggerProxy_FrameworkTypes_IDictionary_Exception() object obj; obj = new ThrowingDictionary(throwAt: 3); - str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("ThrowingDictionary(10) { { 1, 1 }, { 2, 2 }, ! ... }", str); } @@ -468,7 +465,7 @@ public void DebuggerProxy_FrameworkTypes_BitArray() { // BitArray doesn't have debugger proxy/display var obj = new System.Collections.BitArray(new int[] { 1 }); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("BitArray(32) { true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false }", str); } @@ -480,7 +477,7 @@ public void DebuggerProxy_FrameworkTypes_Queue() obj.Enqueue(2); obj.Enqueue(3); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("Queue(3) { 1, 2, 3 }", str); } @@ -492,7 +489,7 @@ public void DebuggerProxy_FrameworkTypes_Stack() obj.Push(2); obj.Push(3); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("Stack(3) { 3, 2, 1 }", str); } @@ -504,13 +501,13 @@ public void DebuggerProxy_FrameworkTypes_Dictionary() { "x", 1 }, }; - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_memberList); + var str = Formatter.SeparateLines.FormatObject(obj); AssertMembers(str, "Dictionary(1)", "{ \"x\", 1 }" ); - str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("Dictionary(1) { { \"x\", 1 } }", str); } @@ -520,7 +517,7 @@ public void DebuggerProxy_FrameworkTypes_KeyValuePair() { var obj = new KeyValuePair(1, "x"); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("KeyValuePair { 1, \"x\" }", str); } @@ -529,7 +526,7 @@ public void DebuggerProxy_FrameworkTypes_List() { var obj = new List { 1, 2, 'c' }; - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("List(3) { 1, 2, 'c' }", str); } @@ -541,7 +538,7 @@ public void DebuggerProxy_FrameworkTypes_LinkedList() obj.AddLast(2); obj.AddLast(3); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("LinkedList(3) { 1, 2, 3 }", str); } @@ -553,13 +550,13 @@ public void DebuggerProxy_FrameworkTypes_SortedList() obj.Add(1, 5); obj.Add(2, 6); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("SortedList(3) { { 1, 5 }, { 2, 6 }, { 3, 4 } }", str); var obj2 = new SortedList(); obj2.Add(new[] { 3 }, new int[] { 4 }); - str = CSharpObjectFormatter.Instance.FormatObject(obj2, s_inline); + str = Formatter.SingleLine.FormatObject(obj2); Assert.Equal("SortedList(1) { { int[1] { 3 }, int[1] { 4 } } }", str); } @@ -571,8 +568,8 @@ public void DebuggerProxy_FrameworkTypes_SortedDictionary() obj.Add(3, 0x3c); obj.Add(2, 0x2b); - var str = CSharpObjectFormatter.Instance. - FormatObject(obj, new ObjectFormattingOptions(useHexadecimalNumbers: true, memberFormat: MemberDisplayFormat.Inline)); + var str = new Formatter(MemberDisplayFormat.SingleLine, useHexadecimalNumbers: true). + FormatObject(obj); Assert.Equal("SortedDictionary(3) { { 0x00000001, 0x0000001a }, { 0x00000002, 0x0000002b }, { 0x00000003, 0x0000003c } }", str); } @@ -586,7 +583,7 @@ public void DebuggerProxy_FrameworkTypes_HashSet() // HashSet doesn't implement ICollection (it only implements ICollection) so we don't call Count, // instead a DebuggerDisplay.Value is used. - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("HashSet(Count = 2) { 1, 2 }", str); } @@ -597,7 +594,7 @@ public void DebuggerProxy_FrameworkTypes_SortedSet() obj.Add(1); obj.Add(2); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("SortedSet(2) { 1, 2 }", str); } @@ -607,7 +604,7 @@ public void DebuggerProxy_FrameworkTypes_ConcurrentDictionary() var obj = new ConcurrentDictionary(); obj.AddOrUpdate("x", 1, (k, v) => v); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("ConcurrentDictionary(1) { { \"x\", 1 } }", str); } @@ -620,7 +617,7 @@ public void DebuggerProxy_FrameworkTypes_ConcurrentQueue() obj.Enqueue(2); obj.Enqueue(3); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("ConcurrentQueue(3) { 1, 2, 3 }", str); } @@ -632,7 +629,7 @@ public void DebuggerProxy_FrameworkTypes_ConcurrentStack() obj.Push(2); obj.Push(3); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("ConcurrentStack(3) { 3, 2, 1 }", str); } @@ -643,7 +640,7 @@ public void DebuggerProxy_FrameworkTypes_BlockingCollection() obj.Add(1); obj.Add(2, new CancellationToken()); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("BlockingCollection(2) { 1, 2 }", str); } @@ -663,7 +660,7 @@ public void DebuggerProxy_FrameworkTypes_ReadOnlyCollection() { var obj = new System.Collections.ObjectModel.ReadOnlyCollection(new[] { 1, 2, 3 }); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("ReadOnlyCollection(3) { 1, 2, 3 }", str); } @@ -674,10 +671,10 @@ public void DebuggerProxy_FrameworkTypes_Lazy() // Lazy has both DebuggerDisplay and DebuggerProxy attributes and both display the same information. - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("Lazy(ThreadSafetyMode=None, IsValueCreated=false, IsValueFaulted=false, Value=null) { IsValueCreated=false, IsValueFaulted=false, Mode=None, Value=null }", str); - str = CSharpObjectFormatter.Instance.FormatObject(obj, s_memberList); + str = Formatter.SeparateLines.FormatObject(obj); AssertMembers(str, "Lazy(ThreadSafetyMode=None, IsValueCreated=false, IsValueFaulted=false, Value=null)", "IsValueCreated: false", "IsValueFaulted: false", @@ -687,10 +684,10 @@ public void DebuggerProxy_FrameworkTypes_Lazy() Assert.NotNull(obj.Value); - str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("Lazy(ThreadSafetyMode=None, IsValueCreated=true, IsValueFaulted=false, Value=int[2] { 1, 2 }) { IsValueCreated=true, IsValueFaulted=false, Mode=None, Value=int[2] { 1, 2 } }", str); - str = CSharpObjectFormatter.Instance.FormatObject(obj, s_memberList); + str = Formatter.SeparateLines.FormatObject(obj); AssertMembers(str, "Lazy(ThreadSafetyMode=None, IsValueCreated=true, IsValueFaulted=false, Value=int[2] { 1, 2 })", "IsValueCreated: true", "IsValueFaulted: false", @@ -708,12 +705,12 @@ public void DebuggerProxy_FrameworkTypes_Task() { var obj = new System.Threading.Tasks.Task(TaskMethod); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal( @"Task(Id = *, Status = Created, Method = ""Void TaskMethod()"") { AsyncState=null, CancellationPending=false, CreationOptions=None, Exception=null, Id=*, Status=Created }", FilterDisplayString(str)); - str = CSharpObjectFormatter.Instance.FormatObject(obj, s_memberList); + str = Formatter.SeparateLines.FormatObject(obj); AssertMembers(FilterDisplayString(str), @"Task(Id = *, Status = Created, Method = ""Void TaskMethod()"")", "AsyncState: null", "CancellationPending: false", @@ -729,10 +726,10 @@ public void DebuggerProxy_FrameworkTypes_SpinLock() { var obj = new SpinLock(); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("SpinLock(IsHeld = false) { IsHeld=false, IsHeldByCurrentThread=false, OwnerThreadID=0 }", str); - str = CSharpObjectFormatter.Instance.FormatObject(obj, s_memberList); + str = Formatter.SeparateLines.FormatObject(obj); AssertMembers(str, "SpinLock(IsHeld = false)", "IsHeld: false", "IsHeldByCurrentThread: false", @@ -749,10 +746,10 @@ public void DebuggerProxy_DiagnosticBag() using (new EnsureEnglishUICulture()) { - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("DiagnosticBag(Count = 2) { =error CS0180: 'bar' cannot be both extern and abstract, =error CS1679: Invalid extern alias for '/reference'; 'foo' is not a valid identifier }", str); - str = CSharpObjectFormatter.Instance.FormatObject(obj, s_memberList); + str = Formatter.SeparateLines.FormatObject(obj); AssertMembers(str, "DiagnosticBag(Count = 2)", ": error CS0180: 'bar' cannot be both extern and abstract", ": error CS1679: Invalid extern alias for '/reference'; 'foo' is not a valid identifier" @@ -766,10 +763,10 @@ public void DebuggerProxy_ArrayBuilder() var obj = new ArrayBuilder(); obj.AddRange(new[] { 1, 2, 3, 4, 5 }); - var str = CSharpObjectFormatter.Instance.FormatObject(obj, s_inline); + var str = Formatter.SingleLine.FormatObject(obj); Assert.Equal("ArrayBuilder(Count = 5) { 1, 2, 3, 4, 5 }", str); - str = CSharpObjectFormatter.Instance.FormatObject(obj, s_memberList); + str = Formatter.SeparateLines.FormatObject(obj); AssertMembers(str, "ArrayBuilder(Count = 5)", "1", "2", diff --git a/src/Scripting/Core/Hosting/CommandLine/CommandLineRunner.cs b/src/Scripting/Core/Hosting/CommandLine/CommandLineRunner.cs index a767e5dc4e9..7ccf1d6233a 100644 --- a/src/Scripting/Core/Hosting/CommandLine/CommandLineRunner.cs +++ b/src/Scripting/Core/Hosting/CommandLine/CommandLineRunner.cs @@ -9,8 +9,6 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; using System.Text; using System.Threading; using Microsoft.CodeAnalysis.Text; @@ -326,44 +324,7 @@ private void DisplayException(Exception e) try { _console.ForegroundColor = ConsoleColor.Red; - _console.Out.WriteLine(e.Message); - - _console.ForegroundColor = ConsoleColor.DarkRed; - - var trace = new StackTrace(e, needFileInfo: true); - foreach (var frame in trace.GetFrames()) - { - if (!frame.HasMethod()) - { - continue; - } - - var method = frame.GetMethod(); - var type = method.DeclaringType; - - if (type == typeof(CommandLineRunner)) - { - break; - } - - string methodDisplay = _objectFormatter.FormatMethodSignature(method); - - // TODO: we don't want to include awaiter helpers, shouldn't they be marked by DebuggerHidden in FX? - if (methodDisplay == null || IsTaskAwaiter(type) || IsTaskAwaiter(type.DeclaringType)) - { - continue; - } - - _console.Out.Write(" + "); - _console.Out.Write(methodDisplay); - - if (frame.HasSource()) - { - _console.Out.Write(string.Format(CultureInfo.CurrentUICulture, ScriptingResources.AtFileLine, frame.GetFileName(), frame.GetFileLineNumber())); - } - - _console.Out.WriteLine(); - } + _console.Out.Write(_objectFormatter.FormatRaisedException(e)); } finally { @@ -371,22 +332,6 @@ private void DisplayException(Exception e) } } - private static bool IsTaskAwaiter(Type type) - { - if (type == typeof(TaskAwaiter) || type == typeof(ConfiguredTaskAwaitable)) - { - return true; - } - - if (type?.GetTypeInfo().IsGenericType == true) - { - var genericDef = type.GetTypeInfo().GetGenericTypeDefinition(); - return genericDef == typeof(TaskAwaiter<>) || type == typeof(ConfiguredTaskAwaitable<>); - } - - return false; - } - private static bool IsHelpCommand(string text) { const string helpCommand = "#help"; diff --git a/src/Scripting/Core/Hosting/CommandLine/CommandLineScriptGlobals.cs b/src/Scripting/Core/Hosting/CommandLine/CommandLineScriptGlobals.cs index 7c9f0f2cf8a..f5fa9c05907 100644 --- a/src/Scripting/Core/Hosting/CommandLine/CommandLineScriptGlobals.cs +++ b/src/Scripting/Core/Hosting/CommandLine/CommandLineScriptGlobals.cs @@ -18,23 +18,22 @@ namespace Microsoft.CodeAnalysis.Scripting.Hosting public class CommandLineScriptGlobals { private readonly TextWriter _outputWriter; - private readonly ObjectFormatter _objectFormatter; /// /// Arguments given to the script. /// public IList Args { get; } + public ObjectFormatter ObjectFormatter { get; } + /// /// Pretty-prints an object. /// public void Print(object value) { - _outputWriter.WriteLine(_objectFormatter.FormatObject(value, PrintOptions)); + _outputWriter.WriteLine(ObjectFormatter.FormatObject(value)); } - internal ObjectFormattingOptions PrintOptions { get; } - public CommandLineScriptGlobals(TextWriter outputWriter, ObjectFormatter objectFormatter) { if (outputWriter == null) @@ -47,10 +46,8 @@ public CommandLineScriptGlobals(TextWriter outputWriter, ObjectFormatter objectF throw new ArgumentNullException(nameof(objectFormatter)); } - PrintOptions = ObjectFormattingOptions.Default; - _outputWriter = outputWriter; - _objectFormatter = objectFormatter; + ObjectFormatter = objectFormatter; Args = new List(); } diff --git a/src/Scripting/Core/Hosting/InteractiveScriptGlobals.cs b/src/Scripting/Core/Hosting/InteractiveScriptGlobals.cs index a6f02277808..a772ccf4829 100644 --- a/src/Scripting/Core/Hosting/InteractiveScriptGlobals.cs +++ b/src/Scripting/Core/Hosting/InteractiveScriptGlobals.cs @@ -20,7 +20,6 @@ namespace Microsoft.CodeAnalysis.Scripting.Hosting public class InteractiveScriptGlobals { private readonly TextWriter _outputWriter; - private readonly ObjectFormatter _objectFormatter; /// /// Arguments given to the script. @@ -32,13 +31,13 @@ public class InteractiveScriptGlobals /// public void Print(object value) { - _outputWriter.WriteLine(_objectFormatter.FormatObject(value, PrintOptions)); + _outputWriter.WriteLine(ObjectFormatter.FormatObject(value)); } public IList ReferencePaths { get; } public IList SourcePaths { get; } - internal ObjectFormattingOptions PrintOptions { get; set; } + public ObjectFormatter ObjectFormatter { get; set; } public InteractiveScriptGlobals(TextWriter outputWriter, ObjectFormatter objectFormatter) { @@ -59,9 +58,8 @@ public InteractiveScriptGlobals(TextWriter outputWriter, ObjectFormatter objectF SourcePaths = new SearchPaths(); Args = new List(); - PrintOptions = ObjectFormattingOptions.Default; + ObjectFormatter = objectFormatter; _outputWriter = outputWriter; - _objectFormatter = objectFormatter; } } } diff --git a/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Builder.cs b/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Builder.cs new file mode 100644 index 00000000000..0191f1555be --- /dev/null +++ b/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Builder.cs @@ -0,0 +1,177 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Text; + +namespace Microsoft.CodeAnalysis.Scripting.Hosting +{ + /// + /// Object pretty printer. + /// + public abstract partial class CommonObjectFormatter + { + private sealed class Builder + { + private readonly StringBuilder _sb; + + private readonly bool _insertEllipsis; + + private readonly BuilderOptions _options; + + private int _currentLimit; + + public Builder(BuilderOptions options, bool insertEllipsis) + { + _sb = new StringBuilder(); + _insertEllipsis = insertEllipsis; + _options = insertEllipsis ? options.SubtractEllipsisLength() : options; + + _currentLimit = Math.Min(_options.LineLengthLimit, _options.TotalLengthLimit); + } + + public bool LimitReached + { + get { return _sb.Length == _options.TotalLengthLimit; } + } + + public int Remaining + { + get { return _options.TotalLengthLimit - _sb.Length; } + } + + // can be negative (the min value is -Ellipsis.Length - 1) + private int CurrentRemaining + { + get { return _currentLimit - _sb.Length; } + } + + public void AppendLine() + { + // remove line length limit so that we can insert a new line even + // if the previous one hit maxed out the line limit: + _currentLimit = _options.TotalLengthLimit; + + Append(_options.NewLine); + + // recalc limit for the next line: + _currentLimit = (int)Math.Min((long)_sb.Length + _options.LineLengthLimit, _options.TotalLengthLimit); + } + + private void AppendEllipsis() + { + if (_sb.Length > 0 && _sb[_sb.Length - 1] != ' ') + { + _sb.Append(' '); + } + + _sb.Append(_options.Ellipsis); + } + + public void Append(char c, int count = 1) + { + if (CurrentRemaining < 0) + { + return; + } + + int length = Math.Min(count, CurrentRemaining); + + _sb.Append(c, length); + + if (_insertEllipsis && length < count) + { + AppendEllipsis(); + } + } + + public void Append(string str, int start = 0, int count = Int32.MaxValue) + { + if (str == null || CurrentRemaining < 0) + { + return; + } + + count = Math.Min(count, str.Length - start); + int length = Math.Min(count, CurrentRemaining); + _sb.Append(str, start, length); + + if (_insertEllipsis && length < count) + { + AppendEllipsis(); + } + } + + public void AppendFormat(string format, params object[] args) + { + Append(string.Format(format, args)); + } + + public void AppendGroupOpening() + { + Append('{'); + } + + public void AppendGroupClosing(bool inline) + { + if (inline) + { + Append(" }"); + } + else + { + AppendLine(); + Append('}'); + AppendLine(); + } + } + + public void AppendCollectionItemSeparator(bool isFirst, bool inline) + { + if (isFirst) + { + if (inline) + { + Append(' '); + } + else + { + AppendLine(); + } + } + else + { + if (inline) + { + Append(", "); + } + else + { + Append(','); + AppendLine(); + } + } + + if (!inline) + { + Append(_options.Indentation); + } + } + + /// + /// This is for conveying cyclic dependencies to the user, not for detecting them. + /// + internal void AppendInfiniteRecursionMarker() + { + AppendGroupOpening(); + AppendCollectionItemSeparator(isFirst: true, inline: true); + Append(_options.Ellipsis); + AppendGroupClosing(inline: true); + } + + public override string ToString() + { + return _sb.ToString(); + } + } + } +} \ No newline at end of file diff --git a/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.BuilderOptions.cs b/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.BuilderOptions.cs new file mode 100644 index 00000000000..9bfc8f7a48d --- /dev/null +++ b/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.BuilderOptions.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.CodeAnalysis.Scripting.Hosting +{ + /// + /// Object pretty printer. + /// + public abstract partial class CommonObjectFormatter + { + /// + /// Internal for testing. + /// + internal struct BuilderOptions + { + public readonly string Indentation; + public readonly string NewLine; + public readonly string Ellipsis; + + public readonly int LineLengthLimit; + public readonly int TotalLengthLimit; + + public BuilderOptions(string indentation, string newLine, string ellipsis, int lineLengthLimit, int totalLengthLimit) + { + Indentation = indentation; + NewLine = newLine; + Ellipsis = ellipsis; + LineLengthLimit = lineLengthLimit; + TotalLengthLimit = totalLengthLimit; + } + + public BuilderOptions WithTotalLengthLimit(int totalLengthLimit) + { + return new BuilderOptions( + Indentation, + NewLine, + Ellipsis, + LineLengthLimit, + totalLengthLimit); + } + + public BuilderOptions SubtractEllipsisLength() + { + return new BuilderOptions( + Indentation, + NewLine, + Ellipsis, + Math.Max(0, LineLengthLimit - Ellipsis.Length - 1), + Math.Max(0, TotalLengthLimit - Ellipsis.Length - 1)); + } + } + } +} \ No newline at end of file diff --git a/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.FormattedMember.cs b/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.FormattedMember.cs new file mode 100644 index 00000000000..a8a635be811 --- /dev/null +++ b/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.FormattedMember.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics; + +namespace Microsoft.CodeAnalysis.Scripting.Hosting +{ + public abstract partial class CommonObjectFormatter + { + private sealed partial class Visitor + { + private struct FormattedMember + { + // Non-negative if the member is an inlined element of an array (DebuggerBrowsableState.RootHidden applied on a member of array type). + public readonly int Index; + + // Formatted name of the member or null if it doesn't have a name (Index is >=0 then). + public readonly string Name; + + // Formatted value of the member. + public readonly string Value; + + public FormattedMember(int index, string name, string value) + { + Debug.Assert((name != null) || (index >= 0)); + Name = name; + Index = index; + Value = value; + } + + /// + /// Doesn't (and doesn't need to) reflect the number of digits in since + /// it's only used for a conservative approximation (shorter is more conservative when trying + /// to determine the minimum number of members that will fill the output). + /// + public int MinimalLength + { + get { return (Name != null ? Name.Length : "[0]".Length) + Value.Length; } + } + + public string GetDisplayName() + { + return Name ?? "[" + Index.ToString() + "]"; + } + + public bool HasKeyName() + { + return Index >= 0 && Name != null && Name.Length >= 2 && Name[0] == '[' && Name[Name.Length - 1] == ']'; + } + + public bool AppendAsCollectionEntry(Builder result) + { + // Some BCL collections use [{key.ToString()}]: {value.ToString()} pattern to display collection entries. + // We want them to be printed initializer-style, i.e. { , } + if (HasKeyName()) + { + result.AppendGroupOpening(); + result.AppendCollectionItemSeparator(isFirst: true, inline: true); + result.Append(Name, 1, Name.Length - 2); + result.AppendCollectionItemSeparator(isFirst: false, inline: true); + result.Append(Value); + result.AppendGroupClosing(inline: true); + } + else + { + result.Append(Value); + } + + return true; + } + + public bool Append(Builder result, string separator) + { + result.Append(GetDisplayName()); + result.Append(separator); + result.Append(Value); + return true; + } + } + } + } +} \ No newline at end of file diff --git a/src/Scripting/Core/Hosting/ObjectFormatter/ObjectFormatter.Formatter.cs b/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.cs similarity index 71% rename from src/Scripting/Core/Hosting/ObjectFormatter/ObjectFormatter.Formatter.cs rename to src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.cs index f36023ca87b..91b3c451891 100644 --- a/src/Scripting/Core/Hosting/ObjectFormatter/ObjectFormatter.Formatter.cs +++ b/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.Visitor.cs @@ -5,21 +5,25 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Reflection; using System.Runtime.CompilerServices; using Roslyn.Utilities; -using System.Reflection; namespace Microsoft.CodeAnalysis.Scripting.Hosting { + using static ObjectFormatterHelpers; using TypeInfo = System.Reflection.TypeInfo; - public abstract partial class ObjectFormatter + public abstract partial class CommonObjectFormatter { - // internal for testing - internal sealed class Formatter + private sealed partial class Visitor { - private readonly ObjectFormatter _language; - private readonly ObjectFormattingOptions _options; + private readonly CommonObjectFormatter _formatter; + + private readonly BuilderOptions _builderOptions; + private CommonPrimitiveFormatter.Options _primitiveOptions; + private MemberDisplayFormat _memberDisplayFormat; + private HashSet _lazyVisitedObjects; private HashSet VisitedObjects @@ -35,24 +39,30 @@ private HashSet VisitedObjects } } - public Formatter(ObjectFormatter language, ObjectFormattingOptions options) + public Visitor( + CommonObjectFormatter formatter, + BuilderOptions builderOptions, + CommonPrimitiveFormatter.Options primitiveOptions, + MemberDisplayFormat memberDisplayFormat) { - _options = options ?? ObjectFormattingOptions.Default; - _language = language; + _formatter = formatter; + _builderOptions = builderOptions; + _primitiveOptions = primitiveOptions; + _memberDisplayFormat = memberDisplayFormat; } private Builder MakeMemberBuilder(int limit) { - return new Builder(Math.Min(_options.MaxLineLength, limit), _options, insertEllipsis: false); + return new Builder(_builderOptions.WithTotalLengthLimit(Math.Min(_builderOptions.LineLengthLimit, limit)), insertEllipsis: false); } public string FormatObject(object obj) { try { - var builder = new Builder(_options.MaxOutputLength, _options, insertEllipsis: true); + var builder = new Builder(_builderOptions, insertEllipsis: true); string _; - return FormatObjectRecursive(builder, obj, _options.QuoteStrings, _options.MemberFormat, out _).ToString(); + return FormatObjectRecursive(builder, obj, isRoot: true, debuggerDisplayName: out _).ToString(); } catch (InsufficientExecutionStackException) { @@ -60,18 +70,45 @@ public string FormatObject(object obj) } } - private Builder FormatObjectRecursive(Builder result, object obj, bool quoteStrings, MemberDisplayFormat memberFormat, out string name) + private Builder FormatObjectRecursive(Builder result, object obj, bool isRoot, out string debuggerDisplayName) { - name = null; - string primitive = _language.FormatPrimitive(obj, quoteStrings, _options.IncludeCodePoints, _options.UseHexadecimalNumbers); + // TODO (https://github.com/dotnet/roslyn/issues/6689): remove this + if (!isRoot && _memberDisplayFormat == MemberDisplayFormat.SeparateLines) + { + _memberDisplayFormat = MemberDisplayFormat.SingleLine; + } + + debuggerDisplayName = null; + string primitive = _formatter.PrimitiveFormatter.FormatPrimitive(obj, _primitiveOptions); if (primitive != null) { result.Append(primitive); return result; } - object originalObj = obj; - TypeInfo originalType = originalObj.GetType().GetTypeInfo(); + MemberDisplayFormat memberFormat = _memberDisplayFormat; + + bool includeNonPublic = memberFormat == MemberDisplayFormat.SeparateLines; + bool inlineMembers = memberFormat == MemberDisplayFormat.SingleLine; + + // Note that we call this after calling the PrimitiveFormatter. If you want to override primitive + // formatting, you should do so using that mechanism. + string compositeValue; + bool suppressCompositeValueMembers; + if (_formatter.TryFormatCompositeObject(obj, out compositeValue, out suppressCompositeValueMembers)) + { + result.Append(compositeValue); + + if (!suppressCompositeValueMembers && memberFormat != MemberDisplayFormat.Hidden) + { + FormatMembers(result, obj, proxy: null, includeNonPublic: includeNonPublic, inlineMembers: inlineMembers); + } + + return result; + } + + Type type = obj.GetType(); + TypeInfo typeInfo = type.GetTypeInfo(); // // Override KeyValuePair<,>.ToString() to get better dictionary elements formatting: @@ -83,139 +120,149 @@ private Builder FormatObjectRecursive(Builder result, object obj, bool quoteStri // This is more general than overriding Dictionary<,> debugger proxy attribute since it applies on all // types that return an array of KeyValuePair in their DebuggerDisplay to display items. // - if (originalType.IsGenericType && originalType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) + if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) { - if (memberFormat != MemberDisplayFormat.InlineValue) + if (isRoot) { - result.Append(_language.FormatTypeName(originalType.AsType(), _options)); + result.Append(_formatter.TypeNameFormatter.FormatTypeName(type, _primitiveOptions.UseHexadecimalNumbers)); result.Append(' '); } - FormatKeyValuePair(result, originalObj); + FormatKeyValuePair(result, obj); return result; } - if (originalType.IsArray) + if (typeInfo.IsArray) { - if (!VisitedObjects.Add(originalObj)) + if (VisitedObjects.Add(obj)) + { + FormatArray(result, (Array)obj, inline: memberFormat != MemberDisplayFormat.SeparateLines); + + VisitedObjects.Remove(obj); + } + else { result.AppendInfiniteRecursionMarker(); - return result; } - FormatArray(result, (Array)originalObj, inline: memberFormat != MemberDisplayFormat.List); - - VisitedObjects.Remove(originalObj); return result; } - DebuggerDisplayAttribute debuggerDisplay = GetApplicableDebuggerDisplayAttribute(originalType); + DebuggerDisplayAttribute debuggerDisplay = GetApplicableDebuggerDisplayAttribute(typeInfo); if (debuggerDisplay != null) { - name = debuggerDisplay.Name; + debuggerDisplayName = debuggerDisplay.Name; } - bool suppressMembers = false; + // Suppresses members if inlineMembers is true, + // does nothing otherwise. + bool suppressInlineMembers = false; // // TypeName(count) for ICollection implementers // or // TypeName([[DebuggerDisplay.Value]]) // Inline - // [[DebuggerDisplay.Value]] // InlineValue + // [[DebuggerDisplay.Value]] // Inline && !isRoot // or // [[ToString()]] if ToString overridden // or // TypeName // ICollection collection; - if ((collection = originalObj as ICollection) != null) + if ((collection = obj as ICollection) != null) { FormatCollectionHeader(result, collection); } - else if (debuggerDisplay != null && !String.IsNullOrEmpty(debuggerDisplay.Value)) + else if (debuggerDisplay != null && !string.IsNullOrEmpty(debuggerDisplay.Value)) { - if (memberFormat != MemberDisplayFormat.InlineValue) + if (isRoot) { - result.Append(_language.FormatTypeName(originalType.AsType(), _options)); + result.Append(_formatter.TypeNameFormatter.FormatTypeName(type, _primitiveOptions.UseHexadecimalNumbers)); result.Append('('); } - FormatWithEmbeddedExpressions(result, debuggerDisplay.Value, originalObj); + FormatWithEmbeddedExpressions(result, debuggerDisplay.Value, obj); - if (memberFormat != MemberDisplayFormat.InlineValue) + if (isRoot) { result.Append(')'); } - suppressMembers = true; + suppressInlineMembers = true; } - else if (HasOverriddenToString(originalType)) + else if (HasOverriddenToString(typeInfo)) { - ObjectToString(result, originalObj); - suppressMembers = true; + ObjectToString(result, obj); + suppressInlineMembers = true; } else { - result.Append(_language.FormatTypeName(originalType.AsType(), _options)); + result.Append(_formatter.TypeNameFormatter.FormatTypeName(type, _primitiveOptions.UseHexadecimalNumbers)); } - if (memberFormat == MemberDisplayFormat.NoMembers) + if (memberFormat == MemberDisplayFormat.Hidden) { return result; } - bool includeNonPublic = memberFormat == MemberDisplayFormat.List; object proxy = GetDebuggerTypeProxy(obj); if (proxy != null) { - obj = proxy; includeNonPublic = false; - suppressMembers = false; + suppressInlineMembers = false; } - if (memberFormat != MemberDisplayFormat.List && suppressMembers) + if (!suppressInlineMembers || !inlineMembers) { - return result; + FormatMembers(result, obj, proxy, includeNonPublic, inlineMembers); } + return result; + } + + #region Members + + private void FormatMembers(Builder result, object obj, object proxy, bool includeNonPublic, bool inlineMembers) + { // TODO (tomat): we should not use recursion RuntimeHelpers.EnsureSufficientExecutionStack(); result.Append(' '); - if (!VisitedObjects.Add(originalObj)) + // Note: Even if we've seen it before, we show a header + if (!VisitedObjects.Add(obj)) { result.AppendInfiniteRecursionMarker(); - return result; + return; } + bool membersFormatted = false; + // handle special types only if a proxy isn't defined if (proxy == null) { IDictionary dictionary; + IEnumerable enumerable; if ((dictionary = obj as IDictionary) != null) { - FormatDictionary(result, dictionary, inline: memberFormat != MemberDisplayFormat.List); - return result; + FormatDictionaryMembers(result, dictionary, inlineMembers); + membersFormatted = true; } - - IEnumerable enumerable; - if ((enumerable = obj as IEnumerable) != null) + else if ((enumerable = obj as IEnumerable) != null) { - FormatSequence(result, enumerable, inline: memberFormat != MemberDisplayFormat.List); - return result; + FormatSequenceMembers(result, enumerable, inlineMembers); + membersFormatted = true; } } - FormatObjectMembers(result, obj, originalType, includeNonPublic, inline: memberFormat != MemberDisplayFormat.List); + if (!membersFormatted) + { + FormatObjectMembers(result, proxy ?? obj, obj.GetType().GetTypeInfo(), includeNonPublic, inlineMembers); + } VisitedObjects.Remove(obj); - - return result; } - #region Members - /// /// Formats object members to a list. /// @@ -233,7 +280,7 @@ private Builder FormatObjectRecursive(Builder result, object obj, bool quoteStri /// } /// /// - private void FormatObjectMembers(Builder result, object obj, TypeInfo originalType, bool includeNonPublic, bool inline) + private void FormatObjectMembers(Builder result, object obj, TypeInfo preProxyTypeInfo, bool includeNonPublic, bool inline) { int lengthLimit = result.Remaining; if (lengthLimit < 0) @@ -246,7 +293,7 @@ private void FormatObjectMembers(Builder result, object obj, TypeInfo originalTy // Limits the number of members added into the result. Some more members may be added than it will fit into the result // and will be thrown away later but not many more. FormatObjectMembersRecursive(members, obj, includeNonPublic, ref lengthLimit); - bool useCollectionFormat = UseCollectionFormat(members, originalType); + bool useCollectionFormat = UseCollectionFormat(members, preProxyTypeInfo); result.AppendGroupOpening(); @@ -276,69 +323,6 @@ private static bool UseCollectionFormat(IEnumerable members, Ty return typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(originalType) && members.All(member => member.Index >= 0); } - private struct FormattedMember - { - // Non-negative if the member is an inlined element of an array (DebuggerBrowsableState.RootHidden applied on a member of array type). - public readonly int Index; - - // Formatted name of the member or null if it doesn't have a name (Index is >=0 then). - public readonly string Name; - - // Formatted value of the member. - public readonly string Value; - - public FormattedMember(int index, string name, string value) - { - Name = name; - Index = index; - Value = value; - } - - public int MinimalLength - { - get { return (Name != null ? Name.Length : "[0]".Length) + Value.Length; } - } - - public string GetDisplayName() - { - return Name ?? "[" + Index.ToString() + "]"; - } - - public bool HasKeyName() - { - return Index >= 0 && Name != null && Name.Length >= 2 && Name[0] == '[' && Name[Name.Length - 1] == ']'; - } - - public bool AppendAsCollectionEntry(Builder result) - { - // Some BCL collections use [{key.ToString()}]: {value.ToString()} pattern to display collection entries. - // We want them to be printed initializer-style, i.e. { , } - if (HasKeyName()) - { - result.AppendGroupOpening(); - result.AppendCollectionItemSeparator(isFirst: true, inline: true); - result.Append(Name, 1, Name.Length - 2); - result.AppendCollectionItemSeparator(isFirst: false, inline: true); - result.Append(Value); - result.AppendGroupClosing(inline: true); - } - else - { - result.Append(Value); - } - - return true; - } - - public bool Append(Builder result, string separator) - { - result.Append(GetDisplayName()); - result.Append(separator); - result.Append(Value); - return true; - } - } - /// /// Enumerates sorted object members to display. /// @@ -372,7 +356,7 @@ private void FormatObjectMembersRecursive(List result, object o foreach (var member in members) { - if (_language.IsHiddenMember(member)) + if (_formatter.IsHiddenMember(member)) { continue; } @@ -427,7 +411,7 @@ private void FormatObjectMembersRecursive(List result, object o var debuggerDisplay = GetApplicableDebuggerDisplayAttribute(member); if (debuggerDisplay != null) { - string k = FormatWithEmbeddedExpressions(lengthLimit, debuggerDisplay.Name, obj) ?? _language.FormatMemberName(member); + string k = FormatWithEmbeddedExpressions(lengthLimit, debuggerDisplay.Name, obj) ?? member.Name; string v = FormatWithEmbeddedExpressions(lengthLimit, debuggerDisplay.Value, obj) ?? string.Empty; // TODO: ? if (!AddMember(result, new FormattedMember(-1, k, v), ref lengthLimit)) { @@ -443,7 +427,7 @@ private void FormatObjectMembersRecursive(List result, object o { var memberValueBuilder = MakeMemberBuilder(lengthLimit); FormatException(memberValueBuilder, exception); - if (!AddMember(result, new FormattedMember(-1, _language.FormatMemberName(member), memberValueBuilder.ToString()), ref lengthLimit)) + if (!AddMember(result, new FormattedMember(-1, member.Name, memberValueBuilder.ToString()), ref lengthLimit)) { return; } @@ -463,7 +447,7 @@ private void FormatObjectMembersRecursive(List result, object o { string name; Builder valueBuilder = MakeMemberBuilder(lengthLimit); - FormatObjectRecursive(valueBuilder, item, _options.QuoteStrings, MemberDisplayFormat.InlineValue, out name); + FormatObjectRecursive(valueBuilder, item, isRoot: false, debuggerDisplayName: out name); if (!string.IsNullOrEmpty(name)) { @@ -478,7 +462,7 @@ private void FormatObjectMembersRecursive(List result, object o i++; } } - else if (_language.FormatPrimitive(value, _options.QuoteStrings, _options.IncludeCodePoints, _options.UseHexadecimalNumbers) == null && VisitedObjects.Add(value)) + else if (_formatter.PrimitiveFormatter.FormatPrimitive(value, _primitiveOptions) == null && VisitedObjects.Add(value)) { FormatObjectMembersRecursive(result, value, includeNonPublic, ref lengthLimit); VisitedObjects.Remove(value); @@ -489,11 +473,11 @@ private void FormatObjectMembersRecursive(List result, object o { string name; Builder valueBuilder = MakeMemberBuilder(lengthLimit); - FormatObjectRecursive(valueBuilder, value, _options.QuoteStrings, MemberDisplayFormat.InlineValue, out name); + FormatObjectRecursive(valueBuilder, value, isRoot: false, debuggerDisplayName: out name); - if (String.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) { - name = _language.FormatMemberName(member); + name = member.Name; } else { @@ -534,7 +518,7 @@ private bool AddMember(List members, FormattedMember member, re private void FormatException(Builder result, Exception exception) { result.Append("!<"); - result.Append(_language.FormatTypeName(exception.GetType(), _options)); + result.Append(_formatter.TypeNameFormatter.FormatTypeName(exception.GetType(), _primitiveOptions.UseHexadecimalNumbers)); result.Append('>'); } @@ -550,9 +534,9 @@ private void FormatKeyValuePair(Builder result, object obj) string _; result.AppendGroupOpening(); result.AppendCollectionItemSeparator(isFirst: true, inline: true); - FormatObjectRecursive(result, key, quoteStrings: true, memberFormat: MemberDisplayFormat.InlineValue, name: out _); + FormatObjectRecursive(result, key, isRoot: false, debuggerDisplayName: out _); result.AppendCollectionItemSeparator(isFirst: false, inline: true); - FormatObjectRecursive(result, value, quoteStrings: true, memberFormat: MemberDisplayFormat.InlineValue, name: out _); + FormatObjectRecursive(result, value, isRoot: false, debuggerDisplayName: out _); result.AppendGroupClosing(inline: true); } @@ -561,11 +545,11 @@ private void FormatCollectionHeader(Builder result, ICollection collection) Array array = collection as Array; if (array != null) { - result.Append(_language.FormatArrayTypeName(array.GetType(), array, _options)); + result.Append(_formatter.TypeNameFormatter.FormatArrayTypeName(array.GetType(), array, _primitiveOptions.UseHexadecimalNumbers)); return; } - result.Append(_language.FormatTypeName(collection.GetType(), _options)); + result.Append(_formatter.TypeNameFormatter.FormatTypeName(collection.GetType(), _primitiveOptions.UseHexadecimalNumbers)); try { result.Append('('); @@ -589,11 +573,11 @@ private void FormatArray(Builder result, Array array, bool inline) else { result.Append(' '); - FormatSequence(result, (IEnumerable)array, inline); + FormatSequenceMembers(result, (IEnumerable)array, inline); } } - private void FormatDictionary(Builder result, IDictionary dict, bool inline) + private void FormatDictionaryMembers(Builder result, IDictionary dict, bool inline) { result.AppendGroupOpening(); @@ -611,9 +595,9 @@ private void FormatDictionary(Builder result, IDictionary dict, bool inline) result.AppendCollectionItemSeparator(isFirst: i == 0, inline: inline); result.AppendGroupOpening(); result.AppendCollectionItemSeparator(isFirst: true, inline: true); - FormatObjectRecursive(result, entry.Key, quoteStrings: true, memberFormat: MemberDisplayFormat.InlineValue, name: out _); + FormatObjectRecursive(result, entry.Key, isRoot: false, debuggerDisplayName: out _); result.AppendCollectionItemSeparator(isFirst: false, inline: true); - FormatObjectRecursive(result, entry.Value, quoteStrings: true, memberFormat: MemberDisplayFormat.InlineValue, name: out _); + FormatObjectRecursive(result, entry.Value, isRoot: false, debuggerDisplayName: out _); result.AppendGroupClosing(inline: true); i++; } @@ -631,13 +615,13 @@ private void FormatDictionary(Builder result, IDictionary dict, bool inline) result.AppendCollectionItemSeparator(isFirst: i == 0, inline: inline); FormatException(result, e); result.Append(' '); - result.Append(_options.Ellipsis); + result.Append(_builderOptions.Ellipsis); } result.AppendGroupClosing(inline); } - private void FormatSequence(Builder result, IEnumerable sequence, bool inline) + private void FormatSequenceMembers(Builder result, IEnumerable sequence, bool inline) { result.AppendGroupOpening(); int i = 0; @@ -646,9 +630,9 @@ private void FormatSequence(Builder result, IEnumerable sequence, bool inline) { foreach (var item in sequence) { - string name; + string _; result.AppendCollectionItemSeparator(isFirst: i == 0, inline: inline); - FormatObjectRecursive(result, item, quoteStrings: true, memberFormat: MemberDisplayFormat.InlineValue, name: out name); + FormatObjectRecursive(result, item, isRoot: false, debuggerDisplayName: out _); i++; } } @@ -715,8 +699,8 @@ private void FormatMultidimensionalArray(Builder result, Array array, bool inlin i--; } - string name; - FormatObjectRecursive(result, array.GetValue(indices), quoteStrings: true, memberFormat: MemberDisplayFormat.InlineValue, name: out name); + string _; + FormatObjectRecursive(result, array.GetValue(indices), isRoot: false, debuggerDisplayName: out _); indices[indices.Length - 1]++; flatIndex++; @@ -761,16 +745,17 @@ private void ObjectToString(Builder result, object obj) /// where parentheses and ,nq suffix (no-quotes) are optional and the name is an arbitrary CLR field, property, or method name. /// We then resolve the member by name using case-sensitive lookup first with fallback to case insensitive and evaluate it. /// If parentheses are present we only look for methods. - /// Only parameter less members are considered. + /// Only parameterless members are considered. /// private string FormatWithEmbeddedExpressions(int lengthLimit, string format, object obj) { - if (String.IsNullOrEmpty(format)) + if (string.IsNullOrEmpty(format)) { return null; } - return FormatWithEmbeddedExpressions(new Builder(lengthLimit, _options, insertEllipsis: false), format, obj).ToString(); + var builder = new Builder(_builderOptions.WithTotalLengthLimit(lengthLimit), insertEllipsis: false); + return FormatWithEmbeddedExpressions(builder, format, obj).ToString(); } private Builder FormatWithEmbeddedExpressions(Builder result, string format, object obj) @@ -814,8 +799,17 @@ private Builder FormatWithEmbeddedExpressions(Builder result, string format, obj } else { - string name; - FormatObjectRecursive(result, value, !noQuotes, MemberDisplayFormat.NoMembers, out name); + MemberDisplayFormat oldMemberDisplayFormat = _memberDisplayFormat; + CommonPrimitiveFormatter.Options oldPrimitiveOptions = _primitiveOptions; + + _memberDisplayFormat = MemberDisplayFormat.Hidden; + _primitiveOptions = new CommonPrimitiveFormatter.Options(_primitiveOptions.UseHexadecimalNumbers, _primitiveOptions.IncludeCharacterCodePoints, omitStringQuotes: noQuotes); + + string _; + FormatObjectRecursive(result, value, isRoot: false, debuggerDisplayName: out _); + + _primitiveOptions = oldPrimitiveOptions; + _memberDisplayFormat = oldMemberDisplayFormat; } } i = expressionEnd + 1; @@ -830,76 +824,6 @@ private Builder FormatWithEmbeddedExpressions(Builder result, string format, obj return result; } - // Parses - // - // ',' 'nq' - // '(' ')' - // '(' ')' ',' 'nq' - // - // Internal for testing purposes. - internal static string ParseSimpleMemberName(string str, int start, int end, out bool noQuotes, out bool isCallable) - { - Debug.Assert(str != null && start >= 0 && end >= start); - - isCallable = false; - noQuotes = false; - - // no-quotes suffix: - if (end - 3 >= start && str[end - 2] == 'n' && str[end - 1] == 'q') - { - int j = end - 3; - while (j >= start && Char.IsWhiteSpace(str[j])) - { - j--; - } - - if (j >= start && str[j] == ',') - { - noQuotes = true; - end = j; - } - } - - int i = end - 1; - EatTrailingWhiteSpace(str, start, ref i); - if (i > start && str[i] == ')') - { - int closingParen = i; - i--; - EatTrailingWhiteSpace(str, start, ref i); - if (str[i] != '(') - { - i = closingParen; - } - else - { - i--; - EatTrailingWhiteSpace(str, start, ref i); - isCallable = true; - } - } - - EatLeadingWhiteSpace(str, ref start, i); - - return str.Substring(start, i - start + 1); - } - - private static void EatTrailingWhiteSpace(string str, int start, ref int i) - { - while (i >= start && Char.IsWhiteSpace(str[i])) - { - i--; - } - } - - private static void EatLeadingWhiteSpace(string str, ref int i, int end) - { - while (i < end && Char.IsWhiteSpace(str[i])) - { - i++; - } - } - #endregion } } diff --git a/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.cs b/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.cs new file mode 100644 index 00000000000..a5b7847c5dd --- /dev/null +++ b/src/Scripting/Core/Hosting/ObjectFormatter/CommonObjectFormatter.cs @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Reflection; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Collections; + +namespace Microsoft.CodeAnalysis.Scripting.Hosting +{ + using static ObjectFormatterHelpers; + + // TODO (acasey): input validation + + /// + /// Object pretty printer. + /// + public abstract partial class CommonObjectFormatter : ObjectFormatter + { + public override string FormatObject(object obj) + { + var formatter = new Visitor(this, InternalBuilderOptions, PrimitiveOptions, MemberDisplayFormat); + return formatter.FormatObject(obj); + } + + internal virtual BuilderOptions InternalBuilderOptions => + new BuilderOptions( + indentation: " ", + newLine: Environment.NewLine, + ellipsis: "...", + lineLengthLimit: int.MaxValue, + totalLengthLimit: 1024); + + protected virtual CommonPrimitiveFormatter.Options PrimitiveOptions => default(CommonPrimitiveFormatter.Options); + protected virtual MemberDisplayFormat MemberDisplayFormat => default(MemberDisplayFormat); + + protected abstract CommonTypeNameFormatter TypeNameFormatter { get; } + protected abstract CommonPrimitiveFormatter PrimitiveFormatter { get; } + + protected abstract bool TryFormatCompositeObject(object obj, out string value, out bool suppressMembers); + + public override string FormatRaisedException(Exception e) + { + if (e == null) + { + throw new ArgumentNullException(nameof(e)); + } + + var pooled = PooledStringBuilder.GetInstance(); + var builder = pooled.Builder; + + builder.AppendLine(e.Message); + + var trace = new StackTrace(e, needFileInfo: true); + foreach (var frame in trace.GetFrames()) + { + if (!frame.HasMethod()) + { + continue; + } + + var method = frame.GetMethod(); + var type = method.DeclaringType; + + // TODO (https://github.com/dotnet/roslyn/issues/5250): look for other types indicating that we're in Roslyn code + if (type == typeof(CommandLineRunner)) + { + break; + } + + // TODO: we don't want to include awaiter helpers, shouldn't they be marked by DebuggerHidden in FX? + if (IsTaskAwaiter(type) || IsTaskAwaiter(type.DeclaringType)) + { + continue; + } + + string methodDisplay = FormatMethodSignature(method, PrimitiveOptions.UseHexadecimalNumbers); + + if (methodDisplay == null) + { + continue; + } + + builder.Append(" + "); + builder.Append(methodDisplay); + + if (frame.HasSource()) + { + builder.Append(string.Format(CultureInfo.CurrentUICulture, ScriptingResources.AtFileLine, frame.GetFileName(), frame.GetFileLineNumber())); + } + + builder.AppendLine(); + } + + return pooled.ToStringAndFree(); + } + + /// + /// Returns a method signature display string. Used to display stack frames. + /// + /// Null if the method is a compiler generated method that shouldn't be displayed to the user. + internal virtual string FormatMethodSignature(MethodBase method, bool useHexadecimalArrayBounds) + { + var declaringType = method.DeclaringType; + + if (IsHiddenMember(declaringType.GetTypeInfo()) || + IsHiddenMember(method) || + method.GetCustomAttributes().Any() || + declaringType.GetTypeInfo().GetCustomAttributes().Any()) + { + return null; + } + + var pooled = PooledStringBuilder.GetInstance(); + var builder = pooled.Builder; + + builder.Append(TypeNameFormatter.FormatTypeName(declaringType, useHexadecimalArrayBounds)); + builder.Append('.'); + builder.Append(method.Name); + builder.Append(TypeNameFormatter.FormatTypeArguments(method.GetGenericArguments(), useHexadecimalArrayBounds)); + + builder.Append('('); + + bool first = true; + foreach (var parameter in method.GetParameters()) + { + if (first) + { + first = false; + } + else + { + builder.Append(", "); + } + + builder.Append(FormatRefKind(parameter)); + builder.Append(TypeNameFormatter.FormatTypeName(parameter.ParameterType, useHexadecimalArrayBounds)); + } + + builder.Append(')'); + + return pooled.ToStringAndFree(); + } + + /// + /// Returns true if the member shouldn't be displayed (e.g. it's a compiler generated field). + /// + protected abstract bool IsHiddenMember(MemberInfo member); + + protected abstract string FormatRefKind(ParameterInfo parameter); + } +} diff --git a/src/Scripting/Core/Hosting/ObjectFormatter/CommonPrimitiveFormatter.Options.cs b/src/Scripting/Core/Hosting/ObjectFormatter/CommonPrimitiveFormatter.Options.cs new file mode 100644 index 00000000000..fb5ad09aa21 --- /dev/null +++ b/src/Scripting/Core/Hosting/ObjectFormatter/CommonPrimitiveFormatter.Options.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.Scripting.Hosting +{ + public abstract partial class CommonPrimitiveFormatter + { + public struct Options + { + public readonly bool UseHexadecimalNumbers; + public readonly bool IncludeCharacterCodePoints; + public readonly bool OmitStringQuotes; + + public Options(bool useHexadecimalNumbers, bool includeCodePoints, bool omitStringQuotes) + { + UseHexadecimalNumbers = useHexadecimalNumbers; + IncludeCharacterCodePoints = includeCodePoints; + OmitStringQuotes = omitStringQuotes; + } + } + } +} diff --git a/src/Scripting/Core/Hosting/ObjectFormatter/CommonPrimitiveFormatter.cs b/src/Scripting/Core/Hosting/ObjectFormatter/CommonPrimitiveFormatter.cs new file mode 100644 index 00000000000..63425c7b2c2 --- /dev/null +++ b/src/Scripting/Core/Hosting/ObjectFormatter/CommonPrimitiveFormatter.cs @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using Microsoft.CodeAnalysis; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Scripting.Hosting +{ + using static ObjectFormatterHelpers; + + public abstract partial class CommonPrimitiveFormatter + { + /// + /// String that describes "null" literal in the language. + /// + protected abstract string NullLiteral { get; } + + protected abstract string FormatLiteral(bool value); + protected abstract string FormatLiteral(string value, bool quote, bool useHexadecimalNumbers = false); + protected abstract string FormatLiteral(char value, bool quote, bool includeCodePoints = false, bool useHexadecimalNumbers = false); + protected abstract string FormatLiteral(sbyte value, bool useHexadecimalNumbers = false); + protected abstract string FormatLiteral(byte value, bool useHexadecimalNumbers = false); + protected abstract string FormatLiteral(short value, bool useHexadecimalNumbers = false); + protected abstract string FormatLiteral(ushort value, bool useHexadecimalNumbers = false); + protected abstract string FormatLiteral(int value, bool useHexadecimalNumbers = false); + protected abstract string FormatLiteral(uint value, bool useHexadecimalNumbers = false); + protected abstract string FormatLiteral(long value, bool useHexadecimalNumbers = false); + protected abstract string FormatLiteral(ulong value, bool useHexadecimalNumbers = false); + protected abstract string FormatLiteral(double value); + protected abstract string FormatLiteral(float value); + protected abstract string FormatLiteral(decimal value); + protected abstract string FormatLiteral(DateTime value); + + /// + /// Returns null if the type is not considered primitive in the target language. + /// + public string FormatPrimitive(object obj, Options options) + { + if (ReferenceEquals(obj, VoidValue)) + { + return string.Empty; + } + + if (obj == null) + { + return NullLiteral; + } + + var type = obj.GetType(); + + if (type.GetTypeInfo().IsEnum) + { + return obj.ToString(); + } + + switch (GetPrimitiveSpecialType(type)) + { + case SpecialType.System_Int32: + return FormatLiteral((int)obj, options.UseHexadecimalNumbers); + + case SpecialType.System_String: + return FormatLiteral((string)obj, !options.OmitStringQuotes, options.UseHexadecimalNumbers); + + case SpecialType.System_Boolean: + return FormatLiteral((bool)obj); + + case SpecialType.System_Char: + return FormatLiteral((char)obj, !options.OmitStringQuotes, options.IncludeCharacterCodePoints, options.UseHexadecimalNumbers); + + case SpecialType.System_Int64: + return FormatLiteral((long)obj, options.UseHexadecimalNumbers); + + case SpecialType.System_Double: + return FormatLiteral((double)obj); + + case SpecialType.System_Byte: + return FormatLiteral((byte)obj, options.UseHexadecimalNumbers); + + case SpecialType.System_Decimal: + return FormatLiteral((decimal)obj); + + case SpecialType.System_UInt32: + return FormatLiteral((uint)obj, options.UseHexadecimalNumbers); + + case SpecialType.System_UInt64: + return FormatLiteral((ulong)obj, options.UseHexadecimalNumbers); + + case SpecialType.System_Single: + return FormatLiteral((float)obj); + + case SpecialType.System_Int16: + return FormatLiteral((short)obj, options.UseHexadecimalNumbers); + + case SpecialType.System_UInt16: + return FormatLiteral((ushort)obj, options.UseHexadecimalNumbers); + + case SpecialType.System_DateTime: + return FormatLiteral((DateTime)obj); + + case SpecialType.System_SByte: + return FormatLiteral((sbyte)obj, options.UseHexadecimalNumbers); + + case SpecialType.System_Object: + case SpecialType.System_Void: + case SpecialType.None: + return null; + + default: + throw ExceptionUtilities.UnexpectedValue(GetPrimitiveSpecialType(type)); + } + } + } +} diff --git a/src/Scripting/Core/Hosting/ObjectFormatter/CommonTypeNameFormatter.cs b/src/Scripting/Core/Hosting/ObjectFormatter/CommonTypeNameFormatter.cs new file mode 100644 index 00000000000..2124c02c21b --- /dev/null +++ b/src/Scripting/Core/Hosting/ObjectFormatter/CommonTypeNameFormatter.cs @@ -0,0 +1,272 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Collections; + +namespace Microsoft.CodeAnalysis.Scripting.Hosting +{ + using static ObjectFormatterHelpers; + using TypeInfo = System.Reflection.TypeInfo; + + public abstract class CommonTypeNameFormatter + { + protected abstract string GetPrimitiveTypeName(SpecialType type); + + protected abstract string GenericParameterOpening { get; } + protected abstract string GenericParameterClosing { get; } + + protected abstract string ArrayOpening { get; } + protected abstract string ArrayClosing { get; } + + protected abstract CommonPrimitiveFormatter PrimitiveFormatter { get; } + + // TODO (tomat): Use DebuggerDisplay.Type if specified? + public virtual string FormatTypeName(Type type, bool useHexadecimalArrayBounds) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + string result = GetPrimitiveTypeName(GetPrimitiveSpecialType(type)); + if (result != null) + { + return result; + } + + if (type.IsArray) + { + return FormatArrayTypeName(type, arrayOpt: null, useHexadecimalArrayBounds: useHexadecimalArrayBounds); + } + + var typeInfo = type.GetTypeInfo(); + if (typeInfo.IsGenericType) + { + return FormatGenericTypeName(typeInfo, useHexadecimalArrayBounds); + } + + if (typeInfo.DeclaringType != null) + { + return typeInfo.Name.Replace('+', '.'); + } + + return typeInfo.Name; + } + + public virtual string FormatTypeArguments(Type[] typeArguments, bool useHexadecimalArrayBounds) + { + if (typeArguments == null) + { + throw new ArgumentNullException(nameof(typeArguments)); + } + + if (typeArguments.Length == 0) + { + return ""; + } + + var pooled = PooledStringBuilder.GetInstance(); + var builder = pooled.Builder; + + builder.Append(GenericParameterOpening); + + var first = true; + foreach (var typeArgument in typeArguments) + { + if (first) + { + first = false; + } + else + { + builder.Append(", "); + } + + builder.Append(FormatTypeName(typeArgument, useHexadecimalArrayBounds)); + } + + builder.Append(GenericParameterClosing); + + return pooled.ToStringAndFree(); + } + + /// + /// Formats an array type name (vector or multidimensional). + /// + public virtual string FormatArrayTypeName(Type arrayType, Array arrayOpt, bool useHexadecimalArrayBounds) + { + if (arrayType == null) + { + throw new ArgumentNullException(nameof(arrayType)); + } + + StringBuilder sb = new StringBuilder(); + + // print the inner-most element type first: + Type elementType = arrayType.GetElementType(); + while (elementType.IsArray) + { + elementType = elementType.GetElementType(); + } + + sb.Append(FormatTypeName(elementType, useHexadecimalArrayBounds)); + + // print all components of a jagged array: + Type type = arrayType; + do + { + if (arrayOpt != null) + { + sb.Append(ArrayOpening); + + int rank = type.GetArrayRank(); + + bool anyNonzeroLowerBound = false; + for (int i = 0; i < rank; i++) + { + if (arrayOpt.GetLowerBound(i) > 0) + { + anyNonzeroLowerBound = true; + break; + } + } + + for (int i = 0; i < rank; i++) + { + int lowerBound = arrayOpt.GetLowerBound(i); + int length = arrayOpt.GetLength(i); + + if (i > 0) + { + sb.Append(", "); + } + + if (anyNonzeroLowerBound) + { + AppendArrayBound(sb, lowerBound, useHexadecimalArrayBounds); + sb.Append(".."); + AppendArrayBound(sb, length + lowerBound, useHexadecimalArrayBounds); + } + else + { + AppendArrayBound(sb, length, useHexadecimalArrayBounds); + } + } + + sb.Append(ArrayClosing); + arrayOpt = null; + } + else + { + AppendArrayRank(sb, type); + } + + type = type.GetElementType(); + } + while (type.IsArray); + + return sb.ToString(); + } + + private void AppendArrayBound(StringBuilder sb, long bound, bool useHexadecimalNumbers) + { + var options = new CommonPrimitiveFormatter.Options(useHexadecimalNumbers, includeCodePoints: false, omitStringQuotes: false); + var formatted = int.MinValue <= bound && bound <= int.MaxValue + ? PrimitiveFormatter.FormatPrimitive((int)bound, options) + : PrimitiveFormatter.FormatPrimitive(bound, options); + sb.Append(formatted); + } + + private void AppendArrayRank(StringBuilder sb, Type arrayType) + { + sb.Append('['); + int rank = arrayType.GetArrayRank(); + if (rank > 1) + { + sb.Append(',', rank - 1); + } + sb.Append(']'); + } + + private string FormatGenericTypeName(TypeInfo typeInfo, bool useHexadecimalArrayBounds) + { + var pooledBuilder = PooledStringBuilder.GetInstance(); + var builder = pooledBuilder.Builder; + + // TODO (https://github.com/dotnet/roslyn/issues/5250): shouldn't need parameters, but StackTrace gives us unconstructed symbols. + // consolidated generic arguments (includes arguments of all declaring types): + Type[] genericArguments = typeInfo.IsGenericTypeDefinition ? typeInfo.GenericTypeParameters : typeInfo.GenericTypeArguments; + + if (typeInfo.DeclaringType != null) + { + var nestedTypes = ArrayBuilder.GetInstance(); + do + { + nestedTypes.Add(typeInfo); + typeInfo = typeInfo.DeclaringType?.GetTypeInfo(); + } + while (typeInfo != null); + + int typeArgumentIndex = 0; + for (int i = nestedTypes.Count - 1; i >= 0; i--) + { + AppendTypeInstantiation(builder, nestedTypes[i], genericArguments, ref typeArgumentIndex, useHexadecimalArrayBounds); + if (i > 0) + { + builder.Append('.'); + } + } + + nestedTypes.Free(); + } + else + { + int typeArgumentIndex = 0; + AppendTypeInstantiation(builder, typeInfo, genericArguments, ref typeArgumentIndex, useHexadecimalArrayBounds); + } + + return pooledBuilder.ToStringAndFree(); + } + + private void AppendTypeInstantiation(StringBuilder builder, TypeInfo typeInfo, Type[] genericArguments, ref int genericArgIndex, bool useHexadecimalArrayBounds) + { + // generic arguments of all the outer types and the current type; + int currentArgCount = (typeInfo.IsGenericTypeDefinition ? typeInfo.GenericTypeParameters.Length : typeInfo.GenericTypeArguments.Length) - genericArgIndex; + + if (currentArgCount > 0) + { + string name = typeInfo.Name; + + int backtick = name.IndexOf('`'); + if (backtick > 0) + { + builder.Append(name.Substring(0, backtick)); + } + else + { + builder.Append(name); + } + + builder.Append(GenericParameterOpening); + + for (int i = 0; i < currentArgCount; i++) + { + if (i > 0) + { + builder.Append(", "); + } + builder.Append(FormatTypeName(genericArguments[genericArgIndex++], useHexadecimalArrayBounds)); + } + + builder.Append(GenericParameterClosing); + } + else + { + builder.Append(typeInfo.Name); + } + } + } +} diff --git a/src/Scripting/Core/Hosting/ObjectFormatter/MemberDisplayFormat.cs b/src/Scripting/Core/Hosting/ObjectFormatter/MemberDisplayFormat.cs index f41fbb3118b..8932824671f 100644 --- a/src/Scripting/Core/Hosting/ObjectFormatter/MemberDisplayFormat.cs +++ b/src/Scripting/Core/Hosting/ObjectFormatter/MemberDisplayFormat.cs @@ -2,37 +2,32 @@ namespace Microsoft.CodeAnalysis.Scripting.Hosting { - internal enum MemberDisplayFormat + // TODO (https://github.com/dotnet/roslyn/issues/6689): change default to SeparateLines + public enum MemberDisplayFormat { - /// - /// Display just a simple description of the object, like type name or ToString(). Don't - /// display any members or items of the object. - /// - NoMembers, - /// /// Display structure of the object on a single line. /// - Inline, + SingleLine, /// - /// Display structure of the object on a single line, where the object is displayed as a value of its container's member. - /// E.g. { a = ... } + /// Displays a simple description of the object followed by list of members. Each member is + /// displayed on a separate line. /// - InlineValue, + SeparateLines, /// - /// Displays a simple description of the object followed by list of members. Each member is - /// displayed on a separate line. + /// Display just a simple description of the object, like type name or ToString(). Don't + /// display any members or items of the object. /// - List, + Hidden, } internal static partial class EnumBounds { internal static bool IsValid(this MemberDisplayFormat value) { - return value >= MemberDisplayFormat.NoMembers && value <= MemberDisplayFormat.List; + return MemberDisplayFormat.SingleLine <= value && value <= MemberDisplayFormat.Hidden; } } } diff --git a/src/Scripting/Core/Hosting/ObjectFormatter/ObjectFormatter.cs b/src/Scripting/Core/Hosting/ObjectFormatter/ObjectFormatter.cs index d6c5dc20c3f..8d622335447 100644 --- a/src/Scripting/Core/Hosting/ObjectFormatter/ObjectFormatter.cs +++ b/src/Scripting/Core/Hosting/ObjectFormatter/ObjectFormatter.cs @@ -1,825 +1,16 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using System.Reflection; -using System.Text; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Collections; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Scripting.Hosting { - using TypeInfo = System.Reflection.TypeInfo; - /// /// Object pretty printer. /// - public abstract partial class ObjectFormatter + public abstract class ObjectFormatter { - internal ObjectFormatter() - { - } - - public string FormatObject(object obj) - { - return new Formatter(this, null).FormatObject(obj); - } - - internal string FormatObject(object obj, ObjectFormattingOptions options) - { - return new Formatter(this, options).FormatObject(obj); - } - - #region Reflection Helpers - - private static bool HasOverriddenToString(TypeInfo type) - { - if (type.IsInterface) - { - return false; - } - - while (type.AsType() != typeof(object)) - { - if (type.GetDeclaredMethod("ToString", Type.EmptyTypes) != null) - { - return true; - } - - type = type.BaseType.GetTypeInfo(); - } - - return false; - } - - private static DebuggerDisplayAttribute GetApplicableDebuggerDisplayAttribute(MemberInfo member) - { - // Includes inherited attributes. The debugger uses the first attribute if multiple are applied. - var result = member.GetCustomAttributes().FirstOrDefault(); - if (result != null) - { - return result; - } - - // TODO (tomat): which assembly should we look at for dd attributes? - var type = member as TypeInfo; - if (type != null) - { - foreach (DebuggerDisplayAttribute attr in type.Assembly.GetCustomAttributes()) - { - if (IsApplicableAttribute(type, attr.Target.GetTypeInfo(), attr.TargetTypeName)) - { - return attr; - } - } - } - - return null; - } - - private static DebuggerTypeProxyAttribute GetApplicableDebuggerTypeProxyAttribute(TypeInfo type) - { - // includes inherited attributes. The debugger uses the first attribute if multiple are applied. - var result = type.GetCustomAttributes().FirstOrDefault(); - if (result != null) - { - return result; - } - - // TODO (tomat): which assembly should we look at for proxy attributes? - foreach (DebuggerTypeProxyAttribute attr in type.Assembly.GetCustomAttributes()) - { - if (IsApplicableAttribute(type, attr.Target.GetTypeInfo(), attr.TargetTypeName)) - { - return attr; - } - } - - return null; - } - - private static bool IsApplicableAttribute(TypeInfo type, TypeInfo targetType, string targetTypeName) - { - return type != null && AreEquivalent(targetType, type) - || targetTypeName != null && type.FullName == targetTypeName; - } - - private static bool AreEquivalent(TypeInfo type, TypeInfo other) - { - // TODO: Unify NoPIA interfaces - // https://github.com/dotnet/corefx/issues/2101 - return type.Equals(other); - } - - private static object GetDebuggerTypeProxy(object obj) - { - // use proxy type if defined: - var type = obj.GetType().GetTypeInfo(); - var debuggerTypeProxy = GetApplicableDebuggerTypeProxyAttribute(type); - if (debuggerTypeProxy != null) - { - try - { - var proxyType = Type.GetType(debuggerTypeProxy.ProxyTypeName, throwOnError: false, ignoreCase: false); - if (proxyType != null) - { - if (proxyType.GetTypeInfo().IsGenericTypeDefinition) - { - proxyType = proxyType.MakeGenericType(type.GenericTypeArguments); - } - - return Activator.CreateInstance(proxyType, new object[] { obj }); - } - } - catch (Exception) - { - // no-op, ignore proxy if it is implemented incorrectly or can't be loaded - } - } - - return null; - } - - private static MemberInfo ResolveMember(object obj, string memberName, bool callableOnly) - { - TypeInfo type = obj.GetType().GetTypeInfo(); - - // case-sensitive: - TypeInfo currentType = type; - while (true) - { - if (!callableOnly) - { - var field = currentType.GetDeclaredField(memberName); - if (field != null) - { - return field; - } - - var property = currentType.GetDeclaredProperty(memberName); - if (property != null) - { - var getter = property.GetMethod; - if (getter != null) - { - return getter; - } - } - } - - var method = currentType.GetDeclaredMethod(memberName, Type.EmptyTypes); - if (method != null) - { - return method; - } - - if (currentType.BaseType == null) - { - break; - } - - currentType = currentType.BaseType.GetTypeInfo(); - } - - // case-insensitive: - currentType = type; - while (true) - { - IEnumerable members; - if (callableOnly) - { - members = type.DeclaredMethods; - } - else - { - members = ((IEnumerable)type.DeclaredFields).Concat(type.DeclaredProperties); - } - - MemberInfo candidate = null; - foreach (var member in members) - { - if (StringComparer.OrdinalIgnoreCase.Equals(memberName, member.Name)) - { - if (candidate != null) - { - return null; - } - - MethodInfo method; - - if (member is FieldInfo) - { - candidate = member; - } - else if ((method = member as MethodInfo) != null) - { - if (method.GetParameters().Length == 0) - { - candidate = member; - } - } - else - { - var getter = ((PropertyInfo)member).GetMethod; - if (getter?.GetParameters().Length == 0) - { - candidate = member; - } - } - } - } - - if (candidate != null) - { - return candidate; - } - - if (currentType.BaseType == null) - { - break; - } - - currentType = currentType.BaseType.GetTypeInfo(); - } - - return null; - } - - private static object GetMemberValue(MemberInfo member, object obj, out Exception exception) - { - exception = null; - - try - { - FieldInfo field; - MethodInfo method; - - if ((field = member as FieldInfo) != null) - { - return field.GetValue(obj); - } - - if ((method = member as MethodInfo) != null) - { - return (method.ReturnType == typeof(void)) ? s_voidValue : method.Invoke(obj, SpecializedCollections.EmptyObjects); - } - - var property = (PropertyInfo)member; - if (property.GetMethod == null) - { - return null; - } - - return property.GetValue(obj, SpecializedCollections.EmptyObjects); - } - catch (TargetInvocationException e) - { - exception = e.InnerException; - } - - return null; - } - - private static readonly object s_voidValue = new object(); - - #endregion - - #region String Builder Helpers - - private sealed class Builder - { - private readonly ObjectFormattingOptions _options; - private readonly StringBuilder _sb; - private readonly int _lineLengthLimit; - private readonly int _lengthLimit; - private readonly bool _insertEllipsis; - private int _currentLimit; - - public Builder(int lengthLimit, ObjectFormattingOptions options, bool insertEllipsis) - { - Debug.Assert(lengthLimit <= options.MaxOutputLength); - - int lineLengthLimit = options.MaxLineLength; - if (insertEllipsis) - { - lengthLimit = Math.Max(0, lengthLimit - options.Ellipsis.Length - 1); - lineLengthLimit = Math.Max(0, lineLengthLimit - options.Ellipsis.Length - 1); - } - - _lengthLimit = lengthLimit; - _lineLengthLimit = lineLengthLimit; - _currentLimit = Math.Min(lineLengthLimit, lengthLimit); - _insertEllipsis = insertEllipsis; - - _options = options; - _sb = new StringBuilder(); - } - - public bool LimitReached - { - get { return _sb.Length == _lengthLimit; } - } - - public int Remaining - { - get { return _lengthLimit - _sb.Length; } - } - - // can be negative (the min value is -Ellipsis.Length - 1) - private int CurrentRemaining - { - get { return _currentLimit - _sb.Length; } - } - - public void AppendLine() - { - // remove line length limit so that we can insert a new line even - // if the previous one hit maxed out the line limit: - _currentLimit = _lengthLimit; - - Append(_options.NewLine); - - // recalc limit for the next line: - _currentLimit = (int)Math.Min((long)_sb.Length + _lineLengthLimit, _lengthLimit); - } - - private void AppendEllipsis() - { - if (_sb.Length > 0 && _sb[_sb.Length - 1] != ' ') - { - _sb.Append(' '); - } - - _sb.Append(_options.Ellipsis); - } - - public void Append(char c, int count = 1) - { - if (CurrentRemaining < 0) - { - return; - } - - int length = Math.Min(count, CurrentRemaining); - - _sb.Append(c, length); - - if (_insertEllipsis && length < count) - { - AppendEllipsis(); - } - } - - public void Append(string str, int start = 0, int count = Int32.MaxValue) - { - if (str == null || CurrentRemaining < 0) - { - return; - } - - count = Math.Min(count, str.Length - start); - int length = Math.Min(count, CurrentRemaining); - _sb.Append(str, start, length); - - if (_insertEllipsis && length < count) - { - AppendEllipsis(); - } - } - - public void AppendFormat(string format, params object[] args) - { - Append(String.Format(format, args)); - } - - public void AppendGroupOpening() - { - Append('{'); - } - - public void AppendGroupClosing(bool inline) - { - if (inline) - { - Append(" }"); - } - else - { - AppendLine(); - Append('}'); - AppendLine(); - } - } - - public void AppendCollectionItemSeparator(bool isFirst, bool inline) - { - if (isFirst) - { - if (inline) - { - Append(' '); - } - else - { - AppendLine(); - } - } - else - { - if (inline) - { - Append(", "); - } - else - { - Append(','); - AppendLine(); - } - } - - if (!inline) - { - Append(_options.MemberIndentation); - } - } - - internal void AppendInfiniteRecursionMarker() - { - AppendGroupOpening(); - AppendCollectionItemSeparator(isFirst: true, inline: true); - Append(_options.Ellipsis); - AppendGroupClosing(inline: true); - } - - public override string ToString() - { - return _sb.ToString(); - } - } - - #endregion - - #region Language Specific Formatting - - /// - /// String that describes "void" return type in the language. - /// - internal abstract object VoidDisplayString { get; } - - /// - /// String that describes "null" literal in the language. - /// - internal abstract string NullLiteral { get; } - - internal abstract string FormatLiteral(bool value); - internal abstract string FormatLiteral(string value, bool quote, bool useHexadecimalNumbers = false); - internal abstract string FormatLiteral(char value, bool quote, bool includeCodePoints = false, bool useHexadecimalNumbers = false); - internal abstract string FormatLiteral(sbyte value, bool useHexadecimalNumbers = false); - internal abstract string FormatLiteral(byte value, bool useHexadecimalNumbers = false); - internal abstract string FormatLiteral(short value, bool useHexadecimalNumbers = false); - internal abstract string FormatLiteral(ushort value, bool useHexadecimalNumbers = false); - internal abstract string FormatLiteral(int value, bool useHexadecimalNumbers = false); - internal abstract string FormatLiteral(uint value, bool useHexadecimalNumbers = false); - internal abstract string FormatLiteral(long value, bool useHexadecimalNumbers = false); - internal abstract string FormatLiteral(ulong value, bool useHexadecimalNumbers = false); - internal abstract string FormatLiteral(double value); - internal abstract string FormatLiteral(float value); - internal abstract string FormatLiteral(decimal value); - internal abstract string FormatLiteral(DateTime value); - - // TODO (tomat): Use DebuggerDisplay.Type if specified? - internal abstract string FormatGeneratedTypeName(Type type); - internal abstract string FormatMemberName(MemberInfo member); - internal abstract string GetPrimitiveTypeName(SpecialType type); - - /// - /// Returns a method signature display string. Used to display stack frames. - /// - /// Null if the method is a compiler generated method that shouldn't be displayed to the user. - internal virtual string FormatMethodSignature(MethodBase method) - { - // TODO: https://github.com/dotnet/roslyn/issues/5250 - - if (method.Name.IndexOfAny(s_generatedNameChars) >= 0 || - method.DeclaringType.Name.IndexOfAny(s_generatedNameChars) >= 0 || - method.GetCustomAttributes().Any() || - method.DeclaringType.GetTypeInfo().GetCustomAttributes().Any()) - { - return null; - } - - return $"{method.DeclaringType.ToString()}.{method.Name}({string.Join(", ", method.GetParameters().Select(p => p.ToString()))})"; - } - - private static readonly char[] s_generatedNameChars = { '$', '<' }; - - internal abstract string GenericParameterOpening { get; } - internal abstract string GenericParameterClosing { get; } - - /// - /// Formats an array type name (vector or multidimensional). - /// - internal abstract string FormatArrayTypeName(Type arrayType, Array arrayOpt, ObjectFormattingOptions options); - - /// - /// Returns true if the member shouldn't be displayed (e.g. it's a compiler generated field). - /// - internal virtual bool IsHiddenMember(MemberInfo member) => false; - - internal static CultureInfo UIFormatCulture => CultureInfo.CurrentUICulture; - - internal static ObjectDisplayOptions GetObjectDisplayOptions(bool useHexadecimalNumbers) - { - return useHexadecimalNumbers - ? ObjectDisplayOptions.UseHexadecimalNumbers - : ObjectDisplayOptions.None; - } - - /// - /// Returns null if the type is not considered primitive in the target language. - /// - private string FormatPrimitive(object obj, bool quoteStrings, bool includeCodePoints, bool useHexadecimalNumbers) - { - if (ReferenceEquals(obj, s_voidValue)) - { - return string.Empty; - } - - if (obj == null) - { - return NullLiteral; - } - - var type = obj.GetType(); - - if (type.GetTypeInfo().IsEnum) - { - return obj.ToString(); - } - - switch (GetPrimitiveSpecialType(type)) - { - case SpecialType.System_Int32: - return FormatLiteral((int)obj, useHexadecimalNumbers); - - case SpecialType.System_String: - return FormatLiteral((string)obj, quoteStrings, useHexadecimalNumbers); - - case SpecialType.System_Boolean: - return FormatLiteral((bool)obj); - - case SpecialType.System_Char: - return FormatLiteral((char)obj, quoteStrings, includeCodePoints, useHexadecimalNumbers); - - case SpecialType.System_Int64: - return FormatLiteral((long)obj, useHexadecimalNumbers); - - case SpecialType.System_Double: - return FormatLiteral((double)obj); - - case SpecialType.System_Byte: - return FormatLiteral((byte)obj, useHexadecimalNumbers); - - case SpecialType.System_Decimal: - return FormatLiteral((decimal)obj); - - case SpecialType.System_UInt32: - return FormatLiteral((uint)obj, useHexadecimalNumbers); - - case SpecialType.System_UInt64: - return FormatLiteral((ulong)obj, useHexadecimalNumbers); - - case SpecialType.System_Single: - return FormatLiteral((float)obj); - - case SpecialType.System_Int16: - return FormatLiteral((short)obj, useHexadecimalNumbers); - - case SpecialType.System_UInt16: - return FormatLiteral((ushort)obj, useHexadecimalNumbers); - - case SpecialType.System_DateTime: - return FormatLiteral((DateTime)obj); - - case SpecialType.System_SByte: - return FormatLiteral((sbyte)obj, useHexadecimalNumbers); - - case SpecialType.System_Object: - case SpecialType.System_Void: - case SpecialType.None: - return null; - - default: - throw ExceptionUtilities.Unreachable; - } - } - - internal static SpecialType GetPrimitiveSpecialType(Type type) - { - Debug.Assert(type != null); - - if (type == typeof(int)) - { - return SpecialType.System_Int32; - } - - if (type == typeof(string)) - { - return SpecialType.System_String; - } - - if (type == typeof(bool)) - { - return SpecialType.System_Boolean; - } - - if (type == typeof(char)) - { - return SpecialType.System_Char; - } - - if (type == typeof(long)) - { - return SpecialType.System_Int64; - } - - if (type == typeof(double)) - { - return SpecialType.System_Double; - } - - if (type == typeof(byte)) - { - return SpecialType.System_Byte; - } - - if (type == typeof(decimal)) - { - return SpecialType.System_Decimal; - } - - if (type == typeof(uint)) - { - return SpecialType.System_UInt32; - } - - if (type == typeof(ulong)) - { - return SpecialType.System_UInt64; - } - - if (type == typeof(float)) - { - return SpecialType.System_Single; - } - - if (type == typeof(short)) - { - return SpecialType.System_Int16; - } - - if (type == typeof(ushort)) - { - return SpecialType.System_UInt16; - } - - if (type == typeof(DateTime)) - { - return SpecialType.System_DateTime; - } - - if (type == typeof(sbyte)) - { - return SpecialType.System_SByte; - } - - if (type == typeof(object)) - { - return SpecialType.System_Object; - } - - if (type == typeof(void)) - { - return SpecialType.System_Void; - } - - return SpecialType.None; - } - - internal string FormatTypeName(Type type, ObjectFormattingOptions options) - { - string result = GetPrimitiveTypeName(GetPrimitiveSpecialType(type)); - if (result != null) - { - return result; - } - - result = FormatGeneratedTypeName(type); - if (result != null) - { - return result; - } - - if (type.IsArray) - { - return FormatArrayTypeName(type, arrayOpt: null, options: options); - } - - var typeInfo = type.GetTypeInfo(); - if (typeInfo.IsGenericType) - { - return FormatGenericTypeName(typeInfo, options); - } - - if (typeInfo.DeclaringType != null) - { - return typeInfo.Name.Replace('+', '.'); - } - - return typeInfo.Name; - } - - private string FormatGenericTypeName(TypeInfo typeInfo, ObjectFormattingOptions options) - { - var pooledBuilder = PooledStringBuilder.GetInstance(); - var builder = pooledBuilder.Builder; - - // consolidated generic arguments (includes arguments of all declaring types): - Type[] genericArguments = typeInfo.GenericTypeArguments; - - if (typeInfo.DeclaringType != null) - { - var nestedTypes = ArrayBuilder.GetInstance(); - do - { - nestedTypes.Add(typeInfo); - typeInfo = typeInfo.DeclaringType?.GetTypeInfo(); - } - while (typeInfo != null); - - int typeArgumentIndex = 0; - for (int i = nestedTypes.Count - 1; i >= 0; i--) - { - AppendTypeInstantiation(builder, nestedTypes[i], genericArguments, ref typeArgumentIndex, options); - if (i > 0) - { - builder.Append('.'); - } - } - - nestedTypes.Free(); - } - else - { - int typeArgumentIndex = 0; - AppendTypeInstantiation(builder, typeInfo, genericArguments, ref typeArgumentIndex, options); - } - - return pooledBuilder.ToStringAndFree(); - } - - private void AppendTypeInstantiation(StringBuilder builder, TypeInfo typeInfo, Type[] genericArguments, ref int genericArgIndex, ObjectFormattingOptions options) - { - // generic arguments of all the outer types and the current type; - int currentArgCount = (typeInfo.IsGenericTypeDefinition ? typeInfo.GenericTypeParameters.Length : typeInfo.GenericTypeArguments.Length) - genericArgIndex; - - if (currentArgCount > 0) - { - string name = typeInfo.Name; - - int backtick = name.IndexOf('`'); - if (backtick > 0) - { - builder.Append(name.Substring(0, backtick)); - } - else - { - builder.Append(name); - } - - builder.Append(GenericParameterOpening); - - for (int i = 0; i < currentArgCount; i++) - { - if (i > 0) - { - builder.Append(", "); - } - builder.Append(FormatTypeName(genericArguments[genericArgIndex++], options)); - } - - builder.Append(GenericParameterClosing); - } - else - { - builder.Append(typeInfo.Name); - } - } + public abstract string FormatObject(object obj); - #endregion + public abstract string FormatRaisedException(Exception e); } -} +} \ No newline at end of file diff --git a/src/Scripting/Core/Hosting/ObjectFormatter/ObjectFormatterHelpers.cs b/src/Scripting/Core/Hosting/ObjectFormatter/ObjectFormatterHelpers.cs new file mode 100644 index 00000000000..b0c34c404e0 --- /dev/null +++ b/src/Scripting/Core/Hosting/ObjectFormatter/ObjectFormatterHelpers.cs @@ -0,0 +1,449 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Scripting.Hosting +{ + using TypeInfo = System.Reflection.TypeInfo; + + internal static class ObjectFormatterHelpers + { + internal static readonly object VoidValue = new object(); + + internal static bool HasOverriddenToString(TypeInfo type) + { + if (type.IsInterface) + { + return false; + } + + while (type.AsType() != typeof(object)) + { + if (type.GetDeclaredMethod("ToString", Type.EmptyTypes) != null) + { + return true; + } + + type = type.BaseType.GetTypeInfo(); + } + + return false; + } + + internal static DebuggerDisplayAttribute GetApplicableDebuggerDisplayAttribute(MemberInfo member) + { + // Includes inherited attributes. The debugger uses the first attribute if multiple are applied. + var result = member.GetCustomAttributes().FirstOrDefault(); + if (result != null) + { + return result; + } + + // TODO (tomat): which assembly should we look at for dd attributes? + var type = member as TypeInfo; + if (type != null) + { + foreach (DebuggerDisplayAttribute attr in type.Assembly.GetCustomAttributes()) + { + if (IsApplicableAttribute(type, attr.Target.GetTypeInfo(), attr.TargetTypeName)) + { + return attr; + } + } + } + + return null; + } + + private static DebuggerTypeProxyAttribute GetApplicableDebuggerTypeProxyAttribute(TypeInfo type) + { + // includes inherited attributes. The debugger uses the first attribute if multiple are applied. + var result = type.GetCustomAttributes().FirstOrDefault(); + if (result != null) + { + return result; + } + + // TODO (tomat): which assembly should we look at for proxy attributes? + foreach (DebuggerTypeProxyAttribute attr in type.Assembly.GetCustomAttributes()) + { + if (IsApplicableAttribute(type, attr.Target.GetTypeInfo(), attr.TargetTypeName)) + { + return attr; + } + } + + return null; + } + + private static bool IsApplicableAttribute(TypeInfo type, TypeInfo targetType, string targetTypeName) + { + return type != null && AreEquivalent(targetType, type) + || targetTypeName != null && type.FullName == targetTypeName; + } + + private static bool AreEquivalent(TypeInfo type, TypeInfo other) + { + // TODO: Unify NoPIA interfaces + // https://github.com/dotnet/corefx/issues/2101 + return type.Equals(other); + } + + internal static object GetDebuggerTypeProxy(object obj) + { + // use proxy type if defined: + var type = obj.GetType().GetTypeInfo(); + var debuggerTypeProxy = GetApplicableDebuggerTypeProxyAttribute(type); + if (debuggerTypeProxy != null) + { + try + { + var proxyType = Type.GetType(debuggerTypeProxy.ProxyTypeName, throwOnError: false, ignoreCase: false); + if (proxyType != null) + { + if (proxyType.GetTypeInfo().IsGenericTypeDefinition) + { + proxyType = proxyType.MakeGenericType(type.GenericTypeArguments); + } + + return Activator.CreateInstance(proxyType, new object[] { obj }); + } + } + catch (Exception) + { + // no-op, ignore proxy if it is implemented incorrectly or can't be loaded + } + } + + return null; + } + + internal static MemberInfo ResolveMember(object obj, string memberName, bool callableOnly) + { + TypeInfo type = obj.GetType().GetTypeInfo(); + + // case-sensitive: + TypeInfo currentType = type; + while (true) + { + if (!callableOnly) + { + var field = currentType.GetDeclaredField(memberName); + if (field != null) + { + return field; + } + + var property = currentType.GetDeclaredProperty(memberName); + if (property != null) + { + var getter = property.GetMethod; + if (getter != null) + { + return getter; + } + } + } + + var method = currentType.GetDeclaredMethod(memberName, Type.EmptyTypes); + if (method != null) + { + return method; + } + + if (currentType.BaseType == null) + { + break; + } + + currentType = currentType.BaseType.GetTypeInfo(); + } + + // case-insensitive: + currentType = type; + while (true) + { + IEnumerable members; + if (callableOnly) + { + members = type.DeclaredMethods; + } + else + { + members = ((IEnumerable)type.DeclaredFields).Concat(type.DeclaredProperties); + } + + MemberInfo candidate = null; + foreach (var member in members) + { + if (StringComparer.OrdinalIgnoreCase.Equals(memberName, member.Name)) + { + if (candidate != null) + { + return null; + } + + MethodInfo method; + + if (member is FieldInfo) + { + candidate = member; + } + else if ((method = member as MethodInfo) != null) + { + if (method.GetParameters().Length == 0) + { + candidate = member; + } + } + else + { + var getter = ((PropertyInfo)member).GetMethod; + if (getter?.GetParameters().Length == 0) + { + candidate = member; + } + } + } + } + + if (candidate != null) + { + return candidate; + } + + if (currentType.BaseType == null) + { + break; + } + + currentType = currentType.BaseType.GetTypeInfo(); + } + + return null; + } + + internal static object GetMemberValue(MemberInfo member, object obj, out Exception exception) + { + exception = null; + + try + { + FieldInfo field; + MethodInfo method; + + if ((field = member as FieldInfo) != null) + { + return field.GetValue(obj); + } + + if ((method = member as MethodInfo) != null) + { + return (method.ReturnType == typeof(void)) ? VoidValue : method.Invoke(obj, SpecializedCollections.EmptyObjects); + } + + var property = (PropertyInfo)member; + if (property.GetMethod == null) + { + return null; + } + + return property.GetValue(obj, SpecializedCollections.EmptyObjects); + } + catch (TargetInvocationException e) + { + exception = e.InnerException; + } + + return null; + } + + internal static bool IsTaskAwaiter(Type type) + { + if (type == typeof(TaskAwaiter) || type == typeof(ConfiguredTaskAwaitable)) + { + return true; + } + + if (type?.GetTypeInfo().IsGenericType == true) + { + var genericDef = type.GetTypeInfo().GetGenericTypeDefinition(); + return genericDef == typeof(TaskAwaiter<>) || type == typeof(ConfiguredTaskAwaitable<>); + } + + return false; + } + + internal static SpecialType GetPrimitiveSpecialType(Type type) + { + Debug.Assert(type != null); + + if (type == typeof(int)) + { + return SpecialType.System_Int32; + } + + if (type == typeof(string)) + { + return SpecialType.System_String; + } + + if (type == typeof(bool)) + { + return SpecialType.System_Boolean; + } + + if (type == typeof(char)) + { + return SpecialType.System_Char; + } + + if (type == typeof(long)) + { + return SpecialType.System_Int64; + } + + if (type == typeof(double)) + { + return SpecialType.System_Double; + } + + if (type == typeof(byte)) + { + return SpecialType.System_Byte; + } + + if (type == typeof(decimal)) + { + return SpecialType.System_Decimal; + } + + if (type == typeof(uint)) + { + return SpecialType.System_UInt32; + } + + if (type == typeof(ulong)) + { + return SpecialType.System_UInt64; + } + + if (type == typeof(float)) + { + return SpecialType.System_Single; + } + + if (type == typeof(short)) + { + return SpecialType.System_Int16; + } + + if (type == typeof(ushort)) + { + return SpecialType.System_UInt16; + } + + if (type == typeof(DateTime)) + { + return SpecialType.System_DateTime; + } + + if (type == typeof(sbyte)) + { + return SpecialType.System_SByte; + } + + if (type == typeof(object)) + { + return SpecialType.System_Object; + } + + if (type == typeof(void)) + { + return SpecialType.System_Void; + } + + return SpecialType.None; + } + + internal static ObjectDisplayOptions GetObjectDisplayOptions(bool useHexadecimalNumbers) + { + return useHexadecimalNumbers ? ObjectDisplayOptions.UseHexadecimalNumbers : ObjectDisplayOptions.None; + } + + // Parses + // + // ',' 'nq' + // '(' ')' + // '(' ')' ',' 'nq' + internal static string ParseSimpleMemberName(string str, int start, int end, out bool noQuotes, out bool isCallable) + { + Debug.Assert(str != null && start >= 0 && end >= start); + + isCallable = false; + noQuotes = false; + + // no-quotes suffix: + if (end - 3 >= start && str[end - 2] == 'n' && str[end - 1] == 'q') + { + int j = end - 3; + while (j >= start && Char.IsWhiteSpace(str[j])) + { + j--; + } + + if (j >= start && str[j] == ',') + { + noQuotes = true; + end = j; + } + } + + int i = end - 1; + EatTrailingWhiteSpace(str, start, ref i); + if (i > start && str[i] == ')') + { + int closingParen = i; + i--; + EatTrailingWhiteSpace(str, start, ref i); + if (str[i] != '(') + { + i = closingParen; + } + else + { + i--; + EatTrailingWhiteSpace(str, start, ref i); + isCallable = true; + } + } + + EatLeadingWhiteSpace(str, ref start, i); + + return str.Substring(start, i - start + 1); + } + + private static void EatTrailingWhiteSpace(string str, int start, ref int i) + { + while (i >= start && Char.IsWhiteSpace(str[i])) + { + i--; + } + } + + private static void EatLeadingWhiteSpace(string str, ref int i, int end) + { + while (i < end && Char.IsWhiteSpace(str[i])) + { + i++; + } + } + } +} diff --git a/src/Scripting/Core/Hosting/ObjectFormatter/ObjectFormattingOptions.cs b/src/Scripting/Core/Hosting/ObjectFormatter/ObjectFormattingOptions.cs deleted file mode 100644 index 56111f8bd54..00000000000 --- a/src/Scripting/Core/Hosting/ObjectFormatter/ObjectFormattingOptions.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.CodeAnalysis.Scripting.Hosting -{ - internal sealed class ObjectFormattingOptions - { - public static ObjectFormattingOptions Default { get; } = new ObjectFormattingOptions(); - - internal bool QuoteStrings { get; } - internal MemberDisplayFormat MemberFormat { get; } - internal int MaxLineLength { get; } - internal int MaxOutputLength { get; } - internal bool UseHexadecimalNumbers { get; } - internal string MemberIndentation { get; } - internal string Ellipsis { get; } - internal string NewLine { get; } - internal bool IncludeCodePoints { get; } - - internal ObjectFormattingOptions( - MemberDisplayFormat memberFormat = MemberDisplayFormat.Inline, - bool quoteStrings = true, - bool useHexadecimalNumbers = false, - bool includeCodePoints = false, - int maxLineLength = int.MaxValue, - int maxOutputLength = 1024, - string memberIndentation = null, - string ellipsis = null, - string lineBreak = null) - { - if (!memberFormat.IsValid()) - { - throw new ArgumentOutOfRangeException(nameof(memberFormat)); - } - - this.MemberFormat = memberFormat; - this.QuoteStrings = quoteStrings; - this.IncludeCodePoints = includeCodePoints; - this.MaxOutputLength = maxOutputLength; - this.MaxLineLength = maxLineLength; - this.UseHexadecimalNumbers = useHexadecimalNumbers; - this.MemberIndentation = memberIndentation ?? " "; - this.Ellipsis = ellipsis ?? "..."; - this.NewLine = lineBreak ?? Environment.NewLine; - } - - internal ObjectFormattingOptions Copy( - MemberDisplayFormat? memberFormat = null, - bool? quoteStrings = null, - bool? useHexadecimalNumbers = null, - bool? includeCodePoints = null, - int? maxLineLength = null, - int? maxOutputLength = null, - string memberIndentation = null, - string ellipsis = null, - string newLine = null) - { - return new ObjectFormattingOptions( - memberFormat ?? this.MemberFormat, - quoteStrings ?? this.QuoteStrings, - useHexadecimalNumbers ?? this.UseHexadecimalNumbers, - includeCodePoints ?? this.IncludeCodePoints, - maxLineLength ?? this.MaxLineLength, - maxOutputLength ?? this.MaxOutputLength, - memberIndentation ?? this.MemberIndentation, - ellipsis ?? this.Ellipsis, - newLine ?? this.NewLine); - } - } -} diff --git a/src/Scripting/Core/Scripting.csproj b/src/Scripting/Core/Scripting.csproj index f3f7f1f99c7..2ad9690e1b0 100644 --- a/src/Scripting/Core/Scripting.csproj +++ b/src/Scripting/Core/Scripting.csproj @@ -78,6 +78,14 @@ + + + + + + + + @@ -94,13 +102,12 @@ ScriptingResources.resx - - + + - diff --git a/src/Scripting/CoreTest/ObjectFormatterTestBase.cs b/src/Scripting/CoreTest/ObjectFormatterTestBase.cs index 206a29bf2b3..1ac67657b13 100644 --- a/src/Scripting/CoreTest/ObjectFormatterTestBase.cs +++ b/src/Scripting/CoreTest/ObjectFormatterTestBase.cs @@ -8,10 +8,6 @@ namespace Microsoft.CodeAnalysis.Scripting.Hosting.UnitTests { public abstract class ObjectFormatterTestBase { - internal static readonly ObjectFormattingOptions s_hexa = new ObjectFormattingOptions(useHexadecimalNumbers: true, maxOutputLength: int.MaxValue); - internal static readonly ObjectFormattingOptions s_memberList = new ObjectFormattingOptions(memberFormat: MemberDisplayFormat.List, maxOutputLength: int.MaxValue); - internal static readonly ObjectFormattingOptions s_inline = new ObjectFormattingOptions(memberFormat: MemberDisplayFormat.Inline, maxOutputLength: int.MaxValue); - public void AssertMembers(string str, params string[] expected) { int i = 0; diff --git a/src/Scripting/CoreTest/ScriptingTest.csproj b/src/Scripting/CoreTest/ScriptingTest.csproj index 6022fcf4a1d..9e3bbe9f6d3 100644 --- a/src/Scripting/CoreTest/ScriptingTest.csproj +++ b/src/Scripting/CoreTest/ScriptingTest.csproj @@ -51,6 +51,10 @@ {066F0DBD-C46C-4C20-AFEC-99829A172625} CSharpScripting + + {3e7dea65-317b-4f43-a25d-62f18d96cfd7} + BasicScripting + @@ -67,6 +71,8 @@ + + diff --git a/src/Scripting/CoreTest/TestCSharpObjectFormatter.cs b/src/Scripting/CoreTest/TestCSharpObjectFormatter.cs new file mode 100644 index 00000000000..58cfc31df78 --- /dev/null +++ b/src/Scripting/CoreTest/TestCSharpObjectFormatter.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.CodeAnalysis.CSharp.Scripting.Hosting; + +namespace Microsoft.CodeAnalysis.Scripting.Hosting.UnitTests +{ + public sealed class TestCSharpObjectFormatter : CSharpObjectFormatter + { + public static readonly CommonObjectFormatter SeparateLines = new TestCSharpObjectFormatter(MemberDisplayFormat.SeparateLines); + public static readonly CommonObjectFormatter SingleLine = new TestCSharpObjectFormatter(MemberDisplayFormat.SingleLine); + public static readonly CommonObjectFormatter Hidden = new TestCSharpObjectFormatter(MemberDisplayFormat.Hidden); + + public TestCSharpObjectFormatter( + MemberDisplayFormat memberDisplayFormat = default(MemberDisplayFormat), + bool useHexadecimalNumbers = false, + int lineLengthLimit = int.MaxValue, + int totalLengthLimit = int.MaxValue) + { + MemberDisplayFormat = memberDisplayFormat; + PrimitiveOptions = new CommonPrimitiveFormatter.Options(useHexadecimalNumbers, includeCodePoints: false, omitStringQuotes: false); + InternalBuilderOptions = new BuilderOptions( + indentation: " ", + newLine: Environment.NewLine, + ellipsis: "...", + lineLengthLimit: lineLengthLimit, + totalLengthLimit: totalLengthLimit); + } + + protected override MemberDisplayFormat MemberDisplayFormat { get; } + protected override CommonPrimitiveFormatter.Options PrimitiveOptions { get; } + + internal override BuilderOptions InternalBuilderOptions { get; } + } +} \ No newline at end of file diff --git a/src/Scripting/CoreTest/TestVisualBasicObjectFormatter.cs b/src/Scripting/CoreTest/TestVisualBasicObjectFormatter.cs new file mode 100644 index 00000000000..cd60e705526 --- /dev/null +++ b/src/Scripting/CoreTest/TestVisualBasicObjectFormatter.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.CodeAnalysis.VisualBasic.Scripting.Hosting; + +namespace Microsoft.CodeAnalysis.Scripting.Hosting.UnitTests +{ + public sealed class TestVisualBasicObjectFormatter : VisualBasicObjectFormatter + { + public static readonly CommonObjectFormatter SeparateLines = new TestVisualBasicObjectFormatter(MemberDisplayFormat.SeparateLines); + public static readonly CommonObjectFormatter SingleLine = new TestVisualBasicObjectFormatter(MemberDisplayFormat.SingleLine); + public static readonly CommonObjectFormatter Hidden = new TestVisualBasicObjectFormatter(MemberDisplayFormat.Hidden); + + public TestVisualBasicObjectFormatter( + MemberDisplayFormat memberDisplayFormat = default(MemberDisplayFormat), + bool useHexadecimalNumbers = false, + bool omitStringQuotes = false, + int lineLengthLimit = int.MaxValue, + int totalLengthLimit = int.MaxValue) + { + MemberDisplayFormat = memberDisplayFormat; + PrimitiveOptions = new CommonPrimitiveFormatter.Options(useHexadecimalNumbers, includeCodePoints: false, omitStringQuotes: omitStringQuotes); + InternalBuilderOptions = new BuilderOptions( + indentation: " ", + newLine: Environment.NewLine, + ellipsis: "...", + lineLengthLimit: lineLengthLimit, + totalLengthLimit: totalLengthLimit); + } + + protected override MemberDisplayFormat MemberDisplayFormat { get; } + protected override CommonPrimitiveFormatter.Options PrimitiveOptions { get; } + + internal override BuilderOptions InternalBuilderOptions { get; } + } +} \ No newline at end of file diff --git a/src/Scripting/VisualBasic/BasicScripting.vbproj b/src/Scripting/VisualBasic/BasicScripting.vbproj index 92b498828b5..d0831bdc8f3 100644 --- a/src/Scripting/VisualBasic/BasicScripting.vbproj +++ b/src/Scripting/VisualBasic/BasicScripting.vbproj @@ -43,6 +43,8 @@ + + True True diff --git a/src/Scripting/VisualBasic/Hosting/ObjectFormatter/VisualBasicObjectFormatter.vb b/src/Scripting/VisualBasic/Hosting/ObjectFormatter/VisualBasicObjectFormatter.vb index 2719cfdb3e9..4cf966b47f9 100644 --- a/src/Scripting/VisualBasic/Hosting/ObjectFormatter/VisualBasicObjectFormatter.vb +++ b/src/Scripting/VisualBasic/Hosting/ObjectFormatter/VisualBasicObjectFormatter.vb @@ -1,248 +1,34 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. Imports System.Reflection -Imports System.Text Imports Microsoft.CodeAnalysis.Scripting.Hosting Namespace Microsoft.CodeAnalysis.VisualBasic.Scripting.Hosting - Public NotInheritable Class VisualBasicObjectFormatter - Inherits ObjectFormatter + Public Class VisualBasicObjectFormatter + Inherits CommonObjectFormatter - Public Shared ReadOnly Property Instance As VisualBasicObjectFormatter = New VisualBasicObjectFormatter() + Protected Overrides ReadOnly Property TypeNameFormatter As CommonTypeNameFormatter + Protected Overrides ReadOnly Property PrimitiveFormatter As CommonPrimitiveFormatter - Private Sub New() + Public Sub New() + PrimitiveFormatter = New VisualBasicPrimitiveFormatter() + TypeNameFormatter = New VisualBasicTypeNameFormatter(PrimitiveFormatter) End Sub - Friend Overrides ReadOnly Property VoidDisplayString As Object - Get - ' TODO - Return "" - End Get - End Property - - Friend Overrides ReadOnly Property NullLiteral As String - Get - Return "Nothing" - End Get - End Property - - Friend Overrides Function FormatLiteral(value As Boolean) As String - Return ObjectDisplay.FormatLiteral(value) - End Function - - Friend Overrides Function FormatLiteral(value As Date) As String - Return ObjectDisplay.FormatLiteral(value) - End Function - - Friend Overrides Function FormatLiteral(value As String, quote As Boolean, Optional useHexadecimalNumbers As Boolean = False) As String - Dim options = ObjectDisplayOptions.None - If quote Then - options = options Or ObjectDisplayOptions.UseQuotes Or ObjectDisplayOptions.EscapeNonPrintableCharacters - End If - If useHexadecimalNumbers Then - options = options Or ObjectDisplayOptions.UseHexadecimalNumbers - End If - Return ObjectDisplay.FormatLiteral(value, options) - End Function - - Friend Overrides Function FormatLiteral(c As Char, quote As Boolean, Optional includeCodePoints As Boolean = False, Optional useHexadecimalNumbers As Boolean = False) As String - Dim options = ObjectDisplayOptions.None - If quote Then - options = options Or ObjectDisplayOptions.UseQuotes Or ObjectDisplayOptions.EscapeNonPrintableCharacters - End If - If includeCodePoints Then - options = options Or ObjectDisplayOptions.IncludeCodePoints - End If - If useHexadecimalNumbers Then - options = options Or ObjectDisplayOptions.UseHexadecimalNumbers - End If - Return ObjectDisplay.FormatLiteral(c, options) - End Function - - Friend Overrides Function FormatLiteral(value As SByte, Optional useHexadecimalNumbers As Boolean = False) As String - Return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers), UIFormatCulture) - End Function - - Friend Overrides Function FormatLiteral(value As Byte, Optional useHexadecimalNumbers As Boolean = False) As String - Return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers), UIFormatCulture) - End Function - - Friend Overrides Function FormatLiteral(value As Short, Optional useHexadecimalNumbers As Boolean = False) As String - Return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers), UIFormatCulture) - End Function - - Friend Overrides Function FormatLiteral(value As UShort, Optional useHexadecimalNumbers As Boolean = False) As String - Return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers), UIFormatCulture) - End Function - - Friend Overrides Function FormatLiteral(value As Integer, Optional useHexadecimalNumbers As Boolean = False) As String - Return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers), UIFormatCulture) - End Function - - Friend Overrides Function FormatLiteral(value As UInteger, Optional useHexadecimalNumbers As Boolean = False) As String - Return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers), UIFormatCulture) - End Function - - Friend Overrides Function FormatLiteral(value As Long, Optional useHexadecimalNumbers As Boolean = False) As String - Return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers), UIFormatCulture) - End Function - - Friend Overrides Function FormatLiteral(value As ULong, Optional useHexadecimalNumbers As Boolean = False) As String - Return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers), UIFormatCulture) - End Function - - Friend Overrides Function FormatLiteral(value As Double) As String - Return ObjectDisplay.FormatLiteral(value, ObjectDisplayOptions.None, UIFormatCulture) - End Function - - Friend Overrides Function FormatLiteral(value As Single) As String - Return ObjectDisplay.FormatLiteral(value, ObjectDisplayOptions.None, UIFormatCulture) - End Function - - Friend Overrides Function FormatLiteral(value As Decimal) As String - Return ObjectDisplay.FormatLiteral(value, ObjectDisplayOptions.None, UIFormatCulture) - End Function - - Friend Overrides Function GetPrimitiveTypeName(type As SpecialType) As String - Select Case type - Case SpecialType.System_Boolean - Return "Boolean" - Case SpecialType.System_Byte - Return "Byte" - Case SpecialType.System_Char - Return "Char" - Case SpecialType.System_Decimal - Return "Decimal" - Case SpecialType.System_Double - Return "Double" - Case SpecialType.System_Int16 - Return "Short" - Case SpecialType.System_Int32 - Return "Integer" - Case SpecialType.System_Int64 - Return "Long" - Case SpecialType.System_SByte - Return "SByte" - Case SpecialType.System_Single - Return "Single" - Case SpecialType.System_String - Return "String" - Case SpecialType.System_UInt16 - Return "UShort" - Case SpecialType.System_UInt32 - Return "UInteger" - Case SpecialType.System_UInt64 - Return "ULong" - Case SpecialType.System_DateTime - Return "Date" - Case SpecialType.System_Object - Return "Object" - Case Else - Return Nothing - End Select - End Function - - Friend Overrides ReadOnly Property GenericParameterOpening As String - Get - Return "(Of " - End Get - End Property - - Friend Overrides ReadOnly Property GenericParameterClosing As String - Get - Return ")" - End Get - End Property - - Friend Overrides Function FormatGeneratedTypeName(type As Type) As String - ' TODO: https://github.com/dotnet/roslyn/issues/3739 - Return Nothing - End Function - - Friend Overrides Function FormatArrayTypeName(arrayType As Type, arrayOpt As Array, options As ObjectFormattingOptions) As String - Dim sb As StringBuilder = New StringBuilder() - ' print the inner-most element type first: - Dim elementType As Type = arrayType.GetElementType() - While elementType.IsArray - elementType = elementType.GetElementType() - End While - - sb.Append(FormatTypeName(elementType, options)) - - ' print all components of a jagged array: - Dim type As Type = arrayType - Do - If arrayOpt IsNot Nothing Then - sb.Append("("c) - Dim rank As Integer = type.GetArrayRank() - Dim anyNonzeroLowerBound As Boolean = False - - For i = 0 To rank - 1 - If arrayOpt.GetLowerBound(i) > 0 Then - anyNonzeroLowerBound = True - Exit Do - End If - - i = i + 1 - Next - - For i = 0 To rank - 1 - Dim lowerBound As Integer = arrayOpt.GetLowerBound(i) - Dim length As Integer = arrayOpt.GetLength(i) - If i > 0 Then - sb.Append(", ") - End If - - If anyNonzeroLowerBound Then - AppendArrayBound(sb, lowerBound, options.UseHexadecimalNumbers) - sb.Append("..") - AppendArrayBound(sb, length + lowerBound, options.UseHexadecimalNumbers) - Else - AppendArrayBound(sb, length, options.UseHexadecimalNumbers) - End If - - i = i + 1 - Next - - sb.Append(")"c) - arrayOpt = Nothing - Else - AppendArrayRank(sb, type) - End If - - type = type.GetElementType() - Loop While type.IsArray - - Return sb.ToString() + Protected Overrides Function IsHiddenMember(member As MemberInfo) As Boolean + ' TODO (tomat) + Return False End Function - Private Sub AppendArrayBound(sb As StringBuilder, bound As Long, useHexadecimalNumbers As Boolean) - If bound <= Int32.MaxValue Then - sb.Append(FormatLiteral(CType(bound, Integer), useHexadecimalNumbers)) - Else - sb.Append(FormatLiteral(bound, useHexadecimalNumbers)) - End If - End Sub - - Private Shared Sub AppendArrayRank(sb As StringBuilder, arrayType As Type) - sb.Append("["c) - Dim rank As Integer = arrayType.GetArrayRank() - If rank > 1 Then - sb.Append(","c, rank - 1) - End If - - sb.Append("]"c) - End Sub - - Friend Overrides Function FormatMemberName(member As MemberInfo) As String - Return member.Name + Protected Overrides Function FormatRefKind(parameter As ParameterInfo) As String + Return If(parameter.IsOut, "ByRef", "") End Function - Friend Overrides Function IsHiddenMember(member As MemberInfo) As Boolean - ' TODO (tomat) + Protected Overrides Function TryFormatCompositeObject(obj As Object, ByRef value As String, ByRef suppressMembers As Boolean) As Boolean Return False End Function End Class End Namespace + diff --git a/src/Scripting/VisualBasic/Hosting/ObjectFormatter/VisualBasicPrimitiveFormatter.vb b/src/Scripting/VisualBasic/Hosting/ObjectFormatter/VisualBasicPrimitiveFormatter.vb new file mode 100644 index 00000000000..0b58ef95fb6 --- /dev/null +++ b/src/Scripting/VisualBasic/Hosting/ObjectFormatter/VisualBasicPrimitiveFormatter.vb @@ -0,0 +1,96 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.Scripting.Hosting +Imports Microsoft.CodeAnalysis.Scripting.Hosting.ObjectFormatterHelpers + +Namespace Microsoft.CodeAnalysis.VisualBasic.Scripting.Hosting + + Public Class VisualBasicPrimitiveFormatter + Inherits CommonPrimitiveFormatter + + Protected Overrides ReadOnly Property NullLiteral As String + Get + Return "Nothing" + End Get + End Property + + Protected Overrides Function FormatLiteral(value As Boolean) As String + Return ObjectDisplay.FormatLiteral(value) + End Function + + Protected Overrides Function FormatLiteral(value As Date) As String + Return ObjectDisplay.FormatLiteral(value) + End Function + + Protected Overrides Function FormatLiteral(value As String, quote As Boolean, Optional useHexadecimalNumbers As Boolean = False) As String + Dim options = ObjectDisplayOptions.None + If quote Then + options = options Or ObjectDisplayOptions.UseQuotes + End If + If useHexadecimalNumbers Then + options = options Or ObjectDisplayOptions.UseHexadecimalNumbers + End If + Return ObjectDisplay.FormatLiteral(value, options) + End Function + + Protected Overrides Function FormatLiteral(c As Char, quote As Boolean, Optional includeCodePoints As Boolean = False, Optional useHexadecimalNumbers As Boolean = False) As String + Dim options = ObjectDisplayOptions.None + If quote Then + options = options Or ObjectDisplayOptions.UseQuotes + End If + If includeCodePoints Then + options = options Or ObjectDisplayOptions.IncludeCodePoints + End If + If useHexadecimalNumbers Then + options = options Or ObjectDisplayOptions.UseHexadecimalNumbers + End If + Return ObjectDisplay.FormatLiteral(c, options) + End Function + + Protected Overrides Function FormatLiteral(value As SByte, Optional useHexadecimalNumbers As Boolean = False) As String + Return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers)) + End Function + + Protected Overrides Function FormatLiteral(value As Byte, Optional useHexadecimalNumbers As Boolean = False) As String + Return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers)) + End Function + + Protected Overrides Function FormatLiteral(value As Short, Optional useHexadecimalNumbers As Boolean = False) As String + Return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers)) + End Function + + Protected Overrides Function FormatLiteral(value As UShort, Optional useHexadecimalNumbers As Boolean = False) As String + Return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers)) + End Function + + Protected Overrides Function FormatLiteral(value As Integer, Optional useHexadecimalNumbers As Boolean = False) As String + Return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers)) + End Function + + Protected Overrides Function FormatLiteral(value As UInteger, Optional useHexadecimalNumbers As Boolean = False) As String + Return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers)) + End Function + + Protected Overrides Function FormatLiteral(value As Long, Optional useHexadecimalNumbers As Boolean = False) As String + Return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers)) + End Function + + Protected Overrides Function FormatLiteral(value As ULong, Optional useHexadecimalNumbers As Boolean = False) As String + Return ObjectDisplay.FormatLiteral(value, GetObjectDisplayOptions(useHexadecimalNumbers)) + End Function + + Protected Overrides Function FormatLiteral(value As Double) As String + Return ObjectDisplay.FormatLiteral(value, ObjectDisplayOptions.None) + End Function + + Protected Overrides Function FormatLiteral(value As Single) As String + Return ObjectDisplay.FormatLiteral(value, ObjectDisplayOptions.None) + End Function + + Protected Overrides Function FormatLiteral(value As Decimal) As String + Return ObjectDisplay.FormatLiteral(value, ObjectDisplayOptions.None) + End Function + End Class + +End Namespace + diff --git a/src/Scripting/VisualBasic/Hosting/ObjectFormatter/VisualBasicTypeNameFormatter.vb b/src/Scripting/VisualBasic/Hosting/ObjectFormatter/VisualBasicTypeNameFormatter.vb new file mode 100644 index 00000000000..6fbe87afd2c --- /dev/null +++ b/src/Scripting/VisualBasic/Hosting/ObjectFormatter/VisualBasicTypeNameFormatter.vb @@ -0,0 +1,88 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.Scripting.Hosting + +Namespace Microsoft.CodeAnalysis.VisualBasic.Scripting.Hosting + + Public Class VisualBasicTypeNameFormatter + Inherits CommonTypeNameFormatter + + Protected Overrides ReadOnly Property PrimitiveFormatter As CommonPrimitiveFormatter + + Public Sub New(pFormatter As CommonPrimitiveFormatter) + PrimitiveFormatter = pFormatter + End Sub + + Protected Overrides Function GetPrimitiveTypeName(type As SpecialType) As String + Select Case type + Case SpecialType.System_Boolean + Return "Boolean" + Case SpecialType.System_Byte + Return "Byte" + Case SpecialType.System_Char + Return "Char" + Case SpecialType.System_Decimal + Return "Decimal" + Case SpecialType.System_Double + Return "Double" + Case SpecialType.System_Int16 + Return "Short" + Case SpecialType.System_Int32 + Return "Integer" + Case SpecialType.System_Int64 + Return "Long" + Case SpecialType.System_SByte + Return "SByte" + Case SpecialType.System_Single + Return "Single" + Case SpecialType.System_String + Return "String" + Case SpecialType.System_UInt16 + Return "UShort" + Case SpecialType.System_UInt32 + Return "UInteger" + Case SpecialType.System_UInt64 + Return "ULong" + Case SpecialType.System_DateTime + Return "Date" + Case SpecialType.System_Object + Return "Object" + Case Else + Return Nothing + End Select + End Function + + Protected Overrides ReadOnly Property GenericParameterOpening As String + Get + Return "(Of " + End Get + End Property + + Protected Overrides ReadOnly Property GenericParameterClosing As String + Get + Return ")" + End Get + End Property + + Protected Overrides ReadOnly Property ArrayOpening As String + Get + Return "(" + End Get + End Property + + Protected Overrides ReadOnly Property ArrayClosing As String + Get + Return ")" + End Get + End Property + + + Public Overrides Function FormatTypeName(type As Type, useHexadecimalArrayBounds As Boolean) As String + ' TODO (https://github.com/dotnet/roslyn/issues/3739): handle generated type names (e.g. state machines as in C#) + + Return MyBase.FormatTypeName(type, useHexadecimalArrayBounds) + End Function + End Class + +End Namespace + diff --git a/src/Scripting/VisualBasicTest.Desktop/ObjectFormatterTests.vb b/src/Scripting/VisualBasicTest.Desktop/ObjectFormatterTests.vb index 986442c98d2..b3f5103faa6 100644 --- a/src/Scripting/VisualBasicTest.Desktop/ObjectFormatterTests.vb +++ b/src/Scripting/VisualBasicTest.Desktop/ObjectFormatterTests.vb @@ -1,8 +1,8 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. Imports Microsoft.CodeAnalysis.Scripting.Hosting.UnitTests -Imports ObjectFormatterFixtures Imports Xunit +Imports Formatter = Microsoft.CodeAnalysis.Scripting.Hosting.UnitTests.TestVisualBasicObjectFormatter Namespace Microsoft.CodeAnalysis.VisualBasic.Scripting.Hosting.UnitTests @@ -12,14 +12,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Scripting.Hosting.UnitTests Public Sub DebuggerProxy_FrameworkTypes_ArrayList() Dim obj = New ArrayList From {1, 2, True, "foo"} - Dim str = VisualBasicObjectFormatter.Instance.FormatObject(obj, s_inline) + Dim str = Formatter.SingleLine.FormatObject(obj) Assert.Equal("ArrayList(4) { 1, 2, True, ""foo"" }", str) End Sub Public Sub DebuggerProxy_FrameworkTypes_Hashtable() Dim obj = New Hashtable From {{New Byte() {1, 2}, {1, 2, 3}}} - Dim str = VisualBasicObjectFormatter.Instance.FormatObject(obj, s_memberList) + Dim str = Formatter.SeparateLines.FormatObject(obj) AssertMembers(str, "Hashtable(1)", "{ Byte(2) { 1, 2 }, Integer(3) { 1, 2, 3 } }") End Sub @@ -30,7 +30,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Scripting.Hosting.UnitTests obj.Enqueue(2) obj.Enqueue(3) - Dim str = VisualBasicObjectFormatter.Instance.FormatObject(obj, s_inline) + Dim str = Formatter.SingleLine.FormatObject(obj) Assert.Equal("Queue(3) { 1, 2, 3 }", str) End Sub @@ -41,7 +41,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Scripting.Hosting.UnitTests obj.Push(2) obj.Push(3) - Dim str = VisualBasicObjectFormatter.Instance.FormatObject(obj, s_inline) + Dim str = Formatter.SingleLine.FormatObject(obj) Assert.Equal("Stack(3) { 3, 2, 1 }", str) End Sub @@ -52,12 +52,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Scripting.Hosting.UnitTests obj.Add(1, 5) obj.Add(2, 6) - Dim str = VisualBasicObjectFormatter.Instance.FormatObject(obj, s_inline) + Dim str = Formatter.SingleLine.FormatObject(obj) Assert.Equal("SortedList(3) { { 1, 5 }, { 2, 6 }, { 3, 4 } }", str) obj = New SortedList() obj.Add({3}, New Integer() {4}) - str = VisualBasicObjectFormatter.Instance.FormatObject(obj, s_inline) + str = Formatter.SingleLine.FormatObject(obj) Assert.Equal("SortedList(1) { { Integer(1) { 3 }, Integer(1) { 4 } } }", str) End Sub End Class diff --git a/src/Scripting/VisualBasicTest/ObjectFormatterTests.vb b/src/Scripting/VisualBasicTest/ObjectFormatterTests.vb index 8478e1f0cbb..2a627cee5af 100644 --- a/src/Scripting/VisualBasicTest/ObjectFormatterTests.vb +++ b/src/Scripting/VisualBasicTest/ObjectFormatterTests.vb @@ -4,6 +4,7 @@ Imports Microsoft.CodeAnalysis.Scripting.Hosting Imports Microsoft.CodeAnalysis.Scripting.Hosting.UnitTests Imports ObjectFormatterFixtures Imports Xunit +Imports Formatter = Microsoft.CodeAnalysis.Scripting.Hosting.UnitTests.TestVisualBasicObjectFormatter Namespace Microsoft.CodeAnalysis.VisualBasic.Scripting.Hosting.UnitTests @@ -12,17 +13,20 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Scripting.Hosting.UnitTests Public Sub InlineCharacters() - Assert.Equal("ChrW(20)", VisualBasicObjectFormatter.Instance.FormatObject(ChrW(20), s_inline)) - Assert.Equal("vbBack", VisualBasicObjectFormatter.Instance.FormatObject(ChrW(&H8), s_inline)) + Assert.Equal("ChrW(20)", Formatter.SingleLine.FormatObject(ChrW(20))) + Assert.Equal("vbBack", Formatter.SingleLine.FormatObject(ChrW(&H8))) End Sub Public Sub QuotedStrings() Dim s = "a" & ChrW(&HFFFE) & ChrW(&HFFFF) & vbCrLf & "b" + Dim withQuotes = New Formatter(useHexadecimalNumbers:=True, omitStringQuotes:=False) + Dim withoutQuotes = New Formatter(useHexadecimalNumbers:=True, omitStringQuotes:=True) + ' ObjectFormatter should substitute spaces for non-printable characters - Assert.Equal("""a"" & ChrW(&HABCF) & ChrW(&HABCD) & vbCrLf & ""b""", VisualBasicObjectFormatter.Instance.FormatObject(s, s_hexa.Copy(quoteStrings:=True))) - Assert.Equal("a b", VisualBasicObjectFormatter.Instance.FormatObject(s, s_hexa.Copy(quoteStrings:=False))) + Assert.Equal("""a"" & ChrW(&HABCF) & ChrW(&HABCD) & vbCrLf & ""b""", withQuotes.FormatObject(s)) + Assert.Equal("a b", withoutQuotes.FormatObject(s)) End Sub @@ -30,41 +34,41 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Scripting.Hosting.UnitTests Dim str As String Dim nested As Object = New Outer.Nested(Of Integer)() - str = VisualBasicObjectFormatter.Instance.FormatObject(nested, s_inline) + str = Formatter.SingleLine.FormatObject(nested) Assert.Equal("Outer.Nested(Of Integer) { A=1, B=2 }", str) - str = VisualBasicObjectFormatter.Instance.FormatObject(nested, New ObjectFormattingOptions(memberFormat:=MemberDisplayFormat.NoMembers)) + str = Formatter.Hidden.FormatObject(nested) Assert.Equal("Outer.Nested(Of Integer)", str) - str = VisualBasicObjectFormatter.Instance.FormatObject(A(Of Integer).X, New ObjectFormattingOptions(memberFormat:=MemberDisplayFormat.NoMembers)) + str = Formatter.Hidden.FormatObject(A(Of Integer).X) Assert.Equal("A(Of Integer).B(Of Integer)", str) Dim obj As Object = New A(Of Integer).B(Of Boolean).C.D(Of String, Double).E() - str = VisualBasicObjectFormatter.Instance.FormatObject(obj, New ObjectFormattingOptions(memberFormat:=MemberDisplayFormat.NoMembers)) + str = Formatter.Hidden.FormatObject(obj) Assert.Equal("A(Of Integer).B(Of Boolean).C.D(Of String, Double).E", str) Dim sort = New Sort() - str = VisualBasicObjectFormatter.Instance.FormatObject(sort, New ObjectFormattingOptions(maxLineLength:=51, memberFormat:=MemberDisplayFormat.Inline)) + str = New Formatter(MemberDisplayFormat.SingleLine, lineLengthLimit:=51).FormatObject(sort) Assert.Equal("Sort { aB=-1, ab=1, Ac=-1, Ad=1, ad=-1, aE=1, a ...", str) Assert.Equal(51, str.Length) - str = VisualBasicObjectFormatter.Instance.FormatObject(sort, New ObjectFormattingOptions(maxLineLength:=5, memberFormat:=MemberDisplayFormat.Inline)) + str = New Formatter(MemberDisplayFormat.SingleLine, lineLengthLimit:=5).FormatObject(sort) Assert.Equal("S ...", str) Assert.Equal(5, str.Length) - str = VisualBasicObjectFormatter.Instance.FormatObject(sort, New ObjectFormattingOptions(maxLineLength:=4, memberFormat:=MemberDisplayFormat.Inline)) + str = New Formatter(MemberDisplayFormat.SingleLine, lineLengthLimit:=4).FormatObject(sort) Assert.Equal("...", str) - str = VisualBasicObjectFormatter.Instance.FormatObject(sort, New ObjectFormattingOptions(maxLineLength:=3, memberFormat:=MemberDisplayFormat.Inline)) + str = New Formatter(MemberDisplayFormat.SingleLine, lineLengthLimit:=3).FormatObject(sort) Assert.Equal("...", str) - str = VisualBasicObjectFormatter.Instance.FormatObject(sort, New ObjectFormattingOptions(maxLineLength:=2, memberFormat:=MemberDisplayFormat.Inline)) + str = New Formatter(MemberDisplayFormat.SingleLine, lineLengthLimit:=2).FormatObject(sort) Assert.Equal("...", str) - str = VisualBasicObjectFormatter.Instance.FormatObject(sort, New ObjectFormattingOptions(maxLineLength:=1, memberFormat:=MemberDisplayFormat.Inline)) + str = New Formatter(MemberDisplayFormat.SingleLine, lineLengthLimit:=1).FormatObject(sort) Assert.Equal("...", str) - str = VisualBasicObjectFormatter.Instance.FormatObject(sort, New ObjectFormattingOptions(maxLineLength:=80, memberFormat:=MemberDisplayFormat.Inline)) + str = New Formatter(MemberDisplayFormat.SingleLine, lineLengthLimit:=80).FormatObject(sort) Assert.Equal("Sort { aB=-1, ab=1, Ac=-1, Ad=1, ad=-1, aE=1, aF=-1, AG=1 }", str) End Sub -- GitLab