Skip to content

Commit cf5a4ed

Browse files
committed
Merge branch 'dathtrevino/feat/slnx_support' into task/slnx_everything
2 parents 27d7848 + 1384df8 commit cf5a4ed

File tree

10 files changed

+172
-3
lines changed

10 files changed

+172
-3
lines changed

.github/workflows/build.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ jobs:
2828
- uses: actions/checkout@v4
2929
with:
3030
fetch-depth: 0
31+
- name: Setup dotnet
32+
uses: actions/setup-dotnet@v4
33+
with:
34+
dotnet-version: |
35+
8.0.x
36+
9.0.x
3137
- run: dotnet tool install -g GitVersion.Tool --version 5.11.1
3238
- name: Resolve version
3339
id: version

ChangeLog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- [CLI] Add support for GitLab analyzer reports ([PR](https://github.com/dotnet/roslynator/pull/1633))
13+
1014
## [4.13.1] - 2025-02-23
1115

1216
### Added

src/CommandLine/Commands/AnalyzeCommand.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Threading;
99
using System.Threading.Tasks;
1010
using Microsoft.CodeAnalysis;
11+
using Roslynator.CommandLine.Json;
1112
using Roslynator.CommandLine.Xml;
1213
using Roslynator.Diagnostics;
1314
using static Roslynator.Logger;
@@ -96,8 +97,15 @@ protected override void ProcessResults(IList<AnalyzeCommandResult> results)
9697
&& analysisResults.Any(f => f.Diagnostics.Any() || f.CompilerDiagnostics.Any()))
9798
{
9899
CultureInfo culture = (Options.Culture is not null) ? CultureInfo.GetCultureInfo(Options.Culture) : null;
99-
100-
DiagnosticXmlSerializer.Serialize(analysisResults, Options.Output, culture);
100+
if (!string.IsNullOrWhiteSpace(Options.OutputFormat) && Options.OutputFormat.Equals("gitlab", StringComparison.CurrentCultureIgnoreCase))
101+
{
102+
DiagnosticGitLabJsonSerializer.Serialize(analysisResults, Options.Output, culture);
103+
}
104+
else
105+
{
106+
// Default output format is xml
107+
DiagnosticXmlSerializer.Serialize(analysisResults, Options.Output, culture);
108+
}
101109
}
102110
}
103111

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Newtonsoft.Json;
2+
3+
namespace Roslynator.CommandLine.GitLab;
4+
5+
internal sealed class GitLabIssue
6+
{
7+
public string Type { get; set; }
8+
public string Fingerprint { get; set; }
9+
[JsonProperty("check_name")]
10+
public string CheckName { get; set; }
11+
public string Description { get; set; }
12+
public string Severity { get; set; }
13+
public GitLabIssueLocation Location { get; set; }
14+
public string[] Categories { get; set; }
15+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Roslynator.CommandLine.GitLab;
2+
3+
internal sealed class GitLabIssueLocation
4+
{
5+
public string Path { get; set; }
6+
public GitLabLocationLines Lines { get; set; }
7+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Roslynator.CommandLine.GitLab;
2+
3+
internal sealed class GitLabLocationLines
4+
{
5+
public int Begin { get; set; }
6+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Security.Cryptography;
6+
using System.Text;
7+
using Microsoft.CodeAnalysis;
8+
using Newtonsoft.Json;
9+
using Newtonsoft.Json.Serialization;
10+
using Roslynator.CommandLine.GitLab;
11+
using Roslynator.Diagnostics;
12+
13+
namespace Roslynator.CommandLine.Json;
14+
15+
internal static class DiagnosticGitLabJsonSerializer
16+
{
17+
private static readonly JsonSerializerSettings _jsonSerializerSettings = new()
18+
{
19+
Formatting = Newtonsoft.Json.Formatting.Indented,
20+
NullValueHandling = NullValueHandling.Ignore,
21+
ContractResolver = new DefaultContractResolver()
22+
{
23+
NamingStrategy = new CamelCaseNamingStrategy()
24+
},
25+
};
26+
27+
public static void Serialize(
28+
IEnumerable<ProjectAnalysisResult> results,
29+
string filePath,
30+
IFormatProvider formatProvider = null)
31+
{
32+
IEnumerable<DiagnosticInfo> diagnostics = results.SelectMany(f => f.CompilerDiagnostics.Concat(f.Diagnostics));
33+
34+
var reportItems = new List<GitLabIssue>();
35+
foreach (DiagnosticInfo diagnostic in diagnostics)
36+
{
37+
GitLabIssueLocation location = null;
38+
if (diagnostic.LineSpan.IsValid)
39+
{
40+
location = new GitLabIssueLocation()
41+
{
42+
Path = diagnostic.LineSpan.Path,
43+
Lines = new GitLabLocationLines()
44+
{
45+
Begin = diagnostic.LineSpan.StartLinePosition.Line
46+
},
47+
};
48+
}
49+
50+
var severity = "minor";
51+
severity = diagnostic.Severity switch
52+
{
53+
DiagnosticSeverity.Warning => "major",
54+
DiagnosticSeverity.Error => "critical",
55+
_ => "minor",
56+
};
57+
58+
string issueFingerPrint = $"{diagnostic.Descriptor.Id}-{diagnostic.Severity}-{location?.Path}-{location?.Lines.Begin}";
59+
byte[] source = Encoding.UTF8.GetBytes(issueFingerPrint);
60+
byte[] hashBytes;
61+
#if NETFRAMEWORK
62+
using (var sha256 = SHA256.Create())
63+
hashBytes = sha256.ComputeHash(source);
64+
#else
65+
hashBytes = SHA256.HashData(source);
66+
#endif
67+
issueFingerPrint = BitConverter.ToString(hashBytes)
68+
.Replace("-", "")
69+
.ToLowerInvariant();
70+
71+
reportItems.Add(new GitLabIssue()
72+
{
73+
Type = "issue",
74+
Fingerprint = issueFingerPrint,
75+
CheckName = diagnostic.Descriptor.Id,
76+
Description = diagnostic.Descriptor.Title.ToString(formatProvider),
77+
Severity = severity,
78+
Location = location,
79+
Categories = new string[] { diagnostic.Descriptor.Category },
80+
});
81+
}
82+
83+
string report = JsonConvert.SerializeObject(reportItems, _jsonSerializerSettings);
84+
85+
File.WriteAllText(filePath, report, Encoding.UTF8);
86+
}
87+
}

src/CommandLine/Options/AnalyzeCommandLineOptions.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,15 @@ public class AnalyzeCommandLineOptions : AbstractAnalyzeCommandLineOptions
2020
[Option(
2121
shortName: OptionShortNames.Output,
2222
longName: "output",
23-
HelpText = "Defines path to file that will store reported diagnostics in XML format.",
23+
HelpText = "Defines path to file that will store reported diagnostics. The format of the file is determined by the --output-format option, with the default being xml.",
2424
MetaValue = "<FILE_PATH>")]
2525
public string Output { get; set; }
2626

27+
[Option(
28+
longName: "output-format",
29+
HelpText = "Defines the file format of the report written to file. Supported options are: gitlab and xml, with xml the default if no option is provided.")]
30+
public string OutputFormat { get; set; }
31+
2732
[Option(
2833
longName: "report-not-configurable",
2934
HelpText = "Indicates whether diagnostics with 'NotConfigurable' tag should be reported.")]
@@ -33,4 +38,9 @@ public class AnalyzeCommandLineOptions : AbstractAnalyzeCommandLineOptions
3338
longName: "report-suppressed-diagnostics",
3439
HelpText = "Indicates whether suppressed diagnostics should be reported.")]
3540
public bool ReportSuppressedDiagnostics { get; set; }
41+
42+
internal bool ValidateOutputFormat()
43+
{
44+
return ParseHelpers.TryParseOutputFormat(OutputFormat);
45+
}
3646
}

src/CommandLine/ParseHelpers.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,4 +349,27 @@ public static bool TryReadAllText(
349349
return false;
350350
}
351351
}
352+
353+
public static bool TryParseOutputFormat(string value)
354+
{
355+
if (string.IsNullOrWhiteSpace(value))
356+
{
357+
// Default to XML if no value is provided
358+
return true;
359+
}
360+
361+
bool valid = value.Trim().ToLowerInvariant() switch
362+
{
363+
"gitlab" => true,
364+
"xml" => true,
365+
_ => false
366+
};
367+
368+
if (!valid)
369+
{
370+
WriteLine($"Unknown output format '{value}'.", Verbosity.Quiet);
371+
}
372+
373+
return valid;
374+
}
352375
}

src/CommandLine/Program.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,9 @@ private static async Task<int> AnalyzeAsync(AnalyzeCommandLineOptions options)
338338
if (!TryParsePaths(options.Paths, out ImmutableArray<PathInfo> paths))
339339
return ExitCodes.Error;
340340

341+
if (!options.ValidateOutputFormat())
342+
return ExitCodes.Error;
343+
341344
var command = new AnalyzeCommand(options, severityLevel, projectFilter, CreateFileSystemFilter(options));
342345

343346
CommandStatus status = await command.ExecuteAsync(paths, options.MSBuildPath, options.Properties);

0 commit comments

Comments
 (0)