Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<IsTestProject>true</IsTestProject>
<!-- Transitive VS SDK packages target net472; restore fallback is expected for extension test host. -->
<NoWarn>$(NoWarn);NU1701</NoWarn>
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,54 @@ public static List<Vulnerability> FindAllVulnerabilitiesForLine(Vulnerability v)
}
}

/// <summary>
/// Merges findings from a specific scanner type into existing findings for a file.
/// Replaces all findings of that scanner type for the file with the new list.
/// Updates gutter, underlines, and the findings window in one call.
/// </summary>
/// <param name="filePath">File path to update findings for.</param>
/// <param name="scannerType">Scanner type to merge findings for.</param>
/// <param name="newFindings">New findings for this scanner; if empty/null, clears findings of this scanner type for the file.</param>
public static void MergeUpdateFindingsForScanner(string filePath, ScannerType scannerType, List<Vulnerability> newFindings)
Comment thread
cx-rakesh-kadu marked this conversation as resolved.
{
if (string.IsNullOrEmpty(filePath)) return;

string key = NormalizePath(filePath);
if (string.IsNullOrEmpty(key)) return;
Comment thread
cx-rahul-pidde marked this conversation as resolved.

IReadOnlyDictionary<string, List<Vulnerability>> snapshot;
lock (_lock)
{
// Get existing findings for this file
List<Vulnerability> merged = new List<Vulnerability>();
if (_fileToIssues.TryGetValue(key, out var existing) && existing != null)
{
// Keep all findings NOT from this scanner
merged.AddRange(existing.FindAll(v => v.Scanner != scannerType));
}

// Add the new findings from this scanner
if (newFindings != null && newFindings.Count > 0)
{
merged.AddRange(newFindings.FindAll(v => CxAssistConstants.IsScannerEnabled(v.Scanner)));
Comment thread
cx-rahul-pidde marked this conversation as resolved.
Outdated
}

// Update the map
if (merged.Count == 0)
_fileToIssues.Remove(key);
else
_fileToIssues[key] = merged;

// Snapshot for IssuesUpdated event
var copy = new Dictionary<string, List<Vulnerability>>(_fileToIssues.Count, StringComparer.OrdinalIgnoreCase);
foreach (var kv in _fileToIssues)
copy[kv.Key] = new List<Vulnerability>(kv.Value);
snapshot = copy;
}

IssuesUpdated?.Invoke(snapshot);
}

/// <summary>
/// Updates gutter icons, underlines (squiggles), and stored findings for the problem window in one call.
/// Stores issues per file and raises IssuesUpdated so the findings window can stay in sync (reference-like).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,51 +1,20 @@
using System;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.Shell;
using ast_visual_studio_extension.CxExtension.Utils;

namespace ast_visual_studio_extension.CxExtension.CxAssist.Core
{
/// <summary>
/// Writes CxAssist messages to the VS Output Window ("Checkmarx" pane).
/// Same pattern as ASCAUIManager.WriteToOutputPane — main lifecycle messages only.
/// Writes CxAssist messages to the VS Output Window (shared "Checkmarx" pane via <see cref="OutputPaneWriter"/>).
/// </summary>
internal static class CxAssistOutputPane
{
private static OutputWindowPane _outputPane;
private static bool _initialized;

private static void EnsureInitialized()
{
if (_initialized) return;
try
{
ThreadHelper.ThrowIfNotOnUIThread();
var dte = Package.GetGlobalService(typeof(DTE)) as DTE2;
if (dte != null)
{
var outputWindow = dte.ToolWindows.OutputWindow;
_outputPane = OutputPaneUtils.InitializeOutputPane(outputWindow, CxConstants.EXTENSION_TITLE);
}
_initialized = true;
}
catch
{
// Output pane is best-effort; swallow initialization errors
}
}

/// <summary>
/// Writes a timestamped message to the Checkmarx Output Window pane.
/// Safe to call from UI thread only (ThreadHelper.ThrowIfNotOnUIThread inside).
/// Writes a timestamped message to the Checkmarx output pane. Thread-safe (marshals to UI when needed).
/// </summary>
public static void WriteToOutputPane(string message)
{
try
{
ThreadHelper.ThrowIfNotOnUIThread();
EnsureInitialized();
_outputPane?.OutputString($"{DateTime.Now}: {message}\n");
OutputPaneWriter.WriteAssistLifecycle(message ?? string.Empty);
}
catch
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using ast_visual_studio_extension.CxCLI;
using ast_visual_studio_extension.CxExtension.CxAssist.Core;
using ast_visual_studio_extension.CxExtension.CxAssist.Core.Models;
using ast_visual_studio_extension.CxExtension.CxAssist.Realtime.Base;
using ast_visual_studio_extension.CxExtension.CxAssist.Realtime.Utils;
using ast_visual_studio_extension.CxExtension.Utils;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

namespace ast_visual_studio_extension.CxExtension.CxAssist.Realtime.Asca
Expand All @@ -20,6 +22,8 @@ public class AscaService : SingletonScannerBase<AscaService>

protected override string ScannerName => "ASCA";

protected override ScannerType CoordinatorScannerType => ScannerType.ASCA;

private AscaService(ast_visual_studio_extension.CxCLI.CxWrapper cxWrapper) : base(cxWrapper)
{
}
Expand All @@ -46,46 +50,34 @@ public override bool ShouldScanFile(string filePath)
/// <summary>
/// Invokes the ASCA realtime scan CLI command.
/// Maps results to Result objects for display in the findings panel.
/// Catches and logs all errors to the output pane (aligned with JetBrains error handling).
/// </summary>
protected override async Task<int> ScanAndDisplayAsync(string tempFilePath, string sourceFilePath)
{
var results = await _cxWrapper.ScanAscaAsync(tempFilePath, ascaLatestVersion: false);

// Log raw JSON response
if (results != null)
try
{
var jsonResponse = JsonConvert.SerializeObject(results, Formatting.Indented);
OutputPaneWriter.WriteDebug($"{ScannerName} scanner: raw JSON response - {jsonResponse}");
}
var results = await _cxWrapper.ScanAscaAsync(tempFilePath, ascaLatestVersion: false);

if (results == null)
{
OutputPaneWriter.WriteDebug($"{ScannerName} scanner: null results returned - {sourceFilePath}");
return 0;
}
if (results?.ScanDetails == null || results.ScanDetails.Count == 0)
{
ClearDisplayForFile(sourceFilePath);
return 0;
}

if (results.ScanDetails == null || results.ScanDetails.Count == 0)
{
OutputPaneWriter.WriteDebug($"{ScannerName} scanner: no scan details returned - {sourceFilePath}");
return 0;
}

int issueCount = results.ScanDetails.Count;
OutputPaneWriter.WriteLine($"{ScannerName} scanner: scan completed - {sourceFilePath} ({issueCount} issues found)");
int issueCount = results.ScanDetails.Count;
OutputPaneWriter.WriteLine($"{ScannerName} scanner: {issueCount} issue(s) found — {Path.GetFileName(sourceFilePath)}");

// Log individual issues like JetBrains does
for (int i = 0; i < issueCount; i++)
var mappedResults = VulnerabilityMapper.FromAsca(results.ScanDetails, sourceFilePath);
CxAssistDisplayCoordinator.MergeUpdateFindingsForScanner(sourceFilePath, CoordinatorScannerType, mappedResults);
return mappedResults.Count;
}
catch (Exception ex)
{
var issue = results.ScanDetails[i];
var severity = issue.Severity ?? "UNKNOWN";
var title = issue.RuleName ?? "Unknown Issue";
OutputPaneWriter.WriteLine($"Issue {i + 1}: {title} [{severity}]");
OutputPaneWriter.WriteError($"{ScannerName} scanner: failed to scan {Path.GetFileName(sourceFilePath)} - {ex.Message}");
_logger.Warn($"{ScannerName} scanner: scan error on {Path.GetFileName(sourceFilePath)}: {ex.Message}", ex);
ClearDisplayForFile(sourceFilePath);
return 0;
}

var mappedResults = VulnerabilityMapper.FromAsca(results.ScanDetails, sourceFilePath);
// TODO: Integrate with findings display (after CxAssistDisplayCoordinator PR merges)
// CxAssistDisplayCoordinator.UpdateFindings(buffer, mappedResults, sourceFilePath);
return mappedResults.Count;
}

/// <summary>
Expand Down
Loading
Loading