diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 32b257eda..4d5d62d76 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -209,6 +209,21 @@ namespace ICSharpCode.Decompiler } } + bool showXmlDocumentation = true; + + /// + /// Gets/Sets whether to include XML documentation comments in the decompiled code + /// + public bool ShowXmlDocumentation { + get { return showXmlDocumentation; } + set { + if (showXmlDocumentation != value) { + showXmlDocumentation = value; + OnPropertyChanged("ShowXmlDocumentation"); + } + } + } + public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) diff --git a/ILSpy/CSharpLanguage.cs b/ILSpy/CSharpLanguage.cs index 8e6948e14..50afed4ea 100644 --- a/ILSpy/CSharpLanguage.cs +++ b/ILSpy/CSharpLanguage.cs @@ -32,6 +32,7 @@ using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Ast; using ICSharpCode.Decompiler.Ast.Transforms; using ICSharpCode.ILSpy.Baml; +using ICSharpCode.ILSpy.XmlDoc; using ICSharpCode.NRefactory.CSharp; using Mono.Cecil; @@ -89,8 +90,7 @@ namespace ICSharpCode.ILSpy WriteCommentLine(output, TypeToString(method.DeclaringType, includeNamespace: true)); AstBuilder codeDomBuilder = CreateAstBuilder(options, currentType: method.DeclaringType, isSingleMember: true); codeDomBuilder.AddMethod(method); - codeDomBuilder.RunTransformations(transformAbortCondition); - codeDomBuilder.GenerateCode(output); + RunTransformsAndGenerateCode(codeDomBuilder, output, options); } public override void DecompileProperty(PropertyDefinition property, ITextOutput output, DecompilationOptions options) @@ -98,8 +98,7 @@ namespace ICSharpCode.ILSpy WriteCommentLine(output, TypeToString(property.DeclaringType, includeNamespace: true)); AstBuilder codeDomBuilder = CreateAstBuilder(options, currentType: property.DeclaringType, isSingleMember: true); codeDomBuilder.AddProperty(property); - codeDomBuilder.RunTransformations(transformAbortCondition); - codeDomBuilder.GenerateCode(output); + RunTransformsAndGenerateCode(codeDomBuilder, output, options); } public override void DecompileField(FieldDefinition field, ITextOutput output, DecompilationOptions options) @@ -107,8 +106,7 @@ namespace ICSharpCode.ILSpy WriteCommentLine(output, TypeToString(field.DeclaringType, includeNamespace: true)); AstBuilder codeDomBuilder = CreateAstBuilder(options, currentType: field.DeclaringType, isSingleMember: true); codeDomBuilder.AddField(field); - codeDomBuilder.RunTransformations(transformAbortCondition); - codeDomBuilder.GenerateCode(output); + RunTransformsAndGenerateCode(codeDomBuilder, output, options); } public override void DecompileEvent(EventDefinition ev, ITextOutput output, DecompilationOptions options) @@ -116,16 +114,22 @@ namespace ICSharpCode.ILSpy WriteCommentLine(output, TypeToString(ev.DeclaringType, includeNamespace: true)); AstBuilder codeDomBuilder = CreateAstBuilder(options, currentType: ev.DeclaringType, isSingleMember: true); codeDomBuilder.AddEvent(ev); - codeDomBuilder.RunTransformations(transformAbortCondition); - codeDomBuilder.GenerateCode(output); + RunTransformsAndGenerateCode(codeDomBuilder, output, options); } public override void DecompileType(TypeDefinition type, ITextOutput output, DecompilationOptions options) { AstBuilder codeDomBuilder = CreateAstBuilder(options, currentType: type); codeDomBuilder.AddType(type); - codeDomBuilder.RunTransformations(transformAbortCondition); - codeDomBuilder.GenerateCode(output); + RunTransformsAndGenerateCode(codeDomBuilder, output, options); + } + + void RunTransformsAndGenerateCode(AstBuilder astBuilder, ITextOutput output, DecompilationOptions options) + { + astBuilder.RunTransformations(transformAbortCondition); + if (options.DecompilerSettings.ShowXmlDocumentation) + AddXmlDocTransform.Run(astBuilder.CompilationUnit); + astBuilder.GenerateCode(output); } public override void DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options) diff --git a/ILSpy/DecompilerSettingsPanel.xaml b/ILSpy/DecompilerSettingsPanel.xaml index d9f0e938e..ae1a96404 100644 --- a/ILSpy/DecompilerSettingsPanel.xaml +++ b/ILSpy/DecompilerSettingsPanel.xaml @@ -7,5 +7,6 @@ Decompile enumerators (yield return) Decompile query expressions Use variable names from debug symbols, if available + Show XML documentation in decompiled code \ No newline at end of file diff --git a/ILSpy/DecompilerSettingsPanel.xaml.cs b/ILSpy/DecompilerSettingsPanel.xaml.cs index a9b04086b..d868afa66 100644 --- a/ILSpy/DecompilerSettingsPanel.xaml.cs +++ b/ILSpy/DecompilerSettingsPanel.xaml.cs @@ -62,6 +62,7 @@ namespace ICSharpCode.ILSpy s.YieldReturn = (bool?)e.Attribute("yieldReturn") ?? s.YieldReturn; s.QueryExpressions = (bool?)e.Attribute("queryExpressions") ?? s.QueryExpressions; s.UseDebugSymbols = (bool?)e.Attribute("useDebugSymbols") ?? s.UseDebugSymbols; + s.ShowXmlDocumentation = (bool?)e.Attribute("xmlDoc") ?? s.ShowXmlDocumentation; return s; } @@ -73,6 +74,7 @@ namespace ICSharpCode.ILSpy section.SetAttributeValue("yieldReturn", s.YieldReturn); section.SetAttributeValue("queryExpressions", s.QueryExpressions); section.SetAttributeValue("useDebugSymbols", s.UseDebugSymbols); + section.SetAttributeValue("xmlDoc", s.ShowXmlDocumentation); XElement existingElement = root.Element("DecompilerSettings"); if (existingElement != null) diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj index 836318799..e14465833 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -156,6 +156,7 @@ + diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index f655e3f8a..1241037b1 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -138,17 +138,17 @@ namespace ICSharpCode.ILSpy.TextView } else if (mr is MethodReference) { mr = ((MethodReference)mr).Resolve() ?? mr; } + XmlDocRenderer renderer = new XmlDocRenderer(); + renderer.AppendText(MainWindow.Instance.CurrentLanguage.GetTooltip(mr)); XmlDocumentationProvider docProvider = XmlDocLoader.LoadDocumentation(mr.Module); if (docProvider != null) { - XmlDocRenderer renderer = new XmlDocRenderer(); - renderer.AppendText(MainWindow.Instance.CurrentLanguage.GetTooltip(mr)); string documentation = docProvider.GetDocumentation(XmlDocKeyProvider.GetKey(mr)); if (documentation != null) { renderer.AppendText(Environment.NewLine); renderer.AddXmlDocumentation(documentation); } - return renderer.CreateTextBlock(); } + return renderer.CreateTextBlock(); } return null; } diff --git a/ILSpy/XmlDoc/AddXmlDocTransform.cs b/ILSpy/XmlDoc/AddXmlDocTransform.cs new file mode 100644 index 000000000..dd0fb3311 --- /dev/null +++ b/ILSpy/XmlDoc/AddXmlDocTransform.cs @@ -0,0 +1,80 @@ +// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using ICSharpCode.NRefactory.CSharp; +using Mono.Cecil; + +namespace ICSharpCode.ILSpy.XmlDoc +{ + /// + /// Adds XML documentation for member definitions. + /// + static class AddXmlDocTransform + { + public static void Run(AstNode node) + { + if (node is AttributedNode) { + MemberReference mr = node.Annotation(); + if (mr != null && mr.Module != null) { + var xmldoc = XmlDocLoader.LoadDocumentation(mr.Module); + if (xmldoc != null) { + string doc = xmldoc.GetDocumentation(XmlDocKeyProvider.GetKey(mr)); + if (doc != null) { + InsertXmlDocumentation(node, new StringReader(doc)); + } + } + } + if (!(node is TypeDeclaration)) + return; // don't recurse into attributed nodes, except for type definitions + } + foreach (AstNode child in node.Children) + Run(child); + } + + static void InsertXmlDocumentation(AstNode node, StringReader r) + { + // Find the first non-empty line: + string firstLine; + do { + firstLine = r.ReadLine(); + if (firstLine == null) + return; + } while (string.IsNullOrWhiteSpace(firstLine)); + string indentation = firstLine.Substring(0, firstLine.Length - firstLine.TrimStart().Length); + string line = firstLine; + int skippedWhitespaceLines = 0; + // Copy all lines from input to output, except for empty lines at the end. + while (line != null) { + if (string.IsNullOrWhiteSpace(line)) { + skippedWhitespaceLines++; + } else { + while (skippedWhitespaceLines > 0) { + node.Parent.InsertChildBefore(node, new Comment(string.Empty, CommentType.Documentation), AstNode.Roles.Comment); + skippedWhitespaceLines--; + } + if (line.StartsWith(indentation, StringComparison.Ordinal)) + line = line.Substring(indentation.Length); + node.Parent.InsertChildBefore(node, new Comment(" " + line, CommentType.Documentation), AstNode.Roles.Comment); + } + line = r.ReadLine(); + } + } + } +}