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.
+ }
+ }
+}