| File: CommandLine\SarifErrorLogger.cs | Web Access |
| Project: src\roslyn\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis) |
// 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.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis { /// <summary> /// Base class for the <see cref="SarifV1ErrorLogger"/> and <see cref="SarifV2ErrorLogger"/> classes. /// The SarifV2ErrorLogger produces the standardized SARIF v2.1.0. The SarifV1ErrorLogger produces /// the non-standardized SARIF v1.0.0. It is retained (and in fact, is retained as the default) /// for compatibility with previous versions of the compiler. Customers who want to integrate /// with standardized SARIF tooling should specify /errorlog:logFilePath;version=2 on the command /// line to produce SARIF v2.1.0. /// </summary> internal abstract class SarifErrorLogger : ErrorLogger, IDisposable { protected JsonWriter _writer { get; } protected CultureInfo _culture { get; } protected SarifErrorLogger(Stream stream, CultureInfo culture) { Debug.Assert(stream != null); Debug.Assert(stream!.Position == 0); _writer = new JsonWriter(new StreamWriter(stream)); _culture = culture; } // protected abstract string PrimaryLocationPropertyName { get; } protected abstract void WritePhysicalLocation(Location diagnosticLocation); public virtual void Dispose() { _writer.Dispose(); } protected void WriteRegion(FileLinePositionSpan span) { // Note that SARIF lines and columns are 1-based, but FileLinePositionSpan is 0-based _writer.WriteObjectStart("region"); _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 } protected static string GetLevel(DiagnosticSeverity severity) { switch (severity) { case DiagnosticSeverity.Info: return "note"; case DiagnosticSeverity.Error: return "error"; case DiagnosticSeverity.Warning: return "warning"; case DiagnosticSeverity.Hidden: // Hidden diagnostics are not reported on the command line but we still report the rule metadata for them // in the error logger. SARIF format does not have a special string for hidden diagnostics, // so we represent them similar to Info diagnostics. // In future, if required, we can represent them with a custom property in the SARIF log. goto case DiagnosticSeverity.Info; default: Debug.Assert(false); goto case DiagnosticSeverity.Warning; } } protected void WriteResultProperties(Diagnostic diagnostic) { // Currently, the following are always inherited from the descriptor and therefore will be // captured as rule metadata and need not be logged here. IsWarningAsError is also omitted // because it can be inferred from level vs. defaultLevel in the log. Debug.Assert(diagnostic.CustomTags.SequenceEqual(diagnostic.Descriptor.ImmutableCustomTags)); Debug.Assert(diagnostic.Category == diagnostic.Descriptor.Category); Debug.Assert(diagnostic.DefaultSeverity == diagnostic.Descriptor.DefaultSeverity); Debug.Assert(diagnostic.IsEnabledByDefault == diagnostic.Descriptor.IsEnabledByDefault); if (diagnostic.WarningLevel > 0 || diagnostic.Properties.Count > 0) { _writer.WriteObjectStart("properties"); if (diagnostic.WarningLevel > 0) { _writer.Write("warningLevel", diagnostic.WarningLevel); } if (diagnostic.Properties.Count > 0) { _writer.WriteObjectStart("customProperties"); foreach (var pair in diagnostic.Properties.OrderBy(x => x.Key, StringComparer.Ordinal)) { _writer.Write(pair.Key, pair.Value); } _writer.WriteObjectEnd(); } _writer.WriteObjectEnd(); // properties } } protected static bool HasPath(Location location) { return !string.IsNullOrEmpty(location.GetLineSpan().Path); } private static readonly Uri s_fileRoot = new Uri("file:///"); protected static string GetUri(string path) { Debug.Assert(!string.IsNullOrEmpty(path)); // Note that in general, these "paths" are opaque strings to be // interpreted by resolvers (see SyntaxTree.FilePath documentation). // Common case: absolute path -> absolute URI if (Path.IsPathRooted(path)) { // N.B. URI does not handle multiple backslashes or `..` well, so call GetFullPath // to normalize before going to URI var fullPath = Path.GetFullPath(path); if (Uri.TryCreate(fullPath, UriKind.Absolute, out var uri)) { // We use Uri.AbsoluteUri and not Uri.ToString() because Uri.ToString() // is unescaped (e.g. spaces remain unreplaced by %20) and therefore // not well-formed. return uri.AbsoluteUri; } } else { // Attempt to normalize the directory separators if (!PathUtilities.IsUnixLikePlatform) { path = path.Replace(@"\\", @"\"); path = PathUtilities.NormalizeWithForwardSlash(path); } if (Uri.TryCreate(path, UriKind.Relative, out var uri)) { // First fallback attempt: attempt to interpret as relative path/URI. // (Perhaps the resolver works that way.) // There is no AbsoluteUri equivalent for relative URI references and ToString() // won't escape without this relative -> absolute -> relative trick. return s_fileRoot.MakeRelativeUri(new Uri(s_fileRoot, uri)).ToString(); } } // Last resort: UrlEncode the whole opaque string. return System.Net.WebUtility.UrlEncode(path); } } }