diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 92bed07080910..b26bfd75c1c26 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -4773,6 +4773,8 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ -modulename:<string> Specify the name of the source module -generatedfilesout:<dir> Place files generated during compilation in the specified directory. +-generatedartifactsout:<dir> Place artifacts generated during compilation in the + specified directory. Visual C# Compiler Options diff --git a/src/Compilers/CSharp/Portable/CommandLine/CSharpCommandLineParser.cs b/src/Compilers/CSharp/Portable/CommandLine/CSharpCommandLineParser.cs index 5e565391886b7..7b8787131c2ec 100644 --- a/src/Compilers/CSharp/Portable/CommandLine/CSharpCommandLineParser.cs +++ b/src/Compilers/CSharp/Portable/CommandLine/CSharpCommandLineParser.cs @@ -78,6 +78,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar string? outputRefFilePath = null; bool refOnly = false; string? generatedFilesOutputDirectory = null; + string? generatedArtifactsOutputDirectory = null; string? documentationPath = null; ErrorLogOptions? errorLogOptions = null; bool parseDocumentationComments = false; //Don't just null check documentationFileName because we want to do this even if the file name is invalid. @@ -626,6 +627,17 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar } continue; + case "generatedartifactsout": + if (string.IsNullOrWhiteSpace(value)) + { + AddDiagnostic(diagnostics, ErrorCode.ERR_SwitchNeedsString, MessageID.IDS_Text.Localize(), arg); + } + else + { + generatedArtifactsOutputDirectory = ParseGenericPathToFile(value, diagnostics, baseDirectory); + } + continue; + case "doc": parseDocumentationComments = true; if (RoslynString.IsNullOrEmpty(value)) @@ -1503,6 +1515,7 @@ internal sealed override CommandLineArguments CommonParse(IEnumerable ar OutputDirectory = outputDirectory!, // error produced when null DocumentationPath = documentationPath, GeneratedFilesOutputDirectory = generatedFilesOutputDirectory, + GeneratedArtifactsOutputDirectory = generatedArtifactsOutputDirectory, ErrorLogOptions = errorLogOptions, AppConfigPath = appConfigPath, SourceFiles = sourceFiles.AsImmutable(), diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index e59d0378498e5..2fc58dda98cc9 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -1288,8 +1288,10 @@ -modulename:<string> Specify the name of the source module -generatedfilesout:<dir> Place files generated during compilation in the specified directory. +-generatedartifactsout:<dir> Place artifacts generated during compilation in the + specified directory. - + Parametry kompilátoru Visual C# - VÝSTUPNÍ SOUBORY - diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index a7f7daf9c9531..e5f83d9d702fb 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -1288,8 +1288,10 @@ -modulename:<string> Specify the name of the source module -generatedfilesout:<dir> Place files generated during compilation in the specified directory. +-generatedartifactsout:<dir> Place artifacts generated during compilation in the + specified directory. - + Visual C#-Compileroptionen – AUSGABEDATEIEN – diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index bf8254701b669..0e891c81cc358 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -1288,8 +1288,10 @@ -modulename:<string> Specify the name of the source module -generatedfilesout:<dir> Place files generated during compilation in the specified directory. +-generatedartifactsout:<dir> Place artifacts generated during compilation in the + specified directory. - + Opciones del compilador de Visual C# - ARCHIVOS DE SALIDA - diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 27851497d9563..1cbe7f6e3b077 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -1288,8 +1288,10 @@ -modulename:<string> Specify the name of the source module -generatedfilesout:<dir> Place files generated during compilation in the specified directory. +-generatedartifactsout:<dir> Place artifacts generated during compilation in the + specified directory. - + Options du compilateur Visual C# - FICHIERS DE SORTIE - diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 8e618c1df1719..91e9f76e13c28 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -1288,8 +1288,10 @@ -modulename:<string> Specify the name of the source module -generatedfilesout:<dir> Place files generated during compilation in the specified directory. +-generatedartifactsout:<dir> Place artifacts generated during compilation in the + specified directory. - + Opzioni del compilatore Visual C# - FILE DI OUTPUT - diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index b3e7c9c2ea654..000ab78b72885 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -1288,6 +1288,8 @@ -modulename:<string> Specify the name of the source module -generatedfilesout:<dir> Place files generated during compilation in the specified directory. +-generatedartifactsout:<dir> Place artifacts generated during compilation in the + specified directory. Visual C# Compiler のオプション diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index b85ec5a5945db..9293330c91158 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -1288,6 +1288,8 @@ -modulename:<string> Specify the name of the source module -generatedfilesout:<dir> Place files generated during compilation in the specified directory. +-generatedartifactsout:<dir> Place artifacts generated during compilation in the + specified directory. Visual C# 컴파일러 옵션 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index e4ffe0585be1f..31d5ac8b13540 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -1288,6 +1288,8 @@ -modulename:<string> Specify the name of the source module -generatedfilesout:<dir> Place files generated during compilation in the specified directory. +-generatedartifactsout:<dir> Place artifacts generated during compilation in the + specified directory. Opcje kompilatora Visual C# diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 96850ee838524..28f9e85c1fbde 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -1288,6 +1288,8 @@ -modulename:<string> Specify the name of the source module -generatedfilesout:<dir> Place files generated during compilation in the specified directory. +-generatedartifactsout:<dir> Place artifacts generated during compilation in the + specified directory. Opções do Compilador do Visual C# diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 2c3eac72f5f74..07e048d2cb9c9 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -1288,6 +1288,8 @@ -modulename:<string> Specify the name of the source module -generatedfilesout:<dir> Place files generated during compilation in the specified directory. +-generatedartifactsout:<dir> Place artifacts generated during compilation in the + specified directory. Параметры компилятора Visual C# diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index f22b18b12866e..a3c3858ce67fc 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -1288,6 +1288,8 @@ -modulename:<string> Specify the name of the source module -generatedfilesout:<dir> Place files generated during compilation in the specified directory. +-generatedartifactsout:<dir> Place artifacts generated during compilation in the + specified directory. Visual C# Derleyici Seçenekleri diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 4c5590fec058c..b8de7f11211c7 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -1288,6 +1288,8 @@ -modulename:<string> Specify the name of the source module -generatedfilesout:<dir> Place files generated during compilation in the specified directory. +-generatedartifactsout:<dir> Place artifacts generated during compilation in the + specified directory. Visual C# 编译器选项 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 3761d02b61a95..f63bd1ec27b79 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -1288,6 +1288,8 @@ -modulename:<string> Specify the name of the source module -generatedfilesout:<dir> Place files generated during compilation in the specified directory. +-generatedartifactsout:<dir> Place artifacts generated during compilation in the + specified directory. Visual C# 編譯器選項 diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs index ce8631fc49ca7..2c6e871b05a68 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs @@ -9811,7 +9811,7 @@ private string VerifyOutput(TempDirectory sourceDir, TempFile sourceFile, if (expectedInfoCount == 0) { - Assert.DoesNotContain("info", output, StringComparison.Ordinal); + Assert.DoesNotContain("info", output.Split('\n').First(), StringComparison.Ordinal); } else { @@ -9831,7 +9831,7 @@ private string VerifyOutput(TempDirectory sourceDir, TempFile sourceFile, if (expectedErrorCount == 0) { - Assert.DoesNotContain("error", output, StringComparison.Ordinal); + Assert.DoesNotContain("error", output.Split('\n').First(), StringComparison.Ordinal); } else { @@ -12466,6 +12466,8 @@ public void TestWarnAsErrorMinusDoesNotNullifyEditorConfig( analyzers: new[] { analyzer }); } + #region Source Generator tests + [Fact] public void SourceGenerators_EmbeddedSources() { @@ -13008,6 +13010,220 @@ public void SourceGeneratorsRunRegardlessOfLanguageVersion(LanguageVersion versi Assert.Contains("CS8785: Generator 'CallbackGenerator' failed to generate source.", output); } + #endregion + + #region Artifact Producer tests + + [Fact] + public void ArtifactProducer_WriteGeneratedSources() + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("temp.cs").WriteAllText(""); + var generatedDir = dir.CreateDirectory("generated"); + + var generatedSource = "public class D { }"; + var producer = new SingleFileArtifactProducer(generatedSource, "generatedSource.cs"); + + VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedartifactsout:" + generatedDir.Path, "/langversion:preview", "/out:embed.exe" }, analyzers: new[] { producer }); + ValidateWrittenSources(new() { { generatedDir.Path, new() { { "generatedSource.cs", generatedSource } } } }); + } + + [Fact] + public void ArtifactProducer_NonClosedStreams() + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("temp.cs").WriteAllText(""); + var generatedDir = dir.CreateDirectory("generated"); + + var generatedSource = "public class D { }"; + var producer = new DoNotCloseStreamArtifactProducer(generatedSource, "generatedSource.cs"); + + VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedartifactsout:" + generatedDir.Path, "/langversion:preview", "/out:embed.exe" }, analyzers: new[] { producer }); + ValidateWrittenSources(new() { { generatedDir.Path, new() { { "generatedSource.cs", generatedSource } } } }); + } + + [Fact] + public void ArtifactProducer_OverwriteGeneratedSources() + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("temp.cs").WriteAllText(""); + var generatedDir = dir.CreateDirectory("generated"); + + var generatedSource1 = "class D { } class E { }"; + var producer1 = new SingleFileArtifactProducer(generatedSource1, "generatedSource.cs"); + + VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedartifactsout:" + generatedDir.Path, "/langversion:preview", "/out:embed.exe" }, analyzers: new[] { producer1 }); + ValidateWrittenSources(new() { { generatedDir.Path, new() { { "generatedSource.cs", generatedSource1 } } } }); + + var generatedSource2 = "public class D { }"; + var producer2 = new SingleFileArtifactProducer(generatedSource2, "generatedSource.cs"); + + VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedartifactsout:" + generatedDir.Path, "/langversion:preview", "/out:embed.exe" }, analyzers: new[] { producer2 }); + ValidateWrittenSources(new() { { generatedDir.Path, new() { { "generatedSource.cs", generatedSource2 } } } }); + } + + [Theory] + [InlineData("partial class D {}", "file1.cs", "partial class E {}", "file2.cs")] // different files, different names + [InlineData("partial class D {}", "file1.cs", "partial class E {}", "file1.cs")] // different files, same names + [InlineData("partial class D {}", "file1.cs", "partial class D {}", "file2.cs")] // same files, different names + [InlineData("partial class D {}", "file1.cs", "partial class D {}", "file1.cs")] // same files, same names + [InlineData("partial class D {}", "file1.cs", "", "file2.cs")] // empty second file + public void ArtifactProducer_WriteGeneratedSources_MultipleFiles(string source1, string source1Name, string source2, string source2Name) + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("temp.cs").WriteAllText(""); + var generatedDir = dir.CreateDirectory("generated"); + + var producer1 = new SingleFileArtifactProducer(source1, Path.Combine("gen1", source1Name)); + var producer2 = new SingleFileArtifactProducer2(source2, Path.Combine("gen2", source2Name)); + + VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedartifactsout:" + generatedDir.Path, "/langversion:preview", "/out:embed.exe" }, analyzers: new[] { producer1, producer2 }); + ValidateWrittenSources(new() + { + { Path.Combine(generatedDir.Path, "gen1"), new() { { source1Name, source1 } } }, + { Path.Combine(generatedDir.Path, "gen2"), new() { { source2Name, source2 } } } + }); + } + + [Fact] + public void ArtifactProducer_MultipleStreamsWithSamePath() + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("temp.cs").WriteAllText(""); + var generatedDir = dir.CreateDirectory("generated"); + + var producer1 = new DoNotCloseStreamArtifactProducer(" ", "file.cs"); + var producer2 = new DoNotCloseStreamArtifactProducer(" ", "file.cs"); + + var output = VerifyOutput(dir, src, expectedWarningCount: 1, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedartifactsout:" + generatedDir.Path, "/langversion:preview", "/out:embed.exe" }, analyzers: new[] { producer1, producer2 }); + Assert.Contains("warning AD0001", output); + } + + [Fact] + public void ArtifactProducer_WithoutAttribute() + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("temp.cs").WriteAllText(""); + var generatedDir = dir.CreateDirectory("generated"); + + var producer1 = new DiagnosticAnalyzerWithoutArtifactProducerAttribute(" ", "file.cs"); + + var output = VerifyOutput(dir, src, expectedWarningCount: 1, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedartifactsout:" + generatedDir.Path, "/langversion:preview", "/out:embed.exe" }, analyzers: new[] { producer1 }); + Assert.Contains("NotSupportedException", output); + } + + [Fact] + public void ArtifactProducer_WithoutCommandLineArgGetsNoContext() + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("temp.cs").WriteAllText(""); + var generatedDir = dir.CreateDirectory("generated"); + + var producer1 = new DiagnosticAnalyzerWithoutCommandLineArgGetsNoContext(" ", "file.cs"); + VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/langversion:preview", "/out:embed.exe" }, analyzers: new[] { producer1 }); + } + + [Fact] + public void ArtifactProducer_WithoutCommandLineArgThrowsWhenUsingContext() + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("temp.cs").WriteAllText(""); + var producer1 = new DiagnosticAnalyzerWithoutCommandLineArgThrowsWhenUsingContext(" ", "file.cs"); + + var output = VerifyOutput(dir, src, expectedWarningCount: 1, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/langversion:preview", "/out:embed.exe" }, analyzers: new[] { producer1 }); + Assert.Contains("NullReferenceException", output); + } + + [Fact] + public void ArtifactProducer_DoNotWriteGeneratedSources_When_No_Directory_Supplied() + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("temp.cs").WriteAllText(""); + var generatedDir = dir.CreateDirectory("generated"); + + var generatedSource = "public class D { }"; + var producer = new SingleFileArtifactProducer(generatedSource, "generatedSource.cs"); + + VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/langversion:preview", "/out:embed.exe" }, analyzers: new[] { producer }); + ValidateWrittenSources(new() { { generatedDir.Path, new() } }); + } + + [Fact] + public void ArtifactProducer_NoError_When_GeneratedDir_NotExist() + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("temp.cs").WriteAllText(""); + var generatedDirPath = Path.Combine(dir.Path, "noexist"); + var generatedSource = "public class D { }"; + var producer = new SingleFileArtifactProducer(generatedSource, "generatedSource.cs"); + + VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedartifactsout:" + generatedDirPath, "/langversion:preview", "/out:embed.exe" }, analyzers: new[] { producer }); + ValidateWrittenSources(new() + { + { generatedDirPath, new() { { "generatedSource.cs", generatedSource } } }, + }); + } + + [Fact] + public void ArtifactProducer_Error_When_NoDirectoryArgumentGiven() + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("temp.cs").WriteAllText(""); + var output = VerifyOutput(dir, src, expectedErrorCount: 2, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedartifactsout:", "/langversion:preview", "/out:embed.exe" }); + Assert.Contains("error CS2006: Command-line syntax error: Missing '' for '/generatedartifactsout:' option", output); + } + + [Fact] + public void ArtifactProducer_ReportedWrittenFiles_To_TouchedFilesLogger() + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("temp.cs").WriteAllText(""); + var generatedDir = dir.CreateDirectory("generated"); + + var generatedSource = "public class D { }"; + var producer = new SingleFileArtifactProducer(generatedSource, "generatedSource.cs"); + + VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedartifactsout:" + generatedDir.Path, $"/touchedfiles:{dir.Path}/touched", "/langversion:preview", "/out:embed.exe" }, analyzers: new[] { producer }); + + var touchedFiles = Directory.GetFiles(dir.Path, "touched*"); + Assert.Equal(2, touchedFiles.Length); + + string[] writtenText = File.ReadAllLines(Path.Combine(dir.Path, "touched.write")); + Assert.Equal(2, writtenText.Length); + Assert.EndsWith("EMBED.EXE", writtenText[0], StringComparison.OrdinalIgnoreCase); + Assert.EndsWith("GENERATEDSOURCE.CS", writtenText[1], StringComparison.OrdinalIgnoreCase); + } + + [Fact] + [WorkItem(44087, "https://github.com/dotnet/roslyn/issues/44087")] + public void ArtifactProducersAndAnalyzerConfig() + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("temp.cs").WriteAllText(""); + var analyzerConfig = dir.CreateFile(".editorconfig").WriteAllText(@" +[*.cs] +key = value"); + + var producer = new SingleFileArtifactProducer("public class D {}", "generated.cs"); + + VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/analyzerconfig:" + analyzerConfig.Path }, analyzers: new[] { producer }); + } + + [Theory] + [CombinatorialData] + public void ArtifactProducersRunRegardlessOfLanguageVersion(LanguageVersion version) + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("temp.cs").WriteAllText(""); + var generatedDir = dir.CreateDirectory("generated"); + var producer = new CallbackArtifactProducer(i => { }, e => throw null); + + var output = VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, additionalFlags: new[] { "/generatedartifactsout:" + generatedDir.Path, "/langversion:" + version.ToDisplayString() }, analyzers: new[] { producer }, expectedWarningCount: 1, expectedErrorCount: 0, expectedExitCode: 0); + Assert.Contains("warning AD0001: Analyzer 'Roslyn.Test.Utilities.TestGenerators.CallbackArtifactProducer' threw an exception of type 'System.NullReferenceException' with message 'Object reference not set to an instance of an object.'", output); + } + + #endregion + [DiagnosticAnalyzer(LanguageNames.CSharp)] private sealed class FieldAnalyzer : DiagnosticAnalyzer { diff --git a/src/Compilers/Core/CodeAnalysisTest/Diagnostics/DiagnosticLocalizationTests.cs b/src/Compilers/Core/CodeAnalysisTest/Diagnostics/DiagnosticLocalizationTests.cs index bac8858df061e..ed9898f3161c9 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Diagnostics/DiagnosticLocalizationTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Diagnostics/DiagnosticLocalizationTests.cs @@ -303,7 +303,7 @@ private static void TestDescriptorIsExceptionSafeCore(DiagnosticDescriptor descr Action onAnalyzerException = (ex, a, diag) => exceptionDiagnostics.Add(diag); var analyzerManager = new AnalyzerManager(analyzer); - var analyzerExecutor = AnalyzerExecutor.CreateForSupportedDiagnostics(onAnalyzerException, analyzerManager); + var analyzerExecutor = AnalyzerExecutor.CreateForSupportedDiagnostics(onAnalyzerException, createArtifactStream: null, analyzerManager); var descriptors = analyzerManager.GetSupportedDiagnosticDescriptors(analyzer, analyzerExecutor); Assert.Equal(1, descriptors.Length); diff --git a/src/Compilers/Core/MSBuildTask/Csc.cs b/src/Compilers/Core/MSBuildTask/Csc.cs index 3dead94c8a72b..37d65c99e07c6 100644 --- a/src/Compilers/Core/MSBuildTask/Csc.cs +++ b/src/Compilers/Core/MSBuildTask/Csc.cs @@ -90,6 +90,12 @@ public string? GeneratedFilesOutputPath get { return (string?)_store[nameof(GeneratedFilesOutputPath)]; } } + public string? GeneratedArtifactsOutputPath + { + set { _store[nameof(GeneratedArtifactsOutputPath)] = value; } + get { return (string?)_store[nameof(GeneratedArtifactsOutputPath)]; } + } + public bool GenerateFullPaths { set { _store[nameof(GenerateFullPaths)] = value; } @@ -210,6 +216,7 @@ protected internal override void AddResponseFileCommands(CommandLineBuilderExten commandLine.AppendPlusOrMinusSwitch("/checked", _store, nameof(CheckForOverflowUnderflow)); commandLine.AppendSwitchWithSplitting("/nowarn:", DisabledWarnings, ",", ';', ','); commandLine.AppendSwitchIfNotNull("/generatedfilesout:", GeneratedFilesOutputPath); + commandLine.AppendSwitchIfNotNull("/generatedartifactsout:", GeneratedArtifactsOutputPath); commandLine.AppendWhenTrue("/fullpaths", _store, nameof(GenerateFullPaths)); commandLine.AppendSwitchIfNotNull("/moduleassemblyname:", ModuleAssemblyName); commandLine.AppendSwitchIfNotNull("/pdb:", PdbFile); diff --git a/src/Compilers/Core/MSBuildTask/Microsoft.CSharp.Core.targets b/src/Compilers/Core/MSBuildTask/Microsoft.CSharp.Core.targets index 395b63a7c7d41..ac7a59ee2bbe0 100644 --- a/src/Compilers/Core/MSBuildTask/Microsoft.CSharp.Core.targets +++ b/src/Compilers/Core/MSBuildTask/Microsoft.CSharp.Core.targets @@ -97,6 +97,7 @@ Features="$(Features)" FileAlignment="$(FileAlignment)" GeneratedFilesOutputPath="$(CompilerGeneratedFilesOutputPath)" + GeneratedArtifactsOutputPath="$(CompilerGeneratedArtifactsOutputPath)" GenerateFullPaths="$(GenerateFullPaths)" HighEntropyVA="$(HighEntropyVA)" Instrument="$(Instrument)" diff --git a/src/Compilers/Core/Portable/CodeAnalysisResources.resx b/src/Compilers/Core/Portable/CodeAnalysisResources.resx index 1d8d8243fe15b..d934213bbfa1a 100644 --- a/src/Compilers/Core/Portable/CodeAnalysisResources.resx +++ b/src/Compilers/Core/Portable/CodeAnalysisResources.resx @@ -724,4 +724,10 @@ Changes must be within bounds of SourceText + + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + + + Multiple artifact streams opened for: {0} + \ No newline at end of file diff --git a/src/Compilers/Core/Portable/CommandLine/CommandLineArguments.cs b/src/Compilers/Core/Portable/CommandLine/CommandLineArguments.cs index 60f83512241e4..255c11f294149 100644 --- a/src/Compilers/Core/Portable/CommandLine/CommandLineArguments.cs +++ b/src/Compilers/Core/Portable/CommandLine/CommandLineArguments.cs @@ -128,6 +128,11 @@ public abstract class CommandLineArguments /// public string? GeneratedFilesOutputDirectory { get; internal set; } + /// + /// Absolute path of the directory to place generated artifacts in, or null to not generate any artifact files. + /// + public string? GeneratedArtifactsOutputDirectory { get; internal set; } + /// /// Options controlling the generation of a SARIF log file containing compilation or /// analysis diagnostics, or null if no log file is desired. diff --git a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs index b71fde50eb41b..36fa8c6c539ae 100644 --- a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs +++ b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -14,6 +15,7 @@ using System.Security.Cryptography; using System.Text; using System.Threading; +using System.Threading.Tasks.Sources; using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.PooledObjects; @@ -920,6 +922,58 @@ private void CompileAndEmit( out CancellationTokenSource? analyzerCts, out bool reportAnalyzer, out AnalyzerDriver? analyzerDriver) + { + var artifactStreams = new ConcurrentDictionary(); + try + { + CompileAndEmit( + touchedFilesLogger, + ref compilation, + analyzers, + generators, + additionalTextFiles, + analyzerConfigSet, + sourceFileAnalyzerConfigOptions, + embeddedTexts, + diagnostics, + artifactStreams, + cancellationToken, + out analyzerCts, + out reportAnalyzer, + out analyzerDriver); + } + finally + { + // after running all full compile, flush and close any artifact producer streams that haven't already been closed. + FlushAndCloseArtifactStreams(diagnostics, artifactStreams); + } + } + + private void FlushAndCloseArtifactStreams(DiagnosticBag diagnostics, ConcurrentDictionary artifactStreams) + { + foreach (var (path, stream) in artifactStreams) + { + using var disposer = new NoThrowStreamDisposer(stream, path, diagnostics, MessageProvider); + if (stream.CanWrite) + stream.Flush(); + } + } + + private void CompileAndEmit( + TouchedFileLogger? touchedFilesLogger, + ref Compilation compilation, + ImmutableArray analyzers, + ImmutableArray generators, + ImmutableArray additionalTextFiles, + AnalyzerConfigSet? analyzerConfigSet, + ImmutableArray sourceFileAnalyzerConfigOptions, + ImmutableArray embeddedTexts, + DiagnosticBag diagnostics, + ConcurrentDictionary artifactStreams, + CancellationToken cancellationToken, + out CancellationTokenSource? analyzerCts, + out bool reportAnalyzer, + out AnalyzerDriver? analyzerDriver) { analyzerCts = null; reportAnalyzer = false; @@ -959,101 +1013,30 @@ private void CompileAndEmit( additionalFileAnalyzerOptions); } - if (!generators.IsEmpty) - { - // At this point we have a compilation with nothing yet computed. - // We pass it to the generators, which will realize any symbols they require. - compilation = RunGenerators(compilation, Arguments.ParseOptions, generators, analyzerConfigProvider, additionalTextFiles, diagnostics); - - bool hasAnalyzerConfigs = !Arguments.AnalyzerConfigPaths.IsEmpty; - bool hasGeneratedOutputPath = !string.IsNullOrWhiteSpace(Arguments.GeneratedFilesOutputDirectory); - - var generatedSyntaxTrees = compilation.SyntaxTrees.Skip(Arguments.SourceFiles.Length).ToList(); - - var analyzerOptionsBuilder = hasAnalyzerConfigs ? ArrayBuilder.GetInstance(generatedSyntaxTrees.Count) : null; - var embeddedTextBuilder = ArrayBuilder.GetInstance(generatedSyntaxTrees.Count); - try - { - foreach (var tree in generatedSyntaxTrees) - { - Debug.Assert(!string.IsNullOrWhiteSpace(tree.FilePath)); - cancellationToken.ThrowIfCancellationRequested(); - - var sourceText = tree.GetText(cancellationToken); - - // embed the generated text and get analyzer options for it if needed - embeddedTextBuilder.Add(EmbeddedText.FromSource(tree.FilePath, sourceText)); - if (analyzerOptionsBuilder is object) - { - analyzerOptionsBuilder.Add(analyzerConfigSet!.GetOptionsForSourcePath(tree.FilePath)); - } - - // write out the file if we have an output path - if (hasGeneratedOutputPath) - { - var path = Path.Combine(Arguments.GeneratedFilesOutputDirectory!, tree.FilePath); - if (Directory.Exists(Arguments.GeneratedFilesOutputDirectory)) - { - Directory.CreateDirectory(Path.GetDirectoryName(path)!); - } - - var fileStream = OpenFile(path, diagnostics, FileMode.Create, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete); - if (fileStream is object) - { - Debug.Assert(tree.Encoding is object); - - using var disposer = new NoThrowStreamDisposer(fileStream, path, diagnostics, MessageProvider); - using var writer = new StreamWriter(fileStream, tree.Encoding); - - sourceText.Write(writer, cancellationToken); - touchedFilesLogger?.AddWritten(path); - } - } - } - - embeddedTexts = embeddedTexts.AddRange(embeddedTextBuilder); - if (analyzerOptionsBuilder is object) - { - analyzerConfigProvider = UpdateAnalyzerConfigOptionsProvider( - analyzerConfigProvider, - generatedSyntaxTrees, - analyzerOptionsBuilder.ToImmutable()); - } - } - finally - { - analyzerOptionsBuilder?.Free(); - embeddedTextBuilder.Free(); - } - } - - AnalyzerOptions analyzerOptions = CreateAnalyzerOptions( - additionalTextFiles, analyzerConfigProvider); - - if (!analyzers.IsEmpty) - { - analyzerCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - analyzerExceptionDiagnostics = new DiagnosticBag(); - - // PERF: Avoid executing analyzers that report only Hidden and/or Info diagnostics, which don't appear in the build output. - // 1. Always filter out 'Hidden' analyzer diagnostics in build. - // 2. Filter out 'Info' analyzer diagnostics if they are not required to be logged in errorlog. - var severityFilter = SeverityFilter.Hidden; - if (Arguments.ErrorLogPath == null) - severityFilter |= SeverityFilter.Info; - - analyzerDriver = AnalyzerDriver.CreateAndAttachToCompilation( - compilation, - analyzers, - analyzerOptions, - new AnalyzerManager(analyzers), - analyzerExceptionDiagnostics.Add, - Arguments.ReportAnalyzer, - severityFilter, - out compilation, - analyzerCts.Token); - reportAnalyzer = Arguments.ReportAnalyzer && !analyzers.IsEmpty; - } + RunGenerators( + touchedFilesLogger, + ref compilation, + generators, + additionalTextFiles, + analyzerConfigSet, + ref embeddedTexts, + diagnostics, + ref analyzerConfigProvider, + cancellationToken); + + RunAnalyzers( + touchedFilesLogger, + ref compilation, + analyzers, + additionalTextFiles, + ref analyzerCts, + ref reportAnalyzer, + ref analyzerDriver, + ref analyzerExceptionDiagnostics, + diagnostics, + analyzerConfigProvider, + artifactStreams, + cancellationToken); } compilation.GetDiagnostics(CompilationStage.Declare, includeEarlierStages: false, diagnostics, cancellationToken); @@ -1327,6 +1310,166 @@ private void CompileAndEmit( } } + private void RunAnalyzers( + TouchedFileLogger? touchedFilesLogger, + ref Compilation compilation, + ImmutableArray analyzers, + ImmutableArray additionalTextFiles, + ref CancellationTokenSource? analyzerCts, + ref bool reportAnalyzer, + ref AnalyzerDriver? analyzerDriver, + ref DiagnosticBag? analyzerExceptionDiagnostics, + DiagnosticBag diagnostics, + CompilerAnalyzerConfigOptionsProvider analyzerConfigProvider, + ConcurrentDictionary artifactStreams, + CancellationToken cancellationToken) + { + AnalyzerOptions analyzerOptions = CreateAnalyzerOptions(additionalTextFiles, analyzerConfigProvider); + + if (!analyzers.IsEmpty) + { + analyzerCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + analyzerExceptionDiagnostics = new DiagnosticBag(); + + // PERF: Avoid executing analyzers that report only Hidden and/or Info diagnostics, which don't appear in the build output. + // 1. Always filter out 'Hidden' analyzer diagnostics in build. + // 2. Filter out 'Info' analyzer diagnostics if they are not required to be logged in errorlog. + var severityFilter = SeverityFilter.Hidden; + if (Arguments.ErrorLogPath == null) + severityFilter |= SeverityFilter.Info; + + // Determine if we should support artifact generators or not. If we have an specified output path, then + // we will run artifact generators. Otherwise, we won't bother as we have no place to put their files. + Func? createArtifactStream = !string.IsNullOrWhiteSpace(Arguments.GeneratedArtifactsOutputDirectory) + ? GetArtifactStreamFactory(Arguments.GeneratedArtifactsOutputDirectory, touchedFilesLogger, artifactStreams) + : null; + + analyzerDriver = AnalyzerDriver.CreateAndAttachToCompilation( + compilation, + analyzers, + analyzerOptions, + new AnalyzerManager(analyzers), + analyzerExceptionDiagnostics.Add, + createArtifactStream, + Arguments.ReportAnalyzer, + severityFilter, + out compilation, + analyzerCts.Token); + reportAnalyzer = Arguments.ReportAnalyzer && !analyzers.IsEmpty; + } + } + + private Func GetArtifactStreamFactory( + string rootDirectory, + TouchedFileLogger? touchedFilesLogger, + ConcurrentDictionary artifactStreams) + { + return fileName => + { + // Get the final destination based on the command line option and file name provided. + var path = Path.Combine(rootDirectory, fileName); + touchedFilesLogger?.AddWritten(path); + + var directory = Path.GetDirectoryName(path); + if (!Directory.Exists(directory)) + Directory.CreateDirectory(directory); + + return artifactStreams.AddOrUpdate( + path, + addValueFactory: path => FileOpen(path, FileMode.Create, FileAccess.Write, FileShare.Delete), + // Multiple streams to the same path are not allowed + updateValueFactory: (path, _) => throw new InvalidOperationException(string.Format(CodeAnalysisResources.Multiple_artifact_streams_opened_for_0, path))); + }; + } + + private void RunGenerators( + TouchedFileLogger? touchedFilesLogger, + ref Compilation compilation, + ImmutableArray generators, + ImmutableArray additionalTextFiles, + AnalyzerConfigSet? analyzerConfigSet, + ref ImmutableArray embeddedTexts, + DiagnosticBag diagnostics, + ref CompilerAnalyzerConfigOptionsProvider analyzerConfigProvider, + CancellationToken cancellationToken) + { + if (!generators.IsEmpty) + { + // At this point we have a compilation with nothing yet computed. + // We pass it to the generators, which will realize any symbols they require. + compilation = RunGenerators(compilation, Arguments.ParseOptions, generators, analyzerConfigProvider, additionalTextFiles, diagnostics); + + bool hasAnalyzerConfigs = !Arguments.AnalyzerConfigPaths.IsEmpty; + + var generatedSyntaxTrees = compilation.SyntaxTrees.Skip(Arguments.SourceFiles.Length).ToList(); + + var analyzerOptionsBuilder = hasAnalyzerConfigs ? ArrayBuilder.GetInstance(generatedSyntaxTrees.Count) : null; + var embeddedTextBuilder = ArrayBuilder.GetInstance(generatedSyntaxTrees.Count); + try + { + foreach (var tree in generatedSyntaxTrees) + { + cancellationToken.ThrowIfCancellationRequested(); + + var filePath = tree.FilePath; + Debug.Assert(!string.IsNullOrWhiteSpace(filePath)); + + var encoding = tree.Encoding; + + var sourceText = tree.GetText(cancellationToken); + + // embed the generated text and get analyzer options for it if needed + embeddedTextBuilder.Add(EmbeddedText.FromSource(filePath, sourceText)); + if (analyzerOptionsBuilder is object) + { + analyzerOptionsBuilder.Add(analyzerConfigSet!.GetOptionsForSourcePath(filePath)); + } + + // write out the file if we have an output path + if (!string.IsNullOrWhiteSpace(Arguments.GeneratedFilesOutputDirectory)) + writeSourceText(Arguments.GeneratedFilesOutputDirectory!, filePath, encoding, sourceText); + } + + embeddedTexts = embeddedTexts.AddRange(embeddedTextBuilder); + if (analyzerOptionsBuilder is object) + { + analyzerConfigProvider = UpdateAnalyzerConfigOptionsProvider( + analyzerConfigProvider, + generatedSyntaxTrees, + analyzerOptionsBuilder.ToImmutable()); + } + } + finally + { + analyzerOptionsBuilder?.Free(); + embeddedTextBuilder.Free(); + } + } + + return; + + void writeSourceText(string directory, string filePath, Encoding? encoding, SourceText sourceText) + { + var path = Path.Combine(directory, filePath); + if (Directory.Exists(directory)) + { + Directory.CreateDirectory(Path.GetDirectoryName(path)); + } + + var fileStream = OpenFile(path, diagnostics, FileMode.Create, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete); + if (fileStream is not null) + { + Debug.Assert(encoding is not null); + + using var disposer = new NoThrowStreamDisposer(fileStream, path, diagnostics, MessageProvider); + using var writer = new StreamWriter(fileStream, encoding); + + sourceText.Write(writer, cancellationToken); + touchedFilesLogger?.AddWritten(path); + } + } + } + // virtual for testing protected virtual Diagnostics.AnalyzerOptions CreateAnalyzerOptions( ImmutableArray additionalTextFiles, diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContext.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContext.cs similarity index 95% rename from src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContext.cs rename to src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContext.cs index 4a71b851ad32b..5fdc973ec8c59 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticAnalysisContext.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisContext.cs @@ -43,6 +43,36 @@ namespace Microsoft.CodeAnalysis.Diagnostics /// public abstract class AnalysisContext { + private readonly Optional _artifactContext; + + public AnalysisContext() + { + } + + protected AnalysisContext(Optional artifactContext) + { + _artifactContext = artifactContext; + } + + /// + /// Returns an instance that can be used to write streams of data to disk during + /// analyzer or source generation. This method will only succeed if the caller has the set on them and it is being called in a context where artifact production + /// is supported. In general that will only be when a compiler is invoked with the generatedartifactsout + /// argument. + /// + /// + /// If the caller does not have the on it. + /// + public bool TryGetArtifactContext([NotNullWhen(true)] out ArtifactContext? artifactContext) + { + if (!_artifactContext.HasValue) + throw new NotSupportedException(CodeAnalysisResources.Acquiring_the_ArtifactContext_is_not_allowed_without_specifying_the_ArtifactProducerAttribute); + + artifactContext = _artifactContext.Value; + return artifactContext != null; + } + /// /// Register an action to be executed at compilation start. /// A compilation start action can register other actions and/or collect state information to be used in diagnostic analysis, @@ -543,7 +573,7 @@ public struct CompilationAnalysisContext public CancellationToken CancellationToken { get { return _cancellationToken; } } public CompilationAnalysisContext(Compilation compilation, AnalyzerOptions options, Action reportDiagnostic, Func isSupportedDiagnostic, CancellationToken cancellationToken) - : this(compilation, options, reportDiagnostic, isSupportedDiagnostic, null, cancellationToken) + : this(compilation, options, reportDiagnostic, isSupportedDiagnostic, compilationAnalysisValueProviderFactoryOpt: null, cancellationToken) { } @@ -648,7 +678,12 @@ public struct SemanticModelAnalysisContext /// public CancellationToken CancellationToken { get { return _cancellationToken; } } - public SemanticModelAnalysisContext(SemanticModel semanticModel, AnalyzerOptions options, Action reportDiagnostic, Func isSupportedDiagnostic, CancellationToken cancellationToken) + public SemanticModelAnalysisContext( + SemanticModel semanticModel, + AnalyzerOptions options, + Action reportDiagnostic, + Func isSupportedDiagnostic, + CancellationToken cancellationToken) { _semanticModel = semanticModel; _options = options; @@ -706,7 +741,13 @@ public struct SymbolAnalysisContext internal Func IsSupportedDiagnostic => _isSupportedDiagnostic; - public SymbolAnalysisContext(ISymbol symbol, Compilation compilation, AnalyzerOptions options, Action reportDiagnostic, Func isSupportedDiagnostic, CancellationToken cancellationToken) + public SymbolAnalysisContext( + ISymbol symbol, + Compilation compilation, + AnalyzerOptions options, + Action reportDiagnostic, + Func isSupportedDiagnostic, + CancellationToken cancellationToken) { _symbol = symbol; _compilation = compilation; @@ -970,7 +1011,14 @@ public struct CodeBlockAnalysisContext /// public CancellationToken CancellationToken { get { return _cancellationToken; } } - public CodeBlockAnalysisContext(SyntaxNode codeBlock, ISymbol owningSymbol, SemanticModel semanticModel, AnalyzerOptions options, Action reportDiagnostic, Func isSupportedDiagnostic, CancellationToken cancellationToken) + public CodeBlockAnalysisContext( + SyntaxNode codeBlock, + ISymbol owningSymbol, + SemanticModel semanticModel, + AnalyzerOptions options, + Action reportDiagnostic, + Func isSupportedDiagnostic, + CancellationToken cancellationToken) { _codeBlock = codeBlock; _owningSymbol = owningSymbol; @@ -1175,15 +1223,8 @@ public OperationBlockAnalysisContext( Action reportDiagnostic, Func isSupportedDiagnostic, CancellationToken cancellationToken) + : this(operationBlocks, owningSymbol, compilation, options, reportDiagnostic, isSupportedDiagnostic, getControlFlowGraph: null, cancellationToken) { - _operationBlocks = operationBlocks; - _owningSymbol = owningSymbol; - _compilation = compilation; - _options = options; - _reportDiagnostic = reportDiagnostic; - _isSupportedDiagnostic = isSupportedDiagnostic; - _cancellationToken = cancellationToken; - _getControlFlowGraph = null; } internal OperationBlockAnalysisContext( @@ -1193,7 +1234,7 @@ internal OperationBlockAnalysisContext( AnalyzerOptions options, Action reportDiagnostic, Func isSupportedDiagnostic, - Func getControlFlowGraph, + Func? getControlFlowGraph, CancellationToken cancellationToken) { _operationBlocks = operationBlocks; @@ -1269,17 +1310,23 @@ public struct SyntaxTreeAnalysisContext internal Compilation? Compilation => _compilationOpt; - public SyntaxTreeAnalysisContext(SyntaxTree tree, AnalyzerOptions options, Action reportDiagnostic, Func isSupportedDiagnostic, CancellationToken cancellationToken) + public SyntaxTreeAnalysisContext( + SyntaxTree tree, + AnalyzerOptions options, + Action reportDiagnostic, + Func isSupportedDiagnostic, + CancellationToken cancellationToken) + : this(tree, options, reportDiagnostic, isSupportedDiagnostic, compilation: null, cancellationToken) { - _tree = tree; - _options = options; - _reportDiagnostic = reportDiagnostic; - _isSupportedDiagnostic = isSupportedDiagnostic; - _compilationOpt = null; - _cancellationToken = cancellationToken; } - internal SyntaxTreeAnalysisContext(SyntaxTree tree, AnalyzerOptions options, Action reportDiagnostic, Func isSupportedDiagnostic, Compilation compilation, CancellationToken cancellationToken) + internal SyntaxTreeAnalysisContext( + SyntaxTree tree, + AnalyzerOptions options, + Action reportDiagnostic, + Func isSupportedDiagnostic, + Compilation? compilation, + CancellationToken cancellationToken) { _tree = tree; _options = options; @@ -1407,7 +1454,14 @@ public struct SyntaxNodeAnalysisContext /// public CancellationToken CancellationToken => _cancellationToken; - public SyntaxNodeAnalysisContext(SyntaxNode node, ISymbol? containingSymbol, SemanticModel semanticModel, AnalyzerOptions options, Action reportDiagnostic, Func isSupportedDiagnostic, CancellationToken cancellationToken) + public SyntaxNodeAnalysisContext( + SyntaxNode node, + ISymbol? containingSymbol, + SemanticModel semanticModel, + AnalyzerOptions options, + Action reportDiagnostic, + Func isSupportedDiagnostic, + CancellationToken cancellationToken) { _node = node; _containingSymbol = containingSymbol; @@ -1485,15 +1539,8 @@ public OperationAnalysisContext( Action reportDiagnostic, Func isSupportedDiagnostic, CancellationToken cancellationToken) + : this(operation, containingSymbol, compilation, options, reportDiagnostic, isSupportedDiagnostic, getControlFlowGraph: null, cancellationToken) { - _operation = operation; - _containingSymbol = containingSymbol; - _compilation = compilation; - _options = options; - _reportDiagnostic = reportDiagnostic; - _isSupportedDiagnostic = isSupportedDiagnostic; - _cancellationToken = cancellationToken; - _getControlFlowGraph = null; } internal OperationAnalysisContext( @@ -1503,7 +1550,7 @@ internal OperationAnalysisContext( AnalyzerOptions options, Action reportDiagnostic, Func isSupportedDiagnostic, - Func getControlFlowGraph, + Func? getControlFlowGraph, CancellationToken cancellationToken) { _operation = operation; diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs index 04b651cedfa92..a006395732f17 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -436,6 +437,7 @@ internal void Initialize( CompilationWithAnalyzersOptions analysisOptions, CompilationData compilationData, bool categorizeDiagnostics, + Func? createArtifactStream, CancellationToken cancellationToken) { Debug.Assert(_lazyInitializeTask == null); @@ -478,10 +480,25 @@ internal void Initialize( }; var analyzerExecutor = AnalyzerExecutor.Create( - compilation, analysisOptions.Options ?? AnalyzerOptions.Empty, addNotCategorizedDiagnostic, newOnAnalyzerException, analysisOptions.AnalyzerExceptionFilter, - IsCompilerAnalyzer, AnalyzerManager, ShouldSkipAnalysisOnGeneratedCode, ShouldSuppressGeneratedCodeDiagnostic, IsGeneratedOrHiddenCodeLocation, IsAnalyzerSuppressedForTree, GetAnalyzerGate, + compilation, + analysisOptions.Options ?? AnalyzerOptions.Empty, + addNotCategorizedDiagnostic, + newOnAnalyzerException, + analysisOptions.AnalyzerExceptionFilter, + IsCompilerAnalyzer, + AnalyzerManager, + ShouldSkipAnalysisOnGeneratedCode, + ShouldSuppressGeneratedCodeDiagnostic, + IsGeneratedOrHiddenCodeLocation, + IsAnalyzerSuppressedForTree, + GetAnalyzerGate, getSemanticModel: GetOrCreateSemanticModel, - analysisOptions.LogAnalyzerExecutionTime, addCategorizedLocalDiagnostic, addCategorizedNonLocalDiagnostic, s => _programmaticSuppressions!.Add(s), cancellationToken); + createArtifactStream: createArtifactStream, + analysisOptions.LogAnalyzerExecutionTime, + addCategorizedLocalDiagnostic, + addCategorizedNonLocalDiagnostic, + s => _programmaticSuppressions!.Add(s), + cancellationToken); Initialize(analyzerExecutor, diagnosticQueue, compilationData, cancellationToken); } @@ -799,6 +816,7 @@ private void ExecuteAdditionalFileActions(AnalysisScope analysisScope, AnalysisS /// Options that are passed to analyzers. /// AnalyzerManager to manage analyzers for the lifetime of analyzer host. /// Delegate to add diagnostics generated for exceptions from third party analyzers. + /// Callback to add additional artifact files to be generated. /// Report additional information related to analyzers, such as analyzer execution time. /// Filtered diagnostic severities in the compilation, i.e. diagnostics with effective severity from this set should not be reported. /// The new compilation with the analyzer driver attached. @@ -814,6 +832,7 @@ public static AnalyzerDriver CreateAndAttachToCompilation( AnalyzerOptions options, AnalyzerManager analyzerManager, Action addExceptionDiagnostic, + Func? createArtifactStream, bool reportAnalyzer, SeverityFilter severityFilter, out Compilation newCompilation, @@ -823,7 +842,18 @@ public static AnalyzerDriver CreateAndAttachToCompilation( (ex, analyzer, diagnostic) => addExceptionDiagnostic?.Invoke(diagnostic); Func? nullFilter = null; - return CreateAndAttachToCompilation(compilation, analyzers, options, analyzerManager, onAnalyzerException, nullFilter, reportAnalyzer, severityFilter, out newCompilation, cancellationToken: cancellationToken); + return CreateAndAttachToCompilation( + compilation, + analyzers, + options, + analyzerManager, + createArtifactStream, + onAnalyzerException, + nullFilter, + reportAnalyzer, + severityFilter, + out newCompilation, + cancellationToken); } // internal for testing purposes @@ -832,6 +862,7 @@ internal static AnalyzerDriver CreateAndAttachToCompilation( ImmutableArray analyzers, AnalyzerOptions options, AnalyzerManager analyzerManager, + Func? createArtifactStream, Action onAnalyzerException, Func? analyzerExceptionFilter, bool reportAnalyzer, @@ -846,7 +877,9 @@ internal static AnalyzerDriver CreateAndAttachToCompilation( var categorizeDiagnostics = false; var analysisOptions = new CompilationWithAnalyzersOptions(options, onAnalyzerException, analyzerExceptionFilter: analyzerExceptionFilter, concurrentAnalysis: true, logAnalyzerExecutionTime: reportAnalyzer, reportSuppressedDiagnostics: false); - analyzerDriver.Initialize(newCompilation, analysisOptions, new CompilationData(newCompilation), categorizeDiagnostics, cancellationToken); + analyzerDriver.Initialize( + newCompilation, analysisOptions, new CompilationData(newCompilation), + categorizeDiagnostics, createArtifactStream, cancellationToken); var analysisScope = new AnalysisScope(newCompilation, options, analyzers, hasAllAnalyzers: true, concurrentAnalysis: newCompilation.Options.ConcurrentBuild, categorizeDiagnostics: categorizeDiagnostics); analyzerDriver.AttachQueueAndStartProcessingEvents(newCompilation.EventQueue!, analysisScope, cancellationToken: cancellationToken); diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs index 1a911c7adb517..a821a2917f50f 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs @@ -8,6 +8,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; @@ -66,6 +67,8 @@ internal partial class AnalyzerExecutor private Func GetControlFlowGraph => _lazyGetControlFlowGraph ??= GetControlFlowGraphImpl; + public readonly Func? CreateArtifactStream; + private bool IsAnalyzerSuppressedForTree(DiagnosticAnalyzer analyzer, SyntaxTree tree) { Debug.Assert(_isAnalyzerSuppressedForTree != null); @@ -118,6 +121,7 @@ public static AnalyzerExecutor Create( Func isAnalyzerSuppressedForTree, Func getAnalyzerGate, Func getSemanticModel, + Func? createArtifactStream, bool logExecutionTime = false, Action? addCategorizedLocalDiagnostic = null, Action? addCategorizedNonLocalDiagnostic = null, @@ -130,10 +134,26 @@ public static AnalyzerExecutor Create( var analyzerExecutionTimeMap = logExecutionTime ? new ConcurrentDictionary>() : null; - return new AnalyzerExecutor(compilation, analyzerOptions, addNonCategorizedDiagnostic, onAnalyzerException, analyzerExceptionFilter, - isCompilerAnalyzer, analyzerManager, shouldSkipAnalysisOnGeneratedCode, shouldSuppressGeneratedCodeDiagnostic, isGeneratedCodeLocation, - isAnalyzerSuppressedForTree, getAnalyzerGate, getSemanticModel, analyzerExecutionTimeMap, addCategorizedLocalDiagnostic, addCategorizedNonLocalDiagnostic, - addSuppression, cancellationToken); + return new AnalyzerExecutor( + compilation, + analyzerOptions, + addNonCategorizedDiagnostic, + onAnalyzerException, + analyzerExceptionFilter, + isCompilerAnalyzer, + analyzerManager, + shouldSkipAnalysisOnGeneratedCode, + shouldSuppressGeneratedCodeDiagnostic, + isGeneratedCodeLocation, + isAnalyzerSuppressedForTree, + getAnalyzerGate, + getSemanticModel, + createArtifactStream, + analyzerExecutionTimeMap, + addCategorizedLocalDiagnostic, + addCategorizedNonLocalDiagnostic, + addSuppression, + cancellationToken); } /// @@ -147,6 +167,7 @@ public static AnalyzerExecutor Create( /// Cancellation token. public static AnalyzerExecutor CreateForSupportedDiagnostics( Action? onAnalyzerException, + Func? createArtifactStream, AnalyzerManager analyzerManager, CancellationToken cancellationToken = default) { @@ -162,6 +183,7 @@ public static AnalyzerExecutor CreateForSupportedDiagnostics( isAnalyzerSuppressedForTree: null, getAnalyzerGate: null, getSemanticModel: null, + createArtifactStream: createArtifactStream, onAnalyzerException: onAnalyzerException, analyzerExceptionFilter: null, analyzerManager: analyzerManager, @@ -186,6 +208,7 @@ private AnalyzerExecutor( Func? isAnalyzerSuppressedForTree, Func? getAnalyzerGate, Func? getSemanticModel, + Func? createArtifactStream, ConcurrentDictionary>? analyzerExecutionTimeMap, Action? addCategorizedLocalDiagnostic, Action? addCategorizedNonLocalDiagnostic, @@ -205,6 +228,7 @@ private AnalyzerExecutor( _isAnalyzerSuppressedForTree = isAnalyzerSuppressedForTree; _getAnalyzerGate = getAnalyzerGate; _getSemanticModel = getSemanticModel; + CreateArtifactStream = createArtifactStream; _analyzerExecutionTimeMap = analyzerExecutionTimeMap; _addCategorizedLocalDiagnostic = addCategorizedLocalDiagnostic; _addCategorizedNonLocalDiagnostic = addCategorizedNonLocalDiagnostic; @@ -221,10 +245,26 @@ public AnalyzerExecutor WithCancellationToken(CancellationToken cancellationToke return this; } - return new AnalyzerExecutor(_compilation, _analyzerOptions, _addNonCategorizedDiagnostic, _onAnalyzerException, _analyzerExceptionFilter, - _isCompilerAnalyzer, _analyzerManager, _shouldSkipAnalysisOnGeneratedCode, _shouldSuppressGeneratedCodeDiagnostic, _isGeneratedCodeLocation, - _isAnalyzerSuppressedForTree, _getAnalyzerGate, _getSemanticModel, _analyzerExecutionTimeMap, _addCategorizedLocalDiagnostic, _addCategorizedNonLocalDiagnostic, - _addSuppression, cancellationToken); + return new AnalyzerExecutor( + _compilation, + _analyzerOptions, + _addNonCategorizedDiagnostic, + _onAnalyzerException, + _analyzerExceptionFilter, + _isCompilerAnalyzer, + _analyzerManager, + _shouldSkipAnalysisOnGeneratedCode, + _shouldSuppressGeneratedCodeDiagnostic, + _isGeneratedCodeLocation, + _isAnalyzerSuppressedForTree, + _getAnalyzerGate, + _getSemanticModel, + CreateArtifactStream, + _analyzerExecutionTimeMap, + _addCategorizedLocalDiagnostic, + _addCategorizedNonLocalDiagnostic, + _addSuppression, + cancellationToken); } internal bool TryGetCompilationAndAnalyzerOptions( @@ -274,9 +314,15 @@ internal ImmutableDictionary AnalyzerExecutionTime /// Use API /// to get execute these actions to get the per-compilation analyzer actions. /// - public void ExecuteInitializeMethod(DiagnosticAnalyzer analyzer, HostSessionStartAnalysisScope sessionScope) + public void ExecuteInitializeMethod( + DiagnosticAnalyzer analyzer, + HostSessionStartAnalysisScope sessionScope) { - var context = new AnalyzerAnalysisContext(analyzer, sessionScope); + // If this analyzer can also produce artifacts, then create an appropriate context they can + // acquire to write files to as the analysis runs. + var artifactContext = GetArtifactContext(analyzer); + + var context = new AnalyzerAnalysisContext(analyzer, sessionScope, artifactContext); // The Initialize method should be run asynchronously in case it is not well behaved, e.g. does not terminate. ExecuteAndCatchIfThrows( @@ -285,6 +331,21 @@ public void ExecuteInitializeMethod(DiagnosticAnalyzer analyzer, HostSessionStar (analyzer, context)); } + private Optional GetArtifactContext(DiagnosticAnalyzer analyzer) + { + // Check if this is actually an artifact producer or not. If not, no ArtifactContext at all is provided and + // any attempt by the client to get it will throw. + if (analyzer.GetType().GetCustomAttributes(typeof(ArtifactProducerAttribute), inherit: true).Length == 0) + return default; + + // If we're not in a context where artifacts can actually be produced, then it's still valid to ask for the + // ArtifactContext, it will just return null + if (CreateArtifactStream == null) + return new Optional(null); + + return new Optional(new ArtifactContext(CreateArtifactStream)); + } + /// /// Executes the compilation start actions. /// diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerManager.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerManager.cs index 66a8e57e072fc..ca8dda3d11d36 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerManager.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerManager.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; @@ -297,6 +298,15 @@ internal bool IsDiagnosticAnalyzerSuppressed( } var supportedDiagnostics = GetSupportedDiagnosticDescriptors(analyzer, analyzerExecutor); + + if (analyzer.GetType().GetCustomAttributes(typeof(ArtifactProducerAttribute), inherit: true).Length > 0 && + supportedDiagnostics.Length == 0) + { + // If we're an artifact producer and we both don't ever produce diagnostics *and* we're can't write to + // disk, then we're definitely suppressed as we can have no impact on the system at all. + return analyzerExecutor.CreateArtifactStream == null; + } + var diagnosticOptions = options.SpecificDiagnosticOptions; analyzerExecutor.TryGetCompilationAndAnalyzerOptions(out var compilation, out var analyzerOptions); diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/ArtifactContext.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/ArtifactContext.cs new file mode 100644 index 0000000000000..32800fb294d3e --- /dev/null +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/ArtifactContext.cs @@ -0,0 +1,121 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Diagnostics +{ + /// + /// Context object that can be used to create non-source-code streams that are saved to disk during a normal + /// compilation. An can be retrieved by calling . However, this call will only succeed if the caller has the set on them and it is being called in a context where artifact production is + /// supported. In general that will only be when a compiler is invoked with the generatedartifactsout + /// argument. + /// + public sealed class ArtifactContext + { + /// + /// Callback the compiler can pass into us to actually generate artifacts. + /// + private readonly Func _createArtifactStream; + + internal ArtifactContext(Func createArtifactStream) + => _createArtifactStream = createArtifactStream; + + /// + /// Writes out an artifact with the contents of . The artifact will be written out + /// with utf8 encoding. + /// + /// The file name to generate this artifact into. Will be concatenated with the + /// generatedartifactsout path provided to the compiler. + /// The text to generate + public void WriteArtifact(string fileName, string source) + { + WriteArtifact(fileName, stream => + { + using var writer = CreateStreamWriter(stream, Encoding.UTF8); + writer.Write(source); + }); + } + + /// + /// Generates an artifact with the contents of . The artifact will be written out + /// with utf8 encoding. + /// + /// The file name to generate this artifact into. Will be concatenated with the + /// generatedartifactsout path provided to the compiler. + /// The string builder containing the contents to generate + public void WriteArtifact(string fileName, StringBuilder builder) + { + WriteArtifact(fileName, stream => + { + using var writer = CreateStreamWriter(stream, Encoding.UTF8); + writer.Write(builder); + }); + } + + /// + /// Generates an artifact with the contents and encoding specified by . + /// + /// The file name to generate this artifact into. Will be concatenated with the + /// generatedartifactsout path provided to the compiler. + /// The to generate + public void WriteArtifact(string fileName, SourceText sourceText) + { + WriteArtifact(fileName, stream => + { + using var writer = CreateStreamWriter(stream, sourceText.Encoding ?? Encoding.UTF8); + sourceText.Write(writer); + }); + } + + private static StreamWriter CreateStreamWriter(Stream stream, Encoding encoding) + { +#if NETCOREAPP + return new StreamWriter(stream, encoding, leaveOpen: true); +#else + // From: https://github.com/microsoft/referencesource/blob/f461f1986ca4027720656a0c77bede9963e20b7e/mscorlib/system/io/streamwriter.cs#L48 + const int DefaultBufferSize = 1024; + + return new StreamWriter(stream, encoding, bufferSize: DefaultBufferSize, leaveOpen: true); +#endif + } + + /// + /// Requests a fresh stream associated with the given to write artifact data into. + /// The callback should not call , , or . The stream will be automatically flushed and closed after is invoked. This overload is useful if there is a large amount of data to write, or if + /// there is binary data to write. + /// + /// The file name to generate this artifact into. Will be concatenated with the + /// generatedartifactsout path provided to the compiler. + /// A callback that will be passed the stream to write into. + public void WriteArtifact(string fileName, Action writeStream) + { + using var stream = CreateArtifactStream(fileName); + writeStream(stream); + stream.Flush(); + } + + /// + /// Requests a fresh stream associated with the given to write artifact data into. + /// After the compilation pass is done, all streams created by this will be flushed and disposed. Calling or will not cause any problems. This overload is useful + /// if there is a large amount of data to write, or if there is binary data to write. + /// + /// + /// There is no locking or ordering guaranteed around this stream. If a client wants to write to this stream + /// from multiple callbacks, it will need to coordinate that work itself internally. + /// + /// The file name to generate this artifact into. Will be concatenated with the + /// generatedartifactsout path provided to the compiler. + public Stream CreateArtifactStream(string fileName) + => _createArtifactStream(fileName); + } +} diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs index 3d5ce4257d1fa..df7ae8d9289a2 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/CompilationWithAnalyzers.cs @@ -415,7 +415,7 @@ private async Task ComputeAnalyzerDiagnosticsWithoutStateTrackingAsync(Cancellat // Create and attach the driver to compilation. var categorizeDiagnostics = true; driver = CreateDriverForComputingDiagnosticsWithoutStateTracking(compilation, analyzers); - driver.Initialize(compilation, _analysisOptions, compilationData, categorizeDiagnostics, cancellationToken); + driver.Initialize(compilation, _analysisOptions, compilationData, categorizeDiagnostics, createArtifactStream: null, cancellationToken); var hasAllAnalyzers = analyzers.Length == Analyzers.Length; var analysisScope = new AnalysisScope(compilation, _analysisOptions.Options, analyzers, hasAllAnalyzers, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: categorizeDiagnostics); driver.AttachQueueAndStartProcessingEvents(compilation.EventQueue!, analysisScope, cancellationToken); @@ -456,7 +456,7 @@ private async Task> GetAllDiagnosticsWithoutStateTrac // Create and attach the driver to compilation. var categorizeDiagnostics = false; driver = CreateDriverForComputingDiagnosticsWithoutStateTracking(compilation, analyzers); - driver.Initialize(compilation, _analysisOptions, compilationData, categorizeDiagnostics, cancellationToken); + driver.Initialize(compilation, _analysisOptions, compilationData, categorizeDiagnostics, createArtifactStream: null, cancellationToken); var hasAllAnalyzers = analyzers.Length == Analyzers.Length; var analysisScope = new AnalysisScope(compilation, _analysisOptions.Options, analyzers, hasAllAnalyzers, concurrentAnalysis: _analysisOptions.ConcurrentAnalysis, categorizeDiagnostics: categorizeDiagnostics); driver.AttachQueueAndStartProcessingEvents(compilation.EventQueue!, analysisScope, cancellationToken); @@ -944,7 +944,7 @@ private async Task GetAnalyzerDriverAsync(CancellationToken canc // Start the initialization task, if required. if (!driver.IsInitialized) { - driver.Initialize(_compilation, _analysisOptions, _compilationData, categorizeDiagnostics: true, cancellationToken: cancellationToken); + driver.Initialize(_compilation, _analysisOptions, _compilationData, categorizeDiagnostics: true, createArtifactStream: null, cancellationToken); } // Wait for driver initialization to complete: this executes the Initialize and CompilationStartActions to compute all registered actions per-analyzer. @@ -1328,7 +1328,7 @@ public static bool IsDiagnosticAnalyzerSuppressed( } var analyzerManager = new AnalyzerManager(analyzer); - var analyzerExecutor = AnalyzerExecutor.CreateForSupportedDiagnostics(onAnalyzerException, analyzerManager); + var analyzerExecutor = AnalyzerExecutor.CreateForSupportedDiagnostics(onAnalyzerException, createArtifactStream: null, analyzerManager); return AnalyzerDriver.IsDiagnosticAnalyzerSuppressed(analyzer, options, analyzerManager, analyzerExecutor, severityFilter: SeverityFilter.None); } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs index 47f3197be569a..bd08f3754ff17 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs @@ -21,7 +21,11 @@ internal sealed class AnalyzerAnalysisContext : AnalysisContext private readonly DiagnosticAnalyzer _analyzer; private readonly HostSessionStartAnalysisScope _scope; - public AnalyzerAnalysisContext(DiagnosticAnalyzer analyzer, HostSessionStartAnalysisScope scope) + public AnalyzerAnalysisContext( + DiagnosticAnalyzer analyzer, + HostSessionStartAnalysisScope scope, + Optional artifactContext) + : base(artifactContext) { _analyzer = analyzer; _scope = scope; diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 220f5b680ddfd..d8171472d23a0 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -1,3 +1,14 @@ +Microsoft.CodeAnalysis.ArtifactProducerAttribute +Microsoft.CodeAnalysis.ArtifactProducerAttribute.ArtifactProducerAttribute() -> void +Microsoft.CodeAnalysis.CommandLineArguments.GeneratedArtifactsOutputDirectory.get -> string? +Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.AnalysisContext(Microsoft.CodeAnalysis.Optional artifactContext) -> void +Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.TryGetArtifactContext(out Microsoft.CodeAnalysis.Diagnostics.ArtifactContext? artifactContext) -> bool +Microsoft.CodeAnalysis.Diagnostics.ArtifactContext +Microsoft.CodeAnalysis.Diagnostics.ArtifactContext.CreateArtifactStream(string! fileName) -> System.IO.Stream! +Microsoft.CodeAnalysis.Diagnostics.ArtifactContext.WriteArtifact(string! fileName, Microsoft.CodeAnalysis.Text.SourceText! sourceText) -> void +Microsoft.CodeAnalysis.Diagnostics.ArtifactContext.WriteArtifact(string! fileName, string! source) -> void +Microsoft.CodeAnalysis.Diagnostics.ArtifactContext.WriteArtifact(string! fileName, System.Action! writeStream) -> void +Microsoft.CodeAnalysis.Diagnostics.ArtifactContext.WriteArtifact(string! fileName, System.Text.StringBuilder! builder) -> void Microsoft.CodeAnalysis.GeneratorAttribute.GeneratorAttribute(string! firstLanguage, params string![]! additionalLanguages) -> void Microsoft.CodeAnalysis.GeneratorAttribute.Languages.get -> string![]! Microsoft.CodeAnalysis.GeneratorExecutionContext.SyntaxContextReceiver.get -> Microsoft.CodeAnalysis.ISyntaxContextReceiver? diff --git a/src/Compilers/Core/Portable/SourceGeneration/ArtifactProducerAttribute.cs b/src/Compilers/Core/Portable/SourceGeneration/ArtifactProducerAttribute.cs new file mode 100644 index 0000000000000..6df63c7a08d03 --- /dev/null +++ b/src/Compilers/Core/Portable/SourceGeneration/ArtifactProducerAttribute.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis +{ + /// + /// Place this attribute onto a type to cause it to be considered an artifact producer. Without this calls to will throw. With this, similar calls may succeed or not + /// depending on if the caller is used in a context where artifact production is supported or not. In general that + /// will only be when a compiler is invoked with the generatedartifactsout argument. + /// + [AttributeUsage(AttributeTargets.Class)] + public sealed class ArtifactProducerAttribute : Attribute + { + } +} diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf index 7af0e5646464a..4cceebc4cb9c7 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.cs.xlf @@ -12,6 +12,11 @@ Pro tuto možnost se musí zadat název jazyka. + + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + + The assembly containing type '{0}' references .NET Framework, which is not supported. Sestavení, které obsahuje typ {0}, se odkazuje na architekturu .NET Framework, což se nepodporuje. @@ -79,6 +84,11 @@ Modul zahrnuje neplatné atributy. + + Multiple artifact streams opened for: {0} + Multiple artifact streams opened for: {0} + + Non-reported diagnostic with ID '{0}' cannot be suppressed. Neohlášená diagnostika s ID {0} se nedá potlačit. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf index 3e5b6e4cf15c7..bb84201f72937 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.de.xlf @@ -12,6 +12,11 @@ Für diese Option muss ein Sprachenname angegeben werden. + + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + + The assembly containing type '{0}' references .NET Framework, which is not supported. Die Assembly mit dem Typ "{0}" verweist auf das .NET Framework. Dies wird nicht unterstützt. @@ -79,6 +84,11 @@ Das Modul weist ungültige Attribute auf. + + Multiple artifact streams opened for: {0} + Multiple artifact streams opened for: {0} + + Non-reported diagnostic with ID '{0}' cannot be suppressed. Die nicht gemeldete Diagnose mit der ID "{0}" kann nicht unterdrückt werden. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf index 1b3ff3ffea107..99ba8f0a24599 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.es.xlf @@ -12,6 +12,11 @@ Se debe especificar un nombre de lenguaje para esta opción. + + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + + The assembly containing type '{0}' references .NET Framework, which is not supported. El ensamblado que contiene el tipo "{0}" hace referencia a .NET Framework, lo cual no se admite. @@ -79,6 +84,11 @@ El módulo tiene atributos no válidos. + + Multiple artifact streams opened for: {0} + Multiple artifact streams opened for: {0} + + Non-reported diagnostic with ID '{0}' cannot be suppressed. No se puede suprimir el diagnóstico no notificado con el id. "{0}". diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf index f424b82388804..073df3a5ec939 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.fr.xlf @@ -12,6 +12,11 @@ Un nom de langage doit être spécifié pour cette option. + + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + + The assembly containing type '{0}' references .NET Framework, which is not supported. L'assembly contenant le type '{0}' référence le .NET Framework, ce qui n'est pas pris en charge. @@ -79,6 +84,11 @@ Le module a des attributs non valides. + + Multiple artifact streams opened for: {0} + Multiple artifact streams opened for: {0} + + Non-reported diagnostic with ID '{0}' cannot be suppressed. Impossible de supprimer le diagnostic non signalé ayant l'ID '{0}'. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf index 8858f5f15739a..3510666023e09 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.it.xlf @@ -12,6 +12,11 @@ È necessario specificare un nome di linguaggio per questa opzione. + + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + + The assembly containing type '{0}' references .NET Framework, which is not supported. L'assembly che contiene il tipo '{0}' fa riferimento a .NET Framework, che non è supportato. @@ -79,6 +84,11 @@ Il modulo contiene attributi non validi. + + Multiple artifact streams opened for: {0} + Multiple artifact streams opened for: {0} + + Non-reported diagnostic with ID '{0}' cannot be suppressed. Non è possibile eliminare la diagnostica non restituita con ID '{0}'. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf index 1ace32468a2c0..142b4496e200d 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ja.xlf @@ -12,6 +12,11 @@ このオプションの言語名を指定する必要があります。 + + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + + The assembly containing type '{0}' references .NET Framework, which is not supported. 型 '{0}' を含むアセンブリが .NET Framework を参照しています。これはサポートされていません。 @@ -79,6 +84,11 @@ モジュールに無効な属性があります。 + + Multiple artifact streams opened for: {0} + Multiple artifact streams opened for: {0} + + Non-reported diagnostic with ID '{0}' cannot be suppressed. ID '{0}' を持つ未報告の診断を抑制することはできません。 diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf index 8455c20fc3e8e..be39261e4f977 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ko.xlf @@ -12,6 +12,11 @@ 이 옵션에 대한 언어 이름을 지정해야 합니다. + + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + + The assembly containing type '{0}' references .NET Framework, which is not supported. '{0}' 형식을 포함하는 어셈블리가 지원되지 않는 .NET Framework를 참조합니다. @@ -79,6 +84,11 @@ 모듈에 잘못된 특성이 있습니다. + + Multiple artifact streams opened for: {0} + Multiple artifact streams opened for: {0} + + Non-reported diagnostic with ID '{0}' cannot be suppressed. ID가 '{0}'인 보고되지 않는 진단은 표시되지 않도록 설정할 수 없습니다. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf index 78cefd7b714eb..2bca4476481ff 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pl.xlf @@ -12,6 +12,11 @@ Nazwa języka musi zostać określona dla tej opcji. + + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + + The assembly containing type '{0}' references .NET Framework, which is not supported. Zestaw zawierający typ „{0}” odwołuje się do platformy .NET Framework, co nie jest obsługiwane. @@ -79,6 +84,11 @@ Moduł ma nieprawidłowe atrybuty. + + Multiple artifact streams opened for: {0} + Multiple artifact streams opened for: {0} + + Non-reported diagnostic with ID '{0}' cannot be suppressed. Nie można pominąć niezgłoszonej diagnostyki o identyfikatorze „{0}”. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf index 1e2a813164d11..53f7126065f86 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.pt-BR.xlf @@ -12,6 +12,11 @@ Um nome de idioma deve ser especificado para esta opção. + + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + + The assembly containing type '{0}' references .NET Framework, which is not supported. O assembly contendo o tipo '{0}' faz referência a .NET Framework, mas não há suporte para isso. @@ -79,6 +84,11 @@ O módulo tem atributos inválidos. + + Multiple artifact streams opened for: {0} + Multiple artifact streams opened for: {0} + + Non-reported diagnostic with ID '{0}' cannot be suppressed. O diagnóstico não relatado com a ID '{0}' não pode ser suprimido. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf index 034d80857a098..920a2fc34fd2c 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.ru.xlf @@ -12,6 +12,11 @@ Для данного параметра необходимо указать имя языка. + + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + + The assembly containing type '{0}' references .NET Framework, which is not supported. Сборка, содержащая тип "{0}", ссылается на платформу .NET Framework, которая не поддерживается. @@ -79,6 +84,11 @@ Модуль содержит недопустимые атрибуты. + + Multiple artifact streams opened for: {0} + Multiple artifact streams opened for: {0} + + Non-reported diagnostic with ID '{0}' cannot be suppressed. Невозможно подавить невыводимую диагностику с идентификатором "{0}". diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf index 5acb4f23c94ca..4364d6bc09b80 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.tr.xlf @@ -12,6 +12,11 @@ Bu seçenek için bir dil adı belirtilmelidir. + + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + + The assembly containing type '{0}' references .NET Framework, which is not supported. '{0}' türünü içeren bütünleştirilmiş kod, desteklenmeyen .NET Framework'e başvuruyor. @@ -79,6 +84,11 @@ Modülde geçersiz öznitelikler var. + + Multiple artifact streams opened for: {0} + Multiple artifact streams opened for: {0} + + Non-reported diagnostic with ID '{0}' cannot be suppressed. '{0}' kimlikli raporlanamayan tanılama gizlenemiyor. diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf index ef18f024c6625..3e765029c4414 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hans.xlf @@ -12,6 +12,11 @@ 必须为此选项指定语言名称。 + + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + + The assembly containing type '{0}' references .NET Framework, which is not supported. 包含类型“{0}”的程序集引用了 .NET Framework,而此操作不受支持。 @@ -79,6 +84,11 @@ 模块具有无效属性。 + + Multiple artifact streams opened for: {0} + Multiple artifact streams opened for: {0} + + Non-reported diagnostic with ID '{0}' cannot be suppressed. 不可禁止显示 ID 为“{0}”的未报告的诊断。 diff --git a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf index 55e05a7d62402..092776f88be45 100644 --- a/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf +++ b/src/Compilers/Core/Portable/xlf/CodeAnalysisResources.zh-Hant.xlf @@ -12,6 +12,11 @@ 必須指定此選項的語言名稱。 + + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + Acquiring the 'ArtifactContext' is not allowed without specifying the 'ArtifactProducerAttribute'. + + The assembly containing type '{0}' references .NET Framework, which is not supported. 包含類型 '{0}' 的組件參考了 .NET Framework,此情形不受支援。 @@ -79,6 +84,11 @@ 模組有無效的屬性。 + + Multiple artifact streams opened for: {0} + Multiple artifact streams opened for: {0} + + Non-reported diagnostic with ID '{0}' cannot be suppressed. 無法隱藏識別碼為 '{0}' 的非回報診斷。 diff --git a/src/Compilers/Test/Core/Diagnostics/DiagnosticExtensions.cs b/src/Compilers/Test/Core/Diagnostics/DiagnosticExtensions.cs index 8430231265453..3e96928ba4a53 100644 --- a/src/Compilers/Test/Core/Diagnostics/DiagnosticExtensions.cs +++ b/src/Compilers/Test/Core/Diagnostics/DiagnosticExtensions.cs @@ -291,8 +291,18 @@ private static TCompilation GetCompilationWithAnalyzerDiagnostics( } var analyzerManager = new AnalyzerManager(analyzersArray); - var driver = AnalyzerDriver.CreateAndAttachToCompilation(c, analyzersArray, options, analyzerManager, onAnalyzerException, - analyzerExceptionFilter: null, reportAnalyzer: false, severityFilter: SeverityFilter.None, out var newCompilation, cancellationToken); + var driver = AnalyzerDriver.CreateAndAttachToCompilation( + c, + analyzersArray, + options, + analyzerManager, + createArtifactStream: null, + onAnalyzerException, + analyzerExceptionFilter: null, + reportAnalyzer: false, + severityFilter: SeverityFilter.None, + out var newCompilation, + cancellationToken); Debug.Assert(newCompilation.SemanticModelProvider != null); var compilerDiagnostics = newCompilation.GetDiagnostics(cancellationToken); var analyzerDiagnostics = driver.GetDiagnosticsAsync(newCompilation).Result; diff --git a/src/Compilers/Test/Core/SourceGeneration/TestProducers.cs b/src/Compilers/Test/Core/SourceGeneration/TestProducers.cs new file mode 100644 index 0000000000000..3e4600ef1559a --- /dev/null +++ b/src/Compilers/Test/Core/SourceGeneration/TestProducers.cs @@ -0,0 +1,181 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using Xunit; + +namespace Roslyn.Test.Utilities.TestGenerators +{ + [DiagnosticAnalyzer(LanguageNames.CSharp), ArtifactProducer] + internal class SingleFileArtifactProducer : DiagnosticAnalyzer + { + private readonly string _content; + private readonly string _hintName; + + public SingleFileArtifactProducer(string content, string hintName = "generatedFile") + { + _content = content; + _hintName = hintName; + } + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Empty; + + public override void Initialize(AnalysisContext context) + { + Assert.True(context.TryGetArtifactContext(out var artifactContext)); + context.RegisterCompilationAction(c => AnalyzeCompilation(c, artifactContext!)); + } + + private void AnalyzeCompilation(CompilationAnalysisContext context, ArtifactContext artifactContext) + { + artifactContext.WriteArtifact(this._hintName, SourceText.From(_content, Encoding.UTF8)); + } + } + + [DiagnosticAnalyzer(LanguageNames.CSharp)] + internal class DiagnosticAnalyzerWithoutArtifactProducerAttribute : DiagnosticAnalyzer + { + private readonly string _content; + private readonly string _hintName; + + public DiagnosticAnalyzerWithoutArtifactProducerAttribute(string content, string hintName = "generatedFile") + { + _content = content; + _hintName = hintName; + } + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(new DiagnosticDescriptor("TEST0000", "Title", "Message", "Category", DiagnosticSeverity.Error, isEnabledByDefault: true)); + + public override void Initialize(AnalysisContext context) + { + Assert.True(context.TryGetArtifactContext(out var artifactContext)); + context.RegisterCompilationAction(c => AnalyzeCompilation(c, artifactContext!)); + } + + private void AnalyzeCompilation(CompilationAnalysisContext context, ArtifactContext artifactContext) + { + artifactContext.WriteArtifact(this._hintName, SourceText.From(_content, Encoding.UTF8)); + } + } + + [DiagnosticAnalyzer(LanguageNames.CSharp), ArtifactProducer] + internal class DiagnosticAnalyzerWithoutCommandLineArgGetsNoContext : DiagnosticAnalyzer + { + private readonly string _content; + private readonly string _hintName; + + public DiagnosticAnalyzerWithoutCommandLineArgGetsNoContext(string content, string hintName = "generatedFile") + { + _content = content; + _hintName = hintName; + } + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(new DiagnosticDescriptor("TEST0000", "Title", "Message", "Category", DiagnosticSeverity.Error, isEnabledByDefault: true)); + + public override void Initialize(AnalysisContext context) + { + Assert.False(context.TryGetArtifactContext(out var artifactContext)); + } + } + + [DiagnosticAnalyzer(LanguageNames.CSharp), ArtifactProducer] + internal class DiagnosticAnalyzerWithoutCommandLineArgThrowsWhenUsingContext : DiagnosticAnalyzer + { + private readonly string _content; + private readonly string _hintName; + + public DiagnosticAnalyzerWithoutCommandLineArgThrowsWhenUsingContext(string content, string hintName = "generatedFile") + { + _content = content; + _hintName = hintName; + } + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(new DiagnosticDescriptor("TEST0000", "Title", "Message", "Category", DiagnosticSeverity.Error, isEnabledByDefault: true)); + + public override void Initialize(AnalysisContext context) + { + Assert.False(context.TryGetArtifactContext(out var artifactContext)); + artifactContext!.WriteArtifact(this._hintName, SourceText.From(_content, Encoding.UTF8)); + } + } + + internal class SingleFileArtifactProducer2 : SingleFileArtifactProducer + { + public SingleFileArtifactProducer2(string content, string hintName = "generatedFile") : base(content, hintName) + { + } + } + + [DiagnosticAnalyzer(LanguageNames.CSharp), ArtifactProducer] + internal class CallbackArtifactProducer : DiagnosticAnalyzer + { + private readonly Action _onInit; + private readonly Action _onExecute; + private readonly string? _source; + + public CallbackArtifactProducer(Action onInit, Action onExecute, string? source = "") + { + _onInit = onInit; + _onExecute = onExecute; + _source = source; + } + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Empty; + + public override void Initialize(AnalysisContext context) + { + _onInit(context); + + Assert.True(context.TryGetArtifactContext(out var artifactContext)); + context.RegisterCompilationAction(c => Execute(c, artifactContext!)); + } + + private void Execute(CompilationAnalysisContext context, ArtifactContext artifactContext) + { + _onExecute(context); + if (!string.IsNullOrWhiteSpace(_source)) + { + artifactContext.WriteArtifact("source", SourceText.From(_source, Encoding.UTF8)); + } + } + } + + [DiagnosticAnalyzer(LanguageNames.CSharp), ArtifactProducer] + internal class DoNotCloseStreamArtifactProducer : DiagnosticAnalyzer + { + private readonly string _content; + private readonly string _hintName; + + public DoNotCloseStreamArtifactProducer(string content, string hintName = "generatedFile") + { + _content = content; + _hintName = hintName; + } + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Empty; + + public override void Initialize(AnalysisContext context) + { + Assert.True(context.TryGetArtifactContext(out var artifactContext)); + context.RegisterCompilationAction(c => AnalyzeCompilation(c, artifactContext!)); + } + + private void AnalyzeCompilation(CompilationAnalysisContext context, ArtifactContext artifactContext) + { + var stream = artifactContext.CreateArtifactStream(_hintName); + var bytes = Encoding.UTF8.GetBytes(_content); + stream.Write(bytes, 0, bytes.Length); + + // purposefully do not close the stream. + } + } +}