From d0bcb4438b545068f2f234bf7d04c05d2dbb7966 Mon Sep 17 00:00:00 2001 From: Jeff Witt <152964771+witt3rd@users.noreply.github.com> Date: Wed, 11 Feb 2026 23:29:24 -0500 Subject: [PATCH] Restore per-element tool attribution and add uniqueElementCount to tsp50 The tsp50 scorer simplified element tracking from tsp's per-element tool attribution (issuePaths[issueID][pathID] = Set) to a flat set (issuePaths[issueName] = Set). This lost the ability for consumers to know which tools detected each element. This change: 1. Restores tool-per-element tracking: issuePaths maps each pathID to the set of tools that reported it, and details.element groups paths by their detecting tool combinations (e.g., "axe + ibm": [paths]). 2. Adds uniqueElementCount to each issue's details: the count of unique path-identified elements, giving consumers a deduplicated instance count based on pathID. This is more accurate than summing per-tool instanceCounts, which double-counts elements found by multiple tools. The element detail format now matches tsp's output structure, making it consistent across scorer versions. --- procs/score/tsp50.js | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/procs/score/tsp50.js b/procs/score/tsp50.js index 8b51dab..60b7caa 100644 --- a/procs/score/tsp50.js +++ b/procs/score/tsp50.js @@ -263,11 +263,12 @@ exports.scorer = report => { .texts .push(what); } - issuePaths[issueName] ??= new Set(); + issuePaths[issueName] ??= {}; // If the element has a path ID: if (pathID) { - // Ensure that it is in the issue-specific set of paths. - issuePaths[issueName].add(pathID); + // Ensure that it is in the issue-specific map of paths to tools. + issuePaths[issueName][pathID] ??= new Set(); + issuePaths[issueName][pathID].add(which); } } } @@ -323,6 +324,10 @@ exports.scorer = report => { issueData.instanceCounts[toolName] = 0; } }); + // Add the count of unique path-identified elements for the issue. + if (issuePaths[issueName]) { + issueData.uniqueElementCount = Object.keys(issuePaths[issueName]).length; + } }); // Add the severity detail totals to the score. details.severity.total = Object @@ -333,9 +338,22 @@ exports.scorer = report => { }); return severityTotals; }, details.severity.total); - // Add the element details to the score. + // Add the element details to the score, grouped by detecting tools. Object.keys(issuePaths).forEach(issueID => { - details.element[issueID] = Array.from(issuePaths[issueID]); + details.element[issueID] = {}; + const issueElementDetails = details.element[issueID]; + // For each element reported as exhibiting the issue: + Object.keys(issuePaths[issueID]).forEach(pathID => { + // Convert the set of tools reporting it to a string. + const toolList = Array.from(issuePaths[issueID][pathID]).sort().join(' + '); + issueElementDetails[toolList] ??= []; + // Classify the path by the set of tools reporting its element for the issue. + issueElementDetails[toolList].push(pathID); + }); + // Sort the paths within each tool list. + Object.keys(issueElementDetails).forEach(toolList => { + issueElementDetails[toolList].sort(); + }); }); // Add the summary issue-count total to the score. summary.issueCount = Object.keys(details.issue).length * issueCountWeight;