Skip to content

Commit

Permalink
Allow xml docs to still be created when 'emit metadata only' is on. (#…
Browse files Browse the repository at this point in the history
…57667)

* Allow xml docs to still be created when 'emit metadata only' is on.

* Add VB tests

* Simplify

* Simplify

* Update src/Compilers/Core/Portable/Compilation/Compilation.cs

* Fix

* Remove parameter

* Pass in doc mode explicitly

* Actually show the full xml docs we get

* Update src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs

* Update src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs

* Update src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs
  • Loading branch information
CyrusNajmabadi authored Jan 21, 2022
1 parent 094bee3 commit 5daad64
Show file tree
Hide file tree
Showing 6 changed files with 639 additions and 36 deletions.
18 changes: 11 additions & 7 deletions src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3274,15 +3274,15 @@ private void GenerateModuleInitializer(PEModuleBuilder moduleBeingBuilt, Diagnos
}
}

internal override bool GenerateResourcesAndDocumentationComments(
internal override bool GenerateResources(
CommonPEModuleBuilder moduleBuilder,
Stream? xmlDocStream,
Stream? win32Resources,
bool useRawWin32Resources,
string? outputNameOverride,
DiagnosticBag diagnostics,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();

// Use a temporary bag so we don't have to refilter pre-existing diagnostics.
DiagnosticBag? resourceDiagnostics = DiagnosticBag.GetInstance();

Expand All @@ -3294,11 +3294,15 @@ internal override bool GenerateResourcesAndDocumentationComments(
AddedModulesResourceNames(resourceDiagnostics),
resourceDiagnostics);

if (!FilterAndAppendAndFreeDiagnostics(diagnostics, ref resourceDiagnostics, cancellationToken))
{
return false;
}
return FilterAndAppendAndFreeDiagnostics(diagnostics, ref resourceDiagnostics, cancellationToken);
}

internal override bool GenerateDocumentationComments(
Stream? xmlDocStream,
string? outputNameOverride,
DiagnosticBag diagnostics,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();

// Use a temporary bag so we don't have to refilter pre-existing diagnostics.
Expand Down
305 changes: 304 additions & 1 deletion src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Emit;
using Microsoft.CodeAnalysis.CSharp.Symbols;
Expand Down Expand Up @@ -178,7 +179,6 @@ namespace N.;
Diagnostic(ErrorCode.WRN_UnassignedInternalField, "field").WithArguments("N.X.field", "null").WithLocation(4, 21));
}

// Check that EmitMetadataOnly works
[Fact]
public void EmitMetadataOnly()
{
Expand Down Expand Up @@ -246,6 +246,309 @@ public static void Main()
}
}

[Fact]
public void EmitMetadataOnly_XmlDocs_NoDocMode_Success()
{
CSharpCompilation comp = CreateCompilation(@"
namespace Goo.Bar
{
/// <summary>This should be emitted</summary>
public class Test1
{
public static void SayHello()
{
Console.WriteLine(""hello"");
}
}
}
", assemblyName: "test", parseOptions: CSharpParseOptions.Default.WithDocumentationMode(DocumentationMode.None));

EmitResult emitResult;
byte[] mdOnlyImage;
byte[] xmlDocBytes;

using (var peStream = new MemoryStream())
using (var xmlStream = new MemoryStream())
{
emitResult = comp.Emit(peStream, xmlDocumentationStream: xmlStream, options: new EmitOptions(metadataOnly: true));
mdOnlyImage = peStream.ToArray();
xmlDocBytes = xmlStream.ToArray();
}

Assert.True(emitResult.Success);
emitResult.Diagnostics.Verify();
Assert.True(mdOnlyImage.Length > 0, "no metadata emitted");
Assert.Equal(
@"<?xml version=""1.0""?>
<doc>
<assembly>
<name>test</name>
</assembly>
<members>
</members>
</doc>
",
Encoding.UTF8.GetString(xmlDocBytes));
}

[Fact]
public void EmitMetadataOnly_XmlDocs_NoDocMode_SyntaxWarning()
{
CSharpCompilation comp = CreateCompilation(@"
namespace Goo.Bar
{
/// <summary>This should still emit
public class Test1
{
public static void SayHello()
{
Console.WriteLine(""hello"");
}
}
}
", assemblyName: "test", parseOptions: CSharpParseOptions.Default.WithDocumentationMode(DocumentationMode.None));

EmitResult emitResult;
byte[] mdOnlyImage;
byte[] xmlDocBytes;

using (var peStream = new MemoryStream())
using (var xmlStream = new MemoryStream())
{
emitResult = comp.Emit(peStream, xmlDocumentationStream: xmlStream, options: new EmitOptions(metadataOnly: true));
mdOnlyImage = peStream.ToArray();
xmlDocBytes = xmlStream.ToArray();
}

Assert.True(emitResult.Success);
emitResult.Diagnostics.Verify();

Assert.True(mdOnlyImage.Length > 0, "no metadata emitted");
Assert.Equal(
@"<?xml version=""1.0""?>
<doc>
<assembly>
<name>test</name>
</assembly>
<members>
</members>
</doc>
",
Encoding.UTF8.GetString(xmlDocBytes));
}

[Fact]
public void EmitMetadataOnly_XmlDocs_DiagnoseDocMode_SyntaxWarning()
{
CSharpCompilation comp = CreateCompilation(@"
namespace Goo.Bar
{
/// <summary>This should still emit
public class Test1
{
public static void SayHello()
{
Console.WriteLine(""hello"");
}
}
}
", assemblyName: "test", parseOptions: CSharpParseOptions.Default.WithDocumentationMode(DocumentationMode.Diagnose));

EmitResult emitResult;
byte[] mdOnlyImage;
byte[] xmlDocBytes;

using (var peStream = new MemoryStream())
using (var xmlStream = new MemoryStream())
{
emitResult = comp.Emit(peStream, xmlDocumentationStream: xmlStream, options: new EmitOptions(metadataOnly: true));
mdOnlyImage = peStream.ToArray();
xmlDocBytes = xmlStream.ToArray();
}

// This should not fail the emit (as it's a warning).
Assert.True(emitResult.Success);
emitResult.Diagnostics.Verify(
// (5,1): warning CS1570: XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'
// public class Test1
Diagnostic(ErrorCode.WRN_XMLParseError, "").WithArguments("summary").WithLocation(5, 1),
// (7,28): warning CS1591: Missing XML comment for publicly visible type or member 'Test1.SayHello()'
// public static void SayHello()
Diagnostic(ErrorCode.WRN_MissingXMLComment, "SayHello").WithArguments("Goo.Bar.Test1.SayHello()").WithLocation(7, 28));

Assert.True(mdOnlyImage.Length > 0, "no metadata emitted");
Assert.Equal(
@"<?xml version=""1.0""?>
<doc>
<assembly>
<name>test</name>
</assembly>
<members>
<!-- Badly formed XML comment ignored for member ""T:Goo.Bar.Test1"" -->
</members>
</doc>
",
Encoding.UTF8.GetString(xmlDocBytes));
}

[Fact]
public void EmitMetadataOnly_XmlDocs_DiagnoseDocMode_SemanticWarning()
{
CSharpCompilation comp = CreateCompilation(@"
namespace Goo.Bar
{
/// <summary><see cref=""T""/></summary>
public class Test1
{
public static void SayHello()
{
Console.WriteLine(""hello"");
}
}
}
", assemblyName: "test", parseOptions: CSharpParseOptions.Default.WithDocumentationMode(DocumentationMode.Diagnose));

EmitResult emitResult;
byte[] mdOnlyImage;
byte[] xmlDocBytes;

using (var peStream = new MemoryStream())
using (var xmlStream = new MemoryStream())
{
emitResult = comp.Emit(peStream, xmlDocumentationStream: xmlStream, options: new EmitOptions(metadataOnly: true));
mdOnlyImage = peStream.ToArray();
xmlDocBytes = xmlStream.ToArray();
}

// This should not fail the emit (as it's a warning).
Assert.True(emitResult.Success);
emitResult.Diagnostics.Verify(
// (4,29): warning CS1574: XML comment has cref attribute 'T' that could not be resolved
// /// <summary><see cref="T"/></summary>
Diagnostic(ErrorCode.WRN_BadXMLRef, "T").WithArguments("T").WithLocation(4, 29),
// (7,28): warning CS1591: Missing XML comment for publicly visible type or member 'Test1.SayHello()'
// public static void SayHello()
Diagnostic(ErrorCode.WRN_MissingXMLComment, "SayHello").WithArguments("Goo.Bar.Test1.SayHello()").WithLocation(7, 28));

Assert.True(mdOnlyImage.Length > 0, "no metadata emitted");
Assert.Equal(
@"<?xml version=""1.0""?>
<doc>
<assembly>
<name>test</name>
</assembly>
<members>
<member name=""T:Goo.Bar.Test1"">
<summary><see cref=""!:T""/></summary>
</member>
</members>
</doc>
",
Encoding.UTF8.GetString(xmlDocBytes));
}

[Fact]
public void EmitMetadataOnly_XmlDocs_DiagnoseDocMode_Success()
{
CSharpCompilation comp = CreateCompilation(@"
namespace Goo.Bar
{
/// <summary>This should emit</summary>
public class Test1
{
public static void SayHello()
{
Console.WriteLine(""hello"");
}
}
}
", assemblyName: "test", parseOptions: CSharpParseOptions.Default.WithDocumentationMode(DocumentationMode.Diagnose));

EmitResult emitResult;
byte[] mdOnlyImage;
byte[] xmlDocBytes;

using (var peStream = new MemoryStream())
using (var xmlStream = new MemoryStream())
{
emitResult = comp.Emit(peStream, xmlDocumentationStream: xmlStream, options: new EmitOptions(metadataOnly: true));
mdOnlyImage = peStream.ToArray();
xmlDocBytes = xmlStream.ToArray();
}

// This should not fail the emit (as it's a warning).
Assert.True(emitResult.Success);
emitResult.Diagnostics.Verify(
// (7,28): warning CS1591: Missing XML comment for publicly visible type or member 'Test1.SayHello()'
// public static void SayHello()
Diagnostic(ErrorCode.WRN_MissingXMLComment, "SayHello").WithArguments("Goo.Bar.Test1.SayHello()").WithLocation(7, 28));

Assert.True(mdOnlyImage.Length > 0, "no metadata emitted");
Assert.Equal(
@"<?xml version=""1.0""?>
<doc>
<assembly>
<name>test</name>
</assembly>
<members>
<member name=""T:Goo.Bar.Test1"">
<summary>This should emit</summary>
</member>
</members>
</doc>
",
Encoding.UTF8.GetString(xmlDocBytes));
}

[Fact]
public void EmitMetadataOnly_XmlDocs_ParseDocMode_Success()
{
CSharpCompilation comp = CreateCompilation(@"
namespace Goo.Bar
{
/// <summary>This should emit</summary>
public class Test1
{
public static void SayHello()
{
Console.WriteLine(""hello"");
}
}
}
", assemblyName: "test", parseOptions: CSharpParseOptions.Default.WithDocumentationMode(DocumentationMode.Parse));

EmitResult emitResult;
byte[] mdOnlyImage;
byte[] xmlDocBytes;

using (var peStream = new MemoryStream())
using (var xmlStream = new MemoryStream())
{
emitResult = comp.Emit(peStream, xmlDocumentationStream: xmlStream, options: new EmitOptions(metadataOnly: true));
mdOnlyImage = peStream.ToArray();
xmlDocBytes = xmlStream.ToArray();
}

Assert.True(emitResult.Success);
emitResult.Diagnostics.Verify();

Assert.True(mdOnlyImage.Length > 0, "no metadata emitted");
Assert.Equal(
@"<?xml version=""1.0""?>
<doc>
<assembly>
<name>test</name>
</assembly>
<members>
<member name=""T:Goo.Bar.Test1"">
<summary>This should emit</summary>
</member>
</members>
</doc>
",
Encoding.UTF8.GetString(xmlDocBytes));
}

[Fact]
public void EmitRefAssembly_PrivateMain()
{
Expand Down
11 changes: 3 additions & 8 deletions src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1259,14 +1259,9 @@ private void CompileAndEmit(
return;
}

success = compilation.GenerateResourcesAndDocumentationComments(
moduleBeingBuilt,
xmlStreamDisposerOpt?.Stream,
win32ResourceStreamOpt,
useRawWin32Resources: false,
emitOptions.OutputNameOverride,
diagnostics,
cancellationToken);
success =
compilation.GenerateResources(moduleBeingBuilt, win32ResourceStreamOpt, useRawWin32Resources: false, diagnostics, cancellationToken) &&
compilation.GenerateDocumentationComments(xmlStreamDisposerOpt?.Stream, emitOptions.OutputNameOverride, diagnostics, cancellationToken);
}
}

Expand Down
Loading

0 comments on commit 5daad64

Please sign in to comment.