提交 fbae6b4d 编写于 作者: N Nick Guerrera

Merge pull request #8166 from nguerrera/sarif-0.4

Bring /errorlog output up to SARIF draft v0.4
......@@ -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)
......@@ -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);
......
......@@ -70,6 +70,7 @@
<Compile Include="DiagnosticAnalyzer\AnalysisContextInfo.cs" />
<Compile Include="DiagnosticAnalyzer\SuppressMessageInfo.cs" />
<Compile Include="Diagnostic\SuppressionInfo.cs" />
<Compile Include="InternalUtilities\JsonWriter.cs" />
<Compile Include="InternalUtilities\SetWithInsertionOrder.cs" />
<Compile Include="InternalUtilities\StackGuard.cs" />
<Compile Include="InternalUtilities\StreamExtensions.cs" />
......@@ -158,9 +159,6 @@
<Compile Include="CommandLine\CommonCompiler.LoggingStrongNameProvider.cs" />
<Compile Include="CommandLine\CommonCompiler.LoggingXmlFileResolver.cs" />
<Compile Include="CommandLine\ErrorLogger.cs" />
<Compile Include="CommandLine\ErrorLogger.Issue.cs" />
<Compile Include="CommandLine\ErrorLogger.Value.cs" />
<Compile Include="CommandLine\ErrorLogger.WellKnownStrings.cs" />
<Compile Include="CommandLine\TouchedFileLogger.cs" />
<Compile Include="Compilation.EmitStreamProvider.cs" />
<Compile Include="Compilation\LoadDirective.cs" />
......
......@@ -34,7 +34,7 @@ internal abstract partial class CommonCompiler
public abstract DiagnosticFormatter DiagnosticFormatter { get; }
private readonly HashSet<Diagnostic> _reportedDiagnostics = new HashSet<Diagnostic>();
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<Diagnostic> diagnostics, TextWriter consoleOutput, ErrorLogger errorLogger)
public bool ReportErrors(IEnumerable<Diagnostic> diagnostics, TextWriter consoleOutput, ErrorLogger errorLoggerOpt)
{
bool hasErrors = false;
foreach (var diag in diagnostics)
......@@ -202,7 +202,7 @@ public bool ReportErrors(IEnumerable<Diagnostic> 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<Diagnostic> diagnostics, TextWriter console
return hasErrors;
}
public bool ReportErrors(IEnumerable<DiagnosticInfo> diagnostics, TextWriter consoleOutput, ErrorLogger errorLogger)
public bool ReportErrors(IEnumerable<DiagnosticInfo> diagnostics, TextWriter consoleOutput, ErrorLogger errorLoggerOpt)
{
bool hasErrors = false;
if (diagnostics != null && diagnostics.Any())
......@@ -235,7 +235,7 @@ public bool ReportErrors(IEnumerable<DiagnosticInfo> 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)
{
......
// 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
{
/// <summary>
/// Represents an issue to be logged into the error log.
/// This could be corresponding to a <see cref="Diagnostic"/> or a <see cref="DiagnosticInfo"/> reported by the <see cref="CommonCompiler"/>.
/// </summary>
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<Location> AdditionalLocations;
public readonly IReadOnlyList<string> CustomTags;
public readonly ImmutableArray<KeyValuePair<string, string>> 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<Location> additionalLocations,
IReadOnlyList<string> customTags, ImmutableDictionary<string, string> 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();
}
}
}
}
// 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
{
/// <summary>
/// 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.
/// </summary>
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<KeyValuePair<string, Value>> values, ErrorLogger owner)
{
return new GroupValue(values, owner);
}
public static Value Create(ImmutableArray<Value> 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<KeyValuePair<string, Value>> _keyValuePairs;
public GroupValue(ImmutableArray<KeyValuePair<string, Value>> 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<Value> _values;
public ListValue(ImmutableArray<Value> 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();
}
}
}
}
}
// 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
{
/// <summary>
/// Contains well known property strings for error log file.
/// </summary>
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 = "<None>";
}
}
}
......@@ -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
{
/// <summary>
......@@ -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<KeyValuePair<string, Value>>.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<KeyValuePair<string, Value>>.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 = "<None>";
}
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<Location> additionalLocations)
private void WriteLocations(Location location, IReadOnlyList<Location> additionalLocations)
{
var builder = ArrayBuilder<Value>.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<KeyValuePair<string, Value>>.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<KeyValuePair<string, Value>>.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<KeyValuePair<string, Value>>.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<string, Value> 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<string, Value> CreateSimpleKeyValuePair(string key, string value)
{
var stringValue = Value.Create(value, this);
return KeyValuePair.Create(key, stringValue);
}
private KeyValuePair<string, Value> 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
// 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
{
/// <summary>
/// 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.
/// </summary>
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();
}
}
}
......@@ -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)
......
......@@ -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() + @"
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册