From 9f2b5770b975faa009afe5f8065574e7c5ec77b7 Mon Sep 17 00:00:00 2001 From: David Bond Date: Fri, 16 Oct 2026 15:15:17 +0100 Subject: [PATCH] refactor: clear Codacy complexity and mapper duplication on main Split CategoryExecutor.BuildCategoryResult and IssueText.Classify under the complexity limit, and move the Jira and GitHub mappers' duplicated JSON string reading into one helper. Co-Authored-By: Claude Sonnet 5.5 --- .../Execution/CategoryExecutor.cs | 15 +++++--- .../Mapping/GitHubIssueMapper.cs | 19 ++++------ .../Mapping/JiraIssueMapper.cs | 38 ++++++++----------- .../Mapping/JsonReading.cs | 22 +++++++++++ .../Text/IssueText.cs | 20 ++++++---- 5 files changed, 68 insertions(+), 46 deletions(-) create mode 100644 PanoramicData.IssueAssessment/Mapping/JsonReading.cs diff --git a/PanoramicData.IssueAssessment/Execution/CategoryExecutor.cs b/PanoramicData.IssueAssessment/Execution/CategoryExecutor.cs index 26adab7..c1a9742 100644 --- a/PanoramicData.IssueAssessment/Execution/CategoryExecutor.cs +++ b/PanoramicData.IssueAssessment/Execution/CategoryExecutor.cs @@ -157,12 +157,7 @@ private static CategoryResult BuildCategoryResult(AssessmentCategory category, I .Zip(category.Assessments) .Where(pair => pair.First.Outcome != AssessmentOutcome.NotApplicable)]; - double? rawScore = null; - if (applicable.Count > 0 && applicable.TrueForAll(pair => pair.Result.Score.RawScore.HasValue)) - { - rawScore = applicable.Sum(pair => pair.Result.Score.RawScore!.Value * pair.Assessment.Weight); - } - + double? rawScore = WeightedRawScore(applicable); double? normalizedScore = rawScore.HasValue ? ScoreCalculator.Normalize(rawScore.Value, minPossibleRaw, maxPossibleRaw) : null; @@ -176,4 +171,12 @@ private static CategoryResult BuildCategoryResult(AssessmentCategory category, I string reason = $"Executed {resultList.Count} assessment(s) in category '{category.Name}'."; return new CategoryResult(category.Name, score, reason, resultList) { Weight = category.Weight }; } + + /// + /// The weighted sum of the applicable assessments' raw scores; null when none applies or any of them has no score. + /// + private static double? WeightedRawScore(List<(AssessmentResult Result, Assessment Assessment)> applicable) + => applicable.Count > 0 && applicable.TrueForAll(pair => pair.Result.Score.RawScore.HasValue) + ? applicable.Sum(pair => pair.Result.Score.RawScore!.Value * pair.Assessment.Weight) + : null; } diff --git a/PanoramicData.IssueAssessment/Mapping/GitHubIssueMapper.cs b/PanoramicData.IssueAssessment/Mapping/GitHubIssueMapper.cs index 10156b3..9cd54e8 100644 --- a/PanoramicData.IssueAssessment/Mapping/GitHubIssueMapper.cs +++ b/PanoramicData.IssueAssessment/Mapping/GitHubIssueMapper.cs @@ -1,4 +1,5 @@ using System.Text.RegularExpressions; +using static PanoramicData.IssueAssessment.Mapping.JsonReading; namespace PanoramicData.IssueAssessment.Mapping; @@ -29,30 +30,30 @@ public static NormalizedIssue FromRestJson(JsonElement issue) } ImmutableList labels = Labels(issue); - string body = Text(issue, "body") ?? string.Empty; + string body = StringAt(issue, "body") ?? string.Empty; return new NormalizedIssue { Key = Key(issue), - Title = Text(issue, "title"), + Title = StringAt(issue, "title"), Description = body, IssueType = IssueType(labels), Priority = Priority(labels), - Status = Text(issue, "state"), + Status = StringAt(issue, "state"), Assignee = FirstAssignee(issue), Components = Components(labels), Labels = labels, AttachmentsCount = MarkdownImage().Count(body) + ImageUrl().Count(body), SupportsFixVersion = false, - DueDate = issue.TryGetProperty("milestone", out JsonElement milestone) ? Text(milestone, "due_on") : null, - Url = Text(issue, "html_url"), + DueDate = issue.TryGetProperty("milestone", out JsonElement milestone) ? StringAt(milestone, "due_on") : null, + Url = StringAt(issue, "html_url"), Platform = "GitHub", }; } private static ImmutableList Labels(JsonElement issue) => issue.TryGetProperty("labels", out JsonElement labels) && labels.ValueKind == JsonValueKind.Array - ? [.. labels.EnumerateArray().Select(label => label.ValueKind == JsonValueKind.String ? label.GetString() : Text(label, "name")).OfType()] + ? [.. labels.EnumerateArray().Select(label => label.ValueKind == JsonValueKind.String ? label.GetString() : StringAt(label, "name")).OfType()] : []; private static string? Key(JsonElement issue) @@ -60,7 +61,7 @@ private static ImmutableList Labels(JsonElement issue) private static string? FirstAssignee(JsonElement issue) => issue.TryGetProperty("assignees", out JsonElement assignees) && assignees.ValueKind == JsonValueKind.Array && assignees.GetArrayLength() > 0 - ? Text(assignees[0], "login") + ? StringAt(assignees[0], "login") : null; private static string IssueType(ImmutableList labels) @@ -92,10 +93,6 @@ private static ImmutableList Components(ImmutableList labels) .Select(match => match.Groups[2].Value.Trim()) .Distinct(StringComparer.Ordinal)]; - private static string? Text(JsonElement element, string property) - => element.ValueKind == JsonValueKind.Object && element.TryGetProperty(property, out JsonElement value) && value.ValueKind == JsonValueKind.String - ? value.GetString() - : null; [GeneratedRegex("enhancement|feature|improvement|story", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant, 1000)] private static partial Regex FeatureLabel(); diff --git a/PanoramicData.IssueAssessment/Mapping/JiraIssueMapper.cs b/PanoramicData.IssueAssessment/Mapping/JiraIssueMapper.cs index abba341..aa928fd 100644 --- a/PanoramicData.IssueAssessment/Mapping/JiraIssueMapper.cs +++ b/PanoramicData.IssueAssessment/Mapping/JiraIssueMapper.cs @@ -1,5 +1,6 @@ using System.Collections.Frozen; using System.Text; +using static PanoramicData.IssueAssessment.Mapping.JsonReading; namespace PanoramicData.IssueAssessment.Mapping; @@ -45,22 +46,22 @@ public static NormalizedIssue FromRestJson(JsonElement issue) public static NormalizedIssue FromRestJson(JsonElement issue, Uri? baseUrl) { JsonElement fields = Fields(issue); - string? key = Text(issue, "key"); + string? key = StringAt(issue, "key"); return new NormalizedIssue { Key = key, - Project = Text(fields, "project", "key"), - Title = Text(fields, "summary"), + Project = StringAt(fields, "project", "key"), + Title = StringAt(fields, "summary"), Description = Description(fields), - IssueType = Text(fields, "issuetype", "name"), - Priority = Text(fields, "priority", "name"), - Status = Text(fields, "status", "name"), - Assignee = Text(fields, "assignee", "displayName"), + IssueType = StringAt(fields, "issuetype", "name"), + Priority = StringAt(fields, "priority", "name"), + Status = StringAt(fields, "status", "name"), + Assignee = StringAt(fields, "assignee", "displayName"), Components = Names(fields, "components"), Labels = Strings(fields, "labels"), FixVersions = Names(fields, "fixVersions"), AttachmentsCount = ArrayLength(fields, "attachment"), - DueDate = Text(fields, "duedate"), + DueDate = StringAt(fields, "duedate"), Url = BrowseUrl(baseUrl, key), Platform = "Jira", }; @@ -106,7 +107,7 @@ public static string FlattenAdf(JsonElement document) private static void AppendBlock(JsonElement node, StringBuilder text, string listPrefix) { - string type = Text(node, "type") ?? string.Empty; + string type = StringAt(node, "type") ?? string.Empty; if (ListMarkers.TryGetValue(type, out string? listMarker)) { AppendList(node, text, listPrefix + listMarker); @@ -150,7 +151,7 @@ private static void AppendListItem(JsonElement item, StringBuilder text, string bool first = true; foreach (JsonElement child in Children(item)) { - if (Text(child, "type") is "bulletList" or "orderedList") + if (StringAt(child, "type") is "bulletList" or "orderedList") { AppendBlock(child, text, marker); } @@ -162,12 +163,12 @@ private static void AppendListItem(JsonElement item, StringBuilder text, string } } - private static string Inline(JsonElement node) => Text(node, "type") switch + private static string Inline(JsonElement node) => StringAt(node, "type") switch { - "text" => Text(node, "text") ?? string.Empty, + "text" => StringAt(node, "text") ?? string.Empty, "hardBreak" => "\n", - "mention" or "emoji" => node.TryGetProperty("attrs", out JsonElement attrs) ? Text(attrs, "text") ?? string.Empty : string.Empty, - "inlineCard" => node.TryGetProperty("attrs", out JsonElement attrs) ? Text(attrs, "url") ?? string.Empty : string.Empty, + "mention" or "emoji" => node.TryGetProperty("attrs", out JsonElement attrs) ? StringAt(attrs, "text") ?? string.Empty : string.Empty, + "inlineCard" => node.TryGetProperty("attrs", out JsonElement attrs) ? StringAt(attrs, "url") ?? string.Empty : string.Empty, _ => string.Concat(Children(node).Select(Inline)), }; @@ -176,17 +177,10 @@ private static JsonElement[] Children(JsonElement node) ? [.. content.EnumerateArray()] : []; - private static string? Text(JsonElement element, string property) - => element.ValueKind == JsonValueKind.Object && element.TryGetProperty(property, out JsonElement value) && value.ValueKind == JsonValueKind.String - ? value.GetString() - : null; - - private static string? Text(JsonElement element, string property, string nested) - => element.ValueKind == JsonValueKind.Object && element.TryGetProperty(property, out JsonElement value) ? Text(value, nested) : null; private static ImmutableList Names(JsonElement fields, string property) => fields.TryGetProperty(property, out JsonElement values) && values.ValueKind == JsonValueKind.Array - ? [.. values.EnumerateArray().Select(value => Text(value, "name")).OfType()] + ? [.. values.EnumerateArray().Select(value => StringAt(value, "name")).OfType()] : []; private static ImmutableList Strings(JsonElement fields, string property) diff --git a/PanoramicData.IssueAssessment/Mapping/JsonReading.cs b/PanoramicData.IssueAssessment/Mapping/JsonReading.cs new file mode 100644 index 0000000..a7309cb --- /dev/null +++ b/PanoramicData.IssueAssessment/Mapping/JsonReading.cs @@ -0,0 +1,22 @@ +namespace PanoramicData.IssueAssessment.Mapping; + +/// +/// Reads values out of the JSON a platform returns, tolerating anything of the wrong shape as absent. +/// +internal static class JsonReading +{ + /// + /// The string at of an object; null when it is not an object, the property is missing or + /// the value is not a string. + /// + public static string? StringAt(JsonElement element, string property) + => element.ValueKind == JsonValueKind.Object && element.TryGetProperty(property, out JsonElement value) && value.ValueKind == JsonValueKind.String + ? value.GetString() + : null; + + /// + /// The string at of the object at . + /// + public static string? StringAt(JsonElement element, string property, string nested) + => element.ValueKind == JsonValueKind.Object && element.TryGetProperty(property, out JsonElement value) ? StringAt(value, nested) : null; +} diff --git a/PanoramicData.IssueAssessment/Text/IssueText.cs b/PanoramicData.IssueAssessment/Text/IssueText.cs index f1caffb..b22dfed 100644 --- a/PanoramicData.IssueAssessment/Text/IssueText.cs +++ b/PanoramicData.IssueAssessment/Text/IssueText.cs @@ -186,23 +186,29 @@ private static void Classify(string? heading, List body, List pa string? first = body.Find(line => line.Trim().Length > 0); Match panel = first is null ? Match.Empty : PanelOpening.Match(first); - bool hasContent = first is not null && !IsPlaceholder(string.Join('\n', body)); - bool isPlain = !body.Exists(line => PanelBlocker.IsMatch(line)); if (panel.Success) { panelable.Add(heading); - if (panel.Groups[1].Value.Contains("title=", StringComparison.OrdinalIgnoreCase)) - { - titled.Add(heading); - } + AddIf(titled, heading, panel.Groups[1].Value.Contains("title=", StringComparison.OrdinalIgnoreCase)); + return; } - else if (hasContent && isPlain) + + bool hasContent = first is not null && !IsPlaceholder(string.Join('\n', body)); + if (hasContent && !body.Exists(line => PanelBlocker.IsMatch(line))) { panelable.Add(heading); unpanelled.Add(heading); } } + private static void AddIf(List list, string item, bool condition) + { + if (condition) + { + list.Add(item); + } + } + /// /// True when a section body is a placeholder (TBD, N/A, none, to do, pending, ...) or blank, rather than content. ///