diff --git a/docs/compilers/Error Log Format.md b/docs/compilers/Error Log Format.md
index a873a78cb79696e529fd6be304be82646dd49120..baec297120759b7eec391c875e79972955ea467c 100644
--- a/docs/compilers/Error Log Format.md
+++ b/docs/compilers/Error Log Format.md
@@ -15,9 +15,9 @@ rather adds information that is specific to the implementation provided
by the C# and Visual Basic Compilers.
-Issue Properties
+Result Properties
================
-The SARIF standard allows the `properties` property of `issue` objects
+The SARIF standard allows the `properties` property of `result` objects
to contain arbitrary (string, string) key-value pairs.
The keys and values used by the C# and VB compilers are serialized from
@@ -33,6 +33,4 @@ Key | Value
"category" | `Diagnostic.Category`
"helpLink" | `DiagnosticDescriptor.HelpLink` (omitted if null or empty)
"isEnabledByDefault" | `Diagnostic.IsEnabledByDefault` ("True" or "False")
-"isSuppressedInSource" | `Diagnostic.IsSuppressedInSource` ("True" or "False")
-"customTags" | `Diagnostic.CustomTags` (joined together in a `;`-delimted list)
"customProperties.[key]" | `Diagnostic.Properties[key]` (for each key in the dictionary)
diff --git a/src/Compilers/CSharp/Test/CommandLine/ErrorLoggerTests.cs b/src/Compilers/CSharp/Test/CommandLine/ErrorLoggerTests.cs
index 9602233ebac307ccbaf347e64ba5f003e2ccfe30..1aff8a586d50cb7f3fd488c31f05a8eb40e60ecc 100644
--- a/src/Compilers/CSharp/Test/CommandLine/ErrorLoggerTests.cs
+++ b/src/Compilers/CSharp/Test/CommandLine/ErrorLoggerTests.cs
@@ -46,7 +46,7 @@ public static void Main(string[] args)
var expectedHeader = GetExpectedErrorLogHeader(actualOutput, cmd);
var expectedIssues = @"
- ""issues"": [
+ ""results"": [
]
}
]
@@ -57,7 +57,7 @@ public static void Main(string[] args)
CleanupAllGeneratedFiles(hello);
CleanupAllGeneratedFiles(errorLogFile);
}
-
+
[Fact]
public void SimpleCompilerDiagnostics()
{
@@ -85,9 +85,10 @@ public class C
var expectedHeader = GetExpectedErrorLogHeader(actualOutput, cmd);
var expectedIssues = string.Format(@"
- ""issues"": [
+ ""results"": [
{{
""ruleId"": ""CS0169"",
+ ""kind"": ""warning"",
""locations"": [
{{
""analysisTarget"": [
@@ -104,29 +105,37 @@ public class C
}}
],
""fullMessage"": ""The field 'C.x' is never used"",
+ ""isSuppressedInSource"": false,
+ ""tags"": [
+ ""Compiler"",
+ ""Telemetry""
+ ],
""properties"": {{
""severity"": ""Warning"",
""warningLevel"": ""3"",
""defaultSeverity"": ""Warning"",
""title"": ""Field is never used"",
""category"": ""Compiler"",
- ""isEnabledByDefault"": ""True"",
- ""isSuppressedInSource"": ""False"",
- ""customTags"": ""Compiler;Telemetry""
+ ""isEnabledByDefault"": ""True""
}}
}},
{{
""ruleId"": ""CS5001"",
+ ""kind"": ""error"",
""locations"": [
],
""fullMessage"": ""Program does not contain a static 'Main' method suitable for an entry point"",
+ ""isSuppressedInSource"": false,
+ ""tags"": [
+ ""Compiler"",
+ ""Telemetry"",
+ ""NotConfigurable""
+ ],
""properties"": {{
""severity"": ""Error"",
""defaultSeverity"": ""Error"",
""category"": ""Compiler"",
- ""isEnabledByDefault"": ""True"",
- ""isSuppressedInSource"": ""False"",
- ""customTags"": ""Compiler;Telemetry;NotConfigurable""
+ ""isEnabledByDefault"": ""True""
}}
}}
]
@@ -171,9 +180,10 @@ public class C
var expectedHeader = GetExpectedErrorLogHeader(actualOutput, cmd);
var expectedIssues = string.Format(@"
- ""issues"": [
+ ""results"": [
{{
""ruleId"": ""CS0169"",
+ ""kind"": ""warning"",
""locations"": [
{{
""analysisTarget"": [
@@ -190,29 +200,37 @@ public class C
}}
],
""fullMessage"": ""The field 'C.x' is never used"",
+ ""isSuppressedInSource"": true,
+ ""tags"": [
+ ""Compiler"",
+ ""Telemetry""
+ ],
""properties"": {{
""severity"": ""Warning"",
""warningLevel"": ""3"",
""defaultSeverity"": ""Warning"",
""title"": ""Field is never used"",
""category"": ""Compiler"",
- ""isEnabledByDefault"": ""True"",
- ""isSuppressedInSource"": ""True"",
- ""customTags"": ""Compiler;Telemetry""
+ ""isEnabledByDefault"": ""True""
}}
}},
{{
""ruleId"": ""CS5001"",
+ ""kind"": ""error"",
""locations"": [
],
""fullMessage"": ""Program does not contain a static 'Main' method suitable for an entry point"",
+ ""isSuppressedInSource"": false,
+ ""tags"": [
+ ""Compiler"",
+ ""Telemetry"",
+ ""NotConfigurable""
+ ],
""properties"": {{
""severity"": ""Error"",
""defaultSeverity"": ""Error"",
""category"": ""Compiler"",
- ""isEnabledByDefault"": ""True"",
- ""isSuppressedInSource"": ""False"",
- ""customTags"": ""Compiler;Telemetry;NotConfigurable""
+ ""isEnabledByDefault"": ""True""
}}
}}
]
@@ -255,7 +273,7 @@ public class C
var actualOutput = File.ReadAllText(errorLogFile).Trim();
var expectedHeader = GetExpectedErrorLogHeader(actualOutput, cmd);
- var expectedIssues = AnalyzerForErrorLogTest.GetExpectedErrorLogIssuesText(cmd.Compilation);
+ var expectedIssues = AnalyzerForErrorLogTest.GetExpectedErrorLogResultsText(cmd.Compilation);
var expectedText = expectedHeader + expectedIssues;
Assert.Equal(expectedText, actualOutput);
diff --git a/src/Compilers/Core/Portable/CodeAnalysis.csproj b/src/Compilers/Core/Portable/CodeAnalysis.csproj
index f572bf5ef13a5a3dbb86e2d552920f6861dade94..33b7ca14830176adf91ca195b9bc1c1970dae752 100644
--- a/src/Compilers/Core/Portable/CodeAnalysis.csproj
+++ b/src/Compilers/Core/Portable/CodeAnalysis.csproj
@@ -70,6 +70,7 @@
+
@@ -158,9 +159,6 @@
-
-
-
diff --git a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs
index dd5fdd911b7567182bcb1bfc850ea92119a07071..a324fc5ffa1a1d004644064524a7f77750943ccb 100644
--- a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs
+++ b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs
@@ -34,7 +34,7 @@ internal abstract partial class CommonCompiler
public abstract DiagnosticFormatter DiagnosticFormatter { get; }
private readonly HashSet _reportedDiagnostics = new HashSet();
- public abstract Compilation CreateCompilation(TextWriter consoleOutput, TouchedFileLogger touchedFilesLogger, ErrorLogger errorLogger);
+ public abstract Compilation CreateCompilation(TextWriter consoleOutput, TouchedFileLogger touchedFilesLogger, ErrorLogger errorLoggerOpt);
public abstract void PrintLogo(TextWriter consoleOutput);
public abstract void PrintHelp(TextWriter consoleOutput);
internal abstract string GetToolName();
@@ -177,7 +177,7 @@ internal static DiagnosticInfo ToFileReadDiagnostics(CommonMessageProvider messa
return diagnosticInfo;
}
- public bool ReportErrors(IEnumerable diagnostics, TextWriter consoleOutput, ErrorLogger errorLogger)
+ public bool ReportErrors(IEnumerable diagnostics, TextWriter consoleOutput, ErrorLogger errorLoggerOpt)
{
bool hasErrors = false;
foreach (var diag in diagnostics)
@@ -202,7 +202,7 @@ public bool ReportErrors(IEnumerable diagnostics, TextWriter console
// We want to report diagnostics with source suppression in the error log file.
// However, these diagnostics should not be reported on the console output.
- ErrorLogger.LogDiagnostic(diag, this.Culture, errorLogger);
+ errorLoggerOpt?.LogDiagnostic(diag, this.Culture);
if (diag.IsSuppressed)
{
continue;
@@ -221,7 +221,7 @@ public bool ReportErrors(IEnumerable diagnostics, TextWriter console
return hasErrors;
}
- public bool ReportErrors(IEnumerable diagnostics, TextWriter consoleOutput, ErrorLogger errorLogger)
+ public bool ReportErrors(IEnumerable diagnostics, TextWriter consoleOutput, ErrorLogger errorLoggerOpt)
{
bool hasErrors = false;
if (diagnostics != null && diagnostics.Any())
@@ -235,7 +235,7 @@ public bool ReportErrors(IEnumerable diagnostics, TextWriter con
}
PrintError(diagnostic, consoleOutput);
- ErrorLogger.LogDiagnostic(Diagnostic.Create(diagnostic), this.Culture, errorLogger);
+ errorLoggerOpt?.LogDiagnostic(Diagnostic.Create(diagnostic), this.Culture);
if (diagnostic.Severity == DiagnosticSeverity.Error)
{
diff --git a/src/Compilers/Core/Portable/CommandLine/ErrorLogger.Issue.cs b/src/Compilers/Core/Portable/CommandLine/ErrorLogger.Issue.cs
deleted file mode 100644
index d9f12427990b00086f74f5e6aabf06af34af0442..0000000000000000000000000000000000000000
--- a/src/Compilers/Core/Portable/CommandLine/ErrorLogger.Issue.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License; Version 2.0. See License.txt in the project root for license information.
-
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Linq;
-
-namespace Microsoft.CodeAnalysis
-{
- internal partial class ErrorLogger
- {
- ///
- /// Represents an issue to be logged into the error log.
- /// This could be corresponding to a or a reported by the .
- ///
- private struct Issue
- {
- public readonly string Id;
- public readonly string Message;
- public readonly string Description;
- public readonly string Title;
- public readonly string Category;
- public readonly string HelpLink;
- public readonly bool IsEnabledByDefault;
- public readonly bool IsSuppressedInSource;
- public readonly DiagnosticSeverity DefaultSeverity;
- public readonly DiagnosticSeverity Severity;
- public readonly int WarningLevel;
- public readonly Location Location;
- public readonly IReadOnlyList AdditionalLocations;
- public readonly IReadOnlyList CustomTags;
- public readonly ImmutableArray> CustomProperties;
-
- public Issue(
- string id, string message, string description,
- string title, string category, string helpLink, bool isEnabledByDefault, bool isSuppressedInSource,
- DiagnosticSeverity defaultSeverity, DiagnosticSeverity severity, int warningLevel,
- Location location, IReadOnlyList additionalLocations,
- IReadOnlyList customTags, ImmutableDictionary customProperties)
- {
- Id = id;
- Message = message;
- Description = description;
- Title = title;
- Category = category;
- HelpLink = helpLink;
- IsEnabledByDefault = isEnabledByDefault;
- IsSuppressedInSource = isSuppressedInSource;
- DefaultSeverity = defaultSeverity;
- Severity = severity;
- WarningLevel = warningLevel;
- Location = location;
- AdditionalLocations = additionalLocations;
- CustomTags = customTags;
- CustomProperties = customProperties.OrderBy(kvp => kvp.Key).ToImmutableArray();
- }
- }
- }
-}
diff --git a/src/Compilers/Core/Portable/CommandLine/ErrorLogger.Value.cs b/src/Compilers/Core/Portable/CommandLine/ErrorLogger.Value.cs
deleted file mode 100644
index 5fa3776c557b6a8de8577a732a3e92caf5a32621..0000000000000000000000000000000000000000
--- a/src/Compilers/Core/Portable/CommandLine/ErrorLogger.Value.cs
+++ /dev/null
@@ -1,128 +0,0 @@
-// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System.Collections.Generic;
-using System.Collections.Immutable;
-
-namespace Microsoft.CodeAnalysis
-{
- internal partial class ErrorLogger
- {
- ///
- /// Represents a value for a key-value pair to be emitted into the error log file.
- /// This could be a simple string or an integer OR could be a list of identical values OR a group of heterogeneous key-value pairs.
- ///
- private abstract class Value
- {
- protected readonly ErrorLogger Owner;
-
- protected Value(ErrorLogger owner)
- {
- Owner = owner;
- }
-
- public static Value Create(string value, ErrorLogger owner)
- {
- return new StringValue(value, owner);
- }
-
- public static Value Create(int value, ErrorLogger owner)
- {
- return new IntegerValue(value, owner);
- }
-
- public static Value Create(ImmutableArray> values, ErrorLogger owner)
- {
- return new GroupValue(values, owner);
- }
-
- public static Value Create(ImmutableArray values, ErrorLogger owner)
- {
- return new ListValue(values, owner);
- }
-
- public abstract void Write();
-
- private class StringValue : Value
- {
- private readonly string _value;
-
- public StringValue(string value, ErrorLogger owner)
- : base(owner)
- {
- _value = value;
- }
-
- public override void Write()
- {
- Owner.WriteValue(_value);
- }
- }
-
- private class IntegerValue : Value
- {
- private readonly int _value;
-
- public IntegerValue(int value, ErrorLogger owner)
- : base(owner)
- {
- _value = value;
- }
-
- public override void Write()
- {
- Owner.WriteValue(_value);
- }
- }
-
- private class GroupValue : Value
- {
- private readonly ImmutableArray> _keyValuePairs;
-
- public GroupValue(ImmutableArray> keyValuePairs, ErrorLogger owner)
- : base(owner)
- {
- _keyValuePairs = keyValuePairs;
- }
-
- public override void Write()
- {
- Owner.StartGroup();
-
- bool isFirst = true;
- foreach (var kvp in _keyValuePairs)
- {
- Owner.WriteKeyValuePair(kvp, isFirst);
- isFirst = false;
- }
-
- Owner.EndGroup();
- }
- }
-
- private class ListValue : Value
- {
- private readonly ImmutableArray _values;
-
- public ListValue(ImmutableArray values, ErrorLogger owner)
- : base(owner)
- {
- _values = values;
- }
-
- public override void Write()
- {
- Owner.StartList();
-
- bool isFirst = true;
- foreach (var value in _values)
- {
- Owner.WriteValue(value, isFirst, valueInList: true);
- isFirst = false;
- }
-
- Owner.EndList();
- }
- }
- }
- }
-}
diff --git a/src/Compilers/Core/Portable/CommandLine/ErrorLogger.WellKnownStrings.cs b/src/Compilers/Core/Portable/CommandLine/ErrorLogger.WellKnownStrings.cs
deleted file mode 100644
index 245a894921b9172175980604399209e74b17727e..0000000000000000000000000000000000000000
--- a/src/Compilers/Core/Portable/CommandLine/ErrorLogger.WellKnownStrings.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-namespace Microsoft.CodeAnalysis
-{
- internal partial class ErrorLogger
- {
- ///
- /// Contains well known property strings for error log file.
- ///
- private static class WellKnownStrings
- {
- public const string OutputFormatVersion = "version";
- public const string ToolFileVersion = "fileVersion";
- public const string ToolAssemblyVersion = "version";
- public const string ToolInfo = "toolInfo";
- public const string ToolName = "name";
- public const string RunLogs = "runLogs";
- public const string Issues = "issues";
- public const string DiagnosticId = "ruleId";
- public const string Locations = "locations";
- public const string ShortMessage = "shortMessage";
- public const string FullMessage = "fullMessage";
- public const string IsSuppressedInSource = "isSuppressedInSource";
- public const string Properties = "properties";
- public const string Location = "analysisTarget";
- public const string LocationSyntaxTreeUri = "uri";
- public const string LocationSpanInfo = "region";
- public const string LocationSpanStartLine = "startLine";
- public const string LocationSpanStartColumn = "startColumn";
- public const string LocationSpanEndLine = "endLine";
- public const string LocationSpanEndColumn = "endColumn";
-
- // Diagnostic/DiagnosticDescriptor properties which are not defined in our log format.
- public const string Category = "category";
- public const string Title = "title";
- public const string HelpLink = "helpLink";
- public const string CustomTags = "customTags";
- public const string IsEnabledByDefault = "isEnabledByDefault";
- public const string DefaultSeverity = "defaultSeverity";
- public const string Severity = "severity";
- public const string WarningLevel = "warningLevel";
- public const string CustomProperties = "customProperties";
-
- public const string None = "";
- }
- }
-}
diff --git a/src/Compilers/Core/Portable/CommandLine/ErrorLogger.cs b/src/Compilers/Core/Portable/CommandLine/ErrorLogger.cs
index 7e604d383f70a5d947928002be8eecc57318aa82..3632e8053cd7b5cc976f3b9fd80f640246f7d977 100644
--- a/src/Compilers/Core/Portable/CommandLine/ErrorLogger.cs
+++ b/src/Compilers/Core/Portable/CommandLine/ErrorLogger.cs
@@ -4,11 +4,12 @@
using System.IO;
using System.Diagnostics;
using System.Collections.Generic;
-using System.Collections.Immutable;
using System.Globalization;
-using System.Runtime.Serialization.Json;
+using System.Linq;
using Roslyn.Utilities;
+#pragma warning disable RS0013 // We need to invoke Diagnostic.Descriptor here to log all the metadata properties of the diagnostic.
+
namespace Microsoft.CodeAnalysis
{
///
@@ -20,154 +21,115 @@ namespace Microsoft.CodeAnalysis
internal partial class ErrorLogger : IDisposable
{
// Internal for testing purposes.
- internal const string OutputFormatVersion = "0.1";
-
- private const string indentDelta = " ";
- private const char groupStartChar = '{';
- private const char groupEndChar = '}';
- private const char listStartChar = '[';
- private const char listEndChar = ']';
-
- private readonly StreamWriter _writer;
- private readonly DataContractJsonSerializer _jsonStringSerializer;
+ internal const string OutputFormatVersion = "0.4";
- private string _currentIndent;
- private bool _reportedAnyIssues;
+ private readonly JsonWriter _writer;
public ErrorLogger(Stream stream, string toolName, string toolFileVersion, Version toolAssemblyVersion)
{
Debug.Assert(stream != null);
Debug.Assert(stream.Position == 0);
- _writer = new StreamWriter(stream);
- _jsonStringSerializer = new DataContractJsonSerializer(typeof(string));
- _currentIndent = string.Empty;
- _reportedAnyIssues = false;
+ _writer = new JsonWriter(new StreamWriter(stream));
- WriteHeader(toolName, toolFileVersion, toolAssemblyVersion);
- }
-
- private void WriteHeader(string toolName, string toolFileVersion, Version toolAssemblyVersion)
- {
- StartGroup();
+ _writer.WriteObjectStart(); // root
+ _writer.Write("version", OutputFormatVersion);
- WriteSimpleKeyValuePair(WellKnownStrings.OutputFormatVersion, OutputFormatVersion, isFirst: true);
+ _writer.WriteArrayStart("runLogs");
+ _writer.WriteObjectStart(); // runLog
- WriteKey(WellKnownStrings.RunLogs, isFirst: false);
- StartList();
- StartNewEntry(isFirst: true);
- StartGroup();
+ WriteToolInfo(toolName, toolFileVersion, toolAssemblyVersion);
- var toolInfo = GetToolInfo(toolName, toolFileVersion, toolAssemblyVersion);
- WriteKeyValuePair(WellKnownStrings.ToolInfo, toolInfo, isFirst: true);
-
- WriteKey(WellKnownStrings.Issues, isFirst: false);
- StartList();
+ _writer.WriteArrayStart("results");
}
- private Value GetToolInfo(string toolName, string toolFileVersion, Version toolAssemblyVersion)
+ private void WriteToolInfo(string name, string fileVersion, Version assemblyVersion)
{
- var builder = ArrayBuilder>.GetInstance();
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.ToolName, toolName));
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.ToolAssemblyVersion, toolAssemblyVersion.ToString(fieldCount: 3)));
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.ToolFileVersion, toolFileVersion));
- return Value.Create(builder.ToImmutableAndFree(), this);
+ _writer.WriteObjectStart("toolInfo");
+ _writer.Write("name", name);
+ _writer.Write("version", assemblyVersion.ToString(fieldCount: 3));
+ _writer.Write("fileVersion", fileVersion);
+ _writer.WriteObjectEnd();
}
-
- internal static void LogDiagnostic(Diagnostic diagnostic, CultureInfo culture, ErrorLogger errorLogger)
+ internal void LogDiagnostic(Diagnostic diagnostic, CultureInfo culture)
{
- if (errorLogger != null)
- {
-#pragma warning disable RS0013 // We need to invoke Diagnostic.Descriptor here to log all the metadata properties of the diagnostic.
- var issue = new Issue(diagnostic.Id, diagnostic.GetMessage(culture),
- diagnostic.Descriptor.Description.ToString(culture), diagnostic.Descriptor.Title.ToString(culture),
- diagnostic.Category, diagnostic.Descriptor.HelpLinkUri, diagnostic.IsEnabledByDefault, diagnostic.IsSuppressed,
- diagnostic.DefaultSeverity, diagnostic.Severity, diagnostic.WarningLevel, diagnostic.Location,
- diagnostic.AdditionalLocations, diagnostic.CustomTags, diagnostic.Properties);
-#pragma warning restore RS0013
-
- errorLogger.LogIssue(issue);
- }
- }
+ _writer.WriteObjectStart(); // result
+ _writer.Write("ruleId", diagnostic.Id);
+ _writer.Write("kind", GetKind(diagnostic.Severity));
- private void LogIssue(Issue issue)
- {
- var issueValue = GetIssueValue(issue);
- WriteValue(issueValue, isFirst: !_reportedAnyIssues, valueInList: true);
- _reportedAnyIssues = true;
- }
-
- private Value GetIssueValue(Issue issue)
- {
- var builder = ArrayBuilder>.GetInstance();
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.DiagnosticId, issue.Id));
+ WriteLocations(diagnostic.Location, diagnostic.AdditionalLocations);
- var locationsValue = GetLocationsValue(issue.Location, issue.AdditionalLocations);
- builder.Add(KeyValuePair.Create(WellKnownStrings.Locations, locationsValue));
+ string message = diagnostic.GetMessage(culture);
+ if (string.IsNullOrEmpty(message))
+ {
+ message = "";
+ }
- var message = string.IsNullOrEmpty(issue.Message) ? WellKnownStrings.None : issue.Message;
- var description = issue.Description;
+ string description = diagnostic.Descriptor.Description.ToString(culture);
if (string.IsNullOrEmpty(description))
{
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.FullMessage, message));
+ _writer.Write("fullMessage", message);
}
else
{
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.ShortMessage, message));
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.FullMessage, description));
+ _writer.Write("shortMessage", message);
+ _writer.Write("fullMessage", description);
}
- var propertiesValue = GetPropertiesValue(issue);
- builder.Add(KeyValuePair.Create(WellKnownStrings.Properties, propertiesValue));
+ _writer.Write("isSuppressedInSource", diagnostic.IsSuppressed);
- return Value.Create(builder.ToImmutableAndFree(), this);
+ WriteTags(diagnostic);
+
+ WriteProperties(diagnostic, culture);
+
+ _writer.WriteObjectEnd(); // result
}
- private Value GetLocationsValue(Location location, IReadOnlyList additionalLocations)
+ private void WriteLocations(Location location, IReadOnlyList additionalLocations)
{
- var builder = ArrayBuilder.GetInstance();
+ _writer.WriteArrayStart("locations");
- var locationValue = GetLocationValue(location);
- if (locationValue != null)
- {
- builder.Add(locationValue);
- }
+ WriteLocation(location);
- if (additionalLocations?.Count > 0)
+ if (additionalLocations != null)
{
foreach (var additionalLocation in additionalLocations)
{
- locationValue = GetLocationValue(additionalLocation);
- if (locationValue != null)
- {
- builder.Add(locationValue);
- }
+ WriteLocation(additionalLocation);
}
}
- return Value.Create(builder.ToImmutableAndFree(), this);
+ _writer.WriteArrayEnd();
}
- private Value GetLocationValue(Location location)
+ private void WriteLocation(Location location)
{
if (location.SourceTree == null)
{
- return null;
+ return;
}
- var builder = ArrayBuilder>.GetInstance();
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.LocationSyntaxTreeUri, GetUri(location.SourceTree)));
+ _writer.WriteObjectStart(); // location
+
+ _writer.WriteArrayStart("analysisTarget");
+ _writer.WriteObjectStart(); // physical location component
- var spanInfoValue = GetSpanInfoValue(location.GetLineSpan());
- builder.Add(KeyValuePair.Create(WellKnownStrings.LocationSpanInfo, spanInfoValue));
+ _writer.Write("uri", GetUri(location.SourceTree));
- var coreLocationValue = Value.Create(builder.ToImmutableAndFree(), this);
+ // Note that SARIF lines and columns are 1-based, but FileLinePositionSpan is 0-based
+ FileLinePositionSpan span = location.GetLineSpan();
+ _writer.WriteKey("region");
+ _writer.WriteObjectStart();
+ _writer.Write("startLine", span.StartLinePosition.Line + 1);
+ _writer.Write("startColumn", span.StartLinePosition.Character + 1);
+ _writer.Write("endLine", span.EndLinePosition.Line + 1);
+ _writer.Write("endColumn", span.EndLinePosition.Character + 1);
+ _writer.WriteObjectEnd(); // region
- // Our log format requires this to be wrapped.
- var wrapperList = Value.Create(ImmutableArray.Create(coreLocationValue), this);
- var wrapperKvp = KeyValuePair.Create(WellKnownStrings.Location, wrapperList);
- return Value.Create(ImmutableArray.Create(wrapperKvp), this);
+ _writer.WriteObjectEnd(); // physical location component
+ _writer.WriteArrayEnd(); // analysisTarget
+ _writer.WriteObjectEnd(); // location
}
private static string GetUri(SyntaxTree syntaxTree)
@@ -186,189 +148,87 @@ private static string GetUri(SyntaxTree syntaxTree)
return uri.ToString();
}
- private Value GetSpanInfoValue(FileLinePositionSpan lineSpan)
+ private void WriteTags(Diagnostic diagnostic)
{
- // Note that SARIF region lines and columns are specified to be 1-based, but FileLinePositionSpan.Line and Character are 0-based.
- var builder = ArrayBuilder>.GetInstance();
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.LocationSpanStartLine, lineSpan.StartLinePosition.Line + 1));
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.LocationSpanStartColumn, lineSpan.StartLinePosition.Character + 1));
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.LocationSpanEndLine, lineSpan.EndLinePosition.Line + 1));
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.LocationSpanEndColumn, lineSpan.EndLinePosition.Character + 1));
- return Value.Create(builder.ToImmutableAndFree(), this);
- }
-
- private Value GetPropertiesValue(Issue issue)
- {
- var builder = ArrayBuilder>.GetInstance();
-
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.Severity, issue.Severity.ToString()));
- if (issue.Severity == DiagnosticSeverity.Warning)
+ if (diagnostic.CustomTags.Count > 0)
{
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.WarningLevel, issue.WarningLevel.ToString()));
- }
+ _writer.WriteArrayStart("tags");
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.DefaultSeverity, issue.DefaultSeverity.ToString()));
+ foreach (string tag in diagnostic.CustomTags)
+ {
+ _writer.Write(tag);
+ }
- if (!string.IsNullOrEmpty(issue.Title))
- {
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.Title, issue.Title));
+ _writer.WriteArrayEnd();
}
+ }
+
+ private void WriteProperties(Diagnostic diagnostic, CultureInfo culture)
+ {
+ _writer.WriteObjectStart("properties");
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.Category, issue.Category));
+ _writer.Write("severity", diagnostic.Severity.ToString());
- if (!string.IsNullOrEmpty(issue.HelpLink))
+ if (diagnostic.Severity == DiagnosticSeverity.Warning)
{
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.HelpLink, issue.HelpLink));
+ _writer.Write("warningLevel", diagnostic.WarningLevel.ToString());
}
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.IsEnabledByDefault, issue.IsEnabledByDefault.ToString()));
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.IsSuppressedInSource, issue.IsSuppressedInSource.ToString()));
+ _writer.Write("defaultSeverity", diagnostic.DefaultSeverity.ToString());
- if (issue.CustomTags.Count > 0)
+ string title = diagnostic.Descriptor.Title.ToString(culture);
+ if (!string.IsNullOrEmpty(title))
{
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.CustomTags, issue.CustomTags.WhereNotNull().Join(";")));
+ _writer.Write("title", title);
}
- foreach (var kvp in issue.CustomProperties)
+ _writer.Write("category", diagnostic.Category);
+
+ string helpLink = diagnostic.Descriptor.HelpLinkUri;
+ if (!string.IsNullOrEmpty(helpLink))
{
- builder.Add(CreateSimpleKeyValuePair(WellKnownStrings.CustomProperties + "." + kvp.Key, kvp.Value));
+ _writer.Write("helpLink", helpLink);
}
- return Value.Create(builder.ToImmutableAndFree(), this);
- }
-
- #region Helper methods for core logging
-
- private void WriteKeyValuePair(KeyValuePair kvp, bool isFirst)
- {
- WriteKeyValuePair(kvp.Key, kvp.Value, isFirst);
- }
-
- private void WriteKeyValuePair(string key, Value value, bool isFirst)
- {
- WriteKey(key, isFirst);
- WriteValue(value);
- }
-
- private void WriteSimpleKeyValuePair(string key, string value, bool isFirst)
- {
- WriteKey(key, isFirst);
- WriteValue(value);
- }
+ _writer.Write("isEnabledByDefault", diagnostic.IsEnabledByDefault.ToString());
- private void WriteKey(string key, bool isFirst)
- {
- StartNewEntry(isFirst);
- _writer.Write($"\"{key}\": ");
- }
-
- private void WriteValue(Value value, bool isFirst = true, bool valueInList = false)
- {
- if (!isFirst || valueInList)
+ foreach (var pair in diagnostic.Properties.OrderBy(x => x.Key, StringComparer.Ordinal))
{
- StartNewEntry(isFirst);
+ _writer.Write("customProperties." + pair.Key, pair.Value);
}
- value.Write();
- }
-
- private void WriteValue(string value)
- {
- _writer.Flush();
- _jsonStringSerializer.WriteObject(_writer.BaseStream, value);
- }
-
- private void WriteValue(int value)
- {
- _writer.Write(value);
+ _writer.WriteObjectEnd(); // properties
}
- private void StartNewEntry(bool isFirst)
+ private static string GetKind(DiagnosticSeverity severity)
{
- if (!isFirst)
- {
- _writer.WriteLine(',');
- }
- else
+ switch (severity)
{
- _writer.WriteLine();
+ case DiagnosticSeverity.Info:
+ return "note";
+
+ case DiagnosticSeverity.Error:
+ return "error";
+
+ case DiagnosticSeverity.Warning:
+ case DiagnosticSeverity.Hidden:
+ default:
+ // note that in the hidden or default cases, we still write out the actual severity as a
+ // property so no information is lost. We have to conform to the SARIF spec for kind,
+ // which allows only pass, warning, error, or notApplicable.
+ return "warning";
}
-
- _writer.Write(_currentIndent);
- }
-
- private void StartGroup()
- {
- StartGroupOrListCommon(groupStartChar);
- }
-
- private void EndGroup()
- {
- EndGroupOrListCommon(groupEndChar);
- }
-
- private void StartList()
- {
- StartGroupOrListCommon(listStartChar);
- }
-
- private void EndList()
- {
- EndGroupOrListCommon(listEndChar);
}
- private void StartGroupOrListCommon(char startChar)
- {
- _writer.Write(startChar);
- IncreaseIndentation();
- }
-
- private void EndGroupOrListCommon(char endChar)
- {
- _writer.WriteLine();
- DecreaseIndentation();
- _writer.Write(_currentIndent + endChar);
- }
-
- private void IncreaseIndentation()
- {
- _currentIndent += indentDelta;
- }
-
- private void DecreaseIndentation()
- {
- _currentIndent = _currentIndent.Substring(indentDelta.Length);
- }
-
- private KeyValuePair CreateSimpleKeyValuePair(string key, string value)
- {
- var stringValue = Value.Create(value, this);
- return KeyValuePair.Create(key, stringValue);
- }
-
- private KeyValuePair CreateSimpleKeyValuePair(string key, int value)
- {
- var intValue = Value.Create(value, this);
- return KeyValuePair.Create(key, intValue);
- }
-
- #endregion
-
public void Dispose()
{
- // End issues list.
- EndList();
-
- // End runLog entry.
- EndGroup();
-
- // End runLogs list.
- EndList();
-
- // End dictionary for log file key-value pairs.
- EndGroup();
-
+ _writer.WriteArrayEnd(); // results
+ _writer.WriteObjectEnd(); // runLog
+ _writer.WriteArrayEnd(); // runLogs
+ _writer.WriteObjectEnd(); // root
_writer.Dispose();
}
}
}
+
+#pragma warning restore RS0013
\ No newline at end of file
diff --git a/src/Compilers/Core/Portable/InternalUtilities/JsonWriter.cs b/src/Compilers/Core/Portable/InternalUtilities/JsonWriter.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1a5f0faad02577c743f3333bf2ecfbf57eb167f8
--- /dev/null
+++ b/src/Compilers/Core/Portable/InternalUtilities/JsonWriter.cs
@@ -0,0 +1,158 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Runtime.Serialization.Json;
+
+namespace Roslyn.Utilities
+{
+ ///
+ /// A simple, forward-only JSON writer to avoid adding dependencies to the compiler.
+ /// Used to generate /errorlogger output.
+ ///
+ /// Does not guarantee well-formed JSON if misused. It is the caller's reponsibility
+ /// to balance array/object start/end, to only write key-value pairs to objects and
+ /// elements to arrays, etc.
+ ///
+ /// Takes ownership of the given StreamWriter at construction and handles its disposal.
+ ///
+ internal sealed class JsonWriter : IDisposable
+ {
+ private readonly StreamWriter _outptut;
+ private readonly DataContractJsonSerializer _stringWriter;
+ private int _indent;
+ private string _pending;
+
+ private static readonly string s_newLine = Environment.NewLine;
+ private static readonly string s_commaNewLine = "," + Environment.NewLine;
+
+ private const string Indentation = " ";
+
+ public JsonWriter(StreamWriter output)
+ {
+ _outptut = output;
+ _stringWriter = new DataContractJsonSerializer(typeof(string));
+ _pending = "";
+ }
+
+ public void WriteObjectStart()
+ {
+ WriteStart('{');
+ }
+
+ public void WriteObjectStart(string key)
+ {
+ WriteKey(key);
+ WriteObjectStart();
+ }
+
+ public void WriteObjectEnd()
+ {
+ WriteEnd('}');
+ }
+
+ public void WriteArrayStart()
+ {
+ WriteStart('[');
+ }
+
+ public void WriteArrayStart(string key)
+ {
+ WriteKey(key);
+ WriteArrayStart();
+ }
+
+ public void WriteArrayEnd()
+ {
+ WriteEnd(']');
+ }
+
+ public void WriteKey(string key)
+ {
+ Write(key);
+ _outptut.Write(": ");
+ _pending = "";
+ }
+
+ public void Write(string key, string value)
+ {
+ WriteKey(key);
+ Write(value);
+ }
+
+ public void Write(string key, int value)
+ {
+ WriteKey(key);
+ Write(value);
+ }
+
+ public void Write(string key, bool value)
+ {
+ WriteKey(key);
+ Write(value);
+ }
+
+ public void Write(string value)
+ {
+ // Consider switching to custom escaping logic here. Flushing all the time (in
+ // order to borrow DataContractJsonSerializer escaping) is expensive.
+ //
+ // Also, it would be nicer not to escape the forward slashes in URIs (which is
+ // optional in JSON.)
+
+ WritePending();
+ _outptut.Flush();
+ _stringWriter.WriteObject(_outptut.BaseStream, value);
+ _pending = s_commaNewLine;
+ }
+
+ public void Write(int value)
+ {
+ WritePending();
+ _outptut.Write(value);
+ _pending = s_commaNewLine;
+ }
+
+ public void Write(bool value)
+ {
+ WritePending();
+ _outptut.Write(value ? "true" : "false");
+ _pending = s_commaNewLine;
+ }
+
+ private void WritePending()
+ {
+ if (_pending.Length > 0)
+ {
+ _outptut.Write(_pending);
+
+ for (int i = 0; i < _indent; i++)
+ {
+ _outptut.Write(Indentation);
+ }
+ }
+ }
+
+ private void WriteStart(char c)
+ {
+ WritePending();
+ _outptut.Write(c);
+ _pending = s_newLine;
+ _indent++;
+ }
+
+ private void WriteEnd(char c)
+ {
+ _pending = s_newLine;
+ _indent--;
+ WritePending();
+ _outptut.Write(c);
+ _pending = s_commaNewLine;
+ }
+
+ public void Dispose()
+ {
+ _outptut.Dispose();
+ }
+ }
+}
diff --git a/src/Compilers/VisualBasic/Test/CommandLine/ErrorLoggerTests.vb b/src/Compilers/VisualBasic/Test/CommandLine/ErrorLoggerTests.vb
index 39be5d80273b8311813619cabdbc48fa2dbd64f2..ce71c683d7a04fd1f2f8970ceeb595f4976f5902 100644
--- a/src/Compilers/VisualBasic/Test/CommandLine/ErrorLoggerTests.vb
+++ b/src/Compilers/VisualBasic/Test/CommandLine/ErrorLoggerTests.vb
@@ -46,7 +46,7 @@ End Class
Dim expectedHeader = GetExpectedErrorLogHeader(actualOutput, cmd)
Dim expectedIssues = "
- ""issues"": [
+ ""results"": [
]
}
]
@@ -91,9 +91,10 @@ End Class
Dim expectedHeader = GetExpectedErrorLogHeader(actualOutput, cmd)
Dim expectedIssues = String.Format("
- ""issues"": [
+ ""results"": [
{{
""ruleId"": ""BC42024"",
+ ""kind"": ""warning"",
""locations"": [
{{
""analysisTarget"": [
@@ -110,29 +111,37 @@ End Class
}}
],
""fullMessage"": ""Unused local variable: 'x'."",
+ ""isSuppressedInSource"": false,
+ ""tags"": [
+ ""Compiler"",
+ ""Telemetry""
+ ],
""properties"": {{
""severity"": ""Warning"",
""warningLevel"": ""1"",
""defaultSeverity"": ""Warning"",
""title"": ""Unused local variable"",
""category"": ""Compiler"",
- ""isEnabledByDefault"": ""True"",
- ""isSuppressedInSource"": ""False"",
- ""customTags"": ""Compiler;Telemetry""
+ ""isEnabledByDefault"": ""True""
}}
}},
{{
""ruleId"": ""BC30420"",
+ ""kind"": ""error"",
""locations"": [
],
""fullMessage"": ""'Sub Main' was not found in '{1}'."",
+ ""isSuppressedInSource"": false,
+ ""tags"": [
+ ""Compiler"",
+ ""Telemetry"",
+ ""NotConfigurable""
+ ],
""properties"": {{
""severity"": ""Error"",
""defaultSeverity"": ""Error"",
""category"": ""Compiler"",
- ""isEnabledByDefault"": ""True"",
- ""isSuppressedInSource"": ""False"",
- ""customTags"": ""Compiler;Telemetry;NotConfigurable""
+ ""isEnabledByDefault"": ""True""
}}
}}
]
@@ -182,9 +191,10 @@ End Class
Dim expectedHeader = GetExpectedErrorLogHeader(actualOutput, cmd)
Dim expectedIssues = String.Format("
- ""issues"": [
+ ""results"": [
{{
""ruleId"": ""BC42024"",
+ ""kind"": ""warning"",
""locations"": [
{{
""analysisTarget"": [
@@ -201,29 +211,37 @@ End Class
}}
],
""fullMessage"": ""Unused local variable: 'x'."",
+ ""isSuppressedInSource"": true,
+ ""tags"": [
+ ""Compiler"",
+ ""Telemetry""
+ ],
""properties"": {{
""severity"": ""Warning"",
""warningLevel"": ""1"",
""defaultSeverity"": ""Warning"",
""title"": ""Unused local variable"",
""category"": ""Compiler"",
- ""isEnabledByDefault"": ""True"",
- ""isSuppressedInSource"": ""True"",
- ""customTags"": ""Compiler;Telemetry""
+ ""isEnabledByDefault"": ""True""
}}
}},
{{
""ruleId"": ""BC30420"",
+ ""kind"": ""error"",
""locations"": [
],
""fullMessage"": ""'Sub Main' was not found in '{1}'."",
+ ""isSuppressedInSource"": false,
+ ""tags"": [
+ ""Compiler"",
+ ""Telemetry"",
+ ""NotConfigurable""
+ ],
""properties"": {{
""severity"": ""Error"",
""defaultSeverity"": ""Error"",
""category"": ""Compiler"",
- ""isEnabledByDefault"": ""True"",
- ""isSuppressedInSource"": ""False"",
- ""customTags"": ""Compiler;Telemetry;NotConfigurable""
+ ""isEnabledByDefault"": ""True""
}}
}}
]
@@ -271,7 +289,7 @@ End Class
Dim actualOutput = File.ReadAllText(errorLogFile).Trim()
Dim expectedHeader = GetExpectedErrorLogHeader(actualOutput, cmd)
- Dim expectedIssues = AnalyzerForErrorLogTest.GetExpectedErrorLogIssuesText(cmd.Compilation)
+ Dim expectedIssues = AnalyzerForErrorLogTest.GetExpectedErrorLogResultsText(cmd.Compilation)
Dim expectedText = expectedHeader + expectedIssues
Assert.Equal(expectedText, actualOutput)
diff --git a/src/Test/Utilities/Desktop/CommonDiagnosticAnalyzers.cs b/src/Test/Utilities/Desktop/CommonDiagnosticAnalyzers.cs
index 0b13373e9d2b4bb7264989c74701031ce79211e8..3566aa5f4306f48789866177fe982b2875a884aa 100644
--- a/src/Test/Utilities/Desktop/CommonDiagnosticAnalyzers.cs
+++ b/src/Test/Utilities/Desktop/CommonDiagnosticAnalyzers.cs
@@ -77,7 +77,7 @@ private static string GetExpectedPropertiesMapText()
return expectedText;
}
- public static string GetExpectedErrorLogIssuesText(Compilation compilation)
+ public static string GetExpectedErrorLogResultsText(Compilation compilation)
{
var tree = compilation.SyntaxTrees.First();
var root = tree.GetRoot();
@@ -85,9 +85,10 @@ public static string GetExpectedErrorLogIssuesText(Compilation compilation)
var filePath = GetEscapedUriForPath(tree.FilePath);
return @"
- ""issues"": [
+ ""results"": [
{
""ruleId"": """ + Descriptor1.Id + @""",
+ ""kind"": """ + (Descriptor1.DefaultSeverity == DiagnosticSeverity.Error ? "error" : "warning") + @""",
""locations"": [
{
""analysisTarget"": [
@@ -105,6 +106,10 @@ public static string GetExpectedErrorLogIssuesText(Compilation compilation)
],
""shortMessage"": """ + Descriptor1.MessageFormat + @""",
""fullMessage"": """ + Descriptor1.Description + @""",
+ ""isSuppressedInSource"": false,
+ ""tags"": [
+ " + String.Join("," + Environment.NewLine + " ", Descriptor1.CustomTags.Select(s => $"\"{s}\"")) + @"
+ ],
""properties"": {
""severity"": """ + Descriptor1.DefaultSeverity + @""",
""warningLevel"": ""1"",
@@ -112,27 +117,28 @@ public static string GetExpectedErrorLogIssuesText(Compilation compilation)
""title"": """ + Descriptor1.Title + @""",
""category"": """ + Descriptor1.Category + @""",
""helpLink"": """ + Descriptor1.HelpLinkUri + @""",
- ""isEnabledByDefault"": """ + Descriptor1.IsEnabledByDefault + @""",
- ""isSuppressedInSource"": ""False"",
- ""customTags"": """ + Descriptor1.CustomTags.Join(";") + @"""" +
+ ""isEnabledByDefault"": """ + Descriptor1.IsEnabledByDefault + @"""" +
GetExpectedPropertiesMapText() + @"
}
},
{
""ruleId"": """ + Descriptor2.Id + @""",
+ ""kind"": """ + (Descriptor2.DefaultSeverity == DiagnosticSeverity.Error ? "error" : "warning") + @""",
""locations"": [
],
""shortMessage"": """ + Descriptor2.MessageFormat + @""",
""fullMessage"": """ + Descriptor2.Description + @""",
+ ""isSuppressedInSource"": false,
+ ""tags"": [
+ " + String.Join("," + Environment.NewLine + " ", Descriptor2.CustomTags.Select(s => $"\"{s}\"")) + @"
+ ],
""properties"": {
""severity"": """ + Descriptor2.DefaultSeverity + @""",
""defaultSeverity"": """ + Descriptor2.DefaultSeverity + @""",
""title"": """ + Descriptor2.Title + @""",
""category"": """ + Descriptor2.Category + @""",
""helpLink"": """ + Descriptor2.HelpLinkUri + @""",
- ""isEnabledByDefault"": """ + Descriptor2.IsEnabledByDefault + @""",
- ""isSuppressedInSource"": ""False"",
- ""customTags"": """ + Descriptor2.CustomTags.Join(";") + @"""" +
+ ""isEnabledByDefault"": """ + Descriptor2.IsEnabledByDefault + @"""" +
GetExpectedPropertiesMapText() + @"
}
}