From c252fbc5198861c12c4019554ce22a3d11dbb461 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 7 Aug 2018 21:48:13 +0200 Subject: [PATCH 1/7] * Move ICSharpCode.Decompiler reference to Microsoft.CodeAnalysis.CSharp.EditorFeatures * Add Microsoft.CodeAnalysis.Editor.IDecompiledSourceService * Add basic implementation of the IDecompiledSourceService for C#: CSharpDecompiledSourceService * Move decompilation feature from MetadataAsSourceFileService to CSharpDecompiledSourceService --- .../CSharpDecompiledSourceService.cs | 174 ++++++++++++++++++ .../CSharpDecompiledSourceServiceFactory.cs | 17 ++ ....CodeAnalysis.CSharp.EditorFeatures.csproj | 1 + .../Core/IDecompiledSourceService.cs | 22 +++ .../MetadataAsSourceFileService.cs | 148 +-------------- ...crosoft.CodeAnalysis.EditorFeatures.csproj | 1 - 6 files changed, 223 insertions(+), 140 deletions(-) create mode 100644 src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs create mode 100644 src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceServiceFactory.cs create mode 100644 src/EditorFeatures/Core/IDecompiledSourceService.cs diff --git a/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs b/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs new file mode 100644 index 0000000000000..d860dc321b90a --- /dev/null +++ b/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs @@ -0,0 +1,174 @@ +// 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.IO; +using System.Linq; +using System.Reflection.PortableExecutable; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.CSharp; +using ICSharpCode.Decompiler.CSharp.Transforms; +using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.Decompiler.TypeSystem; + +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.DecompiledSource +{ + internal class CSharpDecompiledSourceService : IDecompiledSourceService + { + private HostLanguageServices provider; + + public CSharpDecompiledSourceService(HostLanguageServices provider) + { + this.provider = provider; + } + + public async Task AddSourceToAsync(Document document, ISymbol symbol, CancellationToken cancellationToken = default) + { + // Get the name of the type the symbol is in + var containingOrThis = symbol.GetContainingTypeOrThis(); + var fullName = GetFullReflectionName(containingOrThis); + + var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + + string assemblyLocation = null; + var isReferenceAssembly = symbol.ContainingAssembly.GetAttributes().Any(attribute => attribute.AttributeClass.Name == nameof(ReferenceAssemblyAttribute) + && attribute.AttributeClass.ToNameDisplayString() == typeof(ReferenceAssemblyAttribute).FullName); + if (isReferenceAssembly) + { + try + { + var fullAssemblyName = symbol.ContainingAssembly.Identity.GetDisplayName(); + GlobalAssemblyCache.Instance.ResolvePartialName(fullAssemblyName, out assemblyLocation, preferredCulture: CultureInfo.CurrentCulture); + } + catch (Exception e) when (FatalError.ReportWithoutCrash(e)) + { + } + } + + if (assemblyLocation == null) + { + var reference = compilation.GetMetadataReference(symbol.ContainingAssembly); + assemblyLocation = (reference as PortableExecutableReference)?.FilePath; + if (assemblyLocation == null) + { + throw new NotSupportedException(EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret); + } + } + + document = PerformDecompilation(document, fullName, compilation, assemblyLocation); + + return document; + } + + static Document PerformDecompilation(Document document, string fullName, Compilation compilation, string assemblyLocation) + { + // Load the assembly. + var pefile = new PEFile(assemblyLocation, PEStreamOptions.PrefetchEntireImage); + + // Initialize a decompiler with default settings. + var settings = new DecompilerSettings(LanguageVersion.Latest); + var decompiler = new CSharpDecompiler(pefile, new RoslynAssemblyResolver(compilation), settings); + // Escape invalid identifiers to prevent Roslyn from failing to parse the generated code. + // (This happens for example, when there is compiler-generated code that is not yet recognized/transformed by the decompiler.) + decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers()); + + var fullTypeName = new FullTypeName(fullName); + + var decompilerVersion = FileVersionInfo.GetVersionInfo(typeof(CSharpDecompiler).Assembly.Location); + + // Add header to match output of metadata-only view. + // (This also makes debugging easier, because you can see which assembly was decompiled inside VS.) + var header = $"#region {FeaturesResources.Assembly} {pefile.FullName}" + Environment.NewLine + + $"// {assemblyLocation}" + Environment.NewLine + + $"// Decompiled with ICSharpCode.Decompiler {decompilerVersion.FileVersion}" + Environment.NewLine + + "#endregion" + Environment.NewLine; + + // Try to decompile; if an exception is thrown the caller will handle it + var text = decompiler.DecompileTypeAsString(fullTypeName); + return document.WithText(SourceText.From(header + text)); + } + + private class RoslynAssemblyResolver : IAssemblyResolver + { + private readonly Compilation parentCompilation; + private static readonly Version zeroVersion = new Version(0, 0, 0, 0); + + public RoslynAssemblyResolver(Compilation parentCompilation) + { + this.parentCompilation = parentCompilation; + } + + public PEFile Resolve(IAssemblyReference name) + { + foreach (var assembly in parentCompilation.GetReferencedAssemblySymbols()) + { + // First, find the correct IAssemblySymbol by name and PublicKeyToken. + if (assembly.Identity.Name != name.Name + || !assembly.Identity.PublicKeyToken.SequenceEqual(name.PublicKeyToken ?? Array.Empty())) + { + continue; + } + + // Normally we skip versions that do not match, except if the reference is "mscorlib" (see comments below) + // or if the name.Version is '0.0.0.0'. This is because we require the metadata of all transitive references + // and modules, to achieve best decompilation results. + // In the case of .NET Standard projects for example, the 'netstandard' reference contains no references + // with actual versions. All versions are '0.0.0.0', therefore we have to ignore those version numbers, + // and can just use the references provided by Roslyn instead. + if (assembly.Identity.Version != name.Version && name.Version != zeroVersion + && !string.Equals("mscorlib", assembly.Identity.Name, StringComparison.OrdinalIgnoreCase)) + { + // MSBuild treats mscorlib special for the purpose of assembly resolution/unification, where all + // versions of the assembly are considered equal. The same policy is adopted here. + continue; + } + + // reference assemblies should be fine here, we only need the metadata of references. + var reference = parentCompilation.GetMetadataReference(assembly); + return new PEFile(reference.Display, PEStreamOptions.PrefetchMetadata); + } + + // not found + return null; + } + + public PEFile ResolveModule(PEFile mainModule, string moduleName) + { + // Primitive implementation to support multi-module assemblies + // where all modules are located next to the main module. + string baseDirectory = Path.GetDirectoryName(mainModule.FileName); + string moduleFileName = Path.Combine(baseDirectory, moduleName); + if (!File.Exists(moduleFileName)) + return null; + return new PEFile(moduleFileName, PEStreamOptions.PrefetchMetadata); + } + } + + private string GetFullReflectionName(INamedTypeSymbol containingType) + { + var stack = new Stack(); + stack.Push(containingType.MetadataName); + var ns = containingType.ContainingNamespace; + do + { + stack.Push(ns.Name); + ns = ns.ContainingNamespace; + } + while (ns != null && !ns.IsGlobalNamespace); + + return string.Join(".", stack); + } + + } +} diff --git a/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceServiceFactory.cs b/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceServiceFactory.cs new file mode 100644 index 0000000000000..197c516c0ffe7 --- /dev/null +++ b/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceServiceFactory.cs @@ -0,0 +1,17 @@ +// 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.Composition; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.DecompiledSource +{ + [ExportLanguageServiceFactory(typeof(IDecompiledSourceService), LanguageNames.CSharp), Shared] + internal partial class CSharpDecompiledSourceServiceFactory : ILanguageServiceFactory + { + public ILanguageService CreateLanguageService(HostLanguageServices provider) + { + return new CSharpDecompiledSourceService(provider); + } + } +} diff --git a/src/EditorFeatures/CSharp/Microsoft.CodeAnalysis.CSharp.EditorFeatures.csproj b/src/EditorFeatures/CSharp/Microsoft.CodeAnalysis.CSharp.EditorFeatures.csproj index bdd3eabdcb37b..80749c1ba4699 100644 --- a/src/EditorFeatures/CSharp/Microsoft.CodeAnalysis.CSharp.EditorFeatures.csproj +++ b/src/EditorFeatures/CSharp/Microsoft.CodeAnalysis.CSharp.EditorFeatures.csproj @@ -42,6 +42,7 @@ + diff --git a/src/EditorFeatures/Core/IDecompiledSourceService.cs b/src/EditorFeatures/Core/IDecompiledSourceService.cs new file mode 100644 index 0000000000000..b04fdb7be7f73 --- /dev/null +++ b/src/EditorFeatures/Core/IDecompiledSourceService.cs @@ -0,0 +1,22 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis.Editor +{ + internal interface IDecompiledSourceService : ILanguageService + { + /// + /// Generates formatted source code containing general information about the symbol's + /// containing assembly and the decompiled source code which the given ISymbol is or is a part of + /// into the given document + /// + /// The document to generate source into + /// The symbol to generate source for + /// To cancel document operations + /// The updated document + Task AddSourceToAsync(Document document, ISymbol symbol, CancellationToken cancellationToken = default); + } +} diff --git a/src/EditorFeatures/Core/Implementation/MetadataAsSource/MetadataAsSourceFileService.cs b/src/EditorFeatures/Core/Implementation/MetadataAsSource/MetadataAsSourceFileService.cs index a669a37648eb7..9c74929ed04a4 100644 --- a/src/EditorFeatures/Core/Implementation/MetadataAsSource/MetadataAsSourceFileService.cs +++ b/src/EditorFeatures/Core/Implementation/MetadataAsSource/MetadataAsSourceFileService.cs @@ -3,19 +3,11 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; -using System.Diagnostics; -using System.Globalization; using System.IO; using System.Linq; -using System.Reflection.PortableExecutable; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using ICSharpCode.Decompiler; -using ICSharpCode.Decompiler.CSharp; -using ICSharpCode.Decompiler.CSharp.Transforms; -using ICSharpCode.Decompiler.Metadata; -using ICSharpCode.Decompiler.TypeSystem; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.MetadataAsSource; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -128,7 +120,15 @@ public async Task GetGeneratedFileAsync(Project project, I { try { - temporaryDocument = await DecompileSymbolAsync(temporaryDocument, symbol, cancellationToken).ConfigureAwait(false); + var decompiledSourceService = temporaryDocument.Project.LanguageServices.GetService(); + if (decompiledSourceService != null) + { + temporaryDocument = await decompiledSourceService.AddSourceToAsync(temporaryDocument, symbol, cancellationToken).ConfigureAwait(false); + } + else + { + useDecompiler = false; + } } catch (Exception e) when (FatalError.ReportWithoutCrashUnlessCanceled(e)) { @@ -191,136 +191,6 @@ public async Task GetGeneratedFileAsync(Project project, I return new MetadataAsSourceFile(fileInfo.TemporaryFilePath, navigateLocation, documentName, documentTooltip); } - private async Task DecompileSymbolAsync(Document temporaryDocument, ISymbol symbol, CancellationToken cancellationToken) - { - // Get the name of the type the symbol is in - var containingOrThis = symbol.GetContainingTypeOrThis(); - var fullName = GetFullReflectionName(containingOrThis); - - var compilation = await temporaryDocument.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); - - string assemblyLocation = null; - var isReferenceAssembly = symbol.ContainingAssembly.GetAttributes().Any(attribute => attribute.AttributeClass.Name == nameof(ReferenceAssemblyAttribute) - && attribute.AttributeClass.ToNameDisplayString() == typeof(ReferenceAssemblyAttribute).FullName); - if (isReferenceAssembly) - { - try - { - var fullAssemblyName = symbol.ContainingAssembly.Identity.GetDisplayName(); - GlobalAssemblyCache.Instance.ResolvePartialName(fullAssemblyName, out assemblyLocation, preferredCulture: CultureInfo.CurrentCulture); - } - catch (Exception e) when (FatalError.ReportWithoutCrash(e)) - { - } - } - - if (assemblyLocation == null) - { - var reference = compilation.GetMetadataReference(symbol.ContainingAssembly); - assemblyLocation = (reference as PortableExecutableReference)?.FilePath; - if (assemblyLocation == null) - { - throw new NotSupportedException(EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret); - } - } - - // Load the assembly. - var pefile = new PEFile(assemblyLocation, PEStreamOptions.PrefetchEntireImage); - - // Initialize a decompiler with default settings. - var settings = new DecompilerSettings(LanguageVersion.Latest); - var decompiler = new CSharpDecompiler(pefile, new RoslynAssemblyResolver(compilation), settings); - // Escape invalid identifiers to prevent Roslyn from failing to parse the generated code. - // (This happens for example, when there is compiler-generated code that is not yet recognized/transformed by the decompiler.) - decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers()); - - var fullTypeName = new FullTypeName(fullName); - - var decompilerVersion = FileVersionInfo.GetVersionInfo(typeof(CSharpDecompiler).Assembly.Location); - - // Add header to match output of metadata-only view. - // (This also makes debugging easier, because you can see which assembly was decompiled inside VS.) - var header = $"#region {FeaturesResources.Assembly} {pefile.FullName}" + Environment.NewLine - + $"// {assemblyLocation}" + Environment.NewLine - + $"// Decompiled with ICSharpCode.Decompiler {decompilerVersion.FileVersion}" + Environment.NewLine - + "#endregion" + Environment.NewLine; - - // Try to decompile; if an exception is thrown the caller will handle it - var text = decompiler.DecompileTypeAsString(fullTypeName); - return temporaryDocument.WithText(SourceText.From(header + text)); - } - - private class RoslynAssemblyResolver : IAssemblyResolver - { - private readonly Compilation parentCompilation; - private static readonly Version zeroVersion = new Version(0, 0, 0, 0); - - public RoslynAssemblyResolver(Compilation parentCompilation) - { - this.parentCompilation = parentCompilation; - } - - public PEFile Resolve(IAssemblyReference name) - { - foreach (var assembly in parentCompilation.GetReferencedAssemblySymbols()) - { - // First, find the correct IAssemblySymbol by name and PublicKeyToken. - if (assembly.Identity.Name != name.Name - || !assembly.Identity.PublicKeyToken.SequenceEqual(name.PublicKeyToken ?? Array.Empty())) - { - continue; - } - - // Normally we skip versions that do not match, except if the reference is "mscorlib" (see comments below) - // or if the name.Version is '0.0.0.0'. This is because we require the metadata of all transitive references - // and modules, to achieve best decompilation results. - // In the case of .NET Standard projects for example, the 'netstandard' reference contains no references - // with actual versions. All versions are '0.0.0.0', therefore we have to ignore those version numbers, - // and can just use the references provided by Roslyn instead. - if (assembly.Identity.Version != name.Version && name.Version != zeroVersion - && !string.Equals("mscorlib", assembly.Identity.Name, StringComparison.OrdinalIgnoreCase)) - { - // MSBuild treats mscorlib special for the purpose of assembly resolution/unification, where all - // versions of the assembly are considered equal. The same policy is adopted here. - continue; - } - - // reference assemblies should be fine here, we only need the metadata of references. - var reference = parentCompilation.GetMetadataReference(assembly); - return new PEFile(reference.Display, PEStreamOptions.PrefetchMetadata); - } - - // not found - return null; - } - - public PEFile ResolveModule(PEFile mainModule, string moduleName) - { - // Primitive implementation to support multi-module assemblies - // where all modules are located next to the main module. - string baseDirectory = Path.GetDirectoryName(mainModule.FileName); - string moduleFileName = Path.Combine(baseDirectory, moduleName); - if (!File.Exists(moduleFileName)) - return null; - return new PEFile(moduleFileName, PEStreamOptions.PrefetchMetadata); - } - } - - private string GetFullReflectionName(INamedTypeSymbol containingType) - { - var stack = new Stack(); - stack.Push(containingType.MetadataName); - var ns = containingType.ContainingNamespace; - do - { - stack.Push(ns.Name); - ns = ns.ContainingNamespace; - } - while (ns != null && !ns.IsGlobalNamespace); - - return string.Join(".", stack); - } - private async Task RelocateSymbol_NoLock(MetadataAsSourceGeneratedFileInfo fileInfo, SymbolKey symbolId, CancellationToken cancellationToken) { // We need to relocate the symbol in the already existing file. If the file is open, we can just diff --git a/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj b/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj index e5dd21eec683c..ef5dcb0726a21 100644 --- a/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj +++ b/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj @@ -29,7 +29,6 @@ - From bbca32a1633192952286ad2f11c8062171444527 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 7 Aug 2018 21:57:05 +0200 Subject: [PATCH 2/7] Do not show legal notice if decompilation is not available. --- .../VisualStudioSymbolNavigationService.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs index 43b716c9cd1a9..eb49dffd4bca6 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs @@ -113,14 +113,20 @@ public bool TryNavigateToSymbol(ISymbol symbol, Project project, OptionSet optio } // Generate new source or retrieve existing source for the symbol in question - var allowDecompilation = project.Solution.Workspace.Options.GetOption(FeatureOnOffOptions.NavigateToDecompiledSources) && !symbol.IsFromSource(); - if (allowDecompilation && !project.Solution.Workspace.Options.GetOption(FeatureOnOffOptions.AcceptedDecompilerDisclaimer)) + bool allowDecompilation = false; + + // Check whether decompilation is supported for the project. We currently only support this for C# projects. + if (project.LanguageServices.GetService() != null) { - var notificationService = project.Solution.Workspace.Services.GetService(); - allowDecompilation = notificationService.ConfirmMessageBox(ServicesVSResources.Decompiler_Legal_Notice_Message, ServicesVSResources.Decompiler_Legal_Notice_Title, NotificationSeverity.Warning); - if (allowDecompilation) + allowDecompilation = project.Solution.Workspace.Options.GetOption(FeatureOnOffOptions.NavigateToDecompiledSources) && !symbol.IsFromSource(); + if (allowDecompilation && !project.Solution.Workspace.Options.GetOption(FeatureOnOffOptions.AcceptedDecompilerDisclaimer)) { - project.Solution.Workspace.Options = project.Solution.Workspace.Options.WithChangedOption(FeatureOnOffOptions.AcceptedDecompilerDisclaimer, true); + var notificationService = project.Solution.Workspace.Services.GetService(); + allowDecompilation = notificationService.ConfirmMessageBox(ServicesVSResources.Decompiler_Legal_Notice_Message, ServicesVSResources.Decompiler_Legal_Notice_Title, NotificationSeverity.Warning); + if (allowDecompilation) + { + project.Solution.Workspace.Options = project.Solution.Workspace.Options.WithChangedOption(FeatureOnOffOptions.AcceptedDecompilerDisclaimer, true); + } } } From 2d2abcda1e99916075f205e7c9e1f8286979ae48 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 7 Aug 2018 22:17:52 +0200 Subject: [PATCH 3/7] Move AssemblyResolver to separate file. --- .../DecompiledSource/AssemblyResolver.cs | 68 +++++++++++++++++++ .../CSharpDecompiledSourceService.cs | 62 +---------------- 2 files changed, 70 insertions(+), 60 deletions(-) create mode 100644 src/EditorFeatures/CSharp/DecompiledSource/AssemblyResolver.cs diff --git a/src/EditorFeatures/CSharp/DecompiledSource/AssemblyResolver.cs b/src/EditorFeatures/CSharp/DecompiledSource/AssemblyResolver.cs new file mode 100644 index 0000000000000..e2d80d09ae427 --- /dev/null +++ b/src/EditorFeatures/CSharp/DecompiledSource/AssemblyResolver.cs @@ -0,0 +1,68 @@ +// 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.IO; +using System.Linq; +using System.Reflection.PortableExecutable; +using ICSharpCode.Decompiler.Metadata; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.DecompiledSource +{ + internal class AssemblyResolver : IAssemblyResolver + { + private readonly Compilation parentCompilation; + private static readonly Version zeroVersion = new Version(0, 0, 0, 0); + + public AssemblyResolver(Compilation parentCompilation) + { + this.parentCompilation = parentCompilation; + } + + public PEFile Resolve(IAssemblyReference name) + { + foreach (var assembly in parentCompilation.GetReferencedAssemblySymbols()) + { + // First, find the correct IAssemblySymbol by name and PublicKeyToken. + if (assembly.Identity.Name != name.Name + || !assembly.Identity.PublicKeyToken.SequenceEqual(name.PublicKeyToken ?? Array.Empty())) + { + continue; + } + + // Normally we skip versions that do not match, except if the reference is "mscorlib" (see comments below) + // or if the name.Version is '0.0.0.0'. This is because we require the metadata of all transitive references + // and modules, to achieve best decompilation results. + // In the case of .NET Standard projects for example, the 'netstandard' reference contains no references + // with actual versions. All versions are '0.0.0.0', therefore we have to ignore those version numbers, + // and can just use the references provided by Roslyn instead. + if (assembly.Identity.Version != name.Version && name.Version != zeroVersion + && !string.Equals("mscorlib", assembly.Identity.Name, StringComparison.OrdinalIgnoreCase)) + { + // MSBuild treats mscorlib special for the purpose of assembly resolution/unification, where all + // versions of the assembly are considered equal. The same policy is adopted here. + continue; + } + + // reference assemblies should be fine here, we only need the metadata of references. + var reference = parentCompilation.GetMetadataReference(assembly); + return new PEFile(reference.Display, PEStreamOptions.PrefetchMetadata); + } + + // not found + return null; + } + + public PEFile ResolveModule(PEFile mainModule, string moduleName) + { + // Primitive implementation to support multi-module assemblies + // where all modules are located next to the main module. + string baseDirectory = Path.GetDirectoryName(mainModule.FileName); + string moduleFileName = Path.Combine(baseDirectory, moduleName); + if (!File.Exists(moduleFileName)) + return null; + return new PEFile(moduleFileName, PEStreamOptions.PrefetchMetadata); + } + } + +} diff --git a/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs b/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs index d860dc321b90a..be858b2bd15fb 100644 --- a/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs +++ b/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs @@ -71,14 +71,13 @@ public async Task AddSourceToAsync(Document document, ISymbol symbol, return document; } - static Document PerformDecompilation(Document document, string fullName, Compilation compilation, string assemblyLocation) + private static Document PerformDecompilation(Document document, string fullName, Compilation compilation, string assemblyLocation) { // Load the assembly. var pefile = new PEFile(assemblyLocation, PEStreamOptions.PrefetchEntireImage); // Initialize a decompiler with default settings. - var settings = new DecompilerSettings(LanguageVersion.Latest); - var decompiler = new CSharpDecompiler(pefile, new RoslynAssemblyResolver(compilation), settings); + var decompiler = new CSharpDecompiler(pefile, new AssemblyResolver(compilation), new DecompilerSettings()); // Escape invalid identifiers to prevent Roslyn from failing to parse the generated code. // (This happens for example, when there is compiler-generated code that is not yet recognized/transformed by the decompiler.) decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers()); @@ -99,62 +98,6 @@ static Document PerformDecompilation(Document document, string fullName, Compila return document.WithText(SourceText.From(header + text)); } - private class RoslynAssemblyResolver : IAssemblyResolver - { - private readonly Compilation parentCompilation; - private static readonly Version zeroVersion = new Version(0, 0, 0, 0); - - public RoslynAssemblyResolver(Compilation parentCompilation) - { - this.parentCompilation = parentCompilation; - } - - public PEFile Resolve(IAssemblyReference name) - { - foreach (var assembly in parentCompilation.GetReferencedAssemblySymbols()) - { - // First, find the correct IAssemblySymbol by name and PublicKeyToken. - if (assembly.Identity.Name != name.Name - || !assembly.Identity.PublicKeyToken.SequenceEqual(name.PublicKeyToken ?? Array.Empty())) - { - continue; - } - - // Normally we skip versions that do not match, except if the reference is "mscorlib" (see comments below) - // or if the name.Version is '0.0.0.0'. This is because we require the metadata of all transitive references - // and modules, to achieve best decompilation results. - // In the case of .NET Standard projects for example, the 'netstandard' reference contains no references - // with actual versions. All versions are '0.0.0.0', therefore we have to ignore those version numbers, - // and can just use the references provided by Roslyn instead. - if (assembly.Identity.Version != name.Version && name.Version != zeroVersion - && !string.Equals("mscorlib", assembly.Identity.Name, StringComparison.OrdinalIgnoreCase)) - { - // MSBuild treats mscorlib special for the purpose of assembly resolution/unification, where all - // versions of the assembly are considered equal. The same policy is adopted here. - continue; - } - - // reference assemblies should be fine here, we only need the metadata of references. - var reference = parentCompilation.GetMetadataReference(assembly); - return new PEFile(reference.Display, PEStreamOptions.PrefetchMetadata); - } - - // not found - return null; - } - - public PEFile ResolveModule(PEFile mainModule, string moduleName) - { - // Primitive implementation to support multi-module assemblies - // where all modules are located next to the main module. - string baseDirectory = Path.GetDirectoryName(mainModule.FileName); - string moduleFileName = Path.Combine(baseDirectory, moduleName); - if (!File.Exists(moduleFileName)) - return null; - return new PEFile(moduleFileName, PEStreamOptions.PrefetchMetadata); - } - } - private string GetFullReflectionName(INamedTypeSymbol containingType) { var stack = new Stack(); @@ -169,6 +112,5 @@ private string GetFullReflectionName(INamedTypeSymbol containingType) return string.Join(".", stack); } - } } From 5e9c2a9f3976738776fdea24285e25ebda5b708d Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 7 Aug 2018 22:21:38 +0200 Subject: [PATCH 4/7] #26436: Use "metadata-as-source" style for documentation comments in decompilation. --- .../CSharpDecompiledSourceService.cs | 14 ++- .../DecompiledSource/DocCommentConverter.cs | 95 +++++++++++++++++++ 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 src/EditorFeatures/CSharp/DecompiledSource/DocCommentConverter.cs diff --git a/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs b/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs index be858b2bd15fb..81278a070f28a 100644 --- a/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs +++ b/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs @@ -16,7 +16,7 @@ using ICSharpCode.Decompiler.CSharp.Transforms; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; - +using Microsoft.CodeAnalysis.DocumentationComments; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -68,6 +68,9 @@ public async Task AddSourceToAsync(Document document, ISymbol symbol, document = PerformDecompilation(document, fullName, compilation, assemblyLocation); + var docCommentFormattingService = document.GetLanguageService(); + document = await ConvertDocCommentsToRegularComments(document, docCommentFormattingService, cancellationToken); + return document; } @@ -98,6 +101,15 @@ private static Document PerformDecompilation(Document document, string fullName, return document.WithText(SourceText.From(header + text)); } + private async Task ConvertDocCommentsToRegularComments(Document document, IDocumentationCommentFormattingService docCommentFormattingService, CancellationToken cancellationToken) + { + var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + var newSyntaxRoot = DocCommentConverter.ConvertToRegularComments(syntaxRoot, docCommentFormattingService, cancellationToken); + + return document.WithSyntaxRoot(newSyntaxRoot); + } + private string GetFullReflectionName(INamedTypeSymbol containingType) { var stack = new Stack(); diff --git a/src/EditorFeatures/CSharp/DecompiledSource/DocCommentConverter.cs b/src/EditorFeatures/CSharp/DecompiledSource/DocCommentConverter.cs new file mode 100644 index 0000000000000..b13b2ebc7df40 --- /dev/null +++ b/src/EditorFeatures/CSharp/DecompiledSource/DocCommentConverter.cs @@ -0,0 +1,95 @@ +// 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.Collections.Generic; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.DocumentationComments; +using Microsoft.CodeAnalysis.MetadataAsSource; +using Microsoft.CodeAnalysis.Shared.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.DecompiledSource +{ + // This is a copy of CSharpMetadataAsSourceService.DocCommentConverter + internal class DocCommentConverter : CSharpSyntaxRewriter + { + private readonly IDocumentationCommentFormattingService _formattingService; + private readonly CancellationToken _cancellationToken; + + public static SyntaxNode ConvertToRegularComments(SyntaxNode node, IDocumentationCommentFormattingService formattingService, CancellationToken cancellationToken) + { + var converter = new DocCommentConverter(formattingService, cancellationToken); + + return converter.Visit(node); + } + + private DocCommentConverter(IDocumentationCommentFormattingService formattingService, CancellationToken cancellationToken) + : base(visitIntoStructuredTrivia: false) + { + _formattingService = formattingService; + _cancellationToken = cancellationToken; + } + + public override SyntaxNode Visit(SyntaxNode node) + { + _cancellationToken.ThrowIfCancellationRequested(); + + if (node == null) + { + return node; + } + + // Process children first + node = base.Visit(node); + + // Check the leading trivia for doc comments. + if (node.GetLeadingTrivia().Any(SyntaxKind.SingleLineDocumentationCommentTrivia)) + { + var newLeadingTrivia = new List(); + + foreach (var trivia in node.GetLeadingTrivia()) + { + if (trivia.Kind() == SyntaxKind.SingleLineDocumentationCommentTrivia) + { + newLeadingTrivia.Add(SyntaxFactory.Comment("//")); + newLeadingTrivia.Add(SyntaxFactory.ElasticCarriageReturnLineFeed); + + var structuredTrivia = (DocumentationCommentTriviaSyntax)trivia.GetStructure(); + newLeadingTrivia.AddRange(ConvertDocCommentToRegularComment(structuredTrivia)); + } + else + { + newLeadingTrivia.Add(trivia); + } + } + + node = node.WithLeadingTrivia(newLeadingTrivia); + } + + return node; + } + + private IEnumerable ConvertDocCommentToRegularComment(DocumentationCommentTriviaSyntax structuredTrivia) + { + var xmlFragment = DocumentationCommentUtilities.ExtractXMLFragment(structuredTrivia.ToFullString(), "///"); + + var docComment = DocumentationComment.FromXmlFragment(xmlFragment); + + var commentLines = AbstractMetadataAsSourceService.DocCommentFormatter.Format(_formattingService, docComment); + + foreach (var line in commentLines) + { + if (!string.IsNullOrWhiteSpace(line)) + { + yield return SyntaxFactory.Comment("// " + line); + } + else + { + yield return SyntaxFactory.Comment("//"); + } + + yield return SyntaxFactory.ElasticCarriageReturnLineFeed; + } + } + } +} From 6c1cc567cd1000065b01975d90a9e5d79185ef68 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 7 Aug 2018 23:31:40 +0200 Subject: [PATCH 5/7] Apply formatter to decompiled code. --- .../CSharpDecompiledSourceService.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs b/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs index 81278a070f28a..5a1ad92ebb1e3 100644 --- a/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs +++ b/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using System.IO; using System.Linq; using System.Reflection.PortableExecutable; using System.Runtime.CompilerServices; @@ -18,9 +17,12 @@ using ICSharpCode.Decompiler.TypeSystem; using Microsoft.CodeAnalysis.DocumentationComments; using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.CSharp.DecompiledSource { @@ -66,11 +68,21 @@ public async Task AddSourceToAsync(Document document, ISymbol symbol, } } + // Decompile document = PerformDecompilation(document, fullName, compilation, assemblyLocation); + // Convert XML doc comments to regular comments, just like MAS var docCommentFormattingService = document.GetLanguageService(); document = await ConvertDocCommentsToRegularComments(document, docCommentFormattingService, cancellationToken); + // Parse document + var node = await document.GetSyntaxRootAsync(); + + // Apply formatting rules + document = await Formatter.FormatAsync( + document, SpecializedCollections.SingletonEnumerable(node.FullSpan), + options: null, rules: GetFormattingRules(document), cancellationToken: cancellationToken).ConfigureAwait(false); + return document; } @@ -110,6 +122,11 @@ private async Task ConvertDocCommentsToRegularComments(Document docume return document.WithSyntaxRoot(newSyntaxRoot); } + private IEnumerable GetFormattingRules(Document document) + { + return Formatter.GetDefaultFormattingRules(document); + } + private string GetFullReflectionName(INamedTypeSymbol containingType) { var stack = new Stack(); From 3d7c2a764da3eda82e3d6f52db81427fc02fde63 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 8 Aug 2018 23:34:56 +0200 Subject: [PATCH 6/7] Move DocCommentConverter to Microsoft.CodeAnalysis.CSharp.DocumentationComments --- .../CSharpDecompiledSourceService.cs | 1 + .../DocCommentConverter.cs | 4 +- .../CSharpMetadataAsSourceService.cs | 83 +------------------ 3 files changed, 3 insertions(+), 85 deletions(-) rename src/{EditorFeatures/CSharp/DecompiledSource => Features/CSharp/Portable/DocumentationComments}/DocCommentConverter.cs (95%) diff --git a/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs b/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs index 5a1ad92ebb1e3..c7c224e451cd3 100644 --- a/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs +++ b/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs @@ -15,6 +15,7 @@ using ICSharpCode.Decompiler.CSharp.Transforms; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; +using Microsoft.CodeAnalysis.CSharp.DocumentationComments; using Microsoft.CodeAnalysis.DocumentationComments; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Formatting; diff --git a/src/EditorFeatures/CSharp/DecompiledSource/DocCommentConverter.cs b/src/Features/CSharp/Portable/DocumentationComments/DocCommentConverter.cs similarity index 95% rename from src/EditorFeatures/CSharp/DecompiledSource/DocCommentConverter.cs rename to src/Features/CSharp/Portable/DocumentationComments/DocCommentConverter.cs index b13b2ebc7df40..1d898e3695318 100644 --- a/src/EditorFeatures/CSharp/DecompiledSource/DocCommentConverter.cs +++ b/src/Features/CSharp/Portable/DocumentationComments/DocCommentConverter.cs @@ -2,15 +2,13 @@ using System.Collections.Generic; using System.Threading; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.DocumentationComments; using Microsoft.CodeAnalysis.MetadataAsSource; using Microsoft.CodeAnalysis.Shared.Utilities; -namespace Microsoft.CodeAnalysis.Editor.CSharp.DecompiledSource +namespace Microsoft.CodeAnalysis.CSharp.DocumentationComments { - // This is a copy of CSharpMetadataAsSourceService.DocCommentConverter internal class DocCommentConverter : CSharpSyntaxRewriter { private readonly IDocumentationCommentFormattingService _formattingService; diff --git a/src/Features/CSharp/Portable/MetadataAsSource/CSharpMetadataAsSourceService.cs b/src/Features/CSharp/Portable/MetadataAsSource/CSharpMetadataAsSourceService.cs index c5520cf337af0..7f2cf2481345c 100644 --- a/src/Features/CSharp/Portable/MetadataAsSource/CSharpMetadataAsSourceService.cs +++ b/src/Features/CSharp/Portable/MetadataAsSource/CSharpMetadataAsSourceService.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.CSharp.DocumentationComments; using Microsoft.CodeAnalysis.CSharp.Simplification; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Utilities; @@ -127,87 +128,5 @@ protected override bool IsNewLine(char c) return SyntaxFacts.IsNewLine(c); } } - - private class DocCommentConverter : CSharpSyntaxRewriter - { - private readonly IDocumentationCommentFormattingService _formattingService; - private readonly CancellationToken _cancellationToken; - - public static SyntaxNode ConvertToRegularComments(SyntaxNode node, IDocumentationCommentFormattingService formattingService, CancellationToken cancellationToken) - { - var converter = new DocCommentConverter(formattingService, cancellationToken); - - return converter.Visit(node); - } - - private DocCommentConverter(IDocumentationCommentFormattingService formattingService, CancellationToken cancellationToken) - : base(visitIntoStructuredTrivia: false) - { - _formattingService = formattingService; - _cancellationToken = cancellationToken; - } - - public override SyntaxNode Visit(SyntaxNode node) - { - _cancellationToken.ThrowIfCancellationRequested(); - - if (node == null) - { - return node; - } - - // Process children first - node = base.Visit(node); - - // Check the leading trivia for doc comments. - if (node.GetLeadingTrivia().Any(SyntaxKind.SingleLineDocumentationCommentTrivia)) - { - var newLeadingTrivia = new List(); - - foreach (var trivia in node.GetLeadingTrivia()) - { - if (trivia.Kind() == SyntaxKind.SingleLineDocumentationCommentTrivia) - { - newLeadingTrivia.Add(SyntaxFactory.Comment("//")); - newLeadingTrivia.Add(SyntaxFactory.ElasticCarriageReturnLineFeed); - - var structuredTrivia = (DocumentationCommentTriviaSyntax)trivia.GetStructure(); - newLeadingTrivia.AddRange(ConvertDocCommentToRegularComment(structuredTrivia)); - } - else - { - newLeadingTrivia.Add(trivia); - } - } - - node = node.WithLeadingTrivia(newLeadingTrivia); - } - - return node; - } - - private IEnumerable ConvertDocCommentToRegularComment(DocumentationCommentTriviaSyntax structuredTrivia) - { - var xmlFragment = DocumentationCommentUtilities.ExtractXMLFragment(structuredTrivia.ToFullString(), "///"); - - var docComment = DocumentationComment.FromXmlFragment(xmlFragment); - - var commentLines = AbstractMetadataAsSourceService.DocCommentFormatter.Format(_formattingService, docComment); - - foreach (var line in commentLines) - { - if (!string.IsNullOrWhiteSpace(line)) - { - yield return SyntaxFactory.Comment("// " + line); - } - else - { - yield return SyntaxFactory.Comment("//"); - } - - yield return SyntaxFactory.ElasticCarriageReturnLineFeed; - } - } - } } } From 2def6a1ebceba25673acae79cb0d7edb28dca203 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 9 Aug 2018 00:44:16 +0200 Subject: [PATCH 7/7] Apply changes requested in code review. --- .../DecompiledSource/AssemblyResolver.cs | 3 + .../CSharpDecompiledSourceService.cs | 67 ++++++++++++------- .../Core/IDecompiledSourceService.cs | 2 +- .../MetadataAsSourceFileService.cs | 2 +- .../VisualStudioSymbolNavigationService.cs | 2 +- 5 files changed, 47 insertions(+), 29 deletions(-) diff --git a/src/EditorFeatures/CSharp/DecompiledSource/AssemblyResolver.cs b/src/EditorFeatures/CSharp/DecompiledSource/AssemblyResolver.cs index e2d80d09ae427..ede9ce90b2ded 100644 --- a/src/EditorFeatures/CSharp/DecompiledSource/AssemblyResolver.cs +++ b/src/EditorFeatures/CSharp/DecompiledSource/AssemblyResolver.cs @@ -60,7 +60,10 @@ public PEFile ResolveModule(PEFile mainModule, string moduleName) string baseDirectory = Path.GetDirectoryName(mainModule.FileName); string moduleFileName = Path.Combine(baseDirectory, moduleName); if (!File.Exists(moduleFileName)) + { return null; + } + return new PEFile(moduleFileName, PEStreamOptions.PrefetchMetadata); } } diff --git a/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs b/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs index c7c224e451cd3..f65663adff739 100644 --- a/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs +++ b/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs @@ -15,12 +15,13 @@ using ICSharpCode.Decompiler.CSharp.Transforms; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.DocumentationComments; using Microsoft.CodeAnalysis.DocumentationComments; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.MetadataAsSource; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -29,14 +30,15 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.DecompiledSource { internal class CSharpDecompiledSourceService : IDecompiledSourceService { - private HostLanguageServices provider; + private readonly HostLanguageServices provider; + private static readonly FileVersionInfo decompilerVersion = FileVersionInfo.GetVersionInfo(typeof(CSharpDecompiler).Assembly.Location); public CSharpDecompiledSourceService(HostLanguageServices provider) { this.provider = provider; } - public async Task AddSourceToAsync(Document document, ISymbol symbol, CancellationToken cancellationToken = default) + public async Task AddSourceToAsync(Document document, ISymbol symbol, CancellationToken cancellationToken) { // Get the name of the type the symbol is in var containingOrThis = symbol.GetContainingTypeOrThis(); @@ -72,60 +74,73 @@ public async Task AddSourceToAsync(Document document, ISymbol symbol, // Decompile document = PerformDecompilation(document, fullName, compilation, assemblyLocation); + document = await AddAssemblyInfoRegionAsync(document, symbol, cancellationToken); + // Convert XML doc comments to regular comments, just like MAS var docCommentFormattingService = document.GetLanguageService(); - document = await ConvertDocCommentsToRegularComments(document, docCommentFormattingService, cancellationToken); + document = await ConvertDocCommentsToRegularCommentsAsync(document, docCommentFormattingService, cancellationToken); - // Parse document - var node = await document.GetSyntaxRootAsync(); + var node = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); // Apply formatting rules document = await Formatter.FormatAsync( document, SpecializedCollections.SingletonEnumerable(node.FullSpan), - options: null, rules: GetFormattingRules(document), cancellationToken: cancellationToken).ConfigureAwait(false); + options: null, Formatter.GetDefaultFormattingRules(document), cancellationToken).ConfigureAwait(false); return document; } - private static Document PerformDecompilation(Document document, string fullName, Compilation compilation, string assemblyLocation) + private Document PerformDecompilation(Document document, string fullName, Compilation compilation, string assemblyLocation) { // Load the assembly. - var pefile = new PEFile(assemblyLocation, PEStreamOptions.PrefetchEntireImage); + var file = new PEFile(assemblyLocation, PEStreamOptions.PrefetchEntireImage); // Initialize a decompiler with default settings. - var decompiler = new CSharpDecompiler(pefile, new AssemblyResolver(compilation), new DecompilerSettings()); + var decompiler = new CSharpDecompiler(file, new AssemblyResolver(compilation), new DecompilerSettings()); // Escape invalid identifiers to prevent Roslyn from failing to parse the generated code. // (This happens for example, when there is compiler-generated code that is not yet recognized/transformed by the decompiler.) decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers()); var fullTypeName = new FullTypeName(fullName); - var decompilerVersion = FileVersionInfo.GetVersionInfo(typeof(CSharpDecompiler).Assembly.Location); - - // Add header to match output of metadata-only view. - // (This also makes debugging easier, because you can see which assembly was decompiled inside VS.) - var header = $"#region {FeaturesResources.Assembly} {pefile.FullName}" + Environment.NewLine - + $"// {assemblyLocation}" + Environment.NewLine - + $"// Decompiled with ICSharpCode.Decompiler {decompilerVersion.FileVersion}" + Environment.NewLine - + "#endregion" + Environment.NewLine; - // Try to decompile; if an exception is thrown the caller will handle it var text = decompiler.DecompileTypeAsString(fullTypeName); - return document.WithText(SourceText.From(header + text)); + return document.WithText(SourceText.From(text)); } - private async Task ConvertDocCommentsToRegularComments(Document document, IDocumentationCommentFormattingService docCommentFormattingService, CancellationToken cancellationToken) + private async Task AddAssemblyInfoRegionAsync(Document document, ISymbol symbol, CancellationToken cancellationToken) { - var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + string assemblyInfo = MetadataAsSourceHelpers.GetAssemblyInfo(symbol.ContainingAssembly); + var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + string assemblyPath = MetadataAsSourceHelpers.GetAssemblyDisplay(compilation, symbol.ContainingAssembly); - var newSyntaxRoot = DocCommentConverter.ConvertToRegularComments(syntaxRoot, docCommentFormattingService, cancellationToken); + var regionTrivia = SyntaxFactory.RegionDirectiveTrivia(true) + .WithTrailingTrivia(new[] { SyntaxFactory.Space, SyntaxFactory.PreprocessingMessage(assemblyInfo) }); - return document.WithSyntaxRoot(newSyntaxRoot); + var oldRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var newRoot = oldRoot.WithLeadingTrivia(new[] + { + SyntaxFactory.Trivia(regionTrivia), + SyntaxFactory.CarriageReturnLineFeed, + SyntaxFactory.Comment("// " + assemblyPath), + SyntaxFactory.CarriageReturnLineFeed, + SyntaxFactory.Comment($"// Decompiled with ICSharpCode.Decompiler {decompilerVersion.FileVersion}"), + SyntaxFactory.CarriageReturnLineFeed, + SyntaxFactory.Trivia(SyntaxFactory.EndRegionDirectiveTrivia(true)), + SyntaxFactory.CarriageReturnLineFeed, + SyntaxFactory.CarriageReturnLineFeed + }); + + return document.WithSyntaxRoot(newRoot); } - private IEnumerable GetFormattingRules(Document document) + private async Task ConvertDocCommentsToRegularCommentsAsync(Document document, IDocumentationCommentFormattingService docCommentFormattingService, CancellationToken cancellationToken) { - return Formatter.GetDefaultFormattingRules(document); + var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + var newSyntaxRoot = DocCommentConverter.ConvertToRegularComments(syntaxRoot, docCommentFormattingService, cancellationToken); + + return document.WithSyntaxRoot(newSyntaxRoot); } private string GetFullReflectionName(INamedTypeSymbol containingType) diff --git a/src/EditorFeatures/Core/IDecompiledSourceService.cs b/src/EditorFeatures/Core/IDecompiledSourceService.cs index b04fdb7be7f73..838478c289910 100644 --- a/src/EditorFeatures/Core/IDecompiledSourceService.cs +++ b/src/EditorFeatures/Core/IDecompiledSourceService.cs @@ -17,6 +17,6 @@ internal interface IDecompiledSourceService : ILanguageService /// The symbol to generate source for /// To cancel document operations /// The updated document - Task AddSourceToAsync(Document document, ISymbol symbol, CancellationToken cancellationToken = default); + Task AddSourceToAsync(Document document, ISymbol symbol, CancellationToken cancellationToken); } } diff --git a/src/EditorFeatures/Core/Implementation/MetadataAsSource/MetadataAsSourceFileService.cs b/src/EditorFeatures/Core/Implementation/MetadataAsSource/MetadataAsSourceFileService.cs index 9c74929ed04a4..474a1ec9937bd 100644 --- a/src/EditorFeatures/Core/Implementation/MetadataAsSource/MetadataAsSourceFileService.cs +++ b/src/EditorFeatures/Core/Implementation/MetadataAsSource/MetadataAsSourceFileService.cs @@ -120,7 +120,7 @@ public async Task GetGeneratedFileAsync(Project project, I { try { - var decompiledSourceService = temporaryDocument.Project.LanguageServices.GetService(); + var decompiledSourceService = temporaryDocument.GetLanguageService(); if (decompiledSourceService != null) { temporaryDocument = await decompiledSourceService.AddSourceToAsync(temporaryDocument, symbol, cancellationToken).ConfigureAwait(false); diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs index eb49dffd4bca6..6c1f383f82830 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs @@ -113,7 +113,7 @@ public bool TryNavigateToSymbol(ISymbol symbol, Project project, OptionSet optio } // Generate new source or retrieve existing source for the symbol in question - bool allowDecompilation = false; + var allowDecompilation = false; // Check whether decompilation is supported for the project. We currently only support this for C# projects. if (project.LanguageServices.GetService() != null)