Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix analyzer [RCS1046](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1046) to report `async void` methods without `Async` suffix ([PR](https://github.com/dotnet/roslynator/pull/1790))
- Fix analyzer [RCS1265](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1265) to not report catch clauses with a `when` filter ([PR](https://github.com/dotnet/roslynator/pull/1789))
- Fix analyzer [RCS0034](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0034) for types with a primary constructor and multiple constraint clauses ([PR](https://github.com/dotnet/roslynator/pull/1791))
- [CLI] Fix GitLab output format to use relative paths, forward slashes, and 1-based line numbers ([PR](https://github.com/dotnet/roslynator/pull/1792))

## [4.16.0] - 2026-08-08

Expand Down
5 changes: 4 additions & 1 deletion src/CommandLine/CommandResults/AnalyzeCommandResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ namespace Roslynator.CommandLine;

internal class AnalyzeCommandResult : CommandResult
{
public AnalyzeCommandResult(CommandStatus status, ImmutableArray<ProjectAnalysisResult> analysisResults)
public AnalyzeCommandResult(CommandStatus status, ImmutableArray<ProjectAnalysisResult> analysisResults, string rootDirectoryPath = null)
: base(status)
{
AnalysisResults = analysisResults;
RootDirectoryPath = rootDirectoryPath;
}

public ImmutableArray<ProjectAnalysisResult> AnalysisResults { get; }

public string RootDirectoryPath { get; }
}
4 changes: 2 additions & 2 deletions src/CommandLine/Commands/AnalyzeCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public override async Task<AnalyzeCommandResult> ExecuteAsync(ProjectOrSolution
results = await codeAnalyzer.AnalyzeSolutionAsync(solution, f => IsMatch(f), cancellationToken);
}

return new AnalyzeCommandResult(GetCommandStatus(Options, results), results);
return new AnalyzeCommandResult(GetCommandStatus(Options, results), results, FileSystemFilter?.RootDirectoryPath);
}

private static CommandStatus GetCommandStatus(AnalyzeCommandLineOptions options, ImmutableArray<ProjectAnalysisResult> results)
Expand All @@ -100,7 +100,7 @@ protected override void ProcessResults(IList<AnalyzeCommandResult> results)
CultureInfo culture = (Options.Culture is not null) ? CultureInfo.GetCultureInfo(Options.Culture) : null;
if (!string.IsNullOrWhiteSpace(Options.OutputFormat) && Options.OutputFormat.Equals("gitlab", StringComparison.CurrentCultureIgnoreCase))
{
DiagnosticGitLabJsonSerializer.Serialize(analysisResults, Options.Output, culture);
DiagnosticGitLabJsonSerializer.Serialize(results, Options.Output, culture);
}
else
{
Expand Down
97 changes: 58 additions & 39 deletions src/CommandLine/Json/DiagnosticGitLabJsonSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,65 +25,84 @@ internal static class DiagnosticGitLabJsonSerializer
};

public static void Serialize(
IEnumerable<ProjectAnalysisResult> results,
IList<AnalyzeCommandResult> results,
string filePath,
IFormatProvider formatProvider = null)
{
IEnumerable<DiagnosticInfo> diagnostics = results.SelectMany(f => f.CompilerDiagnostics.Concat(f.Diagnostics));

var reportItems = new List<GitLabIssue>();
foreach (DiagnosticInfo diagnostic in diagnostics)

foreach (AnalyzeCommandResult commandResult in results)
{
GitLabIssueLocation location = null;
if (diagnostic.LineSpan.IsValid)
string baseDirectoryPath = commandResult.RootDirectoryPath;

foreach (ProjectAnalysisResult result in commandResult.AnalysisResults)
{
location = new GitLabIssueLocation()
foreach (DiagnosticInfo diagnostic in result.CompilerDiagnostics.Concat(result.Diagnostics))
{
Path = diagnostic.LineSpan.Path,
Lines = new GitLabLocationLines()
GitLabIssueLocation location = null;
if (diagnostic.LineSpan.IsValid)
{
Begin = diagnostic.LineSpan.StartLinePosition.Line
},
};
}
location = new GitLabIssueLocation()
{
Path = FormatPath(diagnostic.LineSpan.Path, baseDirectoryPath),
Lines = new GitLabLocationLines()
{
Begin = diagnostic.LineSpan.StartLinePosition.Line + 1
},
};
}

var severity = "minor";
severity = diagnostic.Severity switch
{
DiagnosticSeverity.Warning => "major",
DiagnosticSeverity.Error => "critical",
_ => "minor",
};
var severity = "minor";
severity = diagnostic.Severity switch
{
DiagnosticSeverity.Warning => "major",
DiagnosticSeverity.Error => "critical",
_ => "minor",
};

string issueFingerPrint = $"{diagnostic.Descriptor.Id}-{diagnostic.Severity}-{location?.Path}-{location?.Lines.Begin}";
byte[] source = Encoding.UTF8.GetBytes(issueFingerPrint);
byte[] hashBytes;
string issueFingerPrint = $"{diagnostic.Descriptor.Id}-{diagnostic.Severity}-{location?.Path}-{location?.Lines.Begin}";
byte[] source = Encoding.UTF8.GetBytes(issueFingerPrint);
byte[] hashBytes;
#if NETFRAMEWORK
using (var sha256 = SHA256.Create())
hashBytes = sha256.ComputeHash(source);
using (var sha256 = SHA256.Create())
hashBytes = sha256.ComputeHash(source);
#else
hashBytes = SHA256.HashData(source);
hashBytes = SHA256.HashData(source);
#endif
#pragma warning disable CA1872 // Use Convert.ToHexString instead of BitConverter.ToString
issueFingerPrint = BitConverter.ToString(hashBytes)
.Replace("-", "")
.ToLowerInvariant();
issueFingerPrint = BitConverter.ToString(hashBytes)
.Replace("-", "")
.ToLowerInvariant();
#pragma warning restore CA1872

reportItems.Add(new GitLabIssue()
{
Type = "issue",
Fingerprint = issueFingerPrint,
CheckName = diagnostic.Descriptor.Id,
Description = diagnostic.Descriptor.Title.ToString(formatProvider),
Severity = severity,
Location = location,
Categories = new string[] { diagnostic.Descriptor.Category },
});
reportItems.Add(new GitLabIssue()
{
Type = "issue",
Fingerprint = issueFingerPrint,
CheckName = diagnostic.Descriptor.Id,
Description = diagnostic.Descriptor.Title.ToString(formatProvider),
Severity = severity,
Location = location,
Categories = new string[] { diagnostic.Descriptor.Category },
});
}
}
}

string report = JsonConvert.SerializeObject(reportItems, _jsonSerializerSettings);

File.WriteAllText(filePath, report, Encoding.UTF8);
}

private static string FormatPath(string path, string baseDirectoryPath)
{
if (!string.IsNullOrEmpty(baseDirectoryPath)
&& FileSystemHelpers.TryGetNormalizedFullPath(path, out string normalizedPath)
&& FileSystemHelpers.TryGetNormalizedFullPath(baseDirectoryPath, out string normalizedBase))
{
path = PathUtilities.TrimStart(normalizedPath, normalizedBase);
}

return path.Replace('\\', '/');
}
}
6 changes: 3 additions & 3 deletions src/Workspaces.Core/PathUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ internal static string TrimStart(string path, string? basePath, bool trimLeading
{
if (basePath is not null)
{
if (string.Equals(path, basePath, StringComparison.Ordinal))
if (string.Equals(path, basePath, FileSystemHelpers.Comparison))
return Path.GetFileName(path);

if (path.StartsWith(basePath))
if (path.StartsWith(basePath, FileSystemHelpers.Comparison))
{
int length = basePath.Length;

if (trimLeadingDirectorySeparator)
{
while (length < path.Length
&& path[length] == Path.DirectorySeparatorChar)
&& FileSystemHelpers.IsDirectorySeparator(path[length]))
{
length++;
}
Expand Down
Loading