From 0be109892eca062d20748fc02daf7838acbb0f12 Mon Sep 17 00:00:00 2001 From: Rahul Pidde <206018639+cx-rahul-pidde@users.noreply.github.com> Date: Tue, 3 Feb 2026 15:44:20 +0530 Subject: [PATCH 01/45] Added images and guttor icon logic --- .../Commands/TestGutterIconsDirectCommand.cs | 172 ++++++++++++ .../CxExtension/CxWindowPackage.cs | 5 +- .../CxExtension/CxWindowPackage.vsct | 8 + .../Core/GutterIcons/DevAssistGlyphFactory.cs | 254 ++++++++++++++++++ .../Core/GutterIcons/DevAssistGlyphTagger.cs | 183 +++++++++++++ .../DevAssistGlyphTaggerProvider.cs | 151 +++++++++++ .../DevAssistTextViewCreationListener.cs | 135 ++++++++++ .../DevAssist/Core/Models/ScannerType.cs | 16 ++ .../DevAssist/Core/Models/SeverityLevel.cs | 21 ++ .../DevAssist/Core/Models/Vulnerability.cs | 35 +++ .../DevAssist/Icons/Dark/critical.svg | 4 + .../Resources/DevAssist/Icons/Dark/high.svg | 4 + .../DevAssist/Icons/Dark/ignored.svg | 4 + .../Resources/DevAssist/Icons/Dark/low.svg | 4 + .../DevAssist/Icons/Dark/malicious.svg | 4 + .../Resources/DevAssist/Icons/Dark/medium.svg | 4 + .../Resources/DevAssist/Icons/Dark/ok.svg | 4 + .../DevAssist/Icons/Dark/unknown.svg | 5 + .../DevAssist/Icons/Light/critical.svg | 4 + .../Resources/DevAssist/Icons/Light/high.svg | 4 + .../DevAssist/Icons/Light/ignored.svg | 4 + .../Resources/DevAssist/Icons/Light/low.svg | 4 + .../DevAssist/Icons/Light/malicious.svg | 4 + .../DevAssist/Icons/Light/medium.svg | 4 + .../Resources/DevAssist/Icons/Light/ok.svg | 4 + .../DevAssist/Icons/Light/unknown.svg | 5 + .../ast-visual-studio-extension.csproj | 27 ++ .../source.extension.vsixmanifest | 1 + 28 files changed, 1073 insertions(+), 1 deletion(-) create mode 100644 ast-visual-studio-extension/CxExtension/Commands/TestGutterIconsDirectCommand.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphFactory.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTagger.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTaggerProvider.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/ScannerType.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/SeverityLevel.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/Vulnerability.cs create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/critical.svg create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/high.svg create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/ignored.svg create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/low.svg create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/malicious.svg create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/medium.svg create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/ok.svg create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/unknown.svg create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/critical.svg create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/high.svg create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/ignored.svg create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/low.svg create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/malicious.svg create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/medium.svg create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/ok.svg create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/unknown.svg diff --git a/ast-visual-studio-extension/CxExtension/Commands/TestGutterIconsDirectCommand.cs b/ast-visual-studio-extension/CxExtension/Commands/TestGutterIconsDirectCommand.cs new file mode 100644 index 00000000..b281f1b7 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/Commands/TestGutterIconsDirectCommand.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Design; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.ComponentModelHost; +using ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons; +using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; +using Task = System.Threading.Tasks.Task; +using Microsoft.VisualStudio.Shell.Interop; + +namespace ast_visual_studio_extension.CxExtension.Commands +{ + /// + /// Direct test command that manually creates tagger without MEF + /// This bypasses MEF to test if the glyph rendering works at all + /// + internal sealed class TestGutterIconsDirectCommand + { + public const int CommandId = 0x0105; + public static readonly Guid CommandSet = new Guid("a6e70b7d-e3e1-4a3b-9b3e-3e3e3e3e3e3e"); + + private readonly AsyncPackage package; + + private TestGutterIconsDirectCommand(AsyncPackage package, OleMenuCommandService commandService) + { + this.package = package ?? throw new ArgumentNullException(nameof(package)); + commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); + + var menuCommandID = new CommandID(CommandSet, CommandId); + var menuItem = new MenuCommand(this.Execute, menuCommandID); + commandService.AddCommand(menuItem); + } + + public static TestGutterIconsDirectCommand Instance { get; private set; } + + private Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider => this.package; + + public static async Task InitializeAsync(AsyncPackage package) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); + + OleMenuCommandService commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; + Instance = new TestGutterIconsDirectCommand(package, commandService); + } + + private void Execute(object sender, EventArgs e) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + try + { + System.Diagnostics.Debug.WriteLine("DevAssist: TestGutterIconsDirectCommand - Starting DIRECT test (no MEF)"); + + var textView = GetActiveTextView(); + if (textView == null) + { + VsShellUtilities.ShowMessageBox( + this.package, + "No active text editor found. Please open a code file first.", + "Test Gutter Icons - Direct", + OLEMSGICON.OLEMSGICON_WARNING, + OLEMSGBUTTON.OLEMSGBUTTON_OK, + OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); + return; + } + + var buffer = textView.TextBuffer; + + // Create tagger DIRECTLY without MEF + System.Diagnostics.Debug.WriteLine("DevAssist: Creating tagger DIRECTLY (bypassing MEF)"); + var tagger = new DevAssistGlyphTagger(buffer); + + // Store it in buffer properties so the glyph factory can find it + try + { + buffer.Properties.AddProperty(typeof(DevAssistGlyphTagger), tagger); + System.Diagnostics.Debug.WriteLine("DevAssist: Tagger stored in buffer properties"); + } + catch + { + buffer.Properties.RemoveProperty(typeof(DevAssistGlyphTagger)); + buffer.Properties.AddProperty(typeof(DevAssistGlyphTagger), tagger); + System.Diagnostics.Debug.WriteLine("DevAssist: Tagger replaced in buffer properties"); + } + + // Create test vulnerabilities - including Ok, Unknown, and Ignored severity levels + var vulnerabilities = new List + { + new Vulnerability { Id = "TEST-001", Severity = SeverityLevel.Malicious, LineNumber = 1, Description = "Test Malicious" }, + new Vulnerability { Id = "TEST-002", Severity = SeverityLevel.Critical, LineNumber = 3, Description = "Test Critical" }, + new Vulnerability { Id = "TEST-003", Severity = SeverityLevel.High, LineNumber = 5, Description = "Test High" }, + new Vulnerability { Id = "TEST-004", Severity = SeverityLevel.Medium, LineNumber = 7, Description = "Test Medium" }, + new Vulnerability { Id = "TEST-005", Severity = SeverityLevel.Low, LineNumber = 9, Description = "Test Low" }, + new Vulnerability { Id = "TEST-006", Severity = SeverityLevel.Unknown, LineNumber = 11, Description = "Test Unknown" }, + new Vulnerability { Id = "TEST-007", Severity = SeverityLevel.Ok, LineNumber = 13, Description = "Test Ok" }, + new Vulnerability { Id = "TEST-008", Severity = SeverityLevel.Ignored, LineNumber = 15, Description = "Test Ignored" } + }; + + System.Diagnostics.Debug.WriteLine($"DevAssist: Adding {vulnerabilities.Count} test vulnerabilities"); + tagger.UpdateVulnerabilities(vulnerabilities); + + // Force the text view to refresh + System.Diagnostics.Debug.WriteLine("DevAssist: Forcing text view refresh"); + textView.VisualElement.InvalidateVisual(); + + VsShellUtilities.ShowMessageBox( + this.package, + $"โœ… Direct test completed!\n\n" + + $"Added {vulnerabilities.Count} test vulnerabilities with gutter icons:\n\n" + + $"๐Ÿ”ด Line 1: Malicious\n" + + $"๐Ÿ”ด Line 3: Critical\n" + + $"๐ŸŸ  Line 5: High\n" + + $"๐ŸŸก Line 7: Medium\n" + + $"๐ŸŸข Line 9: Low\n" + + $"โšช Line 11: Unknown\n" + + $"โœ… Line 13: Ok\n" + + $"๐Ÿšซ Line 15: Ignored\n\n" + + $"Check the LEFT MARGIN (gutter) for severity icons.\n" + + $"Hover over icons to see tooltips.", + "Test Gutter Icons - Direct", + OLEMSGICON.OLEMSGICON_INFO, + OLEMSGBUTTON.OLEMSGBUTTON_OK, + OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"DevAssist: Error in direct test: {ex.Message}\n{ex.StackTrace}"); + VsShellUtilities.ShowMessageBox( + this.package, + $"Error: {ex.Message}", + "Test Gutter Icons - Direct", + OLEMSGICON.OLEMSGICON_CRITICAL, + OLEMSGBUTTON.OLEMSGBUTTON_OK, + OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); + } + } + + private IWpfTextView GetActiveTextView() + { + ThreadHelper.ThrowIfNotOnUIThread(); + + var textManager = Package.GetGlobalService(typeof(Microsoft.VisualStudio.TextManager.Interop.SVsTextManager)) + as Microsoft.VisualStudio.TextManager.Interop.IVsTextManager2; + + if (textManager == null) + return null; + + Microsoft.VisualStudio.TextManager.Interop.IVsTextView textViewCurrent; + int mustHaveFocus = 1; + textManager.GetActiveView2(mustHaveFocus, null, + (uint)Microsoft.VisualStudio.TextManager.Interop._VIEWFRAMETYPE.vftCodeWindow, + out textViewCurrent); + + if (textViewCurrent == null) + return null; + + var userData = textViewCurrent as Microsoft.VisualStudio.TextManager.Interop.IVsUserData; + if (userData == null) + return null; + + Guid guidViewHost = Microsoft.VisualStudio.Editor.DefGuidList.guidIWpfTextViewHost; + object holder; + userData.GetData(ref guidViewHost, out holder); + + var viewHost = holder as IWpfTextViewHost; + return viewHost?.TextView; + } + } +} + diff --git a/ast-visual-studio-extension/CxExtension/CxWindowPackage.cs b/ast-visual-studio-extension/CxExtension/CxWindowPackage.cs index 5c6c47c5..46caa8af 100644 --- a/ast-visual-studio-extension/CxExtension/CxWindowPackage.cs +++ b/ast-visual-studio-extension/CxExtension/CxWindowPackage.cs @@ -69,10 +69,13 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke // Command to create Checkmarx extension main window await CxWindowCommand.InitializeAsync(this); + + // Test Gutter Icons Direct Command (tool command only, not visible in menu) + await TestGutterIconsDirectCommand.InitializeAsync(this); } catch (Exception ex) { - Console.WriteLine(ex.ToString()); + //Console.WriteLine(ex.ToString()); } } private string GetLogFilePath() diff --git a/ast-visual-studio-extension/CxExtension/CxWindowPackage.vsct b/ast-visual-studio-extension/CxExtension/CxWindowPackage.vsct index 2d6d9c20..b22908cf 100644 --- a/ast-visual-studio-extension/CxExtension/CxWindowPackage.vsct +++ b/ast-visual-studio-extension/CxExtension/CxWindowPackage.vsct @@ -34,6 +34,8 @@ Checkmarx + + @@ -55,5 +57,11 @@ + + + + + + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphFactory.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphFactory.cs new file mode 100644 index 00000000..40958e18 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphFactory.cs @@ -0,0 +1,254 @@ +using System; +using System.ComponentModel.Composition; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using Microsoft.VisualStudio.PlatformUI; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Formatting; +using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Utilities; +using SharpVectors.Converters; +using SharpVectors.Renderers.Wpf; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons +{ + /// + /// Factory for creating custom gutter glyphs for DevAssist vulnerabilities + /// Based on JetBrains GutterIconRenderer pattern adapted for Visual Studio MEF + /// Uses IGlyphFactory to display custom severity icons in the gutter margin + /// + internal class DevAssistGlyphFactory : IGlyphFactory + { + private const double GlyphSize = 16.0; + + public UIElement GenerateGlyph(IWpfTextViewLine line, IGlyphTag tag) + { + System.Diagnostics.Debug.WriteLine($"DevAssist: GenerateGlyph called - tag type: {tag?.GetType().Name}"); + + if (tag == null || !(tag is DevAssistGlyphTag)) + { + System.Diagnostics.Debug.WriteLine($"DevAssist: Tag is null or not DevAssistGlyphTag"); + return null; + } + + var glyphTag = (DevAssistGlyphTag)tag; + System.Diagnostics.Debug.WriteLine($"DevAssist: Generating glyph for severity: {glyphTag.Severity}"); + + try + { + // Create image element for the glyph + var iconSource = GetIconForSeverity(glyphTag.Severity); + if (iconSource == null) + { + System.Diagnostics.Debug.WriteLine($"DevAssist: Icon source is null for severity: {glyphTag.Severity}"); + return null; + } + + var image = new Image + { + Width = GlyphSize, + Height = GlyphSize, + Source = iconSource + }; + + // Set tooltip + if (!string.IsNullOrEmpty(glyphTag.TooltipText)) + { + image.ToolTip = glyphTag.TooltipText; + } + + System.Diagnostics.Debug.WriteLine($"DevAssist: Successfully created glyph image for severity: {glyphTag.Severity}"); + return image; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"DevAssist: Icon loading failed: {ex.Message}"); + return null; + } + } + + /// + /// Gets the appropriate icon based on severity level + /// Maps to JetBrains CxIcons.Small pattern (16x16 icons) + /// Uses SVG icons from ast-jetbrains-plugin organized by theme + /// Supports: MALICIOUS, CRITICAL, HIGH, MEDIUM, LOW, OK, IGNORED, UNKNOWN + /// + private ImageSource GetIconForSeverity(string severity) + { + string iconFileName; + + switch (severity?.ToLower()) + { + case "malicious": + iconFileName = "malicious"; + break; + case "critical": + iconFileName = "critical"; + break; + case "high": + iconFileName = "high"; + break; + case "medium": + iconFileName = "medium"; + break; + case "low": + iconFileName = "low"; + break; + case "info": + // Info severity - could use a separate info icon if available + // For now, using low severity icon as fallback + iconFileName = "low"; + break; + case "ok": + iconFileName = "ok"; + break; + case "ignored": + iconFileName = "ignored"; + break; + default: + iconFileName = "unknown"; // Default fallback + break; + } + + // Try to load themed icon, fallback to PNG if loading fails + try + { + return LoadThemedIcon(iconFileName); + } + catch + { + // Fallback to existing PNG if themed icon loading fails + var pngFileName = iconFileName + ".png"; + var iconUri = new Uri($"pack://application:,,,/ast-visual-studio-extension;component/CxExtension/Resources/{pngFileName}"); + return new BitmapImage(iconUri); + } + } + + /// + /// Loads a themed icon from the organized folder structure + /// Detects Visual Studio theme (Light/Dark) and loads appropriate icon + /// Path: CxExtension/Resources/DevAssist/Icons/{Light|Dark}/{iconName}.svg + /// Uses SharpVectors to render SVG files in WPF + /// + private ImageSource LoadThemedIcon(string iconName) + { + // Detect Visual Studio theme + bool isDarkTheme = IsVsDarkTheme(); + string themeFolder = isDarkTheme ? "Dark" : "Light"; + + // Build path to themed SVG icon + var iconUri = new Uri($"pack://application:,,,/ast-visual-studio-extension;component/CxExtension/Resources/DevAssist/Icons/{themeFolder}/{iconName}.svg"); + + try + { + // Load SVG from Pack URI using StreamResourceInfo + var streamInfo = System.Windows.Application.GetResourceStream(iconUri); + if (streamInfo == null) + { + System.Diagnostics.Debug.WriteLine($"DevAssist: Failed to load SVG icon {iconName} - resource not found"); + return null; + } + + // Use SharpVectors to load and render SVG from stream + var settings = new WpfDrawingSettings + { + IncludeRuntime = true, + TextAsGeometry = false, + OptimizePath = true + }; + + using (var stream = streamInfo.Stream) + { + var converter = new FileSvgReader(settings); + var drawing = converter.Read(stream); + + if (drawing != null) + { + var drawingImage = new DrawingImage(drawing); + drawingImage.Freeze(); // Freeze for better performance + return drawingImage; + } + } + + System.Diagnostics.Debug.WriteLine($"DevAssist: Failed to load SVG icon {iconName} - drawing is null"); + return null; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"DevAssist: Failed to load SVG icon {iconName}: {ex.Message}"); + return null; + } + } + + /// + /// Detects if Visual Studio is using a dark theme + /// Uses VSColorTheme to detect current theme + /// + private bool IsVsDarkTheme() + { + try + { + // Get the current VS theme background color + var backgroundColor = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowBackgroundColorKey); + + // Calculate brightness (simple luminance formula) + double brightness = (0.299 * backgroundColor.R + 0.587 * backgroundColor.G + 0.114 * backgroundColor.B) / 255.0; + + // If brightness is less than 0.5, it's a dark theme + return brightness < 0.5; + } + catch + { + // Default to light theme if detection fails + return false; + } + } + } + + /// + /// MEF export for DevAssist glyph factory provider + /// Registers the factory for the "DevAssist" glyph tag type + /// + [Export(typeof(IGlyphFactoryProvider))] + [Name("DevAssistGlyph")] + [Order(After = "VsTextMarker")] + [ContentType("code")] + [ContentType("text")] + [TagType(typeof(DevAssistGlyphTag))] + [TextViewRole(PredefinedTextViewRoles.Document)] + [TextViewRole(PredefinedTextViewRoles.Editable)] + internal sealed class DevAssistGlyphFactoryProvider : IGlyphFactoryProvider + { + public DevAssistGlyphFactoryProvider() + { + System.Diagnostics.Debug.WriteLine("DevAssist: DevAssistGlyphFactoryProvider constructor called - MEF is loading glyph factory provider"); + } + + public IGlyphFactory GetGlyphFactory(IWpfTextView view, IWpfTextViewMargin margin) + { + System.Diagnostics.Debug.WriteLine($"DevAssist: GetGlyphFactory called for margin: {margin?.GetType().Name}"); + return new DevAssistGlyphFactory(); + } + } + + /// + /// Custom glyph tag for DevAssist vulnerabilities + /// Based on JetBrains GutterIconRenderer pattern + /// + internal class DevAssistGlyphTag : IGlyphTag + { + public string Severity { get; } + public string TooltipText { get; } + public string VulnerabilityId { get; } + + public DevAssistGlyphTag(string severity, string tooltipText, string vulnerabilityId) + { + Severity = severity; + TooltipText = tooltipText; + VulnerabilityId = vulnerabilityId; + } + } +} + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTagger.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTagger.cs new file mode 100644 index 00000000..0e8d91b4 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTagger.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Utilities; +using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons +{ + /// + /// Tagger that provides glyph tags for DevAssist vulnerabilities + /// Based on JetBrains MarkupModel.addRangeHighlighter pattern + /// Manages the lifecycle of gutter icons in the text view + /// + internal class DevAssistGlyphTagger : ITagger + { + private readonly ITextBuffer _buffer; + private readonly Dictionary> _vulnerabilitiesByLine; + + public event EventHandler TagsChanged; + + public DevAssistGlyphTagger(ITextBuffer buffer) + { + _buffer = buffer; + _vulnerabilitiesByLine = new Dictionary>(); + } + + public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) + { + System.Diagnostics.Debug.WriteLine($"DevAssist: GetTags called - spans count: {spans.Count}, vulnerabilities count: {_vulnerabilitiesByLine.Count}"); + + if (spans.Count == 0 || _vulnerabilitiesByLine.Count == 0) + { + System.Diagnostics.Debug.WriteLine($"DevAssist: GetTags returning early - no spans or vulnerabilities"); + yield break; + } + + var snapshot = spans[0].Snapshot; + int tagCount = 0; + + foreach (var span in spans) + { + var startLine = snapshot.GetLineNumberFromPosition(span.Start); + var endLine = snapshot.GetLineNumberFromPosition(span.End); + + for (int lineNumber = startLine; lineNumber <= endLine; lineNumber++) + { + if (_vulnerabilitiesByLine.TryGetValue(lineNumber, out var vulnerabilities)) + { + // Get the most severe vulnerability for this line (for gutter icon) + var mostSevere = GetMostSevereVulnerability(vulnerabilities); + if (mostSevere != null) + { + var line = snapshot.GetLineFromLineNumber(lineNumber); + var lineSpan = new SnapshotSpan(snapshot, line.Start, line.Length); + + var tooltipText = BuildTooltipText(vulnerabilities); + var tag = new DevAssistGlyphTag( + mostSevere.Severity.ToString(), + tooltipText, + mostSevere.Id + ); + + tagCount++; + System.Diagnostics.Debug.WriteLine($"DevAssist: Creating tag #{tagCount} for line {lineNumber}, severity: {mostSevere.Severity}"); + yield return new TagSpan(lineSpan, tag); + } + } + } + } + + System.Diagnostics.Debug.WriteLine($"DevAssist: GetTags completed - returned {tagCount} tags"); + } + + /// + /// Updates vulnerabilities for the buffer + /// Based on JetBrains ProblemDecorator.decorateUI pattern + /// + public void UpdateVulnerabilities(List vulnerabilities) + { + System.Diagnostics.Debug.WriteLine($"DevAssist: UpdateVulnerabilities called with {vulnerabilities?.Count ?? 0} vulnerabilities"); + + _vulnerabilitiesByLine.Clear(); + + if (vulnerabilities != null) + { + foreach (var vuln in vulnerabilities) + { + int lineNumber = vuln.LineNumber - 1; // Convert to 0-based + + if (!_vulnerabilitiesByLine.ContainsKey(lineNumber)) + { + _vulnerabilitiesByLine[lineNumber] = new List(); + } + + _vulnerabilitiesByLine[lineNumber].Add(vuln); + } + } + + System.Diagnostics.Debug.WriteLine($"DevAssist: Vulnerabilities stored in {_vulnerabilitiesByLine.Count} lines"); + System.Diagnostics.Debug.WriteLine($"DevAssist: TagsChanged event has {(TagsChanged != null ? TagsChanged.GetInvocationList().Length : 0)} subscribers"); + + // Notify that tags have changed + var snapshot = _buffer.CurrentSnapshot; + var entireSpan = new SnapshotSpan(snapshot, 0, snapshot.Length); + + System.Diagnostics.Debug.WriteLine($"DevAssist: Raising TagsChanged event for span: {entireSpan.Start} to {entireSpan.End}"); + TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(entireSpan)); + System.Diagnostics.Debug.WriteLine($"DevAssist: TagsChanged event raised"); + } + + /// + /// Clears all vulnerabilities + /// Based on JetBrains ProblemDecorator.removeAllHighlighters pattern + /// + public void ClearVulnerabilities() + { + _vulnerabilitiesByLine.Clear(); + + var snapshot = _buffer.CurrentSnapshot; + var entireSpan = new SnapshotSpan(snapshot, 0, snapshot.Length); + TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(entireSpan)); + } + + /// + /// Gets the most severe vulnerability from a list + /// Based on JetBrains ProblemDecorator.getMostSeverity pattern + /// + private Vulnerability GetMostSevereVulnerability(List vulnerabilities) + { + if (vulnerabilities == null || vulnerabilities.Count == 0) + return null; + + // Order by severity: Critical > High > Medium > Low > Info + return vulnerabilities + .OrderByDescending(v => GetSeverityPriority(v.Severity)) + .FirstOrDefault(); + } + + /// + /// Gets severity priority for ordering (higher number = more severe) + /// Based on JetBrains SeverityLevel precedence (inverted for descending order) + /// + private int GetSeverityPriority(SeverityLevel severity) + { + switch (severity) + { + case SeverityLevel.Malicious: return 8; // Highest priority + case SeverityLevel.Critical: return 7; + case SeverityLevel.High: return 6; + case SeverityLevel.Medium: return 5; + case SeverityLevel.Low: return 4; + case SeverityLevel.Unknown: return 3; + case SeverityLevel.Ok: return 2; + case SeverityLevel.Ignored: return 1; + case SeverityLevel.Info: return 1; + default: return 0; + } + } + + /// + /// Builds tooltip text for multiple vulnerabilities on the same line + /// Based on JetBrains GutterIconRenderer.getTooltipText pattern + /// + private string BuildTooltipText(List vulnerabilities) + { + if (vulnerabilities.Count == 1) + { + var vuln = vulnerabilities[0]; + return $"{vuln.Severity} - {vuln.Title}\n{vuln.Description}\n(DevAssist - {vuln.Scanner})"; + } + else + { + return $"{vulnerabilities.Count} vulnerabilities on this line\n" + + string.Join("\n", vulnerabilities.Select(v => $"โ€ข {v.Severity}: {v.Title}")); + } + } + } +} + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTaggerProvider.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTaggerProvider.cs new file mode 100644 index 00000000..7288f061 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTaggerProvider.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Utilities; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons +{ + /// + /// MEF provider for DevAssist glyph tagger + /// Based on JetBrains EditorFactoryListener pattern adapted for Visual Studio + /// Creates and manages tagger instances per buffer (not per view) + /// IMPORTANT: Uses ITaggerProvider (not IViewTaggerProvider) for glyph tags + /// + [Export(typeof(ITaggerProvider))] + [ContentType("code")] + [ContentType("text")] + [TagType(typeof(DevAssistGlyphTag))] + [TextViewRole(PredefinedTextViewRoles.Document)] + [TextViewRole(PredefinedTextViewRoles.Editable)] + internal class DevAssistGlyphTaggerProvider : ITaggerProvider + { + // Static instance for external access + private static DevAssistGlyphTaggerProvider _instance; + + // Cache taggers per buffer to ensure single instance per buffer + private readonly Dictionary _taggers = + new Dictionary(); + + public DevAssistGlyphTaggerProvider() + { + System.Diagnostics.Debug.WriteLine("DevAssist: DevAssistGlyphTaggerProvider constructor called - MEF is loading this provider"); + _instance = this; + } + + public ITagger CreateTagger(ITextBuffer buffer) where T : ITag + { + System.Diagnostics.Debug.WriteLine($"DevAssist: CreateTagger called - buffer: {buffer != null}"); + + if (buffer == null) + return null; + + // Return existing tagger or create new one + lock (_taggers) + { + if (!_taggers.TryGetValue(buffer, out var tagger)) + { + System.Diagnostics.Debug.WriteLine($"DevAssist: CreateTagger - creating NEW tagger for buffer"); + tagger = new DevAssistGlyphTagger(buffer); + _taggers[buffer] = tagger; + + // Store tagger in buffer properties for external access + try + { + buffer.Properties.AddProperty(typeof(DevAssistGlyphTagger), tagger); + System.Diagnostics.Debug.WriteLine($"DevAssist: CreateTagger - tagger stored in buffer properties"); + } + catch + { + System.Diagnostics.Debug.WriteLine($"DevAssist: CreateTagger - tagger already in buffer properties"); + } + + // Clean up when buffer is closed + buffer.Properties.GetOrCreateSingletonProperty(() => new BufferClosedListener(buffer, () => + { + lock (_taggers) + { + _taggers.Remove(buffer); + buffer.Properties.RemoveProperty(typeof(DevAssistGlyphTagger)); + } + })); + } + else + { + System.Diagnostics.Debug.WriteLine($"DevAssist: CreateTagger - returning EXISTING tagger"); + } + + return tagger as ITagger; + } + } + + /// + /// Gets the tagger for a specific buffer (for external access) + /// Allows DevAssistPOC or other components to update vulnerabilities + /// IMPORTANT: Only returns taggers created by MEF through CreateTagger() + /// This ensures Visual Studio is properly subscribed to TagsChanged events + /// + public static DevAssistGlyphTagger GetTaggerForBuffer(ITextBuffer buffer) + { + if (buffer == null) + { + System.Diagnostics.Debug.WriteLine("DevAssist: GetTaggerForBuffer - buffer is null"); + return null; + } + + // ONLY get tagger from buffer properties - do NOT create it directly + // The tagger MUST be created by MEF through CreateTagger() so that + // Visual Studio subscribes to the TagsChanged event + if (buffer.Properties.TryGetProperty(typeof(DevAssistGlyphTagger), out DevAssistGlyphTagger tagger)) + { + System.Diagnostics.Debug.WriteLine("DevAssist: GetTaggerForBuffer - found tagger in buffer properties"); + return tagger; + } + + // Also check instance cache + if (_instance != null) + { + lock (_instance._taggers) + { + if (_instance._taggers.TryGetValue(buffer, out tagger)) + { + System.Diagnostics.Debug.WriteLine("DevAssist: GetTaggerForBuffer - found tagger in instance cache"); + return tagger; + } + } + } + + System.Diagnostics.Debug.WriteLine("DevAssist: GetTaggerForBuffer - tagger NOT found (MEF hasn't created it yet)"); + return null; + } + + /// + /// Helper class to clean up taggers when buffer is closed + /// + private class BufferClosedListener + { + private readonly ITextBuffer _buffer; + private readonly Action _onClosed; + + public BufferClosedListener(ITextBuffer buffer, Action onClosed) + { + _buffer = buffer; + _onClosed = onClosed; + _buffer.Changed += OnBufferChanged; + } + + private void OnBufferChanged(object sender, TextContentChangedEventArgs e) + { + // Check if buffer is being disposed + if (_buffer.Properties.ContainsProperty("BufferClosed")) + { + _buffer.Changed -= OnBufferChanged; + _onClosed?.Invoke(); + } + } + } + } +} + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs new file mode 100644 index 00000000..3ab0fefb --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; +using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons +{ + /// + /// Listens for text view creation and automatically adds test gutter icons + /// This is a temporary POC to test gutter icon functionality + /// + [Export(typeof(IWpfTextViewCreationListener))] + [ContentType("CSharp")] + [TextViewRole(PredefinedTextViewRoles.Document)] + internal class DevAssistTextViewCreationListener : IWpfTextViewCreationListener + { + public void TextViewCreated(IWpfTextView textView) + { + System.Diagnostics.Debug.WriteLine("DevAssist: TextViewCreated - C# file opened"); + + // Wait for MEF to create the tagger, then add test vulnerabilities + // We need to wait because the tagger is created asynchronously by MEF + System.Threading.Tasks.Task.Delay(1000).ContinueWith(_ => + { + try + { + Microsoft.VisualStudio.Shell.ThreadHelper.JoinableTaskFactory.Run(async () => + { + await Microsoft.VisualStudio.Shell.ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + System.Diagnostics.Debug.WriteLine("DevAssist: Attempting to add test vulnerabilities to C# file"); + + var buffer = textView.TextBuffer; + + // Try to get the tagger - it should have been created by MEF by now + DevAssistGlyphTagger tagger = null; + + // Try multiple times with delays in case MEF is still loading + for (int i = 0; i < 8; i++) + { + tagger = DevAssistGlyphTaggerProvider.GetTaggerForBuffer(buffer); + if (tagger != null) + { + System.Diagnostics.Debug.WriteLine($"DevAssist: Tagger found on attempt {i + 1}"); + break; + } + System.Diagnostics.Debug.WriteLine($"DevAssist: Tagger not found, attempt {i + 1}/5, waiting..."); + await System.Threading.Tasks.Task.Delay(200); + } + + if (tagger != null) + { + System.Diagnostics.Debug.WriteLine("DevAssist: Tagger found, adding test vulnerabilities"); + + // Create test vulnerabilities + var vulnerabilities = new List + { + new Vulnerability + { + Id = "TEST-001", + Severity = SeverityLevel.Malicious, + LineNumber = 1, + Description = "Test Malicious vulnerability" + }, + new Vulnerability + { + Id = "TEST-002", + Severity = SeverityLevel.Critical, + LineNumber = 3, + Description = "Test Critical vulnerability" + }, + new Vulnerability + { + Id = "TEST-003", + Severity = SeverityLevel.High, + LineNumber = 5, + Description = "Test High vulnerability" + }, + new Vulnerability + { + Id = "TEST-004", + Severity = SeverityLevel.Medium, + LineNumber = 7, + Description = "Test Medium vulnerability" + }, + new Vulnerability + { + Id = "TEST-005", + Severity = SeverityLevel.Low, + LineNumber = 9, + Description = "Test Low vulnerability" + }, + new Vulnerability + { + Id = "TEST-006", + Severity = SeverityLevel.Ok, + LineNumber = 11, + Description = "Test OK" + }, + new Vulnerability + { + Id = "TEST-007", + Severity = SeverityLevel.Unknown, + LineNumber = 13, + Description = "Test UnKnown" + }, + new Vulnerability + { + Id = "TEST-008", + Severity = SeverityLevel.Ignored, + LineNumber = 15, + Description = "Test Ignored" + } + }; + + tagger.UpdateVulnerabilities(vulnerabilities); + System.Diagnostics.Debug.WriteLine("DevAssist: Test vulnerabilities added successfully"); + } + else + { + System.Diagnostics.Debug.WriteLine("DevAssist: Tagger is NULL - MEF hasn't created it yet"); + } + }); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"DevAssist: Error adding test vulnerabilities: {ex.Message}"); + } + }); + } + } +} + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/ScannerType.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/ScannerType.cs new file mode 100644 index 00000000..0c6006dc --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/ScannerType.cs @@ -0,0 +1,16 @@ +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Models +{ + /// + /// Scanner types for DevAssist + /// Based on JetBrains ScanEngine enum + /// + public enum ScannerType + { + OSS, + Secrets, + Containers, + IaC, + ASCA + } +} + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/SeverityLevel.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/SeverityLevel.cs new file mode 100644 index 00000000..085c14e6 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/SeverityLevel.cs @@ -0,0 +1,21 @@ +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Models +{ + /// + /// Severity levels for vulnerabilities + /// Based on JetBrains SeverityLevel enum + /// Matches: src/main/java/com/checkmarx/intellij/util/SeverityLevel.java + /// + public enum SeverityLevel + { + Malicious, // Highest priority (precedence 1 in JetBrains) + Critical, // precedence 2 + High, // precedence 3 + Medium, // precedence 4 + Low, // precedence 5 + Unknown, // precedence 6 + Ok, // precedence 7 + Ignored, // precedence 8 + Info // Additional level for informational messages + } +} + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/Vulnerability.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/Vulnerability.cs new file mode 100644 index 00000000..392bd406 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/Vulnerability.cs @@ -0,0 +1,35 @@ +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Models +{ + /// + /// Represents a vulnerability found by DevAssist scanners + /// Based on JetBrains ScanIssue model + /// + public class Vulnerability + { + public string Id { get; set; } + public string Title { get; set; } + public string Description { get; set; } + public SeverityLevel Severity { get; set; } + public ScannerType Scanner { get; set; } + public int LineNumber { get; set; } + public int ColumnNumber { get; set; } + public string FilePath { get; set; } + + public Vulnerability() + { + } + + public Vulnerability(string id, string title, string description, SeverityLevel severity, ScannerType scanner, int lineNumber, int columnNumber, string filePath) + { + Id = id; + Title = title; + Description = description; + Severity = severity; + Scanner = scanner; + LineNumber = lineNumber; + ColumnNumber = columnNumber; + FilePath = filePath; + } + } +} + diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/critical.svg b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/critical.svg new file mode 100644 index 00000000..9c89888d --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/critical.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/high.svg b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/high.svg new file mode 100644 index 00000000..d9b8a81f --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/high.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/ignored.svg b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/ignored.svg new file mode 100644 index 00000000..20246d56 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/ignored.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/low.svg b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/low.svg new file mode 100644 index 00000000..69f9b3a6 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/low.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/malicious.svg b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/malicious.svg new file mode 100644 index 00000000..32a94bd0 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/malicious.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/medium.svg b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/medium.svg new file mode 100644 index 00000000..5be2c823 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/medium.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/ok.svg b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/ok.svg new file mode 100644 index 00000000..21fa16ef --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/ok.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/unknown.svg b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/unknown.svg new file mode 100644 index 00000000..a5270a2a --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/unknown.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/critical.svg b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/critical.svg new file mode 100644 index 00000000..6e1929e8 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/critical.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/high.svg b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/high.svg new file mode 100644 index 00000000..4c815e84 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/high.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/ignored.svg b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/ignored.svg new file mode 100644 index 00000000..4ec04da0 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/ignored.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/low.svg b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/low.svg new file mode 100644 index 00000000..40b203e4 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/low.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/malicious.svg b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/malicious.svg new file mode 100644 index 00000000..32a94bd0 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/malicious.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/medium.svg b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/medium.svg new file mode 100644 index 00000000..3a6cda49 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/medium.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/ok.svg b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/ok.svg new file mode 100644 index 00000000..21fa16ef --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/ok.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/unknown.svg b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/unknown.svg new file mode 100644 index 00000000..d63f29bf --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/unknown.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ast-visual-studio-extension/ast-visual-studio-extension.csproj b/ast-visual-studio-extension/ast-visual-studio-extension.csproj index e15da914..732aa1d0 100644 --- a/ast-visual-studio-extension/ast-visual-studio-extension.csproj +++ b/ast-visual-studio-extension/ast-visual-studio-extension.csproj @@ -132,6 +132,7 @@ + CxWindowControl.xaml @@ -141,6 +142,13 @@ True CxWindowPackage.vsct + + + + + + + Component @@ -201,6 +209,7 @@ 7.5.1 + @@ -292,6 +301,24 @@ Always + + + + + + + + + + + + + + + + + + diff --git a/ast-visual-studio-extension/source.extension.vsixmanifest b/ast-visual-studio-extension/source.extension.vsixmanifest index ea1e323b..7c052b47 100644 --- a/ast-visual-studio-extension/source.extension.vsixmanifest +++ b/ast-visual-studio-extension/source.extension.vsixmanifest @@ -23,5 +23,6 @@ + From 6849566ad1c0eac93ac4b7a0eaf3b744399e3210 Mon Sep 17 00:00:00 2001 From: Rahul Pidde <206018639+cx-rahul-pidde@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:11:11 +0530 Subject: [PATCH 02/45] POC changes of underline --- .../Commands/TestGutterIconsDirectCommand.cs | 86 +++--- .../DevAssistTextViewCreationListener.cs | 36 +-- .../Core/Markers/DevAssistErrorTag.cs | 47 ++++ .../Core/Markers/DevAssistErrorTagger.cs | 173 ++++++++++++ .../Markers/DevAssistErrorTaggerProvider.cs | 105 ++++++++ .../Markers/DevAssistSquigglyAdornment.cs | 246 ++++++++++++++++++ .../DevAssistSquigglyAdornmentProvider.cs | 76 ++++++ .../ast-visual-studio-extension.csproj | 5 + 8 files changed, 730 insertions(+), 44 deletions(-) create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTag.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTaggerProvider.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSquigglyAdornment.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSquigglyAdornmentProvider.cs diff --git a/ast-visual-studio-extension/CxExtension/Commands/TestGutterIconsDirectCommand.cs b/ast-visual-studio-extension/CxExtension/Commands/TestGutterIconsDirectCommand.cs index b281f1b7..41a14917 100644 --- a/ast-visual-studio-extension/CxExtension/Commands/TestGutterIconsDirectCommand.cs +++ b/ast-visual-studio-extension/CxExtension/Commands/TestGutterIconsDirectCommand.cs @@ -6,6 +6,7 @@ using Microsoft.VisualStudio.Text.Tagging; using Microsoft.VisualStudio.ComponentModelHost; using ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons; +using ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers; using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; using Task = System.Threading.Tasks.Task; using Microsoft.VisualStudio.Shell.Interop; @@ -68,38 +69,58 @@ private void Execute(object sender, EventArgs e) var buffer = textView.TextBuffer; - // Create tagger DIRECTLY without MEF - System.Diagnostics.Debug.WriteLine("DevAssist: Creating tagger DIRECTLY (bypassing MEF)"); - var tagger = new DevAssistGlyphTagger(buffer); + // Create glyph tagger DIRECTLY without MEF + System.Diagnostics.Debug.WriteLine("DevAssist: Creating glyph tagger DIRECTLY (bypassing MEF)"); + var glyphTagger = new DevAssistGlyphTagger(buffer); // Store it in buffer properties so the glyph factory can find it try { - buffer.Properties.AddProperty(typeof(DevAssistGlyphTagger), tagger); - System.Diagnostics.Debug.WriteLine("DevAssist: Tagger stored in buffer properties"); + buffer.Properties.AddProperty(typeof(DevAssistGlyphTagger), glyphTagger); + System.Diagnostics.Debug.WriteLine("DevAssist: Glyph tagger stored in buffer properties"); } catch { buffer.Properties.RemoveProperty(typeof(DevAssistGlyphTagger)); - buffer.Properties.AddProperty(typeof(DevAssistGlyphTagger), tagger); - System.Diagnostics.Debug.WriteLine("DevAssist: Tagger replaced in buffer properties"); + buffer.Properties.AddProperty(typeof(DevAssistGlyphTagger), glyphTagger); + System.Diagnostics.Debug.WriteLine("DevAssist: Glyph tagger replaced in buffer properties"); + } + + // Create error tagger DIRECTLY without MEF (for colored squiggly underlines) + System.Diagnostics.Debug.WriteLine("DevAssist: Creating error tagger DIRECTLY (bypassing MEF)"); + var errorTagger = new DevAssistErrorTagger(buffer); + + // Store it in buffer properties + try + { + buffer.Properties.AddProperty(typeof(DevAssistErrorTagger), errorTagger); + System.Diagnostics.Debug.WriteLine("DevAssist: Error tagger stored in buffer properties"); + } + catch + { + buffer.Properties.RemoveProperty(typeof(DevAssistErrorTagger)); + buffer.Properties.AddProperty(typeof(DevAssistErrorTagger), errorTagger); + System.Diagnostics.Debug.WriteLine("DevAssist: Error tagger replaced in buffer properties"); } // Create test vulnerabilities - including Ok, Unknown, and Ignored severity levels var vulnerabilities = new List { - new Vulnerability { Id = "TEST-001", Severity = SeverityLevel.Malicious, LineNumber = 1, Description = "Test Malicious" }, - new Vulnerability { Id = "TEST-002", Severity = SeverityLevel.Critical, LineNumber = 3, Description = "Test Critical" }, - new Vulnerability { Id = "TEST-003", Severity = SeverityLevel.High, LineNumber = 5, Description = "Test High" }, - new Vulnerability { Id = "TEST-004", Severity = SeverityLevel.Medium, LineNumber = 7, Description = "Test Medium" }, - new Vulnerability { Id = "TEST-005", Severity = SeverityLevel.Low, LineNumber = 9, Description = "Test Low" }, - new Vulnerability { Id = "TEST-006", Severity = SeverityLevel.Unknown, LineNumber = 11, Description = "Test Unknown" }, - new Vulnerability { Id = "TEST-007", Severity = SeverityLevel.Ok, LineNumber = 13, Description = "Test Ok" }, - new Vulnerability { Id = "TEST-008", Severity = SeverityLevel.Ignored, LineNumber = 15, Description = "Test Ignored" } + new Vulnerability { Id = "TEST-001", Severity = SeverityLevel.Malicious, LineNumber = 1, Description = "Test Malicious vulnerability" }, + new Vulnerability { Id = "TEST-002", Severity = SeverityLevel.Critical, LineNumber = 3, Description = "Test Critical vulnerability" }, + new Vulnerability { Id = "TEST-003", Severity = SeverityLevel.High, LineNumber = 5, Description = "Test High vulnerability" }, + new Vulnerability { Id = "TEST-004", Severity = SeverityLevel.Medium, LineNumber = 7, Description = "Test Medium vulnerability" }, + new Vulnerability { Id = "TEST-005", Severity = SeverityLevel.Low, LineNumber = 9, Description = "Test Low vulnerability" }, + new Vulnerability { Id = "TEST-006", Severity = SeverityLevel.Unknown, LineNumber = 11, Description = "Test Unknown vulnerability" }, + new Vulnerability { Id = "TEST-007", Severity = SeverityLevel.Ok, LineNumber = 13, Description = "Test Ok vulnerability" }, + new Vulnerability { Id = "TEST-008", Severity = SeverityLevel.Ignored, LineNumber = 15, Description = "Test Ignored vulnerability" } }; - System.Diagnostics.Debug.WriteLine($"DevAssist: Adding {vulnerabilities.Count} test vulnerabilities"); - tagger.UpdateVulnerabilities(vulnerabilities); + System.Diagnostics.Debug.WriteLine($"DevAssist: Adding {vulnerabilities.Count} test vulnerabilities to both taggers"); + + // Update both taggers with the same vulnerabilities + glyphTagger.UpdateVulnerabilities(vulnerabilities); + errorTagger.UpdateVulnerabilities(vulnerabilities); // Force the text view to refresh System.Diagnostics.Debug.WriteLine("DevAssist: Forcing text view refresh"); @@ -108,18 +129,25 @@ private void Execute(object sender, EventArgs e) VsShellUtilities.ShowMessageBox( this.package, $"โœ… Direct test completed!\n\n" + - $"Added {vulnerabilities.Count} test vulnerabilities with gutter icons:\n\n" + - $"๐Ÿ”ด Line 1: Malicious\n" + - $"๐Ÿ”ด Line 3: Critical\n" + - $"๐ŸŸ  Line 5: High\n" + - $"๐ŸŸก Line 7: Medium\n" + - $"๐ŸŸข Line 9: Low\n" + - $"โšช Line 11: Unknown\n" + - $"โœ… Line 13: Ok\n" + - $"๐Ÿšซ Line 15: Ignored\n\n" + - $"Check the LEFT MARGIN (gutter) for severity icons.\n" + - $"Hover over icons to see tooltips.", - "Test Gutter Icons - Direct", + $"Added {vulnerabilities.Count} test vulnerabilities with:\n" + + $"โ€ข Gutter icons (left margin) - ALL 8 severities\n" + + $"โ€ข Custom colored squiggly underlines - ONLY 5 main severities\n\n" + + $"CUSTOM COLORED UNDERLINES:\n" + + $"๐Ÿ”ด Line 1: Malicious (CRIMSON squiggly)\n" + + $"๐Ÿ”ด Line 3: Critical (RED squiggly)\n" + + $"๐ŸŸ  Line 5: High (ORANGE squiggly)\n" + + $"๐ŸŸก Line 7: Medium (GOLD squiggly)\n" + + $"๐ŸŸข Line 9: Low (GREEN squiggly)\n\n" + + $"GUTTER ICONS ONLY (No underlines):\n" + + $"โšช Line 11: Unknown (icon only)\n" + + $"โœ… Line 13: Ok (icon only)\n" + + $"๐Ÿšซ Line 15: Ignored (icon only)\n\n" + + $"Features:\n" + + $"โœ… Custom colored squiggly underlines (adornment layer)\n" + + $"โœ… Tooltips on hover\n" + + $"โœ… Gutter icons for all severities\n" + + $"โœ… Similar to JetBrains plugin", + "Test DevAssist Gutter Icons & Custom Colored Markers", OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs index 3ab0fefb..23a903ff 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs @@ -4,12 +4,13 @@ using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities; using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; +using ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers; namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons { /// - /// Listens for text view creation and automatically adds test gutter icons - /// This is a temporary POC to test gutter icon functionality + /// Listens for text view creation and automatically adds test gutter icons and colored markers + /// This is a temporary POC to test gutter icon and marker functionality /// [Export(typeof(IWpfTextViewCreationListener))] [ContentType("CSharp")] @@ -20,8 +21,8 @@ public void TextViewCreated(IWpfTextView textView) { System.Diagnostics.Debug.WriteLine("DevAssist: TextViewCreated - C# file opened"); - // Wait for MEF to create the tagger, then add test vulnerabilities - // We need to wait because the tagger is created asynchronously by MEF + // Wait for MEF to create the taggers, then add test vulnerabilities + // We need to wait because the taggers are created asynchronously by MEF System.Threading.Tasks.Task.Delay(1000).ContinueWith(_ => { try @@ -34,25 +35,28 @@ public void TextViewCreated(IWpfTextView textView) var buffer = textView.TextBuffer; - // Try to get the tagger - it should have been created by MEF by now - DevAssistGlyphTagger tagger = null; + // Try to get the glyph tagger - it should have been created by MEF by now + DevAssistGlyphTagger glyphTagger = null; + DevAssistErrorTagger errorTagger = null; // Try multiple times with delays in case MEF is still loading for (int i = 0; i < 8; i++) { - tagger = DevAssistGlyphTaggerProvider.GetTaggerForBuffer(buffer); - if (tagger != null) + glyphTagger = DevAssistGlyphTaggerProvider.GetTaggerForBuffer(buffer); + errorTagger = DevAssistErrorTaggerProvider.GetTaggerForBuffer(buffer); + + if (glyphTagger != null && errorTagger != null) { - System.Diagnostics.Debug.WriteLine($"DevAssist: Tagger found on attempt {i + 1}"); + System.Diagnostics.Debug.WriteLine($"DevAssist: Both taggers found on attempt {i + 1}"); break; } - System.Diagnostics.Debug.WriteLine($"DevAssist: Tagger not found, attempt {i + 1}/5, waiting..."); + System.Diagnostics.Debug.WriteLine($"DevAssist: Taggers not found, attempt {i + 1}/8, waiting..."); await System.Threading.Tasks.Task.Delay(200); } - if (tagger != null) + if (glyphTagger != null && errorTagger != null) { - System.Diagnostics.Debug.WriteLine("DevAssist: Tagger found, adding test vulnerabilities"); + System.Diagnostics.Debug.WriteLine("DevAssist: Both taggers found, adding test vulnerabilities"); // Create test vulnerabilities var vulnerabilities = new List @@ -115,12 +119,14 @@ public void TextViewCreated(IWpfTextView textView) } }; - tagger.UpdateVulnerabilities(vulnerabilities); - System.Diagnostics.Debug.WriteLine("DevAssist: Test vulnerabilities added successfully"); + // Update both taggers with the same vulnerabilities + glyphTagger.UpdateVulnerabilities(vulnerabilities); + errorTagger.UpdateVulnerabilities(vulnerabilities); + System.Diagnostics.Debug.WriteLine("DevAssist: Test vulnerabilities added to both taggers successfully"); } else { - System.Diagnostics.Debug.WriteLine("DevAssist: Tagger is NULL - MEF hasn't created it yet"); + System.Diagnostics.Debug.WriteLine("DevAssist: Taggers are NULL - MEF hasn't created them yet"); } }); } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTag.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTag.cs new file mode 100644 index 00000000..b92138c4 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTag.cs @@ -0,0 +1,47 @@ +using Microsoft.VisualStudio.Text.Adornments; +using Microsoft.VisualStudio.Text.Tagging; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +{ + /// + /// Custom error tag for DevAssist vulnerabilities + /// Based on JetBrains TextAttributes with EffectType.WAVE_UNDERSCORE pattern + /// Provides tooltip support and metadata for vulnerabilities + /// NOTE: The actual colored squiggly underlines are drawn by DevAssistSquigglyAdornment + /// This tag provides the infrastructure (tooltips, accessibility) without visible squiggles + /// We use an empty/null error type to completely disable the default Visual Studio squiggle + /// + internal class DevAssistErrorTag : IErrorTag + { + /// + /// Gets the error type - using Suggestion which has minimal visual impact + /// The actual colored squiggles are drawn by the adornment layer + /// Suggestion type shows as a subtle dotted underline that our custom squiggles will override + /// + public string ErrorType => PredefinedErrorTypeNames.Suggestion; + + /// + /// Gets the tooltip text shown when hovering over the vulnerability + /// + public object ToolTipContent { get; } + + /// + /// Gets the severity level of the vulnerability + /// Used by the adornment layer to determine squiggle color + /// + public string Severity { get; } + + /// + /// Gets the vulnerability ID + /// + public string VulnerabilityId { get; } + + public DevAssistErrorTag(string severity, string tooltipText, string vulnerabilityId) + { + Severity = severity; + VulnerabilityId = vulnerabilityId; + ToolTipContent = tooltipText; + } + } +} + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs new file mode 100644 index 00000000..49b114c1 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs @@ -0,0 +1,173 @@ +using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Tagging; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +{ + /// + /// Tagger that provides error tags for DevAssist vulnerabilities + /// Based on JetBrains MarkupModel.addRangeHighlighter pattern + /// Creates severity-based colored squiggly underlines in the text editor + /// Similar to JetBrains EffectType.WAVE_UNDERSCORE implementation + /// + internal class DevAssistErrorTagger : ITagger + { + private readonly ITextBuffer _buffer; + private readonly Dictionary> _vulnerabilitiesByLine; + + public event EventHandler TagsChanged; + + public DevAssistErrorTagger(ITextBuffer buffer) + { + _buffer = buffer; + _vulnerabilitiesByLine = new Dictionary>(); + } + + public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) + { + System.Diagnostics.Debug.WriteLine($"DevAssist Markers: GetTags called - spans count: {spans.Count}, vulnerabilities count: {_vulnerabilitiesByLine.Count}"); + + if (spans.Count == 0 || _vulnerabilitiesByLine.Count == 0) + { + System.Diagnostics.Debug.WriteLine($"DevAssist Markers: GetTags returning early - no spans or vulnerabilities"); + yield break; + } + + var snapshot = spans[0].Snapshot; + int tagCount = 0; + + foreach (var span in spans) + { + var startLine = snapshot.GetLineNumberFromPosition(span.Start); + var endLine = snapshot.GetLineNumberFromPosition(span.End); + + for (int lineNumber = startLine; lineNumber <= endLine; lineNumber++) + { + if (_vulnerabilitiesByLine.TryGetValue(lineNumber, out var vulnerabilities)) + { + // Create error tags ONLY for actual severity vulnerabilities (not Unknown, Ok, Ignored) + // Similar to JetBrains plugin which shows underlines only for real issues + // Each vulnerability gets its own squiggly underline + foreach (var vulnerability in vulnerabilities) + { + // Filter: Show underlines ONLY for Malicious, Critical, High, Medium, Low + // Do NOT show underlines for Unknown, Ok, Ignored (they only get gutter icons) + if (!ShouldShowUnderline(vulnerability.Severity)) + { + System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Skipping underline for {vulnerability.Severity} on line {lineNumber}"); + continue; + } + + var line = snapshot.GetLineFromLineNumber(lineNumber); + + // Create span for the entire line (similar to JetBrains TextRange) + // In the future, this could be refined to highlight specific code ranges + var lineSpan = new SnapshotSpan(snapshot, line.Start, line.Length); + + var tooltipText = BuildTooltipText(vulnerability); + var tag = new DevAssistErrorTag( + vulnerability.Severity.ToString(), + tooltipText, + vulnerability.Id + ); + + tagCount++; + System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Creating error tag #{tagCount} for line {lineNumber}, severity: {vulnerability.Severity}"); + yield return new TagSpan(lineSpan, tag); + } + } + } + } + + System.Diagnostics.Debug.WriteLine($"DevAssist Markers: GetTags completed - returned {tagCount} error tags"); + } + + /// + /// Determines if a severity level should show an underline + /// Similar to JetBrains plugin: only show underlines for actual issues (Malicious, Critical, High, Medium, Low) + /// Do NOT show underlines for Unknown, Ok, Ignored (they only get gutter icons) + /// + private bool ShouldShowUnderline(SeverityLevel severity) + { + switch (severity) + { + case SeverityLevel.Malicious: + case SeverityLevel.Critical: + case SeverityLevel.High: + case SeverityLevel.Medium: + case SeverityLevel.Low: + case SeverityLevel.Info: // Info maps to Low + return true; + + case SeverityLevel.Unknown: + case SeverityLevel.Ok: + case SeverityLevel.Ignored: + return false; + + default: + return false; + } + } + + /// + /// Builds tooltip text for a vulnerability + /// Similar to JetBrains GutterIconRenderer.getTooltipText() + /// + private string BuildTooltipText(Vulnerability vulnerability) + { + return $"[{vulnerability.Severity}] {vulnerability.Description}\nID: {vulnerability.Id}"; + } + + /// + /// Updates the vulnerabilities and triggers a refresh of error tags + /// Similar to JetBrains MarkupModel.removeAllHighlighters() + addRangeHighlighter() + /// + public void UpdateVulnerabilities(List vulnerabilities) + { + System.Diagnostics.Debug.WriteLine($"DevAssist Markers: UpdateVulnerabilities called with {vulnerabilities?.Count ?? 0} vulnerabilities"); + + _vulnerabilitiesByLine.Clear(); + + if (vulnerabilities != null) + { + foreach (var vulnerability in vulnerabilities) + { + // Convert 1-based line number to 0-based for Visual Studio + int lineNumber = vulnerability.LineNumber - 1; + + if (!_vulnerabilitiesByLine.ContainsKey(lineNumber)) + { + _vulnerabilitiesByLine[lineNumber] = new List(); + } + + _vulnerabilitiesByLine[lineNumber].Add(vulnerability); + System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Added vulnerability {vulnerability.Id} to line {lineNumber}"); + } + } + + System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Vulnerabilities stored in {_vulnerabilitiesByLine.Count} lines"); + + // Notify that tags have changed + var snapshot = _buffer.CurrentSnapshot; + var entireSpan = new SnapshotSpan(snapshot, 0, snapshot.Length); + + System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Raising TagsChanged event for span: {entireSpan.Start} to {entireSpan.End}"); + TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(entireSpan)); + System.Diagnostics.Debug.WriteLine($"DevAssist Markers: TagsChanged event raised"); + } + + /// + /// Clears all vulnerabilities and error tags + /// Similar to JetBrains MarkupModel.removeAllHighlighters() + /// + public void ClearVulnerabilities() + { + System.Diagnostics.Debug.WriteLine("DevAssist Markers: ClearVulnerabilities called"); + UpdateVulnerabilities(null); + } + } +} + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTaggerProvider.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTaggerProvider.cs new file mode 100644 index 00000000..acbdd17e --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTaggerProvider.cs @@ -0,0 +1,105 @@ +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Utilities; +using System.Collections.Generic; +using System.ComponentModel.Composition; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +{ + /// + /// MEF provider for DevAssist error tagger + /// Based on JetBrains EditorFactoryListener pattern adapted for Visual Studio + /// Creates and manages error tagger instances per buffer (not per view) + /// Provides severity-based colored squiggly underlines similar to JetBrains WAVE_UNDERSCORE + /// IMPORTANT: Uses ITaggerProvider (not IViewTaggerProvider) for error tags + /// + [Export(typeof(ITaggerProvider))] + [ContentType("code")] + [ContentType("text")] + [TagType(typeof(DevAssistErrorTag))] + [TextViewRole(PredefinedTextViewRoles.Document)] + [TextViewRole(PredefinedTextViewRoles.Editable)] + internal class DevAssistErrorTaggerProvider : ITaggerProvider + { + // Static instance for external access + private static DevAssistErrorTaggerProvider _instance; + + // Cache taggers per buffer to ensure single instance per buffer + private readonly Dictionary _taggers = + new Dictionary(); + + public DevAssistErrorTaggerProvider() + { + System.Diagnostics.Debug.WriteLine("DevAssist Markers: DevAssistErrorTaggerProvider constructor called - MEF is loading error tagger provider"); + _instance = this; + } + + public ITagger CreateTagger(ITextBuffer buffer) where T : ITag + { + System.Diagnostics.Debug.WriteLine($"DevAssist Markers: CreateTagger called - buffer: {buffer != null}"); + + if (buffer == null) + return null; + + // Return cached tagger if it exists, otherwise create new one + lock (_taggers) + { + if (_taggers.TryGetValue(buffer, out var existingTagger)) + { + System.Diagnostics.Debug.WriteLine("DevAssist Markers: Returning existing error tagger from cache"); + return existingTagger as ITagger; + } + + System.Diagnostics.Debug.WriteLine("DevAssist Markers: Creating new error tagger"); + var tagger = new DevAssistErrorTagger(buffer); + _taggers[buffer] = tagger; + + // Clean up when buffer is disposed + buffer.Properties.GetOrCreateSingletonProperty(() => + { + buffer.Changed += (sender, args) => + { + // Could add buffer change handling here if needed + }; + return tagger; + }); + + return tagger as ITagger; + } + } + + /// + /// Gets the error tagger for a specific buffer + /// Used by external components to update vulnerability markers + /// Similar to JetBrains MarkupModel access pattern + /// + public static DevAssistErrorTagger GetTaggerForBuffer(ITextBuffer buffer) + { + if (_instance == null || buffer == null) + return null; + + lock (_instance._taggers) + { + _instance._taggers.TryGetValue(buffer, out var tagger); + return tagger; + } + } + + /// + /// Gets all active error taggers + /// Useful for debugging and diagnostics + /// + public static IEnumerable GetAllTaggers() + { + if (_instance == null) + return new List(); + + lock (_instance._taggers) + { + return new List(_instance._taggers.Values); + } + } + } +} + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSquigglyAdornment.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSquigglyAdornment.cs new file mode 100644 index 00000000..4efc57d0 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSquigglyAdornment.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Shapes; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Formatting; +using Microsoft.VisualStudio.Text.Tagging; +using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +{ + /// + /// Custom adornment layer that draws colored squiggly underlines for DevAssist vulnerabilities + /// Based on JetBrains EffectType.WAVE_UNDERSCORE pattern + /// Provides full control over squiggle colors for each severity level + /// + internal class DevAssistSquigglyAdornment + { + private readonly IAdornmentLayer _adornmentLayer; + private readonly IWpfTextView _textView; + private readonly ITextBuffer _textBuffer; + private readonly DevAssistErrorTagger _errorTagger; + + // Severity color mapping - based on Checkmarx UI colors + // Note: JetBrains and VSCode plugins use single red color for all severities + // This Visual Studio extension provides enhanced UX with severity-specific colors + private static readonly Dictionary SeverityColors = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "Malicious", Color.FromRgb(139, 0, 0) }, // Dark Red (darker than Critical) + { "Critical", Color.FromRgb(217, 75, 72) }, // Red (#D94B48 from Checkmarx UI) + { "High", Color.FromRgb(217, 75, 72) }, // Red (#D94B48 from Checkmarx UI - same as Critical) + { "Medium", Color.FromRgb(249, 174, 77) }, // Orange/Gold (#F9AE4D from Checkmarx UI) + { "Low", Color.FromRgb(2, 147, 2) }, // Green (#029302 from Checkmarx UI) + { "Info", Color.FromRgb(2, 147, 2) }, // Green (same as Low) + { "Unknown", Color.FromRgb(135, 190, 209) }, // Blue (#87bed1 from Checkmarx UI) + { "Ok", Color.FromRgb(160, 160, 160) }, // Light Gray + { "Ignored", Color.FromRgb(128, 128, 128) } // Dark Gray + }; + + public DevAssistSquigglyAdornment(IWpfTextView textView, DevAssistErrorTagger errorTagger) + { + _textView = textView ?? throw new ArgumentNullException(nameof(textView)); + _errorTagger = errorTagger ?? throw new ArgumentNullException(nameof(errorTagger)); + _textBuffer = textView.TextBuffer; + + // Get or create the adornment layer + _adornmentLayer = textView.GetAdornmentLayer("DevAssistSquigglyAdornment"); + + // Subscribe to events + _textView.LayoutChanged += OnLayoutChanged; + _textView.Closed += OnViewClosed; + _errorTagger.TagsChanged += OnTagsChanged; + + System.Diagnostics.Debug.WriteLine("DevAssist Adornment: Squiggly adornment layer initialized"); + } + + /// + /// Handle layout changes (scrolling, text changes, zoom, etc.) + /// + private void OnLayoutChanged(object sender, TextViewLayoutChangedEventArgs e) + { + // Only redraw if the view has changed significantly + if (e.NewOrReformattedLines.Count > 0 || e.VerticalTranslation || e.NewViewState.ViewportWidth != e.OldViewState.ViewportWidth) + { + RedrawAdornments(); + } + } + + /// + /// Handle tags changed event from the error tagger + /// + private void OnTagsChanged(object sender, SnapshotSpanEventArgs e) + { + RedrawAdornments(); + } + + /// + /// Clean up when view is closed + /// + private void OnViewClosed(object sender, EventArgs e) + { + _textView.LayoutChanged -= OnLayoutChanged; + _textView.Closed -= OnViewClosed; + _errorTagger.TagsChanged -= OnTagsChanged; + } + + /// + /// Redraw all squiggly adornments for visible lines + /// Uses virtualization - only draws squiggles for visible text + /// + private void RedrawAdornments() + { + try + { + System.Diagnostics.Debug.WriteLine("DevAssist Adornment: RedrawAdornments called"); + + // Clear all existing adornments + _adornmentLayer.RemoveAllAdornments(); + System.Diagnostics.Debug.WriteLine("DevAssist Adornment: Removed all existing adornments"); + + // Get the visible span + var visibleSpan = new SnapshotSpan(_textView.TextSnapshot, _textView.TextViewLines.FormattedSpan.Start, _textView.TextViewLines.FormattedSpan.Length); + System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Visible span: {visibleSpan.Start.Position} to {visibleSpan.End.Position}"); + + // Get all error tags in the visible span + var tags = _errorTagger.GetTags(new NormalizedSnapshotSpanCollection(visibleSpan)); + var tagsList = tags.ToList(); + System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Found {tagsList.Count} error tags"); + + int adornmentCount = 0; + foreach (var tagSpan in tagsList) + { + System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: About to draw squiggly #{adornmentCount + 1} for severity: {tagSpan.Tag.Severity}"); + DrawSquiggly(tagSpan.Span, tagSpan.Tag.Severity); + adornmentCount++; + System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Successfully drew squiggly #{adornmentCount}"); + } + + System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Drew {adornmentCount} squiggly adornments"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Error redrawing adornments: {ex.Message}"); + System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Stack trace: {ex.StackTrace}"); + } + } + + /// + /// Draw a squiggly underline for a specific span with severity-based color + /// + private void DrawSquiggly(SnapshotSpan span, string severity) + { + try + { + // Get the geometry for the span + var geometry = _textView.TextViewLines.GetMarkerGeometry(span); + if (geometry == null) + { + System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: No geometry for span at {span.Start.Position}"); + return; + } + + // Get color for severity + if (!SeverityColors.TryGetValue(severity, out var color)) + { + System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Unknown severity '{severity}', using default"); + color = SeverityColors["Unknown"]; + } + + System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Drawing {severity} squiggly with color R={color.R} G={color.G} B={color.B}"); + + // Create the squiggly path + var squigglyPath = CreateSquigglyPath(geometry.Bounds, color); + if (squigglyPath == null) + { + System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Failed to create squiggly path"); + return; + } + + // Add to adornment layer + Canvas.SetLeft(squigglyPath, geometry.Bounds.Left); + Canvas.SetTop(squigglyPath, geometry.Bounds.Bottom - 2); + + _adornmentLayer.AddAdornment( + AdornmentPositioningBehavior.TextRelative, + span, + null, + squigglyPath, + null); + + System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Successfully added squiggly adornment"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Error drawing squiggly: {ex.Message}"); + } + } + + /// + /// Creates a smooth wavy/squiggly path for the underline using Bezier curves + /// Similar to Visual Studio's error squiggles but with custom colors + /// + private Path CreateSquigglyPath(Rect bounds, Color color) + { + try + { + const double waveHeight = 2.5; // Height of the wave (amplitude) + const double waveLength = 5.0; // Length of one complete wave cycle + + if (bounds.Width <= 0 || bounds.Height <= 0) + return null; + + var pathGeometry = new PathGeometry(); + var pathFigure = new PathFigure { StartPoint = new Point(0, 0) }; + + // Create smooth wave pattern using Bezier curves + double x = 0; + bool goingDown = true; // Start by going down + + while (x < bounds.Width) + { + double halfWave = waveLength / 2.0; + double nextX = Math.Min(x + halfWave, bounds.Width); + + // Control points for smooth Bezier curve + double controlX1 = x + halfWave / 3.0; + double controlX2 = x + 2.0 * halfWave / 3.0; + double peakY = goingDown ? waveHeight : -waveHeight; + + // Create a smooth curve using quadratic Bezier + pathFigure.Segments.Add(new QuadraticBezierSegment( + new Point(x + halfWave / 2.0, peakY), // Control point at peak + new Point(nextX, 0), // End point back at baseline + true)); + + x = nextX; + goingDown = !goingDown; + } + + pathGeometry.Figures.Add(pathFigure); + + var path = new Path + { + Data = pathGeometry, + Stroke = new SolidColorBrush(color), + StrokeThickness = 1.5, // Bolder/darker stroke for better visibility + Width = bounds.Width, + Height = waveHeight * 2, + Opacity = 1.0 // Full opacity for maximum visibility + }; + + return path; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Error creating squiggly path: {ex.Message}"); + return null; + } + } + } +} + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSquigglyAdornmentProvider.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSquigglyAdornmentProvider.cs new file mode 100644 index 00000000..2c9533ff --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSquigglyAdornmentProvider.cs @@ -0,0 +1,76 @@ +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +{ + /// + /// MEF provider for DevAssist squiggly adornment layer + /// Creates the custom colored squiggly underlines for vulnerabilities + /// Based on JetBrains EffectType.WAVE_UNDERSCORE pattern + /// + [Export(typeof(IWpfTextViewCreationListener))] + [ContentType("code")] + [ContentType("text")] + [TextViewRole(PredefinedTextViewRoles.Document)] + [TextViewRole(PredefinedTextViewRoles.Editable)] + internal class DevAssistSquigglyAdornmentProvider : IWpfTextViewCreationListener + { + /// + /// Defines the adornment layer for squiggly underlines + /// Order: After selection, before text (so squiggles appear under text) + /// + [Export(typeof(AdornmentLayerDefinition))] + [Name("DevAssistSquigglyAdornment")] + [Order(After = PredefinedAdornmentLayers.Selection, Before = PredefinedAdornmentLayers.Text)] + [TextViewRole(PredefinedTextViewRoles.Document)] + public AdornmentLayerDefinition AdornmentLayer = null; + + /// + /// Called when a text view is created + /// Creates the squiggly adornment for the view + /// + public void TextViewCreated(IWpfTextView textView) + { + if (textView == null) + return; + + System.Diagnostics.Debug.WriteLine("DevAssist Adornment: TextViewCreated called"); + + // Get the error tagger for this buffer + var errorTagger = DevAssistErrorTaggerProvider.GetTaggerForBuffer(textView.TextBuffer); + if (errorTagger == null) + { + System.Diagnostics.Debug.WriteLine("DevAssist Adornment: Error tagger not found, will retry with longer delay"); + + // The error tagger might not be created yet, so we'll wait longer and retry multiple times + System.Threading.Tasks.Task.Delay(1500).ContinueWith(_ => + { + Microsoft.VisualStudio.Shell.ThreadHelper.JoinableTaskFactory.Run(async () => + { + await Microsoft.VisualStudio.Shell.ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + errorTagger = DevAssistErrorTaggerProvider.GetTaggerForBuffer(textView.TextBuffer); + if (errorTagger != null) + { + System.Diagnostics.Debug.WriteLine("DevAssist Adornment: Error tagger found on retry, creating adornment"); + textView.Properties.GetOrCreateSingletonProperty(() => new DevAssistSquigglyAdornment(textView, errorTagger)); + } + else + { + System.Diagnostics.Debug.WriteLine("DevAssist Adornment: Error tagger still not found after retry"); + } + }); + }, System.Threading.Tasks.TaskScheduler.Default); + + return; + } + + // Create the adornment and store it in the view's property bag + textView.Properties.GetOrCreateSingletonProperty(() => new DevAssistSquigglyAdornment(textView, errorTagger)); + + System.Diagnostics.Debug.WriteLine("DevAssist Adornment: Squiggly adornment created successfully"); + } + } +} + diff --git a/ast-visual-studio-extension/ast-visual-studio-extension.csproj b/ast-visual-studio-extension/ast-visual-studio-extension.csproj index 732aa1d0..1cab9bbe 100644 --- a/ast-visual-studio-extension/ast-visual-studio-extension.csproj +++ b/ast-visual-studio-extension/ast-visual-studio-extension.csproj @@ -146,6 +146,11 @@ + + + + + From 1bd92dc6e0c05c74b73c6d783d853e846c6b32ee Mon Sep 17 00:00:00 2001 From: Rahul Pidde <206018639+cx-rahul-pidde@users.noreply.github.com> Date: Wed, 4 Feb 2026 23:07:20 +0530 Subject: [PATCH 03/45] Implemented Problem window --- .../Commands/ShowFindingsWindowCommand.cs | 247 +++++++++ .../TestErrorListCustomizationCommand.cs | 151 +++++ .../CxExtension/CxWindowControl.xaml | 131 ++++- .../CxExtension/CxWindowControl.xaml.cs | 65 +++ .../CxExtension/CxWindowPackage.cs | 8 + .../CxExtension/CxWindowPackage.vsct | 20 + .../DevAssistFindingsControl.xaml | 391 +++++++++++++ .../DevAssistFindingsControl.xaml.cs | 515 ++++++++++++++++++ .../FindingsWindow/DevAssistFindingsWindow.cs | 40 ++ .../UI/FindingsWindow/FindingsTreeNode.cs | 181 ++++++ .../DevAssist/Icons/Dark/container.png | Bin 0 -> 421 bytes .../DevAssist/Icons/Dark/critical.png | Bin 0 -> 643 bytes .../DevAssist/Icons/Dark/cxone_assist.png | Bin 0 -> 1905 bytes .../Resources/DevAssist/Icons/Dark/high.png | Bin 0 -> 511 bytes .../Resources/DevAssist/Icons/Dark/low.png | Bin 0 -> 508 bytes .../DevAssist/Icons/Dark/malicious.png | Bin 0 -> 569 bytes .../Resources/DevAssist/Icons/Dark/medium.png | Bin 0 -> 537 bytes .../DevAssist/Icons/Dark/package.png | Bin 0 -> 456 bytes .../DevAssist/Icons/Light/container.png | Bin 0 -> 416 bytes .../DevAssist/Icons/Light/critical.png | Bin 0 -> 638 bytes .../DevAssist/Icons/Light/cxone_assist.png | Bin 0 -> 2132 bytes .../Resources/DevAssist/Icons/Light/high.png | Bin 0 -> 498 bytes .../Resources/DevAssist/Icons/Light/low.png | Bin 0 -> 481 bytes .../DevAssist/Icons/Light/malicious.png | Bin 0 -> 569 bytes .../DevAssist/Icons/Light/medium.png | Bin 0 -> 491 bytes .../DevAssist/Icons/Light/package.png | Bin 0 -> 454 bytes .../ast-visual-studio-extension.csproj | 33 +- 27 files changed, 1764 insertions(+), 18 deletions(-) create mode 100644 ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs create mode 100644 ast-visual-studio-extension/CxExtension/Commands/TestErrorListCustomizationCommand.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsWindow.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/FindingsTreeNode.cs create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/container.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/critical.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/cxone_assist.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/high.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/low.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/malicious.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/medium.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/package.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/container.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/critical.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/cxone_assist.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/high.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/low.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/malicious.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/medium.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/package.png diff --git a/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs b/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs new file mode 100644 index 00000000..d67601f0 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs @@ -0,0 +1,247 @@ +using System; +using System.ComponentModel.Design; +using System.Collections.ObjectModel; +using System.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Task = System.Threading.Tasks.Task; +using ast_visual_studio_extension.CxExtension.DevAssist.UI.FindingsWindow; + +namespace ast_visual_studio_extension.CxExtension.Commands +{ + /// + /// Command handler to show and populate the DevAssist Findings window + /// + internal sealed class ShowFindingsWindowCommand + { + public const int CommandId = 0x0110; + public static readonly Guid CommandSet = new Guid("a6e8b6e3-8e3e-4e3e-8e3e-8e3e8e3e8e3f"); + + private readonly AsyncPackage package; + + private ShowFindingsWindowCommand(AsyncPackage package, OleMenuCommandService commandService) + { + this.package = package ?? throw new ArgumentNullException(nameof(package)); + commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); + + var menuCommandID = new CommandID(CommandSet, CommandId); + var menuItem = new MenuCommand(this.Execute, menuCommandID); + commandService.AddCommand(menuItem); + } + + public static ShowFindingsWindowCommand Instance { get; private set; } + + private Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider => this.package; + + public static async Task InitializeAsync(AsyncPackage package) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); + + OleMenuCommandService commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; + Instance = new ShowFindingsWindowCommand(package, commandService); + } + + private void Execute(object sender, EventArgs e) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + try + { + // Show the existing Checkmarx window (not the standalone DevAssistFindingsWindow) + ToolWindowPane window = this.package.FindToolWindow(typeof(CxWindow), 0, true); + if ((null == window) || (null == window.Frame)) + { + throw new NotSupportedException("Cannot create Checkmarx window"); + } + + IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame; + Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show()); + + // Get the CxWindowControl and switch to DevAssist tab + var cxWindow = window as CxWindow; + if (cxWindow != null && cxWindow.Content is CxWindowControl cxWindowControl) + { + // Switch to the DevAssist Findings tab + cxWindowControl.SwitchToDevAssistTab(); + + // Get the DevAssist Findings Control and populate with test data + var findingsControl = cxWindowControl.GetDevAssistFindingsControl(); + if (findingsControl != null) + { + PopulateTestData(findingsControl); + } + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Error showing DevAssist findings: {ex.Message}"); + } + } + + private void PopulateTestData(DevAssistFindingsControl control) + { + if (control == null) return; + + var fileNodes = new ObservableCollection(); + + // File 1: go.mod with High and Medium vulnerabilities + var file1 = new FileNode + { + FileName = "go.mod", + FilePath = "C:\\Projects\\TestProject\\go.mod", + FileIcon = LoadIcon("document.png") + }; + + file1.Vulnerabilities.Add(new VulnerabilityNode + { + Severity = "High", + SeverityIcon = LoadSeverityIcon("High"), + PackageName = "helm.sh/helm/v3", + PackageVersion = "v3.18.2", + Line = 38, + Column = 1, + FilePath = file1.FilePath + }); + + file1.Vulnerabilities.Add(new VulnerabilityNode + { + Severity = "Medium", + SeverityIcon = LoadSeverityIcon("Medium"), + PackageName = "github.com/docker/docker", + PackageVersion = "v20.10.7", + Line = 42, + Column = 1, + FilePath = file1.FilePath + }); + + // Add severity counts + file1.SeverityCounts.Add(new SeverityCount { Severity = "High", Count = 1, Icon = LoadSeverityIcon("High") }); + file1.SeverityCounts.Add(new SeverityCount { Severity = "Medium", Count = 1, Icon = LoadSeverityIcon("Medium") }); + + fileNodes.Add(file1); + + // File 2: package.json with Malicious and Low vulnerabilities + var file2 = new FileNode + { + FileName = "package.json", + FilePath = "C:\\Projects\\TestProject\\package.json", + FileIcon = LoadIcon("document.png") + }; + + file2.Vulnerabilities.Add(new VulnerabilityNode + { + Severity = "Malicious", + SeverityIcon = LoadSeverityIcon("Malicious"), + PackageName = "evil-package", + PackageVersion = "1.0.0", + Line = 15, + Column = 4, + FilePath = file2.FilePath + }); + + file2.Vulnerabilities.Add(new VulnerabilityNode + { + Severity = "Low", + SeverityIcon = LoadSeverityIcon("Low"), + PackageName = "old-library", + PackageVersion = "2.3.1", + Line = 23, + Column = 4, + FilePath = file2.FilePath + }); + + // Add severity counts + file2.SeverityCounts.Add(new SeverityCount { Severity = "Malicious", Count = 1, Icon = LoadSeverityIcon("Malicious") }); + file2.SeverityCounts.Add(new SeverityCount { Severity = "Low", Count = 1, Icon = LoadSeverityIcon("Low") }); + + fileNodes.Add(file2); + + // Use SetAllFileNodes to enable filtering + control.SetAllFileNodes(fileNodes); + } + + /// + /// Detect if Visual Studio is using dark theme + /// + private bool IsDarkTheme() + { + try + { + // Get the VS theme color using PlatformUI + var color = Microsoft.VisualStudio.PlatformUI.VSColorTheme.GetThemedColor(Microsoft.VisualStudio.PlatformUI.EnvironmentColors.ToolWindowBackgroundColorKey); + + // Calculate brightness (simple luminance formula) + int brightness = (int)Math.Sqrt( + color.R * color.R * 0.299 + + color.G * color.G * 0.587 + + color.B * color.B * 0.114); + + // If brightness is less than 128, it's a dark theme + return brightness < 128; + } + catch + { + // Default to dark theme if detection fails + return true; + } + } + + /// + /// Load severity icon based on severity level - uses JetBrains PNG icons with theme support + /// + private System.Windows.Media.ImageSource LoadSeverityIcon(string severity) + { + try + { + // Determine theme folder + string themeFolder = IsDarkTheme() ? "Dark" : "Light"; + + // Build the icon path + string iconName = severity.ToLower(); + string iconPath = $"pack://application:,,,/ast-visual-studio-extension;component/CxExtension/Resources/DevAssist/Icons/{themeFolder}/{iconName}.png"; + + // Load the PNG image + var bitmap = new BitmapImage(); + bitmap.BeginInit(); + bitmap.UriSource = new Uri(iconPath, UriKind.Absolute); + bitmap.CacheOption = BitmapCacheOption.OnLoad; + bitmap.EndInit(); + bitmap.Freeze(); + + return bitmap; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Error loading severity icon for {severity}: {ex.Message}"); + return null; + } + } + + /// + /// Load generic file icon + /// + private System.Windows.Media.ImageSource LoadIcon(string iconName) + { + try + { + // Use existing info icon as placeholder for file icon + var uri = new Uri("pack://application:,,,/ast-visual-studio-extension;component/CxExtension/Resources/info.png", UriKind.Absolute); + var bitmap = new BitmapImage(); + bitmap.BeginInit(); + bitmap.UriSource = uri; + bitmap.CacheOption = BitmapCacheOption.OnLoad; + bitmap.EndInit(); + bitmap.Freeze(); // Important for cross-thread access + return bitmap; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Error loading file icon: {ex.Message}"); + return null; + } + } + } +} + diff --git a/ast-visual-studio-extension/CxExtension/Commands/TestErrorListCustomizationCommand.cs b/ast-visual-studio-extension/CxExtension/Commands/TestErrorListCustomizationCommand.cs new file mode 100644 index 00000000..862a8eda --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/Commands/TestErrorListCustomizationCommand.cs @@ -0,0 +1,151 @@ +using System; +using System.ComponentModel.Design; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Task = System.Threading.Tasks.Task; +using Microsoft.VisualStudio.Shell.TableControl; +using Microsoft.VisualStudio.Shell.TableManager; +using EnvDTE; + +namespace ast_visual_studio_extension.CxExtension.Commands +{ + /// + /// POC Command to test Visual Studio Error List customization capabilities + /// Tests: Custom icons, severity-based styling, context menus, grouping + /// Part of AST-133228 - POC - Problem Window Customization Validation + /// + internal sealed class TestErrorListCustomizationCommand + { + public const int CommandId = 0x0109; + public static readonly Guid CommandSet = new Guid("a6e8b6e3-8e3e-4e3e-8e3e-8e3e8e3e8e3e"); + + private readonly AsyncPackage package; + private ErrorListProvider _errorListProvider; + + private TestErrorListCustomizationCommand(AsyncPackage package, OleMenuCommandService commandService) + { + this.package = package ?? throw new ArgumentNullException(nameof(package)); + commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); + + var menuCommandID = new CommandID(CommandSet, CommandId); + var menuItem = new MenuCommand(this.Execute, menuCommandID); + commandService.AddCommand(menuItem); + } + + public static TestErrorListCustomizationCommand Instance { get; private set; } + + private Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider => this.package; + + public static async Task InitializeAsync(AsyncPackage package) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); + + OleMenuCommandService commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; + Instance = new TestErrorListCustomizationCommand(package, commandService); + } + + private void Execute(object sender, EventArgs e) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + try + { + System.Diagnostics.Debug.WriteLine("DevAssist: Testing Error List customization..."); + + // Initialize ErrorListProvider + if (_errorListProvider == null) + { + _errorListProvider = new ErrorListProvider(Microsoft.VisualStudio.Shell.ServiceProvider.GlobalProvider) + { + ProviderName = "Checkmarx DevAssist", + ProviderGuid = new Guid("12345678-1234-1234-1234-123456789ABC") + }; + } + + _errorListProvider.Tasks.Clear(); + + // Test 1: Add tasks with different severities + AddTestTask("Malicious", TaskErrorCategory.Error, "SQL Injection detected", "TestFile.cs", 10, 5); + AddTestTask("Critical", TaskErrorCategory.Error, "Remote Code Execution vulnerability", "TestFile.cs", 25, 12); + AddTestTask("High", TaskErrorCategory.Error, "Cross-Site Scripting (XSS) found", "TestFile.cs", 42, 8); + AddTestTask("Medium", TaskErrorCategory.Warning, "Hardcoded password detected", "TestFile.cs", 58, 15); + AddTestTask("Low", TaskErrorCategory.Message, "Weak encryption algorithm used", "TestFile.cs", 75, 20); + + // Test 2: Add tasks from different files (test grouping) + AddTestTask("High", TaskErrorCategory.Error, "Path Traversal vulnerability", "AnotherFile.cs", 15, 3); + AddTestTask("Medium", TaskErrorCategory.Warning, "Insecure deserialization", "AnotherFile.cs", 30, 7); + + // Show the Error List window + var dte = Microsoft.VisualStudio.Shell.ServiceProvider.GlobalProvider.GetService(typeof(DTE)) as DTE; + if (dte != null) + { + dte.ExecuteCommand("View.ErrorList"); + } + + VsShellUtilities.ShowMessageBox( + this.package, + $"โœ… Error List Customization Test Complete!\n\n" + + $"Added 7 test items to Error List:\n" + + $"โ€ข 3 Errors (Malicious, Critical, High)\n" + + $"โ€ข 2 Warnings (Medium)\n" + + $"โ€ข 2 Messages (Low)\n\n" + + $"TESTING:\n" + + $"โ“ Can we add custom icons per severity?\n" + + $"โ“ Can we customize the appearance?\n" + + $"โ“ Can we add right-click context menu?\n" + + $"โ“ Does grouping by file work?\n\n" + + $"Check the Error List window to see the results.", + "Test Error List Customization - POC", + OLEMSGICON.OLEMSGICON_INFO, + OLEMSGBUTTON.OLEMSGBUTTON_OK, + OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"DevAssist: Error in Error List test: {ex.Message}\n{ex.StackTrace}"); + VsShellUtilities.ShowMessageBox( + this.package, + $"Error: {ex.Message}", + "Test Error List Customization", + OLEMSGICON.OLEMSGICON_CRITICAL, + OLEMSGBUTTON.OLEMSGBUTTON_OK, + OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); + } + } + + private void AddTestTask(string severity, TaskErrorCategory category, string description, string file, int line, int column) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + var task = new ErrorTask + { + Category = TaskCategory.CodeSense, + ErrorCategory = category, + Text = $"[{severity}] {description} (DevAssist)", + Document = file, + Line = line - 1, + Column = column, + HelpKeyword = $"CX-{severity}-{Guid.NewGuid().ToString().Substring(0, 8)}" + }; + + // Test: Can we add custom properties? + // Note: ErrorTask doesn't have a way to add custom icons or metadata directly + + // Add navigation handler + task.Navigate += (s, e) => + { + ThreadHelper.ThrowIfNotOnUIThread(); + VsShellUtilities.ShowMessageBox( + this.package, + $"Navigating to:\n{file}\nLine: {line}, Column: {column}\nSeverity: {severity}", + "Navigation Test", + OLEMSGICON.OLEMSGICON_INFO, + OLEMSGBUTTON.OLEMSGBUTTON_OK, + OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); + }; + + _errorListProvider.Tasks.Add(task); + } + } +} + diff --git a/ast-visual-studio-extension/CxExtension/CxWindowControl.xaml b/ast-visual-studio-extension/CxExtension/CxWindowControl.xaml index c907b0f9..95824177 100644 --- a/ast-visual-studio-extension/CxExtension/CxWindowControl.xaml +++ b/ast-visual-studio-extension/CxExtension/CxWindowControl.xaml @@ -12,6 +12,7 @@ xmlns:vsfx="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.15.0" xmlns:vs="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0" xmlns:cxextension="clr-namespace:ast_visual_studio_extension.CxExtension" d:DataContext="{d:DesignInstance Type=cxextension:CxWindowControl}" + xmlns:devassist="clr-namespace:ast_visual_studio_extension.CxExtension.DevAssist.UI.FindingsWindow" local:VsTheme.UseVsTheme="True" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="1308" @@ -385,20 +386,74 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml.cs b/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml.cs new file mode 100644 index 00000000..114e2783 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml.cs @@ -0,0 +1,515 @@ +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using Microsoft.VisualStudio.Shell; +using EnvDTE; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.UI.FindingsWindow +{ + /// + /// Interaction logic for DevAssistFindingsControl.xaml + /// + public partial class DevAssistFindingsControl : UserControl, INotifyPropertyChanged + { + private ObservableCollection _fileNodes; + private ObservableCollection _allFileNodes; // Store unfiltered data + private string _statusBarText; + private bool _isLoading; + + public event PropertyChangedEventHandler PropertyChanged; + + public ObservableCollection FileNodes + { + get => _fileNodes; + set + { + _fileNodes = value; + OnPropertyChanged(nameof(FileNodes)); + OnPropertyChanged(nameof(HasFindings)); + UpdateStatusBar(); + } + } + + public bool HasFindings => FileNodes == null || FileNodes.Count == 0; + + public string StatusBarText + { + get => _statusBarText; + set + { + _statusBarText = value; + OnPropertyChanged(nameof(StatusBarText)); + } + } + + public bool IsLoading + { + get => _isLoading; + set + { + _isLoading = value; + OnPropertyChanged(nameof(IsLoading)); + } + } + + public DevAssistFindingsControl() + { + InitializeComponent(); + FileNodes = new ObservableCollection(); + _allFileNodes = new ObservableCollection(); + DataContext = this; + + // Load filter icons after InitializeComponent so named controls are available + Loaded += (s, e) => LoadFilterIcons(); + } + + /// + /// Load severity icons for filter buttons + /// + private void LoadFilterIcons() + { + try + { + string themeFolder = IsDarkTheme() ? "Dark" : "Light"; + + // Set icons directly to Image controls + CriticalFilterIcon.Source = LoadIcon(themeFolder, "critical.png"); + HighFilterIcon.Source = LoadIcon(themeFolder, "high.png"); + MediumFilterIcon.Source = LoadIcon(themeFolder, "medium.png"); + LowFilterIcon.Source = LoadIcon(themeFolder, "low.png"); + MaliciousFilterIcon.Source = LoadIcon(themeFolder, "malicious.png"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Error loading filter icons: {ex.Message}"); + } + } + + /// + /// Load icon from resources + /// + private ImageSource LoadIcon(string themeFolder, string iconName) + { + try + { + string iconPath = $"pack://application:,,,/ast-visual-studio-extension;component/CxExtension/Resources/DevAssist/Icons/{themeFolder}/{iconName}"; + var bitmap = new BitmapImage(); + bitmap.BeginInit(); + bitmap.UriSource = new Uri(iconPath, UriKind.Absolute); + bitmap.CacheOption = BitmapCacheOption.OnLoad; + bitmap.EndInit(); + bitmap.Freeze(); + return bitmap; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Error loading icon {iconName}: {ex.Message}"); + return null; + } + } + + /// + /// Get file type icon based on file extension (for future enhancement) + /// Currently returns generic document icon + /// + public static ImageSource GetFileTypeIcon(string fileName) + { + if (string.IsNullOrEmpty(fileName)) + return null; + + // For now, use generic document icon + // In future, can add specific icons for .go, .json, .xml, .cs, etc. + try + { + string iconPath = "pack://application:,,,/ast-visual-studio-extension;component/CxExtension/Resources/DevAssist/Icons/document.png"; + var bitmap = new BitmapImage(); + bitmap.BeginInit(); + bitmap.UriSource = new Uri(iconPath, UriKind.Absolute); + bitmap.CacheOption = BitmapCacheOption.OnLoad; + bitmap.EndInit(); + bitmap.Freeze(); + return bitmap; + } + catch + { + return null; + } + } + + /// + /// Detect if dark theme is active + /// + private bool IsDarkTheme() + { + try + { + var color = Microsoft.VisualStudio.PlatformUI.VSColorTheme.GetThemedColor( + Microsoft.VisualStudio.PlatformUI.EnvironmentColors.ToolWindowBackgroundColorKey); + int brightness = (int)Math.Sqrt( + color.R * color.R * 0.299 + + color.G * color.G * 0.587 + + color.B * color.B * 0.114); + return brightness < 128; + } + catch + { + return true; + } + } + + protected void OnPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + /// + /// Update status bar with vulnerability count + /// + private void UpdateStatusBar() + { + if (FileNodes == null || FileNodes.Count == 0) + { + StatusBarText = "No vulnerabilities found"; + return; + } + + int totalVulnerabilities = FileNodes.Sum(f => f.Vulnerabilities?.Count ?? 0); + int fileCount = FileNodes.Count; + + if (totalVulnerabilities == 0) + { + StatusBarText = "No vulnerabilities found"; + } + else if (totalVulnerabilities == 1) + { + StatusBarText = $"1 vulnerability found in {fileCount} file{(fileCount == 1 ? "" : "s")}"; + } + else + { + StatusBarText = $"{totalVulnerabilities} vulnerabilities found in {fileCount} file{(fileCount == 1 ? "" : "s")}"; + } + } + + /// + /// Handle double-click on tree item to navigate to file location + /// + private void TreeViewItem_MouseDoubleClick(object sender, MouseButtonEventArgs e) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + var item = sender as TreeViewItem; + if (item?.DataContext is VulnerabilityNode vulnerability) + { + NavigateToVulnerability(vulnerability); + e.Handled = true; + } + } + + /// + /// Navigate to vulnerability location in code + /// + private void NavigateToVulnerability(VulnerabilityNode vulnerability) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + try + { + var dte = Microsoft.VisualStudio.Shell.ServiceProvider.GlobalProvider.GetService(typeof(DTE)) as DTE; + if (dte != null && !string.IsNullOrEmpty(vulnerability.FilePath)) + { + // Open the file + var window = dte.ItemOperations.OpenFile(vulnerability.FilePath); + + // Navigate to line and column + if (dte.ActiveDocument != null) + { + var textDocument = (TextDocument)dte.ActiveDocument.Object("TextDocument"); + textDocument.Selection.MoveToLineAndOffset(vulnerability.Line, vulnerability.Column); + textDocument.Selection.SelectLine(); + } + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Error navigating to vulnerability: {ex.Message}"); + } + } + + #region Context Menu Handlers + + private void FixWithCxOneAssist_Click(object sender, RoutedEventArgs e) + { + var vulnerability = GetSelectedVulnerability(); + if (vulnerability != null) + { + MessageBox.Show($"Fix with CxOne Assist:\n{vulnerability.DisplayText}", + "Checkmarx DevAssist", MessageBoxButton.OK, MessageBoxImage.Information); + // TODO: Implement actual fix logic + } + } + + private void ViewDetails_Click(object sender, RoutedEventArgs e) + { + var vulnerability = GetSelectedVulnerability(); + if (vulnerability != null) + { + MessageBox.Show($"View Details:\n{vulnerability.DisplayText}\n\nSeverity: {vulnerability.Severity}\nFile: {vulnerability.FilePath}\nLine: {vulnerability.Line}, Column: {vulnerability.Column}", + "Checkmarx DevAssist", MessageBoxButton.OK, MessageBoxImage.Information); + // TODO: Implement actual details view + } + } + + private void Ignore_Click(object sender, RoutedEventArgs e) + { + var vulnerability = GetSelectedVulnerability(); + if (vulnerability != null) + { + var result = MessageBox.Show($"Ignore this vulnerability?\n{vulnerability.DisplayText}", + "Checkmarx DevAssist", MessageBoxButton.YesNo, MessageBoxImage.Question); + if (result == MessageBoxResult.Yes) + { + // TODO: Implement ignore logic + MessageBox.Show("Vulnerability ignored.", "Checkmarx DevAssist", MessageBoxButton.OK, MessageBoxImage.Information); + } + } + } + + private void IgnoreAll_Click(object sender, RoutedEventArgs e) + { + var vulnerability = GetSelectedVulnerability(); + if (vulnerability != null) + { + var result = MessageBox.Show($"Ignore all vulnerabilities of this type?\n{vulnerability.Description}", + "Checkmarx DevAssist", MessageBoxButton.YesNo, MessageBoxImage.Warning); + if (result == MessageBoxResult.Yes) + { + // TODO: Implement ignore all logic + MessageBox.Show("All vulnerabilities of this type ignored.", "Checkmarx DevAssist", MessageBoxButton.OK, MessageBoxImage.Information); + } + } + } + + private VulnerabilityNode GetSelectedVulnerability() + { + return FindingsTreeView.SelectedItem as VulnerabilityNode; + } + + #endregion + + #region Severity Filter Handlers + + /// + /// Handle severity filter button clicks + /// + private void SeverityFilter_Click(object sender, RoutedEventArgs e) + { + ApplyFilters(); + } + + /// + /// Apply severity filters to the tree view + /// + private void ApplyFilters() + { + if (_allFileNodes == null || _allFileNodes.Count == 0) + return; + + // Get active filters + var activeFilters = new System.Collections.Generic.List(); + + if (CriticalFilterButton.IsChecked == true) + activeFilters.Add("Critical"); + if (HighFilterButton.IsChecked == true) + activeFilters.Add("High"); + if (MediumFilterButton.IsChecked == true) + activeFilters.Add("Medium"); + if (LowFilterButton.IsChecked == true) + activeFilters.Add("Low"); + if (MaliciousFilterButton.IsChecked == true) + activeFilters.Add("Malicious"); + + // If no filters are active, show all + if (activeFilters.Count == 0) + { + FileNodes = new ObservableCollection(_allFileNodes); + return; + } + + // Filter files and vulnerabilities + var filteredFiles = new ObservableCollection(); + + foreach (var file in _allFileNodes) + { + var filteredVulns = file.Vulnerabilities + .Where(v => activeFilters.Contains(v.Severity, StringComparer.OrdinalIgnoreCase)) + .ToList(); + + if (filteredVulns.Count > 0) + { + var filteredFile = new FileNode + { + FileName = file.FileName, + FilePath = file.FilePath, + FileIcon = file.FileIcon + }; + + foreach (var vuln in filteredVulns) + { + filteredFile.Vulnerabilities.Add(vuln); + } + + filteredFiles.Add(filteredFile); + } + } + + FileNodes = filteredFiles; + } + + /// + /// Store all file nodes for filtering (called from ShowFindingsWindowCommand) + /// + public void SetAllFileNodes(ObservableCollection allNodes) + { + _allFileNodes = allNodes; + FileNodes = new ObservableCollection(allNodes); + } + + #endregion + + #region Toolbar Button Handlers + + /// + /// Expand all tree view items + /// + private void ExpandAll_Click(object sender, RoutedEventArgs e) + { + ExpandCollapseAll(FindingsTreeView, true); + } + + /// + /// Collapse all tree view items + /// + private void CollapseAll_Click(object sender, RoutedEventArgs e) + { + ExpandCollapseAll(FindingsTreeView, false); + } + + /// + /// Open settings (placeholder for now) + /// + private void Settings_Click(object sender, RoutedEventArgs e) + { + MessageBox.Show("Settings functionality coming soon!", "Settings", MessageBoxButton.OK, MessageBoxImage.Information); + } + + /// + /// Recursively expand or collapse all TreeView items + /// + private void ExpandCollapseAll(ItemsControl items, bool expand) + { + if (items == null) return; + + foreach (object obj in items.Items) + { + ItemsControl childControl = items.ItemContainerGenerator.ContainerFromItem(obj) as ItemsControl; + if (childControl != null) + { + if (childControl is TreeViewItem treeItem) + { + treeItem.IsExpanded = expand; + ExpandCollapseAll(treeItem, expand); + } + } + } + } + + #endregion + + #region Context Menu Handlers + + /// + /// Copy selected item details to clipboard + /// + private void CopyMenuItem_Click(object sender, RoutedEventArgs e) + { + var selectedItem = FindingsTreeView.SelectedItem; + if (selectedItem == null) return; + + string textToCopy = ""; + if (selectedItem is FileNode fileNode) + { + textToCopy = $"{fileNode.FileName} - {fileNode.FilePath}"; + } + else if (selectedItem is VulnerabilityNode vulnNode) + { + textToCopy = vulnNode.DisplayText; + } + + if (!string.IsNullOrEmpty(textToCopy)) + { + Clipboard.SetText(textToCopy); + } + } + + /// + /// Navigate to code location + /// + private void NavigateMenuItem_Click(object sender, RoutedEventArgs e) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + var selectedItem = FindingsTreeView.SelectedItem; + if (selectedItem is VulnerabilityNode vulnNode) + { + NavigateToVulnerability(vulnNode); + } + else if (selectedItem is FileNode fileNode && !string.IsNullOrEmpty(fileNode.FilePath)) + { + try + { + var dte = Microsoft.VisualStudio.Shell.ServiceProvider.GlobalProvider.GetService(typeof(DTE)) as DTE; + if (dte != null) + { + dte.ItemOperations.OpenFile(fileNode.FilePath); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Error navigating to file: {ex.Message}"); + } + } + } + + /// + /// Ignore selected finding (placeholder for now) + /// + private void IgnoreMenuItem_Click(object sender, RoutedEventArgs e) + { + var selectedItem = FindingsTreeView.SelectedItem; + if (selectedItem == null) return; + + string itemName = ""; + if (selectedItem is FileNode fileNode) + { + itemName = fileNode.FileName; + } + else if (selectedItem is VulnerabilityNode vulnNode) + { + itemName = vulnNode.DisplayText; + } + + MessageBox.Show($"Ignore functionality coming soon!\n\nSelected: {itemName}", + "Ignore Finding", MessageBoxButton.OK, MessageBoxImage.Information); + } + + #endregion + } +} + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsWindow.cs b/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsWindow.cs new file mode 100644 index 00000000..b5b82a92 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsWindow.cs @@ -0,0 +1,40 @@ +using System; +using System.Runtime.InteropServices; +using Microsoft.VisualStudio.Shell; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.UI.FindingsWindow +{ + /// + /// This class implements the tool window exposed by this package and hosts a user control. + /// + /// + /// In Visual Studio tool windows are composed of a frame (implemented by the shell) and a pane, + /// usually implemented by the package implementer. + /// + /// This class derives from the ToolWindowPane class provided from the MPF in order to use its + /// implementation of the IVsUIElementPane interface. + /// + /// + [Guid("8F3E8B6A-1234-4567-89AB-CDEF01234567")] + public class DevAssistFindingsWindow : ToolWindowPane + { + /// + /// Initializes a new instance of the class. + /// + public DevAssistFindingsWindow() : base(null) + { + this.Caption = "Checkmarx Findings"; + + // This is the user control hosted by the tool window; Note that, even if this class implements IDisposable, + // we are not calling Dispose on this object. This is because ToolWindowPane calls Dispose on + // the object returned by the Content property. + this.Content = new DevAssistFindingsControl(); + } + + /// + /// Get the control hosted in this tool window + /// + public DevAssistFindingsControl FindingsControl => this.Content as DevAssistFindingsControl; + } +} + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/FindingsTreeNode.cs b/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/FindingsTreeNode.cs new file mode 100644 index 00000000..e9bf75fb --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/FindingsTreeNode.cs @@ -0,0 +1,181 @@ +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Windows.Media; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.UI.FindingsWindow +{ + /// + /// Base class for tree nodes in the Findings window + /// + public abstract class FindingsTreeNode : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + protected void OnPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } + + /// + /// Represents a file node with vulnerability count badges + /// + public class FileNode : FindingsTreeNode + { + private string _fileName; + private string _filePath; + private ImageSource _fileIcon; + private ObservableCollection _severityCounts; + private ObservableCollection _vulnerabilities; + + public string FileName + { + get => _fileName; + set { _fileName = value; OnPropertyChanged(nameof(FileName)); } + } + + public string FilePath + { + get => _filePath; + set { _filePath = value; OnPropertyChanged(nameof(FilePath)); } + } + + public ImageSource FileIcon + { + get => _fileIcon; + set { _fileIcon = value; OnPropertyChanged(nameof(FileIcon)); } + } + + public ObservableCollection SeverityCounts + { + get => _severityCounts; + set { _severityCounts = value; OnPropertyChanged(nameof(SeverityCounts)); } + } + + public ObservableCollection Vulnerabilities + { + get => _vulnerabilities; + set { _vulnerabilities = value; OnPropertyChanged(nameof(Vulnerabilities)); } + } + + public FileNode() + { + SeverityCounts = new ObservableCollection(); + Vulnerabilities = new ObservableCollection(); + } + } + + /// + /// Represents a severity count badge (e.g., "๐Ÿ”ด 2") + /// + public class SeverityCount : INotifyPropertyChanged + { + private string _severity; + private int _count; + private ImageSource _icon; + + public string Severity + { + get => _severity; + set { _severity = value; OnPropertyChanged(nameof(Severity)); } + } + + public int Count + { + get => _count; + set { _count = value; OnPropertyChanged(nameof(Count)); } + } + + public ImageSource Icon + { + get => _icon; + set { _icon = value; OnPropertyChanged(nameof(Icon)); } + } + + public event PropertyChangedEventHandler PropertyChanged; + + protected void OnPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } + + /// + /// Represents a vulnerability node with severity icon and details + /// + public class VulnerabilityNode : FindingsTreeNode + { + private string _severity; + private ImageSource _severityIcon; + private string _description; + private string _packageName; + private string _packageVersion; + private int _line; + private int _column; + private string _filePath; + + public string Severity + { + get => _severity; + set { _severity = value; OnPropertyChanged(nameof(Severity)); } + } + + public ImageSource SeverityIcon + { + get => _severityIcon; + set { _severityIcon = value; OnPropertyChanged(nameof(SeverityIcon)); } + } + + public string Description + { + get => _description; + set { _description = value; OnPropertyChanged(nameof(Description)); } + } + + public string PackageName + { + get => _packageName; + set { _packageName = value; OnPropertyChanged(nameof(PackageName)); } + } + + public string PackageVersion + { + get => _packageVersion; + set { _packageVersion = value; OnPropertyChanged(nameof(PackageVersion)); } + } + + public int Line + { + get => _line; + set { _line = value; OnPropertyChanged(nameof(Line)); } + } + + public int Column + { + get => _column; + set { _column = value; OnPropertyChanged(nameof(Column)); } + } + + public string FilePath + { + get => _filePath; + set { _filePath = value; OnPropertyChanged(nameof(FilePath)); } + } + + /// + /// Formatted display text (e.g., "High-risk package: helm.sh/helm/v3@v3.18.2 Checkmarx One Assist [Ln 38, Col 1]") + /// + public string DisplayText + { + get + { + if (!string.IsNullOrEmpty(PackageName)) + { + return $"{Severity}-risk package: {PackageName}@{PackageVersion} Checkmarx One Assist [Ln {Line}, Col {Column}]"; + } + return $"{Description} Checkmarx One Assist [Ln {Line}, Col {Column}]"; + } + } + } +} + diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/container.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/container.png new file mode 100644 index 0000000000000000000000000000000000000000..71980914b25807298596df3b00b47671b73275f8 GIT binary patch literal 421 zcmV;W0b2fvP) z!$1(7=MM)?TS7X(mH;V8D7b@AK}ZL5KnYelNC&tBe6fj7i#rGv;3E=vhgl^Spcu!B zB@TI#<@xM;nztG)(NQ2T^fFuNq|aGr-+?UC0e}e{Ol&+zuqetId^|Cc0#W7o(0Q)! z4-yoy2LoP$sY1;m)f`EA4G;0N^#0=EXJRcGhm_}HCh46_R(&$hVW_fw*Rhi#7S@QF zlG^aX*00B5x+AzM%9z2Ob~=!bFN5fps7h&3vYEXijlC6{1tMf21%w>PU0r($5SqY`JRuYbE?iy0Y-NbSqXWO1?<9cmH zfTr&sn11JYZ1zk3N_uYq?VT{CLt(dKXTqB2>{Gy|-AWfuPpH>RbfowK$ZvTT6|FEC P00000NkvXXu0mjf0b{7+ literal 0 HcmV?d00001 diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/critical.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/critical.png new file mode 100644 index 0000000000000000000000000000000000000000..98aff91b40aba5c5a068ecc026ca2811803042f1 GIT binary patch literal 643 zcmV-}0(||6P)ZYl-cveKGc9)am8S9DJ%Q>P)Z}bPY^{zAt*_`dC8QPR!Wo z`Mw*9(yofnyqbR(=Vr`+;tC7la*cCtRaoUbkOZx90U9(B2JkV4SwO8jg>XaF%;CCH zzGpgSL>5q2Ne)}N9+W!dge%J`#?CQ82j1fkYH>YNl)7HJHM+7iEEi~?{u3XBFkf!v d=;ncLO_!8R+2s~L`LF;0002ovPDHLkV1iguALal6 literal 0 HcmV?d00001 diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/cxone_assist.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/cxone_assist.png new file mode 100644 index 0000000000000000000000000000000000000000..2962c7a94aacefaab42d95e9213279c262ef6876 GIT binary patch literal 1905 zcmV-%2afoOP)&Txy&4QUFCvg%l%E+X1>`SulM%WKOl;I$#{2W-n_qg^JZr6nfJe3UlcJPTd@-+ z5hDuL;*T%*Z>$(?(|^|WOX6`%jEldD=LT3C+8l05Q^`nT9W8@Oz;j~^It;3gZbFLS zjYp#W80K)vOENh<4xG1NB+#?XF4@?Ia0 z6Kt!M7giqpyfy!kQOB{3^RG^Q+j{GX)KEhST)*;Y;^2iDH2$)0G|*5xO$}Ax$6r5q zuHomoydu(Y0kno1s)FBS)o~J@TWYAG5_o@Uj6}2cl9tO-0Tgc%xLU^Y?Q%u{+Ijl_ z7oe>!$-e~D{ZzIjjjM7BX#8=`Ho}R&1n#kv2?K|@udf~AcC~&xLg}2L^UTQzdH!p_ zL}=;i>#~*=eSdhCizR|3IlV36*h`}ppY|^l;~1Nq|I;+mD|Ycf5O&WNbf*0Qf< zL9c8J;te>TA2Tdkl&vH>L=C$l|%suVfrSQuf3!^*^VzIr->4d!&6VhTI@LR-(F*ewa_iHgp&pZWxxenfXB z7a5-+YLF(j-ka<{G+7Zqra?h_kGzWXpQjGceU|z!Q2#k81}u_Q(j5yj^BLQ4E$W|U z6inN5G%*efXOXrT$Gi|9(9X>v!1+tF;EV&}<9y%C^;!18Ai-GUQVn{1?nZoO_}=mh z!nIAa4SgLash`9}CxEpuG@KPt22muQwAg{+INQOOM_eUuk1Gfek9U&RA&5RgwLHuM5JA+{RC_4Wk60OT@#-w^&bsIEXH=2ex$BbE(XpSaUDoRyFK!Ob6+b=?x0G zRe*Oz#5-L}jt8%c--5w! zPlh{0Sn1f}+?mK0Ut>WBXR6!LAszU(!lLPuf6)Je6w8B&MRaXNTddorxFy}fae_91 z-DZZgPv`W5^Ig?`Y=Jn-T+HDdy_Dc5K)qEpr36A)hcY9KfWs6N>JT3u`#{I+RG`1g zac}}F*})9T;?R1N^;NE}m;`We{RN+aR0BkTyEGcc?UvxS$albV-#x6*cttY&VL-UGSHoer?m#BTgtBqR;eSk?XHNGxXRU&9CmK77Mitt_ zY*D0-u@!%EsG){KK#!^NlS2(PMBqOdH#F}2_ReSDPe~0mjDYJ?k0vzGOyF?bw13N| zFPrAzz2zT7e3qt9F-%hweX5~ucD$-b^J; z<)nQiOYQVFvN5079*6iGO{X_klHdHOFtPQAR00000NkvXXu0mjf5qg6E literal 0 HcmV?d00001 diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/high.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/high.png new file mode 100644 index 0000000000000000000000000000000000000000..8251b6406d526653abc123302e5f92b35f3159d4 GIT binary patch literal 511 zcmV~E4sM=A-4QFlJmKoi^wcpP&s-K=tB^H`hyAZq~c1eifts&B;8ic26Mbqt4+ML;fYu(5c1YRw)8-Or=o8Q7p}#=!yU zT~fp=B%aGuKVn^}9u8r1K&_nRu5~BXi<7e&an~GBSGi-B{s{HK$0fZc&PdA^XO)KB zPCX&S(~GQ002ovPDHLkV1lP! B(lh`7 literal 0 HcmV?d00001 diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/low.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/low.png new file mode 100644 index 0000000000000000000000000000000000000000..545c17659e340532fa71283f311f76c4487ffa18 GIT binary patch literal 508 zcmV~CjxGlQ@~orY;3;*GLLNTt0a*$RrsU=Wl!#PBa5 z=@8U%1zBGTY(AT*jTjg_ND;!mKse>83)TokYF(|jBvvE zJ2HUXNXu%8~>X*Mf4(QnC5GwEgZS>NL);5UD)R y0lpfns?26#fj}4fVrIZmjTg(@KvF@Vf735)Sd|Lwsb`M>0000=G`P)1K8P z{uq9?*24aFP7*O9*Tvi1Ed0_AUi}(|{^HHv6w(u8E!wU+lUCWRcs(~yak|dWKcYLA z)^0mT7=ZbFT&OXxPqo{rQ66RUUD1TB99xg>FvOAG`#ZCCm z9)hC^8pR?X#Z}a#68Q*W9A!80*)Dxm{!GWfI=7z3tw+7K9)#nm*W7w;=hpkkJ(=R( z)K$BOVJRFIAv9G*I;&(b$A%$(2Jb^E3a}HB>`b#+!i&@$ZrCSL1={Fy00000NkvXX Hu0mjfWJLKl literal 0 HcmV?d00001 diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/medium.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/medium.png new file mode 100644 index 0000000000000000000000000000000000000000..f5e8825036492e74cbcd073e9579d87b366a563b GIT binary patch literal 537 zcmV+!0_OdRP)WK zvn*yrK$$g1FRA?$_gfFhlZ=5@w!_*6yV6BFl{Bld2a`h?0|(jJf3(9cyKg|}h4zll z@nF*c93_@a`6DTHW?X96@ixPB(QSi#alxjn$&ku+S>NKr2_2h&if+qT$vo_0`rd-~ zQ7_Y8rzKPRdqF0$6E!A}gL3G$4Ddd_&-1B>s&JBrqR9^J;Rr6~l|U5(wx zq(PE4t(_c50YjHX?BjpXzMdO?lQL|OI)_91*_;V*db!%!G%ObwLLGW!zv<)!f%%Cu bf|jOVP|SZ-9dy9h00000NkvXXu0mjfM+okh literal 0 HcmV?d00001 diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/package.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/package.png new file mode 100644 index 0000000000000000000000000000000000000000..899a77f10331c333746e57b90eb7949bef4f3058 GIT binary patch literal 456 zcmV;(0XP1MP){%rS15lf4a=)&4U yIbR2dJ`Odgw4>Z%Qk;n35hLpzV`lBaztubJZi*VYxL}3=0000jJes1e~7<#1=G8U_B^J=Ypp3h)E)P>d)DnY_! zj_4{#75$2;IrRGMZmwX{V|&6K@b84ZhI4}#VxX^I4uVD)mp4=9XFe)@j1k&`a6|_( z`m#iC*Tfk3UK--oC38+)5x)FJh0%LVuMu8~8rl|zzSW;-Pw@fQNnFSaugGcu0000< KMNUMnLSTYeJgL_J literal 0 HcmV?d00001 diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/critical.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/critical.png new file mode 100644 index 0000000000000000000000000000000000000000..5ebf58ac3f3af2aeb98b38d6c56c458d1662eb5f GIT binary patch literal 638 zcmV-^0)hRBP)XTn1RqViC^!YqwRQNl$k^pFiJscQ%lM9c0}GY>&@(oGGQ{i{pqg z$gGg-j^0t5eS{z8(5oFuxCJR~v|Rxu1*{SUAJQq{7%0G}{8|XLt)S}h3pC*_>~%6r zH~~VSdK;aJ6}?SKoDddNo^3WYQA*gQyV~#6zab2Q6k@P$F)>S4Uf^cy7p{H&q|f!O zuQ>N~VIolo@pni_lE(M@HsxWphJUZ9)tQ7BVOH&H+Y}C_fk3}JORHl=`mMT|kk)(? zoxvP5;T-mTVe~PU$r58+SXstZ8cs46K?Qfv)kO{0MnQ1EjjvnPclyE0?>7)s-@?jE zTwGn&mGhts!P_v=gk7O8MkUXm$}83Nd==w>{JqkBW#;X|SsYTwjz`~f4{eVs9ova) z;fA)ovC1O!A&T5K_N_QrL=ez$s*@gIqNB)Yhu31V5kf`|4*w*4Ob@?}v+K#g2q9NV z<(H=dhxw~m?QJ<C5Akf_^ z(4N*x;jVbs=FWc}`ZfEN#RnM2wl?i>oXj`ktO791Q3}5mp93X3?acl89^eP8LzA4D)c#@^Ei(GLwdSa-auK}p5ZO@ z#1`S)s<|6lhJPUZRg8wv!xNxEG?Afy;ss$Jv9L$_MckT@C zC*yc^L_pec`kxBWR=m_l0@3}0Y)Kle`m+&uszW#3s_=F*i_`RU(wIw^E-_%{IyyS? zc$~$38PC^jKk-KuJfJmk?AWnc^$7tT0ezQYt9e|DcwRcn#bN=vPk*Q_F~5)gY_|BI zPqyBe@P$$BdcNjsZR5#jw0X(fWV_M0ot>Q-+$y`fyV>>i^(+mD=gIh-M%f~1B@v&b zr0sWUxU>>@W*TM2dUWbN6S!#L2nKS2y5pL_^IZh@oTcy5JE;CHAKXUZj!ig!tW_Gq zGs441C0x0uPoGv)16#=WEQ2gAuMHM2_xE0~_(i)PXDC54g{y$aH!#LETzMGgS!`TC zZVM1*3c{2v-loGxj~=aIOaXI?pfvy#h58kAZpnORrb%2C2I;$Z?`BDdh62rbAxGhF za}HMm>45Ic@bK^z(9LoVl*|A>jpr?l?MM4H+N)@fk|)|r4<9~U5xP0xdW7~S=*|nC z8S^&fp+2BFh4vQuZ=OGYehdrZ8u;FD^@pBQpq(L&m|d2u)oT99lPAR{`hSJnRkkU! z)j#eJ;Xm&C{tE6{$T8*gCys1@h6xcf`hdk+Y1x zCHm0v>iO(1zK&n^!w*IdkQj`yfC<;Ravnc^%xjeb6UQ{{?&H9n!c~qdOJ*)Zj444V zD$DG2-R`x(kI_lew(}9?`$dxa~>f(Id=P0|- zr9sw+7ajw#%=nTgxyRZ7qY_+!3dWj35asB22;VRL1E3ww zxqttDnaVwQ@W49g6s}pwx8&fI8^EAdLH{$`7Jf=HCb!-!`4SI(dg5|gzPK@1WQujZ|dMEOIL#a)`4_x#nGQZcri{(wkg^poD14pl3~KF2R8=!40R$O+CcDO*1WTb+-h1CT~Gk*9P+5tL3BJL&x9V1hlrXlPtMz9-&PQ$a{~guOp-y)ApJ1R z2F@Qvtb7dxtb;b7)HoaR)|PjadWrFibiQPbgTQGzi2OjG8MWb|R95MaD& zC{hQW)$BoB&K$Zs(LVyndy(0d3)QDWM&>=;Ule z*-q0=q``y$T#kMW)5upcLn>cd`x*YT#blRgu~ahc9POQ$>U^{Q`w-%fw4N}*-I z>-cj9j-UR-nZjYf57J-9Q#cTqUpIX(2>$!#w|{$83Mn*!?(TctI2w2JaNLgg`9-xI zFMWNxqHLLD`14d%h2@M&$`8W=L>{AUd8jrenQpmhV9HEw>8Pi)ltoJEr;PL5vQtyS z$dXEJ%PwonP6JaiDFnRzQdE|Yy5mjBWKG$qlxKQUVrok;_j#JK*huH7Y}Au7%)lu% zjY?dFx0HKY-2<_YE#2f*)l%vi(a_53Ds-$(0$aW*C7*$m%0?xo+ARgGE$glEZdm6D zLY|a`;=hkJhkK^_Q`J^Pcr|6C%BVoAc<=2+JDLQ3{^#xIFa8HbNRdU(0fga_DD7M3K( zxk;P;NnA?Brzm#p(=VUxyM!=DT&ejWx&>mn_?SK~d$xG8f`EoZ_){|-Fx}C|MYj;h zZrw#W^r6TF@xC}Zl2H$K?^jG8I}>FEzr73eY#jy#H*%Jg#jyN zpt6=fX4(R&)NU{{bK(P!=fL4^Hnz8sjG4J-++D!+>`^UtfRnTFanZpY2K;@ayYZ;bJGQ$UX%X84sf5F zuEDS*v$SDYYt$;1YDtv=i68J+QQR4IM=$ZDp5yR6ciHn)3b#pLX^cCReKece#-cJc zoWdr6$&(`=i3M_!Ho0S3&j4fJpN0m}x2ZrRm^#{d8T07*qoM6N<$f=gr4(f|Me literal 0 HcmV?d00001 diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/low.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/low.png new file mode 100644 index 0000000000000000000000000000000000000000..d0f4bb3b04bd9bb3dd881caa376b83f0b181c270 GIT binary patch literal 481 zcmV<70UrK|P)0)GM}Rjdyg@rN(5`H3NH7EjSkotHDhg~6m}o^} zE^$Lznx-v*Z?PSpzg+B`31d(Xn-&Z{iw1$(qm?TsE1t4NRF)8Ha6;8YT&LA*d!$e` ztpW_g0&_uzYhc=y)rp>{!p;3_)Hhe>aj?HU7(mpByM02w5UG_Wtcftj z+E^SG22$U#`#Zm|p66zp(w)9)I$We;v~5YHP|^0FP{p(;oW!yLVMMoEL3oQyj@w&< zRAH1QaS4-1l^kM+OVOHs$H@4{?PHlOr%dUsk<5O|GKG=!dB>C=RxaM~=9a_C5d9lp XrxJ^|fU6jY00000NkvXXu0mjfek8%) literal 0 HcmV?d00001 diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/malicious.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/malicious.png new file mode 100644 index 0000000000000000000000000000000000000000..6e145e3617bfc2fe3ff859ca33efb614eed6b96f GIT binary patch literal 569 zcmV-90>=G`P)1K8P z{uq9?*24aFP7*O9*Tvi1Ed0_AUi}(|{^HHv6w(u8E!wU+lUCWRcs(~yak|dWKcYLA z)^0mT7=ZbFT&OXxPqo{rQ66RUUD1TB99xg>FvOAG`#ZCCm z9)hC^8pR?X#Z}a#68Q*W9A!80*)Dxm{!GWfI=7z3tw+7K9)#nm*W7w;=hpkkJ(=R( z)K$BOVJRFIAv9G*I;&(b$A%$(2Jb^E3a}HB>`b#+!i&@$ZrCSL1={Fy00000NkvXX Hu0mjfWJLKl literal 0 HcmV?d00001 diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/medium.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/medium.png new file mode 100644 index 0000000000000000000000000000000000000000..ecfb6fa47dcc91af86b5f42120310f955da11fd6 GIT binary patch literal 491 zcmViEz zPpP7F@&nkF6F4P%q~}cjfCL5vxy33f5fXIdHbW1q%E1o14Q+6f_&u_Z0{AS{*w#RJ zU5q3vR3pbD;Obde&r@M?e(yphRd_Q4r5M)t+wrasi;6BV*QRh=N1b0sF_TDY4&dTU#Mb@E(Tv zt4iEUm;!8q0ZeC7FU}3R6AG}dvcwsdgJzuDbk;IV*;Rr&a=NP_wuZX-4YLAkXjK2n h@+W}7o(;4%eFG*7e2_1ph<*S7002ovPDHLkV1ngP#_|9F literal 0 HcmV?d00001 diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/package.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/package.png new file mode 100644 index 0000000000000000000000000000000000000000..ce6048f0bdcd9ebdf3e466a341996c31ecef7fb8 GIT binary patch literal 454 zcmV;%0XhDOP)~*1OUpmT8*l^U1|ESUus50ZB?>|a2$WRu z7O6^4=j*B0od$MYvh2{9q=69EkiY;sf@%*L-kh_;wSne5F$jVVGa9oeIz+Cid$n#Q zpt(q5hFcJcu~YRCiv zjK?*_>VOrn>U&{60~{e?*Xuzm0LNT*d!JobmA^Yb?cqOZi@+@V;B$mc6#h=b1jvaW zv70H@mHN~@*m+ + + + + + + DevAssistFindingsControl.xaml + CxWindowControl.xaml @@ -306,7 +313,7 @@ Always - + @@ -315,7 +322,7 @@ - + @@ -324,6 +331,24 @@ + + + + + + + + + + + + + + + + + + @@ -345,6 +370,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + From 482da9b96fc9841c9939ee051d5552d8855049c6 Mon Sep 17 00:00:00 2001 From: Rahul Pidde <206018639+cx-rahul-pidde@users.noreply.github.com> Date: Thu, 12 Feb 2026 10:06:48 +0530 Subject: [PATCH 04/45] Line Marker POC --- .../Commands/TestGutterIconsDirectCommand.cs | 64 ++-- .../CxExtension/CxWindowPackage.vsct | 10 +- .../Core/Markers/DevAssistErrorTag.cs | 47 --- .../Core/Markers/DevAssistErrorTagger.cs | 34 +- .../Markers/DevAssistErrorTaggerProvider.cs | 7 +- .../Core/Markers/DevAssistHoverPopup.xaml | 204 +++++++++++ .../Core/Markers/DevAssistHoverPopup.xaml.cs | 331 ++++++++++++++++++ .../Markers/DevAssistSquigglyAdornment.cs | 246 ------------- .../DevAssistSquigglyAdornmentProvider.cs | 76 ---- .../Core/Markers/DevAssistTestHelper.cs | 171 +++++++++ .../DevAssist/Core/Models/Vulnerability.cs | 26 +- .../ast-visual-studio-extension.csproj | 13 +- 12 files changed, 796 insertions(+), 433 deletions(-) delete mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTag.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml.cs delete mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSquigglyAdornment.cs delete mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSquigglyAdornmentProvider.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistTestHelper.cs diff --git a/ast-visual-studio-extension/CxExtension/Commands/TestGutterIconsDirectCommand.cs b/ast-visual-studio-extension/CxExtension/Commands/TestGutterIconsDirectCommand.cs index 41a14917..178b75c4 100644 --- a/ast-visual-studio-extension/CxExtension/Commands/TestGutterIconsDirectCommand.cs +++ b/ast-visual-studio-extension/CxExtension/Commands/TestGutterIconsDirectCommand.cs @@ -103,17 +103,21 @@ private void Execute(object sender, EventArgs e) System.Diagnostics.Debug.WriteLine("DevAssist: Error tagger replaced in buffer properties"); } - // Create test vulnerabilities - including Ok, Unknown, and Ignored severity levels + // Create test vulnerabilities: Critical, High, Medium, Low for colored marker verification (AST-133227) var vulnerabilities = new List { - new Vulnerability { Id = "TEST-001", Severity = SeverityLevel.Malicious, LineNumber = 1, Description = "Test Malicious vulnerability" }, - new Vulnerability { Id = "TEST-002", Severity = SeverityLevel.Critical, LineNumber = 3, Description = "Test Critical vulnerability" }, - new Vulnerability { Id = "TEST-003", Severity = SeverityLevel.High, LineNumber = 5, Description = "Test High vulnerability" }, - new Vulnerability { Id = "TEST-004", Severity = SeverityLevel.Medium, LineNumber = 7, Description = "Test Medium vulnerability" }, - new Vulnerability { Id = "TEST-005", Severity = SeverityLevel.Low, LineNumber = 9, Description = "Test Low vulnerability" }, - new Vulnerability { Id = "TEST-006", Severity = SeverityLevel.Unknown, LineNumber = 11, Description = "Test Unknown vulnerability" }, - new Vulnerability { Id = "TEST-007", Severity = SeverityLevel.Ok, LineNumber = 13, Description = "Test Ok vulnerability" }, - new Vulnerability { Id = "TEST-008", Severity = SeverityLevel.Ignored, LineNumber = 15, Description = "Test Ignored vulnerability" } + // Scanner-specific vulnerabilities (cover Critical, High, Medium) + DevAssistTestHelper.CreateOssVulnerability(), // Line 5: High (red) + DevAssistTestHelper.CreateLowSeverityVulnerability(), // Line 7: Low (green) - visual distinction + DevAssistTestHelper.CreateAscaVulnerability(), // Line 42: Critical (dark red) + DevAssistTestHelper.CreateIacVulnerability(), // Line 28: High (red) + DevAssistTestHelper.CreateSecretsVulnerability(), // Line 12: Critical (dark red) + DevAssistTestHelper.CreateContainersVulnerability(), // Line 1: Medium (orange) + + // Additional test vulnerabilities for other severity levels (gutter only, no underline) + new Vulnerability { Id = "TEST-006", Severity = SeverityLevel.Unknown, LineNumber = 11, Description = "Test Unknown vulnerability", Scanner = ScannerType.ASCA }, + new Vulnerability { Id = "TEST-007", Severity = SeverityLevel.Ok, LineNumber = 13, Description = "Test Ok vulnerability", Scanner = ScannerType.OSS }, + new Vulnerability { Id = "TEST-008", Severity = SeverityLevel.Ignored, LineNumber = 15, Description = "Test Ignored vulnerability", Scanner = ScannerType.IaC } }; System.Diagnostics.Debug.WriteLine($"DevAssist: Adding {vulnerabilities.Count} test vulnerabilities to both taggers"); @@ -128,26 +132,28 @@ private void Execute(object sender, EventArgs e) VsShellUtilities.ShowMessageBox( this.package, - $"โœ… Direct test completed!\n\n" + - $"Added {vulnerabilities.Count} test vulnerabilities with:\n" + - $"โ€ข Gutter icons (left margin) - ALL 8 severities\n" + - $"โ€ข Custom colored squiggly underlines - ONLY 5 main severities\n\n" + - $"CUSTOM COLORED UNDERLINES:\n" + - $"๐Ÿ”ด Line 1: Malicious (CRIMSON squiggly)\n" + - $"๐Ÿ”ด Line 3: Critical (RED squiggly)\n" + - $"๐ŸŸ  Line 5: High (ORANGE squiggly)\n" + - $"๐ŸŸก Line 7: Medium (GOLD squiggly)\n" + - $"๐ŸŸข Line 9: Low (GREEN squiggly)\n\n" + - $"GUTTER ICONS ONLY (No underlines):\n" + - $"โšช Line 11: Unknown (icon only)\n" + - $"โœ… Line 13: Ok (icon only)\n" + - $"๐Ÿšซ Line 15: Ignored (icon only)\n\n" + - $"Features:\n" + - $"โœ… Custom colored squiggly underlines (adornment layer)\n" + - $"โœ… Tooltips on hover\n" + - $"โœ… Gutter icons for all severities\n" + - $"โœ… Similar to JetBrains plugin", - "Test DevAssist Gutter Icons & Custom Colored Markers", + $"โœ… DevAssist Hover Popup Test - Scanner-Specific Data!\n\n" + + $"Added {vulnerabilities.Count} test vulnerabilities with rich scanner-specific data:\n\n" + + $"SCANNER-SPECIFIC VULNERABILITIES:\n" + + $"๐ŸŸข Line 5: OSS - Package vulnerability\n" + + $" โ€ข Package: lodash@4.17.15\n" + + $" โ€ข CVE: CVE-2020-8203, CVSS: 7.4\n" + + $" โ€ข Recommended: 4.17.21\n\n" + + $"๐Ÿ”ด Line 42: ASCA - SQL Injection\n" + + $" โ€ข Remediation advice included\n\n" + + $"๐ŸŸฃ Line 28: IaC - S3 Bucket Public\n" + + $" โ€ข Expected vs Actual values\n\n" + + $"๐Ÿ”ด Line 12: Secrets - Hardcoded API Key\n" + + $" โ€ข Secret type: API Key\n\n" + + $"๐ŸŸ  Line 1: Containers - Vulnerable Image\n" + + $" โ€ข CVE: CVE-2021-3711, CVSS: 9.8\n\n" + + $"HOVER OVER ANY LINE TO SEE:\n" + + $"โœ… Rich hover popup with scanner badge\n" + + $"โœ… Scanner-specific content (CVE, CVSS, remediation, etc.)\n" + + $"โœ… Action links (View Details, Navigate, Learn More, Apply Fix)\n" + + $"โœ… Theme-aware styling\n" + + $"โœ… Similar to JetBrains implementation", + "Test DevAssist Hover Popup - Scanner-Specific Content", OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); diff --git a/ast-visual-studio-extension/CxExtension/CxWindowPackage.vsct b/ast-visual-studio-extension/CxExtension/CxWindowPackage.vsct index 664a9c11..3ff79463 100644 --- a/ast-visual-studio-extension/CxExtension/CxWindowPackage.vsct +++ b/ast-visual-studio-extension/CxExtension/CxWindowPackage.vsct @@ -1,4 +1,4 @@ -๏ปฟ + @@ -45,7 +45,13 @@ --> - + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTag.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTag.cs deleted file mode 100644 index b92138c4..00000000 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTag.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Microsoft.VisualStudio.Text.Adornments; -using Microsoft.VisualStudio.Text.Tagging; - -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers -{ - /// - /// Custom error tag for DevAssist vulnerabilities - /// Based on JetBrains TextAttributes with EffectType.WAVE_UNDERSCORE pattern - /// Provides tooltip support and metadata for vulnerabilities - /// NOTE: The actual colored squiggly underlines are drawn by DevAssistSquigglyAdornment - /// This tag provides the infrastructure (tooltips, accessibility) without visible squiggles - /// We use an empty/null error type to completely disable the default Visual Studio squiggle - /// - internal class DevAssistErrorTag : IErrorTag - { - /// - /// Gets the error type - using Suggestion which has minimal visual impact - /// The actual colored squiggles are drawn by the adornment layer - /// Suggestion type shows as a subtle dotted underline that our custom squiggles will override - /// - public string ErrorType => PredefinedErrorTypeNames.Suggestion; - - /// - /// Gets the tooltip text shown when hovering over the vulnerability - /// - public object ToolTipContent { get; } - - /// - /// Gets the severity level of the vulnerability - /// Used by the adornment layer to determine squiggle color - /// - public string Severity { get; } - - /// - /// Gets the vulnerability ID - /// - public string VulnerabilityId { get; } - - public DevAssistErrorTag(string severity, string tooltipText, string vulnerabilityId) - { - Severity = severity; - VulnerabilityId = vulnerabilityId; - ToolTipContent = tooltipText; - } - } -} - diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs index 49b114c1..7247e1a4 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs @@ -8,12 +8,10 @@ namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers { /// - /// Tagger that provides error tags for DevAssist vulnerabilities - /// Based on JetBrains MarkupModel.addRangeHighlighter pattern - /// Creates severity-based colored squiggly underlines in the text editor - /// Similar to JetBrains EffectType.WAVE_UNDERSCORE implementation + /// Tagger that provides IErrorTag for DevAssist vulnerabilities. + /// Uses VS built-in ErrorTag only; no custom tag. VS draws squiggles and shows tooltip. /// - internal class DevAssistErrorTagger : ITagger + internal class DevAssistErrorTagger : ITagger { private readonly ITextBuffer _buffer; private readonly Dictionary> _vulnerabilitiesByLine; @@ -26,7 +24,7 @@ public DevAssistErrorTagger(ITextBuffer buffer) _vulnerabilitiesByLine = new Dictionary>(); } - public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) + public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) { System.Diagnostics.Debug.WriteLine($"DevAssist Markers: GetTags called - spans count: {spans.Count}, vulnerabilities count: {_vulnerabilitiesByLine.Count}"); @@ -48,13 +46,8 @@ public IEnumerable> GetTags(NormalizedSnapshotSpanCo { if (_vulnerabilitiesByLine.TryGetValue(lineNumber, out var vulnerabilities)) { - // Create error tags ONLY for actual severity vulnerabilities (not Unknown, Ok, Ignored) - // Similar to JetBrains plugin which shows underlines only for real issues - // Each vulnerability gets its own squiggly underline foreach (var vulnerability in vulnerabilities) { - // Filter: Show underlines ONLY for Malicious, Critical, High, Medium, Low - // Do NOT show underlines for Unknown, Ok, Ignored (they only get gutter icons) if (!ShouldShowUnderline(vulnerability.Severity)) { System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Skipping underline for {vulnerability.Severity} on line {lineNumber}"); @@ -62,21 +55,14 @@ public IEnumerable> GetTags(NormalizedSnapshotSpanCo } var line = snapshot.GetLineFromLineNumber(lineNumber); - - // Create span for the entire line (similar to JetBrains TextRange) - // In the future, this could be refined to highlight specific code ranges var lineSpan = new SnapshotSpan(snapshot, line.Start, line.Length); var tooltipText = BuildTooltipText(vulnerability); - var tag = new DevAssistErrorTag( - vulnerability.Severity.ToString(), - tooltipText, - vulnerability.Id - ); + IErrorTag tag = new ErrorTag("Error", tooltipText); tagCount++; System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Creating error tag #{tagCount} for line {lineNumber}, severity: {vulnerability.Severity}"); - yield return new TagSpan(lineSpan, tag); + yield return new TagSpan(lineSpan, tag); } } } @@ -113,12 +99,12 @@ private bool ShouldShowUnderline(SeverityLevel severity) } /// - /// Builds tooltip text for a vulnerability - /// Similar to JetBrains GutterIconRenderer.getTooltipText() + /// Builds plain string tooltip for a vulnerability (VS default tooltip). /// - private string BuildTooltipText(Vulnerability vulnerability) + private static string BuildTooltipText(Vulnerability vulnerability) { - return $"[{vulnerability.Severity}] {vulnerability.Description}\nID: {vulnerability.Id}"; + if (vulnerability == null) return string.Empty; + return $"[{vulnerability.Severity}] {vulnerability.Title}\n{vulnerability.Description}\nID: {vulnerability.Id}"; } /// diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTaggerProvider.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTaggerProvider.cs index acbdd17e..fca8bcff 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTaggerProvider.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTaggerProvider.cs @@ -10,14 +10,13 @@ namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers /// /// MEF provider for DevAssist error tagger /// Based on JetBrains EditorFactoryListener pattern adapted for Visual Studio - /// Creates and manages error tagger instances per buffer (not per view) - /// Provides severity-based colored squiggly underlines similar to JetBrains WAVE_UNDERSCORE - /// IMPORTANT: Uses ITaggerProvider (not IViewTaggerProvider) for error tags + /// Creates and manages error tagger instances per buffer (not per view). + /// Exports IErrorTag so VS built-in error layer draws squiggles using IErrorType (CompilerError / syntax error colour). /// [Export(typeof(ITaggerProvider))] [ContentType("code")] [ContentType("text")] - [TagType(typeof(DevAssistErrorTag))] + [TagType(typeof(IErrorTag))] [TextViewRole(PredefinedTextViewRoles.Document)] [TextViewRole(PredefinedTextViewRoles.Editable)] internal class DevAssistErrorTaggerProvider : ITaggerProvider diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml new file mode 100644 index 00000000..b1ed63fe --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml @@ -0,0 +1,204 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Package: + + + + + + + + + + + + + Recommended: + + + + + + + + + Remediation: + + + + + + + + Expected Value: + + + + + Actual Value: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml.cs new file mode 100644 index 00000000..edb64b96 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml.cs @@ -0,0 +1,331 @@ +using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; +using System; +using System.IO; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +{ + /// + /// Rich hover popup for DevAssist vulnerabilities + /// Similar to JetBrains hover popup with custom formatting, icons, and links + /// + public partial class DevAssistHoverPopup : UserControl + { + private readonly Vulnerability _vulnerability; + + public DevAssistHoverPopup(Vulnerability vulnerability) + { + InitializeComponent(); + _vulnerability = vulnerability; + PopulateContent(); + } + + private void PopulateContent() + { + // Set severity icon + SetSeverityIcon(); + + // Set title + TitleText.Text = !string.IsNullOrEmpty(_vulnerability.Title) + ? _vulnerability.Title + : !string.IsNullOrEmpty(_vulnerability.RuleName) + ? _vulnerability.RuleName + : _vulnerability.Description; + + // Set scanner badge + SetScannerBadge(); + + // Set description + DescriptionText.Text = _vulnerability.Description; + + // Set scanner-specific content + SetScannerSpecificContent(); + + // Set location + var fileName = Path.GetFileName(_vulnerability.FilePath); + LocationText.Text = $"{fileName}:{_vulnerability.LineNumber}:{_vulnerability.ColumnNumber}"; + + // Wire up event handlers for links + ViewDetailsLink.Click += ViewDetailsLink_Click; + NavigateToCodeLink.Click += NavigateToCodeLink_Click; + LearnMoreLink.Click += LearnMoreLink_Click; + ApplyFixLink.Click += ApplyFixLink_Click; + } + + private void SetScannerSpecificContent() + { + switch (_vulnerability.Scanner) + { + case ScannerType.OSS: + SetOssContent(); + break; + case ScannerType.ASCA: + SetAscaContent(); + break; + case ScannerType.IaC: + SetIacContent(); + break; + case ScannerType.Secrets: + SetSecretsContent(); + break; + case ScannerType.Containers: + SetContainersContent(); + break; + } + } + + private void SetOssContent() + { + // Show package information for OSS/SCA vulnerabilities + if (!string.IsNullOrEmpty(_vulnerability.PackageName)) + { + PackageInfoPanel.Visibility = Visibility.Visible; + PackageNameText.Text = $"{_vulnerability.PackageName}@{_vulnerability.PackageVersion}"; + + // Show CVE if available + if (!string.IsNullOrEmpty(_vulnerability.CveName)) + { + CveText.Text = $"CVE: {_vulnerability.CveName}"; + CveText.Visibility = Visibility.Visible; + } + + // Show CVSS score if available + if (_vulnerability.CvssScore.HasValue) + { + CvssScoreText.Text = $"CVSS Score: {_vulnerability.CvssScore.Value:F1}"; + CvssScoreText.Visibility = Visibility.Visible; + } + + // Show recommended version if available + if (!string.IsNullOrEmpty(_vulnerability.RecommendedVersion)) + { + RecommendedVersionPanel.Visibility = Visibility.Visible; + RecommendedVersionText.Text = _vulnerability.RecommendedVersion; + + // Show "Apply Fix" link + ApplyFixLinkBlock.Visibility = Visibility.Visible; + } + } + + // Show "Learn More" link if available + if (!string.IsNullOrEmpty(_vulnerability.LearnMoreUrl) || !string.IsNullOrEmpty(_vulnerability.FixLink)) + { + LearnMoreLinkBlock.Visibility = Visibility.Visible; + } + } + + private void SetAscaContent() + { + // Show remediation advice if available for ASCA/SAST vulnerabilities + if (!string.IsNullOrEmpty(_vulnerability.RemediationAdvice)) + { + RemediationPanel.Visibility = Visibility.Visible; + RemediationText.Text = _vulnerability.RemediationAdvice; + } + + // Show "Learn More" link if available + if (!string.IsNullOrEmpty(_vulnerability.LearnMoreUrl)) + { + LearnMoreLinkBlock.Visibility = Visibility.Visible; + } + } + + private void SetIacContent() + { + // Show expected vs actual values for IaC/KICS vulnerabilities + if (!string.IsNullOrEmpty(_vulnerability.ExpectedValue) || !string.IsNullOrEmpty(_vulnerability.ActualValue)) + { + IacValuesPanel.Visibility = Visibility.Visible; + ExpectedValueText.Text = _vulnerability.ExpectedValue ?? "N/A"; + ActualValueText.Text = _vulnerability.ActualValue ?? "N/A"; + } + + // Show "Learn More" link if available + if (!string.IsNullOrEmpty(_vulnerability.LearnMoreUrl)) + { + LearnMoreLinkBlock.Visibility = Visibility.Visible; + } + } + + private void SetSecretsContent() + { + // For secrets, we might want to show the secret type + if (!string.IsNullOrEmpty(_vulnerability.SecretType)) + { + // Could add a SecretTypePanel in XAML if needed + } + + // Show "Learn More" link if available + if (!string.IsNullOrEmpty(_vulnerability.LearnMoreUrl)) + { + LearnMoreLinkBlock.Visibility = Visibility.Visible; + } + } + + private void SetContainersContent() + { + // For containers, similar to OSS - show package/image information + if (!string.IsNullOrEmpty(_vulnerability.PackageName)) + { + PackageInfoPanel.Visibility = Visibility.Visible; + PackageNameText.Text = $"{_vulnerability.PackageName}@{_vulnerability.PackageVersion}"; + + // Show CVE if available + if (!string.IsNullOrEmpty(_vulnerability.CveName)) + { + CveText.Text = $"CVE: {_vulnerability.CveName}"; + CveText.Visibility = Visibility.Visible; + } + + // Show CVSS score if available + if (_vulnerability.CvssScore.HasValue) + { + CvssScoreText.Text = $"CVSS Score: {_vulnerability.CvssScore.Value:F1}"; + CvssScoreText.Visibility = Visibility.Visible; + } + } + + // Show "Learn More" link if available + if (!string.IsNullOrEmpty(_vulnerability.LearnMoreUrl)) + { + LearnMoreLinkBlock.Visibility = Visibility.Visible; + } + } + + private void SetSeverityIcon() + { + string iconFileName = GetSeverityIconFileName(_vulnerability.Severity); + if (!string.IsNullOrEmpty(iconFileName)) + { + try + { + // Determine theme (Dark/Light) + var theme = GetCurrentTheme(); + var iconPath = $"pack://application:,,,/ast-visual-studio-extension;component/Resources/Icons/{theme}/{iconFileName}"; + + SeverityIcon.Source = new BitmapImage(new Uri(iconPath)); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Failed to load severity icon: {ex.Message}"); + } + } + } + + private void SetScannerBadge() + { + // Set scanner text + ScannerText.Text = _vulnerability.Scanner.ToString().ToUpper(); + + // Set scanner badge color based on scanner type + var badgeColor = GetScannerBadgeColor(_vulnerability.Scanner); + ScannerBadge.Background = new SolidColorBrush(badgeColor); + } + + private string GetSeverityIconFileName(SeverityLevel severity) + { + switch (severity) + { + case SeverityLevel.Malicious: + return "malicious.png"; + case SeverityLevel.Critical: + return "critical.png"; + case SeverityLevel.High: + return "high.png"; + case SeverityLevel.Medium: + return "medium.png"; + case SeverityLevel.Low: + case SeverityLevel.Info: + return "low.png"; + default: + return null; + } + } + + private Color GetScannerBadgeColor(ScannerType scanner) + { + switch (scanner) + { + case ScannerType.ASCA: + return Color.FromRgb(0, 122, 204); // Blue for ASCA/SAST + case ScannerType.OSS: + return Color.FromRgb(16, 124, 16); // Green for OSS/SCA + case ScannerType.IaC: + return Color.FromRgb(156, 39, 176); // Purple for IaC/KICS + case ScannerType.Secrets: + return Color.FromRgb(211, 47, 47); // Red for Secrets + case ScannerType.Containers: + return Color.FromRgb(255, 140, 0); // Orange for Containers + default: + return Color.FromRgb(117, 117, 117); // Gray + } + } + + private string GetCurrentTheme() + { + // Simple theme detection based on background color + // In a real implementation, you'd use VS theme service + return "Dark"; // Default to Dark for now + } + + private void ViewDetailsLink_Click(object sender, RoutedEventArgs e) + { + // TODO: Open DevAssist Findings Window and select this vulnerability + System.Diagnostics.Debug.WriteLine($"View Details clicked for vulnerability: {_vulnerability.Id}"); + } + + private void NavigateToCodeLink_Click(object sender, RoutedEventArgs e) + { + // TODO: Navigate to the vulnerability location in code + System.Diagnostics.Debug.WriteLine($"Navigate to Code clicked for vulnerability: {_vulnerability.Id}"); + } + + private void LearnMoreLink_Click(object sender, RoutedEventArgs e) + { + try + { + // Open external URL in default browser + var url = !string.IsNullOrEmpty(_vulnerability.LearnMoreUrl) + ? _vulnerability.LearnMoreUrl + : _vulnerability.FixLink; + + if (!string.IsNullOrEmpty(url)) + { + System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo + { + FileName = url, + UseShellExecute = true + }); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"DevAssist Hover: Error opening Learn More link: {ex.Message}"); + } + } + + private void ApplyFixLink_Click(object sender, RoutedEventArgs e) + { + try + { + // TODO: Implement auto-remediation for SCA packages + // This would update package.json/requirements.txt/etc with recommended version + System.Diagnostics.Debug.WriteLine($"DevAssist Hover: Apply Fix for {_vulnerability.PackageName} -> {_vulnerability.RecommendedVersion}"); + + // For now, just show a message + // In the future, this would: + // 1. Detect package manager (npm, pip, maven, etc.) + // 2. Update the package file with recommended version + // 3. Optionally run package manager update command + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"DevAssist Hover: Error applying fix: {ex.Message}"); + } + } + } +} + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSquigglyAdornment.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSquigglyAdornment.cs deleted file mode 100644 index 4efc57d0..00000000 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSquigglyAdornment.cs +++ /dev/null @@ -1,246 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; -using System.Windows.Shapes; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Text.Formatting; -using Microsoft.VisualStudio.Text.Tagging; -using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; - -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers -{ - /// - /// Custom adornment layer that draws colored squiggly underlines for DevAssist vulnerabilities - /// Based on JetBrains EffectType.WAVE_UNDERSCORE pattern - /// Provides full control over squiggle colors for each severity level - /// - internal class DevAssistSquigglyAdornment - { - private readonly IAdornmentLayer _adornmentLayer; - private readonly IWpfTextView _textView; - private readonly ITextBuffer _textBuffer; - private readonly DevAssistErrorTagger _errorTagger; - - // Severity color mapping - based on Checkmarx UI colors - // Note: JetBrains and VSCode plugins use single red color for all severities - // This Visual Studio extension provides enhanced UX with severity-specific colors - private static readonly Dictionary SeverityColors = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "Malicious", Color.FromRgb(139, 0, 0) }, // Dark Red (darker than Critical) - { "Critical", Color.FromRgb(217, 75, 72) }, // Red (#D94B48 from Checkmarx UI) - { "High", Color.FromRgb(217, 75, 72) }, // Red (#D94B48 from Checkmarx UI - same as Critical) - { "Medium", Color.FromRgb(249, 174, 77) }, // Orange/Gold (#F9AE4D from Checkmarx UI) - { "Low", Color.FromRgb(2, 147, 2) }, // Green (#029302 from Checkmarx UI) - { "Info", Color.FromRgb(2, 147, 2) }, // Green (same as Low) - { "Unknown", Color.FromRgb(135, 190, 209) }, // Blue (#87bed1 from Checkmarx UI) - { "Ok", Color.FromRgb(160, 160, 160) }, // Light Gray - { "Ignored", Color.FromRgb(128, 128, 128) } // Dark Gray - }; - - public DevAssistSquigglyAdornment(IWpfTextView textView, DevAssistErrorTagger errorTagger) - { - _textView = textView ?? throw new ArgumentNullException(nameof(textView)); - _errorTagger = errorTagger ?? throw new ArgumentNullException(nameof(errorTagger)); - _textBuffer = textView.TextBuffer; - - // Get or create the adornment layer - _adornmentLayer = textView.GetAdornmentLayer("DevAssistSquigglyAdornment"); - - // Subscribe to events - _textView.LayoutChanged += OnLayoutChanged; - _textView.Closed += OnViewClosed; - _errorTagger.TagsChanged += OnTagsChanged; - - System.Diagnostics.Debug.WriteLine("DevAssist Adornment: Squiggly adornment layer initialized"); - } - - /// - /// Handle layout changes (scrolling, text changes, zoom, etc.) - /// - private void OnLayoutChanged(object sender, TextViewLayoutChangedEventArgs e) - { - // Only redraw if the view has changed significantly - if (e.NewOrReformattedLines.Count > 0 || e.VerticalTranslation || e.NewViewState.ViewportWidth != e.OldViewState.ViewportWidth) - { - RedrawAdornments(); - } - } - - /// - /// Handle tags changed event from the error tagger - /// - private void OnTagsChanged(object sender, SnapshotSpanEventArgs e) - { - RedrawAdornments(); - } - - /// - /// Clean up when view is closed - /// - private void OnViewClosed(object sender, EventArgs e) - { - _textView.LayoutChanged -= OnLayoutChanged; - _textView.Closed -= OnViewClosed; - _errorTagger.TagsChanged -= OnTagsChanged; - } - - /// - /// Redraw all squiggly adornments for visible lines - /// Uses virtualization - only draws squiggles for visible text - /// - private void RedrawAdornments() - { - try - { - System.Diagnostics.Debug.WriteLine("DevAssist Adornment: RedrawAdornments called"); - - // Clear all existing adornments - _adornmentLayer.RemoveAllAdornments(); - System.Diagnostics.Debug.WriteLine("DevAssist Adornment: Removed all existing adornments"); - - // Get the visible span - var visibleSpan = new SnapshotSpan(_textView.TextSnapshot, _textView.TextViewLines.FormattedSpan.Start, _textView.TextViewLines.FormattedSpan.Length); - System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Visible span: {visibleSpan.Start.Position} to {visibleSpan.End.Position}"); - - // Get all error tags in the visible span - var tags = _errorTagger.GetTags(new NormalizedSnapshotSpanCollection(visibleSpan)); - var tagsList = tags.ToList(); - System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Found {tagsList.Count} error tags"); - - int adornmentCount = 0; - foreach (var tagSpan in tagsList) - { - System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: About to draw squiggly #{adornmentCount + 1} for severity: {tagSpan.Tag.Severity}"); - DrawSquiggly(tagSpan.Span, tagSpan.Tag.Severity); - adornmentCount++; - System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Successfully drew squiggly #{adornmentCount}"); - } - - System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Drew {adornmentCount} squiggly adornments"); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Error redrawing adornments: {ex.Message}"); - System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Stack trace: {ex.StackTrace}"); - } - } - - /// - /// Draw a squiggly underline for a specific span with severity-based color - /// - private void DrawSquiggly(SnapshotSpan span, string severity) - { - try - { - // Get the geometry for the span - var geometry = _textView.TextViewLines.GetMarkerGeometry(span); - if (geometry == null) - { - System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: No geometry for span at {span.Start.Position}"); - return; - } - - // Get color for severity - if (!SeverityColors.TryGetValue(severity, out var color)) - { - System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Unknown severity '{severity}', using default"); - color = SeverityColors["Unknown"]; - } - - System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Drawing {severity} squiggly with color R={color.R} G={color.G} B={color.B}"); - - // Create the squiggly path - var squigglyPath = CreateSquigglyPath(geometry.Bounds, color); - if (squigglyPath == null) - { - System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Failed to create squiggly path"); - return; - } - - // Add to adornment layer - Canvas.SetLeft(squigglyPath, geometry.Bounds.Left); - Canvas.SetTop(squigglyPath, geometry.Bounds.Bottom - 2); - - _adornmentLayer.AddAdornment( - AdornmentPositioningBehavior.TextRelative, - span, - null, - squigglyPath, - null); - - System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Successfully added squiggly adornment"); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Error drawing squiggly: {ex.Message}"); - } - } - - /// - /// Creates a smooth wavy/squiggly path for the underline using Bezier curves - /// Similar to Visual Studio's error squiggles but with custom colors - /// - private Path CreateSquigglyPath(Rect bounds, Color color) - { - try - { - const double waveHeight = 2.5; // Height of the wave (amplitude) - const double waveLength = 5.0; // Length of one complete wave cycle - - if (bounds.Width <= 0 || bounds.Height <= 0) - return null; - - var pathGeometry = new PathGeometry(); - var pathFigure = new PathFigure { StartPoint = new Point(0, 0) }; - - // Create smooth wave pattern using Bezier curves - double x = 0; - bool goingDown = true; // Start by going down - - while (x < bounds.Width) - { - double halfWave = waveLength / 2.0; - double nextX = Math.Min(x + halfWave, bounds.Width); - - // Control points for smooth Bezier curve - double controlX1 = x + halfWave / 3.0; - double controlX2 = x + 2.0 * halfWave / 3.0; - double peakY = goingDown ? waveHeight : -waveHeight; - - // Create a smooth curve using quadratic Bezier - pathFigure.Segments.Add(new QuadraticBezierSegment( - new Point(x + halfWave / 2.0, peakY), // Control point at peak - new Point(nextX, 0), // End point back at baseline - true)); - - x = nextX; - goingDown = !goingDown; - } - - pathGeometry.Figures.Add(pathFigure); - - var path = new Path - { - Data = pathGeometry, - Stroke = new SolidColorBrush(color), - StrokeThickness = 1.5, // Bolder/darker stroke for better visibility - Width = bounds.Width, - Height = waveHeight * 2, - Opacity = 1.0 // Full opacity for maximum visibility - }; - - return path; - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"DevAssist Adornment: Error creating squiggly path: {ex.Message}"); - return null; - } - } - } -} - diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSquigglyAdornmentProvider.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSquigglyAdornmentProvider.cs deleted file mode 100644 index 2c9533ff..00000000 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSquigglyAdornmentProvider.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.ComponentModel.Composition; -using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Utilities; - -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers -{ - /// - /// MEF provider for DevAssist squiggly adornment layer - /// Creates the custom colored squiggly underlines for vulnerabilities - /// Based on JetBrains EffectType.WAVE_UNDERSCORE pattern - /// - [Export(typeof(IWpfTextViewCreationListener))] - [ContentType("code")] - [ContentType("text")] - [TextViewRole(PredefinedTextViewRoles.Document)] - [TextViewRole(PredefinedTextViewRoles.Editable)] - internal class DevAssistSquigglyAdornmentProvider : IWpfTextViewCreationListener - { - /// - /// Defines the adornment layer for squiggly underlines - /// Order: After selection, before text (so squiggles appear under text) - /// - [Export(typeof(AdornmentLayerDefinition))] - [Name("DevAssistSquigglyAdornment")] - [Order(After = PredefinedAdornmentLayers.Selection, Before = PredefinedAdornmentLayers.Text)] - [TextViewRole(PredefinedTextViewRoles.Document)] - public AdornmentLayerDefinition AdornmentLayer = null; - - /// - /// Called when a text view is created - /// Creates the squiggly adornment for the view - /// - public void TextViewCreated(IWpfTextView textView) - { - if (textView == null) - return; - - System.Diagnostics.Debug.WriteLine("DevAssist Adornment: TextViewCreated called"); - - // Get the error tagger for this buffer - var errorTagger = DevAssistErrorTaggerProvider.GetTaggerForBuffer(textView.TextBuffer); - if (errorTagger == null) - { - System.Diagnostics.Debug.WriteLine("DevAssist Adornment: Error tagger not found, will retry with longer delay"); - - // The error tagger might not be created yet, so we'll wait longer and retry multiple times - System.Threading.Tasks.Task.Delay(1500).ContinueWith(_ => - { - Microsoft.VisualStudio.Shell.ThreadHelper.JoinableTaskFactory.Run(async () => - { - await Microsoft.VisualStudio.Shell.ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - errorTagger = DevAssistErrorTaggerProvider.GetTaggerForBuffer(textView.TextBuffer); - if (errorTagger != null) - { - System.Diagnostics.Debug.WriteLine("DevAssist Adornment: Error tagger found on retry, creating adornment"); - textView.Properties.GetOrCreateSingletonProperty(() => new DevAssistSquigglyAdornment(textView, errorTagger)); - } - else - { - System.Diagnostics.Debug.WriteLine("DevAssist Adornment: Error tagger still not found after retry"); - } - }); - }, System.Threading.Tasks.TaskScheduler.Default); - - return; - } - - // Create the adornment and store it in the view's property bag - textView.Properties.GetOrCreateSingletonProperty(() => new DevAssistSquigglyAdornment(textView, errorTagger)); - - System.Diagnostics.Debug.WriteLine("DevAssist Adornment: Squiggly adornment created successfully"); - } - } -} - diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistTestHelper.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistTestHelper.cs new file mode 100644 index 00000000..6af74e04 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistTestHelper.cs @@ -0,0 +1,171 @@ +using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; +using System.Collections.Generic; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +{ + /// + /// Helper class for testing DevAssist hover popups with mock data + /// Use this to generate sample vulnerabilities for each scanner type + /// + public static class DevAssistTestHelper + { + /// + /// Creates a sample OSS vulnerability with package information + /// + public static Vulnerability CreateOssVulnerability() + { + return new Vulnerability + { + Id = "OSS-001", + Title = "Vulnerable Package Detected", + Description = "The package 'lodash' has a known security vulnerability that allows prototype pollution attacks.", + Severity = SeverityLevel.High, + Scanner = ScannerType.OSS, + LineNumber = 5, + ColumnNumber = 10, + FilePath = "package.json", + + // OSS-specific fields + PackageName = "lodash", + PackageVersion = "4.17.15", + PackageManager = "npm", + RecommendedVersion = "4.17.21", + CveName = "CVE-2020-8203", + CvssScore = 7.4, + LearnMoreUrl = "https://nvd.nist.gov/vuln/detail/CVE-2020-8203", + FixLink = "https://github.com/lodash/lodash/security/advisories" + }; + } + + /// + /// Creates a sample ASCA vulnerability with remediation advice + /// + public static Vulnerability CreateAscaVulnerability() + { + return new Vulnerability + { + Id = "ASCA-001", + Title = "SQL Injection Vulnerability", + Description = "User input is directly concatenated into SQL query without sanitization.", + Severity = SeverityLevel.Critical, + Scanner = ScannerType.ASCA, + LineNumber = 42, + ColumnNumber = 15, + FilePath = "UserController.cs", + + // ASCA-specific fields + RuleName = "SQL_INJECTION", + RemediationAdvice = "Use parameterized queries or prepared statements instead of string concatenation. Replace the current query with SqlCommand.Parameters.AddWithValue() to safely handle user input.", + LearnMoreUrl = "https://owasp.org/www-community/attacks/SQL_Injection" + }; + } + + /// + /// Creates a sample IaC vulnerability with expected vs actual values + /// + public static Vulnerability CreateIacVulnerability() + { + return new Vulnerability + { + Id = "IAC-001", + Title = "S3 Bucket Publicly Accessible", + Description = "S3 bucket is configured to allow public access, which may expose sensitive data.", + Severity = SeverityLevel.High, + Scanner = ScannerType.IaC, + LineNumber = 28, + ColumnNumber = 5, + FilePath = "terraform/s3.tf", + + // IaC-specific fields + ExpectedValue = "acl = 'private'", + ActualValue = "acl = 'public-read'", + LearnMoreUrl = "https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html" + }; + } + + /// + /// Creates a sample Secrets vulnerability + /// + public static Vulnerability CreateSecretsVulnerability() + { + return new Vulnerability + { + Id = "SECRETS-001", + Title = "Hardcoded API Key Detected", + Description = "An API key is hardcoded in the source code. This is a security risk as the key may be exposed in version control.", + Severity = SeverityLevel.Critical, + Scanner = ScannerType.Secrets, + LineNumber = 12, + ColumnNumber = 20, + FilePath = "config.js", + + // Secrets-specific fields + SecretType = "API Key", + LearnMoreUrl = "https://owasp.org/www-community/vulnerabilities/Use_of_hard-coded_password" + }; + } + + /// + /// Creates a sample Containers vulnerability + /// + public static Vulnerability CreateContainersVulnerability() + { + return new Vulnerability + { + Id = "CONTAINERS-001", + Title = "Vulnerable Base Image", + Description = "The Docker base image contains known vulnerabilities in system packages.", + Severity = SeverityLevel.Medium, + Scanner = ScannerType.Containers, + LineNumber = 1, + ColumnNumber = 1, + FilePath = "Dockerfile", + + // Containers-specific fields (similar to OSS) + PackageName = "openssl", + PackageVersion = "1.1.1d", + CveName = "CVE-2021-3711", + CvssScore = 9.8, + LearnMoreUrl = "https://nvd.nist.gov/vuln/detail/CVE-2021-3711" + }; + } + + /// + /// Creates a sample Low severity vulnerability for testing colored markers (AST-133227). + /// Use with Critical, High, Medium to verify visual distinction between all four severities. + /// + public static Vulnerability CreateLowSeverityVulnerability() + { + return new Vulnerability + { + Id = "LOW-001", + Title = "Low Severity Finding", + Description = "Minor code quality or low-risk security finding for testing Low severity underline color (green).", + Severity = SeverityLevel.Low, + Scanner = ScannerType.ASCA, + LineNumber = 7, + ColumnNumber = 1, + FilePath = "Sample.cs", + RuleName = "LOW_RISK", + RemediationAdvice = "Consider addressing when convenient." + }; + } + + /// + /// Creates a collection of all sample vulnerabilities for testing + /// + public static List CreateAllSampleVulnerabilities() + { + return new List + { + CreateOssVulnerability(), + CreateAscaVulnerability(), + CreateIacVulnerability(), + CreateSecretsVulnerability(), + CreateContainersVulnerability(), + CreateLowSeverityVulnerability() + }; + } + } +} + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/Vulnerability.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/Vulnerability.cs index 392bd406..7ad37563 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/Vulnerability.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/Vulnerability.cs @@ -2,10 +2,11 @@ namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Models { /// /// Represents a vulnerability found by DevAssist scanners - /// Based on JetBrains ScanIssue model + /// Based on JetBrains ScanIssue model with scanner-specific fields /// public class Vulnerability { + // Common fields for all scanners public string Id { get; set; } public string Title { get; set; } public string Description { get; set; } @@ -15,6 +16,29 @@ public class Vulnerability public int ColumnNumber { get; set; } public string FilePath { get; set; } + // SCA/OSS specific fields + public string PackageName { get; set; } + public string PackageVersion { get; set; } + public string PackageManager { get; set; } + public string RecommendedVersion { get; set; } + public string CveName { get; set; } // CVE-2024-1234 + public double? CvssScore { get; set; } + + // SAST/ASCA specific fields + public string RuleName { get; set; } + public string RemediationAdvice { get; set; } + + // KICS/IaC specific fields + public string ExpectedValue { get; set; } + public string ActualValue { get; set; } + + // Secrets specific fields + public string SecretType { get; set; } + + // Common remediation fields + public string FixLink { get; set; } // Link to learn more about the vulnerability + public string LearnMoreUrl { get; set; } + public Vulnerability() { } diff --git a/ast-visual-studio-extension/ast-visual-studio-extension.csproj b/ast-visual-studio-extension/ast-visual-studio-extension.csproj index f7b027c3..05fa58cf 100644 --- a/ast-visual-studio-extension/ast-visual-studio-extension.csproj +++ b/ast-visual-studio-extension/ast-visual-studio-extension.csproj @@ -1,4 +1,4 @@ -๏ปฟ + 17.0 @@ -140,6 +140,9 @@ DevAssistFindingsControl.xaml + + DevAssistHoverPopup.xaml + CxWindowControl.xaml @@ -153,11 +156,9 @@ - - - + @@ -374,6 +375,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + From 8955e8fb1c9b57622575578a6338f418d8f342cf Mon Sep 17 00:00:00 2001 From: Rahul Pidde <206018639+cx-rahul-pidde@users.noreply.github.com> Date: Fri, 13 Feb 2026 10:12:19 +0530 Subject: [PATCH 05/45] Chnges of pophover --- .../TestErrorListCustomizationCommand.cs | 2 +- .../Commands/TestGutterIconsDirectCommand.cs | 51 +-- .../CxExtension/CxWindowPackage.cs | 6 +- .../CxExtension/CxWindowPackage.vsct | 23 +- .../Core/Markers/DevAssistErrorTagger.cs | 14 +- .../Markers/DevAssistHoverPopup.Designer.cs | 105 +++++ .../Core/Markers/DevAssistHoverPopup.xaml | 166 ++++---- .../Core/Markers/DevAssistHoverPopup.xaml.cs | 402 ++++++++++++++++-- .../Markers/DevAssistQuickInfoController.cs | 117 +++++ .../DevAssistQuickInfoControllerProvider.cs | 24 ++ .../Core/Markers/DevAssistQuickInfoSource.cs | 127 ++++++ .../DevAssistQuickInfoSourceProvider.cs | 20 + .../Icons/Dark/severity_count/critical.png | Bin 0 -> 410 bytes .../Icons/Dark/severity_count/high.png | Bin 0 -> 361 bytes .../Icons/Dark/severity_count/low.png | Bin 0 -> 360 bytes .../Icons/Dark/severity_count/medium.png | Bin 0 -> 362 bytes .../Icons/Light/severity_count/critical.png | Bin 0 -> 385 bytes .../Icons/Light/severity_count/high.png | Bin 0 -> 360 bytes .../Icons/Light/severity_count/low.png | Bin 0 -> 349 bytes .../Icons/Light/severity_count/medium.png | Bin 0 -> 359 bytes .../ast-visual-studio-extension.csproj | 25 +- .../ast_visual_studio_extensionPackage.cs | 9 +- docs/DEVASSIST_POC_VS_DEFAULT_VS_CUSTOM.md | 110 +++++ 23 files changed, 1056 insertions(+), 145 deletions(-) create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.Designer.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoController.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoControllerProvider.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSource.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSourceProvider.cs create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/severity_count/critical.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/severity_count/high.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/severity_count/low.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/severity_count/medium.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/severity_count/critical.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/severity_count/high.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/severity_count/low.png create mode 100644 ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/severity_count/medium.png create mode 100644 docs/DEVASSIST_POC_VS_DEFAULT_VS_CUSTOM.md diff --git a/ast-visual-studio-extension/CxExtension/Commands/TestErrorListCustomizationCommand.cs b/ast-visual-studio-extension/CxExtension/Commands/TestErrorListCustomizationCommand.cs index 862a8eda..ead04e42 100644 --- a/ast-visual-studio-extension/CxExtension/Commands/TestErrorListCustomizationCommand.cs +++ b/ast-visual-studio-extension/CxExtension/Commands/TestErrorListCustomizationCommand.cs @@ -12,7 +12,7 @@ namespace ast_visual_studio_extension.CxExtension.Commands /// /// POC Command to test Visual Studio Error List customization capabilities /// Tests: Custom icons, severity-based styling, context menus, grouping - /// Part of AST-133228 - POC - Problem Window Customization Validation + /// Part of AST-133228 - POC - Problem Window Customization Validation /// internal sealed class TestErrorListCustomizationCommand { diff --git a/ast-visual-studio-extension/CxExtension/Commands/TestGutterIconsDirectCommand.cs b/ast-visual-studio-extension/CxExtension/Commands/TestGutterIconsDirectCommand.cs index 178b75c4..5a0737f3 100644 --- a/ast-visual-studio-extension/CxExtension/Commands/TestGutterIconsDirectCommand.cs +++ b/ast-visual-studio-extension/CxExtension/Commands/TestGutterIconsDirectCommand.cs @@ -14,8 +14,8 @@ namespace ast_visual_studio_extension.CxExtension.Commands { /// - /// Direct test command that manually creates tagger without MEF - /// This bypasses MEF to test if the glyph rendering works at all + /// Test command that injects sample vulnerabilities into the provider-managed taggers + /// so that squiggles, gutter icons, and the rich Quick Info hover (badge, links, etc.) all see the same data. /// internal sealed class TestGutterIconsDirectCommand { @@ -69,39 +69,25 @@ private void Execute(object sender, EventArgs e) var buffer = textView.TextBuffer; - // Create glyph tagger DIRECTLY without MEF - System.Diagnostics.Debug.WriteLine("DevAssist: Creating glyph tagger DIRECTLY (bypassing MEF)"); - var glyphTagger = new DevAssistGlyphTagger(buffer); + // Use the same taggers the editor and Quick Info use (from MEF providers). + // Creating new taggers and storing in buffer.Properties would not be used by + // the error layer or Quick Info source, so the rich hover would never see data. + var glyphTagger = DevAssistGlyphTaggerProvider.GetTaggerForBuffer(buffer); + var errorTagger = DevAssistErrorTaggerProvider.GetTaggerForBuffer(buffer); - // Store it in buffer properties so the glyph factory can find it - try + if (glyphTagger == null || errorTagger == null) { - buffer.Properties.AddProperty(typeof(DevAssistGlyphTagger), glyphTagger); - System.Diagnostics.Debug.WriteLine("DevAssist: Glyph tagger stored in buffer properties"); - } - catch - { - buffer.Properties.RemoveProperty(typeof(DevAssistGlyphTagger)); - buffer.Properties.AddProperty(typeof(DevAssistGlyphTagger), glyphTagger); - System.Diagnostics.Debug.WriteLine("DevAssist: Glyph tagger replaced in buffer properties"); + VsShellUtilities.ShowMessageBox( + this.package, + "DevAssist taggers not ready for this buffer. Ensure the code file is open and focused, then run this command again.", + "Test DevAssist Hover Popup", + OLEMSGICON.OLEMSGICON_WARNING, + OLEMSGBUTTON.OLEMSGBUTTON_OK, + OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); + return; } - // Create error tagger DIRECTLY without MEF (for colored squiggly underlines) - System.Diagnostics.Debug.WriteLine("DevAssist: Creating error tagger DIRECTLY (bypassing MEF)"); - var errorTagger = new DevAssistErrorTagger(buffer); - - // Store it in buffer properties - try - { - buffer.Properties.AddProperty(typeof(DevAssistErrorTagger), errorTagger); - System.Diagnostics.Debug.WriteLine("DevAssist: Error tagger stored in buffer properties"); - } - catch - { - buffer.Properties.RemoveProperty(typeof(DevAssistErrorTagger)); - buffer.Properties.AddProperty(typeof(DevAssistErrorTagger), errorTagger); - System.Diagnostics.Debug.WriteLine("DevAssist: Error tagger replaced in buffer properties"); - } + System.Diagnostics.Debug.WriteLine("DevAssist: Using provider taggers for glyph and error (same as editor and Quick Info)"); // Create test vulnerabilities: Critical, High, Medium, Low for colored marker verification (AST-133227) var vulnerabilities = new List @@ -114,6 +100,9 @@ private void Execute(object sender, EventArgs e) DevAssistTestHelper.CreateSecretsVulnerability(), // Line 12: Critical (dark red) DevAssistTestHelper.CreateContainersVulnerability(), // Line 1: Medium (orange) + // Line 5: second finding on same line -> popup shows severity count row (Critical + High) + new Vulnerability { Id = "TEST-005B", Title = "Second finding on line 5", Severity = SeverityLevel.Critical, LineNumber = 5, Scanner = ScannerType.ASCA, Description = "Multiple findings on one line test." }, + // Additional test vulnerabilities for other severity levels (gutter only, no underline) new Vulnerability { Id = "TEST-006", Severity = SeverityLevel.Unknown, LineNumber = 11, Description = "Test Unknown vulnerability", Scanner = ScannerType.ASCA }, new Vulnerability { Id = "TEST-007", Severity = SeverityLevel.Ok, LineNumber = 13, Description = "Test Ok vulnerability", Scanner = ScannerType.OSS }, diff --git a/ast-visual-studio-extension/CxExtension/CxWindowPackage.cs b/ast-visual-studio-extension/CxExtension/CxWindowPackage.cs index be38d69d..ce342f5b 100644 --- a/ast-visual-studio-extension/CxExtension/CxWindowPackage.cs +++ b/ast-visual-studio-extension/CxExtension/CxWindowPackage.cs @@ -1,4 +1,4 @@ -๏ปฟusing ast_visual_studio_extension.CxExtension.Commands; +using ast_visual_studio_extension.CxExtension.Commands; using log4net; using log4net.Appender; using log4net.Config; @@ -36,7 +36,6 @@ namespace ast_visual_studio_extension.CxExtension /// [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] [InstalledProductRegistration("#14110", "#14112", "1.0", IconResourceID = 14400)] // Info on this package for Help/About - [ProvideMenuResource("Menus1.ctmenu", 1)] [ProvideAutoLoad(UIContextGuids80.NoSolution, PackageAutoLoadFlags.BackgroundLoad)] [ProvideAutoLoad(UIContextGuids80.SolutionExists, PackageAutoLoadFlags.BackgroundLoad)] [ProvideToolWindow(typeof(CxWindow),Style = VsDockStyle.Tabbed,Orientation = ToolWindowOrientation.Right,Window = EnvDTE.Constants.vsWindowKindOutput,Transient = false)] @@ -71,8 +70,7 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke // Command to create Checkmarx extension main window await CxWindowCommand.InitializeAsync(this); - // Test Gutter Icons Direct Command (tool command only, not visible in menu) - await TestGutterIconsDirectCommand.InitializeAsync(this); + // Test DevAssist Hover Popup is registered in ast_visual_studio_extensionPackage so it appears under Tools. // Test Error List Customization Command (POC for AST-133228) await TestErrorListCustomizationCommand.InitializeAsync(this); diff --git a/ast-visual-studio-extension/CxExtension/CxWindowPackage.vsct b/ast-visual-studio-extension/CxExtension/CxWindowPackage.vsct index 3ff79463..497bcb68 100644 --- a/ast-visual-studio-extension/CxExtension/CxWindowPackage.vsct +++ b/ast-visual-studio-extension/CxExtension/CxWindowPackage.vsct @@ -15,12 +15,25 @@ Checkmarx extension toolbar + + + + DevAssist + DevAssist + + + + + + DevAssist + + @@ -44,12 +57,12 @@ --> - - @@ -65,6 +78,8 @@ + + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs index 7247e1a4..7145e180 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs @@ -99,12 +99,12 @@ private bool ShouldShowUnderline(SeverityLevel severity) } /// - /// Builds plain string tooltip for a vulnerability (VS default tooltip). + /// Builds tooltip text for ErrorTag. Minimal so the rich Quick Info popup (inserted first) is the main hover content. /// private static string BuildTooltipText(Vulnerability vulnerability) { if (vulnerability == null) return string.Empty; - return $"[{vulnerability.Severity}] {vulnerability.Title}\n{vulnerability.Description}\nID: {vulnerability.Id}"; + return "Hover for details"; } /// @@ -154,6 +154,16 @@ public void ClearVulnerabilities() System.Diagnostics.Debug.WriteLine("DevAssist Markers: ClearVulnerabilities called"); UpdateVulnerabilities(null); } + + /// + /// Gets vulnerabilities on the given line (0-based) for rich Quick Info hover. + /// + public IReadOnlyList GetVulnerabilitiesForLine(int zeroBasedLineNumber) + { + if (_vulnerabilitiesByLine.TryGetValue(zeroBasedLineNumber, out var list)) + return list; + return Array.Empty(); + } } } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.Designer.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.Designer.cs new file mode 100644 index 00000000..34b32c84 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.Designer.cs @@ -0,0 +1,105 @@ +using System.Reflection; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Markup; +using System.Windows.Media; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +{ + /// + /// Designer partial: declares XAML-named elements and loads the XAML from embedded resource + /// so that the project compiles without relying on MSBuild-generated .g.cs. + /// + public partial class DevAssistHoverPopup + { + // Header: Checkmarx One Assist logo image (not text/label) + internal Image HeaderLogoImage; + internal Button MoreOptionsButton; + // Main line + internal Image SeverityIcon; + internal TextBlock TitleText; + internal Border ScannerBadge; + internal TextBlock ScannerText; + internal StackPanel SeverityCountPanel; + // Description and panels + internal TextBlock DescriptionText; + internal StackPanel PackageInfoPanel; + internal TextBlock PackageNameText; + internal TextBlock CveText; + internal TextBlock CvssScoreText; + internal StackPanel RecommendedVersionPanel; + internal TextBlock RecommendedVersionText; + internal StackPanel RemediationPanel; + internal TextBlock RemediationText; + internal StackPanel IacValuesPanel; + internal TextBlock ExpectedValueText; + internal TextBlock ActualValueText; + // Location and links + internal TextBlock LocationTextBlock; + internal Run LocationText; + internal Border SeparatorLine1; + internal Border SeparatorLine2; + internal System.Windows.Documents.Hyperlink FixWithCxOneAssistLink; + internal System.Windows.Documents.Hyperlink ViewDetailsLink; + internal System.Windows.Documents.Hyperlink IgnoreThisLink; + internal TextBlock IgnoreAllOfThisTypeBlock; + internal System.Windows.Documents.Hyperlink IgnoreAllOfThisTypeLink; + internal StackPanel SecondaryLinksPanel; + internal System.Windows.Documents.Hyperlink NavigateToCodeLink; + internal TextBlock LearnMoreLinkBlock; + internal System.Windows.Documents.Hyperlink LearnMoreLink; + internal TextBlock ApplyFixLinkBlock; + internal System.Windows.Documents.Hyperlink ApplyFixLink; + + private void InitializeComponent() + { + const string resourceName = "ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers.DevAssistHoverPopup.xaml"; + var asm = Assembly.GetExecutingAssembly(); + using (var stream = asm.GetManifestResourceStream(resourceName)) + { + if (stream == null) + { + System.Diagnostics.Debug.WriteLine($"DevAssistHoverPopup: Embedded resource not found: {resourceName}"); + return; + } + var root = (FrameworkElement)XamlReader.Load(stream); + Content = root; + HeaderLogoImage = (Image)root.FindName("HeaderLogoImage"); + MoreOptionsButton = (Button)root.FindName("MoreOptionsButton"); + SeverityIcon = (Image)root.FindName("SeverityIcon"); + TitleText = (TextBlock)root.FindName("TitleText"); + ScannerBadge = (Border)root.FindName("ScannerBadge"); + ScannerText = (TextBlock)root.FindName("ScannerText"); + SeverityCountPanel = (StackPanel)root.FindName("SeverityCountPanel"); + DescriptionText = (TextBlock)root.FindName("DescriptionText"); + PackageInfoPanel = (StackPanel)root.FindName("PackageInfoPanel"); + PackageNameText = (TextBlock)root.FindName("PackageNameText"); + CveText = (TextBlock)root.FindName("CveText"); + CvssScoreText = (TextBlock)root.FindName("CvssScoreText"); + RecommendedVersionPanel = (StackPanel)root.FindName("RecommendedVersionPanel"); + RecommendedVersionText = (TextBlock)root.FindName("RecommendedVersionText"); + RemediationPanel = (StackPanel)root.FindName("RemediationPanel"); + RemediationText = (TextBlock)root.FindName("RemediationText"); + IacValuesPanel = (StackPanel)root.FindName("IacValuesPanel"); + ExpectedValueText = (TextBlock)root.FindName("ExpectedValueText"); + ActualValueText = (TextBlock)root.FindName("ActualValueText"); + LocationTextBlock = (TextBlock)root.FindName("LocationTextBlock"); + LocationText = (Run)root.FindName("LocationText"); + SeparatorLine1 = (Border)root.FindName("SeparatorLine1"); + SeparatorLine2 = (Border)root.FindName("SeparatorLine2"); + FixWithCxOneAssistLink = (System.Windows.Documents.Hyperlink)root.FindName("FixWithCxOneAssistLink"); + ViewDetailsLink = (System.Windows.Documents.Hyperlink)root.FindName("ViewDetailsLink"); + IgnoreThisLink = (System.Windows.Documents.Hyperlink)root.FindName("IgnoreThisLink"); + IgnoreAllOfThisTypeBlock = (TextBlock)root.FindName("IgnoreAllOfThisTypeBlock"); + IgnoreAllOfThisTypeLink = (System.Windows.Documents.Hyperlink)root.FindName("IgnoreAllOfThisTypeLink"); + SecondaryLinksPanel = (StackPanel)root.FindName("SecondaryLinksPanel"); + NavigateToCodeLink = (System.Windows.Documents.Hyperlink)root.FindName("NavigateToCodeLink"); + LearnMoreLinkBlock = (TextBlock)root.FindName("LearnMoreLinkBlock"); + LearnMoreLink = (System.Windows.Documents.Hyperlink)root.FindName("LearnMoreLink"); + ApplyFixLinkBlock = (TextBlock)root.FindName("ApplyFixLinkBlock"); + ApplyFixLink = (System.Windows.Documents.Hyperlink)root.FindName("ApplyFixLink"); + } + } + } +} diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml index b1ed63fe..8e7ab185 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml @@ -1,61 +1,73 @@ - - - + + - - + + - - - - - - + + + + + + + + + + - - - - - + Foreground="#DCDCDC"/> + + + + + + + - @@ -63,33 +75,33 @@ TextWrapping="Wrap" FontSize="12" Margin="0,0,0,8" - Foreground="{DynamicResource {x:Static vsui:EnvironmentColors.ToolTipTextBrushKey}}"/> + Foreground="#DCDCDC"/> Package: @@ -97,7 +109,7 @@ + Foreground="#DCDCDC"> Recommended: Remediation: + Foreground="#DCDCDC"/> Expected Value: @@ -138,7 +150,7 @@ Actual Value: @@ -150,55 +162,67 @@ - + - - - + + + + + + + - + Foreground="#3794FF"> + - - - + - + Foreground="#3794FF"> + - - - + + + + + + + + + + + Foreground="#3794FF"> - - - + + Foreground="#3794FF"> - - + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml.cs index edb64b96..2b7c753b 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml.cs @@ -1,8 +1,13 @@ using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; +using Microsoft.VisualStudio.PlatformUI; using System; +using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Reflection; using System.Windows; using System.Windows.Controls; +using System.Windows.Documents; using System.Windows.Media; using System.Windows.Media.Imaging; @@ -10,49 +15,142 @@ namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers { /// /// Rich hover popup for DevAssist vulnerabilities - /// Similar to JetBrains hover popup with custom formatting, icons, and links + /// Similar to JetBrains hover popup: DevAssist logo, severity icon, badge, description, severity counts, links /// public partial class DevAssistHoverPopup : UserControl { private readonly Vulnerability _vulnerability; + private readonly IReadOnlyList _allForLine; public DevAssistHoverPopup(Vulnerability vulnerability) + : this(vulnerability, new[] { vulnerability }) + { + } + + public DevAssistHoverPopup(Vulnerability first, IReadOnlyList allForLine) { InitializeComponent(); - _vulnerability = vulnerability; + _vulnerability = first ?? throw new ArgumentNullException(nameof(first)); + _allForLine = allForLine ?? new[] { first }; PopulateContent(); } private void PopulateContent() { + if (TitleText == null) + return; // XAML failed to load from embedded resource + // DevAssist logo (JetBrains-style at top of tooltip) + SetDevAssistIcon(); + // Set severity icon SetSeverityIcon(); - // Set title - TitleText.Text = !string.IsNullOrEmpty(_vulnerability.Title) + // Severity count row when multiple findings on same line (JetBrains buildVulnerabilitySection) + BuildSeverityCountRow(); + + // Set title: JetBrains-style "Package@Version - Title" when package present (e.g. node-ipc@10.1.1 - Malicious Package) + string titlePart = !string.IsNullOrEmpty(_vulnerability.Title) ? _vulnerability.Title : !string.IsNullOrEmpty(_vulnerability.RuleName) ? _vulnerability.RuleName : _vulnerability.Description; + if (!string.IsNullOrEmpty(_vulnerability.PackageName)) + { + var version = !string.IsNullOrEmpty(_vulnerability.PackageVersion) ? _vulnerability.PackageVersion : ""; + TitleText.Text = $"{_vulnerability.PackageName}@{version} - {titlePart}"; + } + else + TitleText.Text = titlePart ?? ""; - // Set scanner badge + // Set scanner badge (show as pill below title when used) SetScannerBadge(); + if (!string.IsNullOrEmpty(ScannerText.Text)) + ScannerBadge.Visibility = Visibility.Visible; - // Set description - DescriptionText.Text = _vulnerability.Description; + // Set description (static fallback when empty) + DescriptionText.Text = !string.IsNullOrEmpty(_vulnerability.Description) + ? _vulnerability.Description + : "Vulnerability detected by " + _vulnerability.Scanner + "."; // Set scanner-specific content SetScannerSpecificContent(); // Set location - var fileName = Path.GetFileName(_vulnerability.FilePath); + var fileName = !string.IsNullOrEmpty(_vulnerability.FilePath) ? Path.GetFileName(_vulnerability.FilePath) : "file"; LocationText.Text = $"{fileName}:{_vulnerability.LineNumber}:{_vulnerability.ColumnNumber}"; - // Wire up event handlers for links + // JetBrains-style action links (static handlers for now) + FixWithCxOneAssistLink.Click += FixWithCxOneAssistLink_Click; ViewDetailsLink.Click += ViewDetailsLink_Click; - NavigateToCodeLink.Click += NavigateToCodeLink_Click; + IgnoreThisLink.Click += IgnoreThisLink_Click; + IgnoreAllOfThisTypeLink.Click += IgnoreAllOfThisTypeLink_Click; LearnMoreLink.Click += LearnMoreLink_Click; ApplyFixLink.Click += ApplyFixLink_Click; + + // Mitigation 4: PreviewMouseDown fires before tooltip dismisses on mouse move, so links work reliably. + FixWithCxOneAssistLink.PreviewMouseDown += (s, e) => FixWithCxOneAssistLink_Click(s, e); + ViewDetailsLink.PreviewMouseDown += (s, e) => ViewDetailsLink_Click(s, e); + IgnoreThisLink.PreviewMouseDown += (s, e) => IgnoreThisLink_Click(s, e); + IgnoreAllOfThisTypeLink.PreviewMouseDown += (s, e) => IgnoreAllOfThisTypeLink_Click(s, e); + LearnMoreLink.PreviewMouseDown += (s, e) => LearnMoreLink_Click(s, e); + ApplyFixLink.PreviewMouseDown += (s, e) => ApplyFixLink_Click(s, e); + + // Show "Ignore all of this type" for OSS and Containers (like JetBrains) + if (_vulnerability.Scanner == ScannerType.OSS || _vulnerability.Scanner == ScannerType.Containers) + IgnoreAllOfThisTypeBlock.Visibility = Visibility.Visible; + + if (MoreOptionsButton != null) + MoreOptionsButton.Click += MoreOptionsButton_Click; + + // Apply VS theme so popup respects light/dark (was always dark due to hardcoded XAML colors) + ApplyVsTheme(); + } + + /// Applies Visual Studio theme (light/dark) so popup is not always black in light theme. + private void ApplyVsTheme() + { + try + { + var root = Content as Border; + if (root != null) + { + root.SetResourceReference(Border.BackgroundProperty, EnvironmentColors.ToolTipBrushKey); + root.SetResourceReference(Border.BorderBrushProperty, EnvironmentColors.ToolTipBorderBrushKey); + } + SetResourceRef(TitleText, TextElement.ForegroundProperty, EnvironmentColors.ToolTipTextBrushKey); + SetResourceRef(DescriptionText, TextElement.ForegroundProperty, EnvironmentColors.ToolTipTextBrushKey); + SetResourceRef(LocationTextBlock, TextElement.ForegroundProperty, EnvironmentColors.ToolTipTextBrushKey); + SetResourceRef(LocationText, TextElement.ForegroundProperty, EnvironmentColors.ToolTipTextBrushKey); + SetResourceRef(PackageNameText, TextElement.ForegroundProperty, EnvironmentColors.ToolTipTextBrushKey); + SetResourceRef(CveText, TextElement.ForegroundProperty, EnvironmentColors.ToolTipTextBrushKey); + SetResourceRef(CvssScoreText, TextElement.ForegroundProperty, EnvironmentColors.ToolTipTextBrushKey); + SetResourceRef(RecommendedVersionText, TextElement.ForegroundProperty, EnvironmentColors.ToolTipTextBrushKey); + SetResourceRef(RemediationText, TextElement.ForegroundProperty, EnvironmentColors.ToolTipTextBrushKey); + SetResourceRef(ExpectedValueText, TextElement.ForegroundProperty, EnvironmentColors.ToolTipTextBrushKey); + SetResourceRef(ActualValueText, TextElement.ForegroundProperty, EnvironmentColors.ToolTipTextBrushKey); + SetResourceRef(FixWithCxOneAssistLink, TextElement.ForegroundProperty, EnvironmentColors.ControlLinkTextBrushKey); + SetResourceRef(ViewDetailsLink, TextElement.ForegroundProperty, EnvironmentColors.ControlLinkTextBrushKey); + SetResourceRef(IgnoreThisLink, TextElement.ForegroundProperty, EnvironmentColors.ControlLinkTextBrushKey); + SetResourceRef(IgnoreAllOfThisTypeLink, TextElement.ForegroundProperty, EnvironmentColors.ControlLinkTextBrushKey); + SetResourceRef(NavigateToCodeLink, TextElement.ForegroundProperty, EnvironmentColors.ControlLinkTextBrushKey); + SetResourceRef(LearnMoreLink, TextElement.ForegroundProperty, EnvironmentColors.ControlLinkTextBrushKey); + SetResourceRef(ApplyFixLink, TextElement.ForegroundProperty, EnvironmentColors.ControlLinkTextBrushKey); + if (SeparatorLine1 != null) SeparatorLine1.SetResourceReference(Border.BackgroundProperty, EnvironmentColors.ToolTipBorderBrushKey); + if (SeparatorLine2 != null) SeparatorLine2.SetResourceReference(Border.BackgroundProperty, EnvironmentColors.ToolTipBorderBrushKey); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"DevAssist Hover: ApplyVsTheme failed: {ex.Message}"); + } + } + + private static void SetResourceRef(DependencyObject element, DependencyProperty property, object resourceKey) + { + if (element == null) return; + if (element is FrameworkElement fe) + fe.SetResourceReference(property, resourceKey); + else if (element is FrameworkContentElement fce) + fce.SetResourceReference(property, resourceKey); } private void SetScannerSpecificContent() @@ -200,19 +298,214 @@ private void SetSeverityIcon() string iconFileName = GetSeverityIconFileName(_vulnerability.Severity); if (!string.IsNullOrEmpty(iconFileName)) { + var theme = GetCurrentTheme(); + var source = LoadIconFromAssembly(theme, iconFileName); + if (source != null) + SeverityIcon.Source = source; + } + } + + /// Sets the header to the Checkmarx One Assist logo image (not text/label). + private void SetDevAssistIcon() + { + if (HeaderLogoImage == null) return; + var theme = GetCurrentTheme(); + var source = LoadIconFromAssembly(theme, "cxone_assist.png"); + if (source != null) + HeaderLogoImage.Source = source; + } + + /// + /// Loads a PNG from DevAssist Icons so badge/severity icons show when hosted in VS + /// (pack URI alone can fail; fallback to manifest resource stream). + /// + private static BitmapImage LoadIconFromAssembly(string theme, string fileName) + { + var packPath = $"pack://application:,,,/ast-visual-studio-extension;component/CxExtension/Resources/DevAssist/Icons/{theme}/{fileName}"; + try + { + var streamInfo = System.Windows.Application.GetResourceStream(new Uri(packPath, UriKind.Absolute)); + if (streamInfo != null && streamInfo.Stream != null) + { + using (var ms = new MemoryStream()) + { + streamInfo.Stream.CopyTo(ms); + ms.Position = 0; + var img = new BitmapImage(); + img.BeginInit(); + img.StreamSource = ms; + img.CacheOption = BitmapCacheOption.OnLoad; + img.EndInit(); + img.Freeze(); + return img; + } + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"DevAssist hover: pack URI load failed for {fileName}: {ex.Message}"); + } + + try + { + var asm = Assembly.GetExecutingAssembly(); + var resourceName = asm.GetManifestResourceNames() + .FirstOrDefault(n => n.Replace('\\', '/').EndsWith($"DevAssist/Icons/{theme}/{fileName}", StringComparison.OrdinalIgnoreCase) + || n.Replace('\\', '.').EndsWith($"DevAssist.Icons.{theme}.{fileName}", StringComparison.OrdinalIgnoreCase)); + if (resourceName != null) + { + using (var stream = asm.GetManifestResourceStream(resourceName)) + { + if (stream != null) + { + var img = new BitmapImage(); + img.BeginInit(); + img.StreamSource = stream; + img.CacheOption = BitmapCacheOption.OnLoad; + img.EndInit(); + img.Freeze(); + return img; + } + } + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"DevAssist hover: manifest load failed for {fileName}: {ex.Message}"); + } + + return null; + } + + /// + /// Same as LoadIconFromAssembly but for severity_count subfolder (e.g. severity_count/critical.png). + /// + private static BitmapImage LoadSeverityCountIcon(string theme, string fileName) + { + var packPath = $"pack://application:,,,/ast-visual-studio-extension;component/CxExtension/Resources/DevAssist/Icons/{theme}/severity_count/{fileName}"; + try + { + var streamInfo = System.Windows.Application.GetResourceStream(new Uri(packPath, UriKind.Absolute)); + if (streamInfo != null && streamInfo.Stream != null) + { + using (var ms = new MemoryStream()) + { + streamInfo.Stream.CopyTo(ms); + ms.Position = 0; + var img = new BitmapImage(); + img.BeginInit(); + img.StreamSource = ms; + img.CacheOption = BitmapCacheOption.OnLoad; + img.EndInit(); + img.Freeze(); + return img; + } + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"DevAssist hover: pack URI load failed for severity_count/{fileName}: {ex.Message}"); + } + + try + { + var asm = Assembly.GetExecutingAssembly(); + var subPath = $"severity_count/{fileName}"; + var resourceName = asm.GetManifestResourceNames() + .FirstOrDefault(n => n.Replace('\\', '/').EndsWith($"DevAssist/Icons/{theme}/{subPath}", StringComparison.OrdinalIgnoreCase) + || n.Replace('\\', '.').EndsWith($"DevAssist.Icons.{theme}.severity_count.{fileName}", StringComparison.OrdinalIgnoreCase)); + if (resourceName != null) + { + using (var stream = asm.GetManifestResourceStream(resourceName)) + { + if (stream != null) + { + var img = new BitmapImage(); + img.BeginInit(); + img.StreamSource = stream; + img.CacheOption = BitmapCacheOption.OnLoad; + img.EndInit(); + img.Freeze(); + return img; + } + } + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"DevAssist hover: manifest load failed for severity_count/{fileName}: {ex.Message}"); + } + + return null; + } + + /// + /// Builds severity count row (icon + number per severity) when multiple findings on same line, like JetBrains buildVulnerabilitySection. + /// Uses severity_count icons from JetBrains (critical, high, medium, low). + /// + private void BuildSeverityCountRow() + { + if (_allForLine == null || _allForLine.Count <= 1) + return; + + var counts = _allForLine + .GroupBy(v => v.Severity) + .ToDictionary(g => g.Key, g => g.Count()); + + var theme = GetCurrentTheme(); + + // Order: Malicious, Critical, High, Medium, Low (JetBrains order; severity_count has critical/high/medium/low) + var severitiesToShow = new[] { SeverityLevel.Malicious, SeverityLevel.Critical, SeverityLevel.High, SeverityLevel.Medium, SeverityLevel.Low }; + foreach (var severity in severitiesToShow) + { + if (!counts.TryGetValue(severity, out int count) || count <= 0) + continue; + + string fileName = GetSeverityCountIconFileName(severity); + if (string.IsNullOrEmpty(fileName)) + continue; + + var iconSource = LoadSeverityCountIcon(theme, fileName); + if (iconSource == null) + continue; + try { - // Determine theme (Dark/Light) - var theme = GetCurrentTheme(); - var iconPath = $"pack://application:,,,/ast-visual-studio-extension;component/Resources/Icons/{theme}/{iconFileName}"; - - SeverityIcon.Source = new BitmapImage(new Uri(iconPath)); + var panel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 0, 12, 0) }; + var img = new Image + { + Source = iconSource, + Width = 16, + Height = 16, + VerticalAlignment = VerticalAlignment.Center, + Margin = new Thickness(0, 0, 2, 0) + }; + var tb = new TextBlock + { + Text = count.ToString(), + FontSize = 11, + VerticalAlignment = VerticalAlignment.Center + }; + try + { + tb.SetResourceReference(TextBlock.ForegroundProperty, EnvironmentColors.ToolTipTextBrushKey); + } + catch + { + tb.Foreground = Brushes.Gray; + } + panel.Children.Add(img); + panel.Children.Add(tb); + SeverityCountPanel.Children.Add(panel); } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"Failed to load severity icon: {ex.Message}"); + System.Diagnostics.Debug.WriteLine($"Failed to add severity count for {severity}: {ex.Message}"); } } + + if (SeverityCountPanel.Children.Count > 0) + SeverityCountPanel.Visibility = Visibility.Visible; } private void SetScannerBadge() @@ -245,6 +538,28 @@ private string GetSeverityIconFileName(SeverityLevel severity) } } + /// + /// Returns severity_count icon filename for the given severity (C# 7.3 compatible). + /// + private static string GetSeverityCountIconFileName(SeverityLevel severity) + { + switch (severity) + { + case SeverityLevel.Malicious: + return "critical.png"; // use critical icon for malicious (no separate severity_count in JB) + case SeverityLevel.Critical: + return "critical.png"; + case SeverityLevel.High: + return "high.png"; + case SeverityLevel.Medium: + return "medium.png"; + case SeverityLevel.Low: + return "low.png"; + default: + return null; + } + } + private Color GetScannerBadgeColor(ScannerType scanner) { switch (scanner) @@ -271,10 +586,45 @@ private string GetCurrentTheme() return "Dark"; // Default to Dark for now } + private void FixWithCxOneAssistLink_Click(object sender, RoutedEventArgs e) + { + // Static implementation for now: copy prompt / open AI assist + System.Diagnostics.Debug.WriteLine($"Fix with Checkmarx One Assist for: {_vulnerability.Id}"); + MessageBox.Show( + $"Fix with Checkmarx One Assist\nVulnerability: {_vulnerability.Title}\nID: {_vulnerability.Id}", + "DevAssist", + MessageBoxButton.OK, + MessageBoxImage.Information); + } + private void ViewDetailsLink_Click(object sender, RoutedEventArgs e) { - // TODO: Open DevAssist Findings Window and select this vulnerability - System.Diagnostics.Debug.WriteLine($"View Details clicked for vulnerability: {_vulnerability.Id}"); + System.Diagnostics.Debug.WriteLine($"View details clicked for vulnerability: {_vulnerability.Id}"); + MessageBox.Show( + $"{_vulnerability.Title}\n\n{_vulnerability.Description}\n\nScanner: {_vulnerability.Scanner} | Severity: {_vulnerability.Severity}", + "View details", + MessageBoxButton.OK, + MessageBoxImage.Information); + } + + private void IgnoreThisLink_Click(object sender, RoutedEventArgs e) + { + System.Diagnostics.Debug.WriteLine($"Ignore this vulnerability: {_vulnerability.Id}"); + MessageBox.Show( + $"Ignore this vulnerability: {_vulnerability.Title}\n(Static demo โ€“ ignore not persisted yet)", + "DevAssist", + MessageBoxButton.OK, + MessageBoxImage.Information); + } + + private void IgnoreAllOfThisTypeLink_Click(object sender, RoutedEventArgs e) + { + System.Diagnostics.Debug.WriteLine($"Ignore all of this type: {_vulnerability.Id}"); + MessageBox.Show( + $"Ignore all of this type: {_vulnerability.Title}\n(Static demo โ€“ ignore not persisted yet)", + "DevAssist", + MessageBoxButton.OK, + MessageBoxImage.Information); } private void NavigateToCodeLink_Click(object sender, RoutedEventArgs e) @@ -312,20 +662,22 @@ private void ApplyFixLink_Click(object sender, RoutedEventArgs e) try { // TODO: Implement auto-remediation for SCA packages - // This would update package.json/requirements.txt/etc with recommended version System.Diagnostics.Debug.WriteLine($"DevAssist Hover: Apply Fix for {_vulnerability.PackageName} -> {_vulnerability.RecommendedVersion}"); - - // For now, just show a message - // In the future, this would: - // 1. Detect package manager (npm, pip, maven, etc.) - // 2. Update the package file with recommended version - // 3. Optionally run package manager update command } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"DevAssist Hover: Error applying fix: {ex.Message}"); } } + + /// JetBrains-style header ellipsis: more options for the popup. + private void MoreOptionsButton_Click(object sender, RoutedEventArgs e) + { + e.Handled = true; + System.Diagnostics.Debug.WriteLine($"DevAssist Hover: More options for {_vulnerability.Id}"); + MessageBox.Show("More actions: View details, Ignore, Navigate to Code, Learn More, Apply Fix.", "Checkmarx One Assist", MessageBoxButton.OK, MessageBoxImage.Information); + } + } } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoController.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoController.cs new file mode 100644 index 00000000..8bb67be5 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoController.cs @@ -0,0 +1,117 @@ +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Controls.Primitives; +using System.Windows.Threading; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +{ + /// + /// Shows the JetBrains-like rich hover via a custom WPF Popup on mouse hover + /// (Quick Info often does not display our WPF content; this guarantees the badge/links appear). + /// + internal class DevAssistQuickInfoController : IIntellisenseController + { + private readonly ITextView _textView; + private readonly IList _subjectBuffers; + private readonly DevAssistQuickInfoControllerProvider _provider; + private Popup _hoverPopup; + + internal DevAssistQuickInfoController( + ITextView textView, + IList subjectBuffers, + DevAssistQuickInfoControllerProvider provider) + { + _textView = textView; + _subjectBuffers = subjectBuffers; + _provider = provider; + _textView.MouseHover += OnTextViewMouseHover; + } + + private void OnTextViewMouseHover(object sender, MouseHoverEventArgs e) + { + var point = _textView.BufferGraph.MapDownToFirstMatch( + new SnapshotPoint(_textView.TextSnapshot, e.Position), + PointTrackingMode.Positive, + snapshot => _subjectBuffers.Contains(snapshot.TextBuffer), + PositionAffinity.Predecessor); + + if (!point.HasValue) + return; + + var buffer = point.Value.Snapshot.TextBuffer; + int lineNumber = point.Value.Snapshot.GetLineNumberFromPosition(point.Value.Position); + + var tagger = DevAssistErrorTaggerProvider.GetTaggerForBuffer(buffer); + if (tagger == null) + return; + + var vulnerabilities = tagger.GetVulnerabilitiesForLine(lineNumber); + if (vulnerabilities == null || vulnerabilities.Count == 0) + { + CloseHoverPopup(); + return; + } + + // Trigger default Quick Info so our source adds ContainerElement/ClassifiedText content (description, links). + if (!_provider.QuickInfoBroker.IsQuickInfoActive(_textView)) + { + var triggerPoint = point.Value.Snapshot.CreateTrackingPoint(point.Value.Position, PointTrackingMode.Positive); + _provider.QuickInfoBroker.TriggerQuickInfo(_textView, triggerPoint, trackMouse: true); + } + + // Also show custom WPF popup (badge, full layout) as fallback/alternative. + var wpfView = _textView as IWpfTextView; + if (wpfView?.VisualElement != null) + { + var element = wpfView.VisualElement; + var vulnList = vulnerabilities; + Dispatcher.CurrentDispatcher.Invoke(() => ShowHoverPopup(element, vulnList)); + } + } + + private void ShowHoverPopup(FrameworkElement placementTarget, IReadOnlyList vulnerabilities) + { + CloseHoverPopup(); + + var first = vulnerabilities[0]; + var content = new DevAssistHoverPopup(first, vulnerabilities); + + _hoverPopup = new Popup + { + Child = content, + PlacementTarget = placementTarget, + Placement = PlacementMode.MousePoint, + StaysOpen = false, + AllowsTransparency = false + }; + + _hoverPopup.Closed += (s, args) => _hoverPopup = null; + _hoverPopup.IsOpen = true; + } + + private void CloseHoverPopup() + { + if (_hoverPopup != null) + { + _hoverPopup.IsOpen = false; + _hoverPopup = null; + } + } + + public void Detach(ITextView textView) + { + if (_textView == textView) + { + _textView.MouseHover -= OnTextViewMouseHover; + CloseHoverPopup(); + } + } + + public void ConnectSubjectBuffer(ITextBuffer subjectBuffer) { } + + public void DisconnectSubjectBuffer(ITextBuffer subjectBuffer) { } + } +} diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoControllerProvider.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoControllerProvider.cs new file mode 100644 index 00000000..34505897 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoControllerProvider.cs @@ -0,0 +1,24 @@ +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; +using System.Collections.Generic; +using System.ComponentModel.Composition; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +{ + [Export(typeof(IIntellisenseControllerProvider))] + [Name("DevAssist QuickInfo Controller")] + [ContentType("code")] + [ContentType("text")] + internal class DevAssistQuickInfoControllerProvider : IIntellisenseControllerProvider + { + [Import] + internal IQuickInfoBroker QuickInfoBroker { get; set; } + + public IIntellisenseController TryCreateIntellisenseController(ITextView textView, IList subjectBuffers) + { + return new DevAssistQuickInfoController(textView, subjectBuffers, this); + } + } +} diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSource.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSource.cs new file mode 100644 index 00000000..cd702f02 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSource.cs @@ -0,0 +1,127 @@ +using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Adornments; +using System; +using System.Collections.Generic; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +{ + /// + /// Quick Info source using official content types (ContainerElement, ClassifiedTextElement, ClassifiedTextRun) + /// so the default Quick Info presenter shows description, links, and optional image. + /// + internal class DevAssistQuickInfoSource : IQuickInfoSource + { + internal const bool UseRichHover = true; + + private readonly DevAssistQuickInfoSourceProvider _provider; + private readonly ITextBuffer _buffer; + private bool _disposed; + + public DevAssistQuickInfoSource(DevAssistQuickInfoSourceProvider provider, ITextBuffer buffer) + { + _provider = provider; + _buffer = buffer; + } + + public void AugmentQuickInfoSession(IQuickInfoSession session, IList qiContent, out ITrackingSpan applicableToSpan) + { + applicableToSpan = null; + + if (!UseRichHover) + return; + + SnapshotPoint? triggerPoint = session.GetTriggerPoint(_buffer.CurrentSnapshot); + if (!triggerPoint.HasValue && session.TextView != null) + { + var viewSnapshot = session.TextView.TextSnapshot; + var viewTrigger = session.GetTriggerPoint(viewSnapshot); + if (viewTrigger.HasValue && viewTrigger.Value.Snapshot.TextBuffer != _buffer) + { + var mapped = session.TextView.BufferGraph.MapDownToFirstMatch( + viewTrigger.Value, + Microsoft.VisualStudio.Text.PointTrackingMode.Positive, + sb => sb == _buffer, + Microsoft.VisualStudio.Text.PositionAffinity.Predecessor); + if (mapped.HasValue) + triggerPoint = mapped.Value; + } + else if (viewTrigger.HasValue && viewTrigger.Value.Snapshot.TextBuffer == _buffer) + triggerPoint = viewTrigger; + } + + if (!triggerPoint.HasValue) + return; + + var snapshot = triggerPoint.Value.Snapshot; + int lineNumber = snapshot.GetLineNumberFromPosition(triggerPoint.Value.Position); + + var tagger = DevAssistErrorTaggerProvider.GetTaggerForBuffer(_buffer); + if (tagger == null) + return; + + var vulnerabilities = tagger.GetVulnerabilitiesForLine(lineNumber); + if (vulnerabilities == null || vulnerabilities.Count == 0) + return; + + var first = vulnerabilities[0]; + + object content = BuildQuickInfoContent(first); + if (content == null) + return; + + var line = snapshot.GetLineFromLineNumber(lineNumber); + applicableToSpan = snapshot.CreateTrackingSpan(line.Extent, SpanTrackingMode.EdgeInclusive); + qiContent.Insert(0, content); + } + + /// + /// Builds content using official Quick Info types: ContainerElement, ClassifiedTextElement, ClassifiedTextRun (with navigation). + /// Default presenter resolves these via IViewElementFactoryService for description, links, and theming. + /// + private static object BuildQuickInfoContent(Vulnerability v) + { + if (v == null) return null; + + var title = !string.IsNullOrEmpty(v.Title) ? v.Title : (!string.IsNullOrEmpty(v.RuleName) ? v.RuleName : v.Description); + var description = !string.IsNullOrEmpty(v.Description) ? v.Description : "Vulnerability detected by " + v.Scanner + "."; + + var elements = new List(); + + elements.Add(new ClassifiedTextElement( + new ClassifiedTextRun("keyword", "DevAssist", ClassifiedTextRunStyle.UseClassificationFont), + new ClassifiedTextRun("plain text", " โ€ข ", ClassifiedTextRunStyle.UseClassificationFont), + new ClassifiedTextRun("keyword", v.Scanner.ToString(), ClassifiedTextRunStyle.UseClassificationFont), + new ClassifiedTextRun("plain text", " โ€ข ", ClassifiedTextRunStyle.UseClassificationFont), + new ClassifiedTextRun("keyword", v.Severity.ToString(), ClassifiedTextRunStyle.UseClassificationFont) + )); + + elements.Add(new ClassifiedTextElement( + new ClassifiedTextRun("plain text", title ?? "", ClassifiedTextRunStyle.UseClassificationFont) + )); + + elements.Add(new ClassifiedTextElement( + new ClassifiedTextRun("plain text", description, ClassifiedTextRunStyle.UseClassificationFont) + )); + + elements.Add(new ClassifiedTextElement( + new ClassifiedTextRun("plain text", "Fix with Checkmarx One Assist", ClassifiedTextRunStyle.UseClassificationFont), + new ClassifiedTextRun("plain text", " | ", ClassifiedTextRunStyle.UseClassificationFont), + new ClassifiedTextRun("plain text", "View details", ClassifiedTextRunStyle.UseClassificationFont), + new ClassifiedTextRun("plain text", " | ", ClassifiedTextRunStyle.UseClassificationFont), + new ClassifiedTextRun("plain text", "Ignore this vulnerability", ClassifiedTextRunStyle.UseClassificationFont) + )); + + return new ContainerElement(ContainerElementStyle.Stacked, elements); + } + + public void Dispose() + { + if (_disposed) + return; + GC.SuppressFinalize(this); + _disposed = true; + } + } +} diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSourceProvider.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSourceProvider.cs new file mode 100644 index 00000000..63c07a7d --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSourceProvider.cs @@ -0,0 +1,20 @@ +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Utilities; +using System.ComponentModel.Composition; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +{ + [Export(typeof(IQuickInfoSourceProvider))] + [Name("DevAssist QuickInfo Source")] + [Order(Before = "Default Quick Info Presenter")] + [ContentType("code")] + [ContentType("text")] + internal class DevAssistQuickInfoSourceProvider : IQuickInfoSourceProvider + { + public IQuickInfoSource TryCreateQuickInfoSource(ITextBuffer textBuffer) + { + return new DevAssistQuickInfoSource(this, textBuffer); + } + } +} diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/severity_count/critical.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/severity_count/critical.png new file mode 100644 index 0000000000000000000000000000000000000000..c743dd71c743a01f0add3b8b9d312932a616ccfa GIT binary patch literal 410 zcmV;L0cHM)P)fCJKef*TTkE-6tCgN6No*4SZ~mU3n_>rDP%ygcpJGpE(C|0qWBAaYvtjR2_=ovN0N(|I53&U6;%i_3X8ZIpWZgz&``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{y$F_$B+ufvq6sQ7##)H{xbd_{ddD=m!#yQ zK{o`%6a62t?Pz4}ly!f=;=!`9h`E`2gNm`&R*9-8{<1wwmc0)3-MjLoO@lx4U#Tg2 z&kr<63AjFd@}%(I#yGBkWkFBUyH-loL$4`eOM&fIzopr E0C10v;Q#;t literal 0 HcmV?d00001 diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/severity_count/low.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Dark/severity_count/low.png new file mode 100644 index 0000000000000000000000000000000000000000..81df61cf9c73c19ff6d606deb28087a7b810b4cf GIT binary patch literal 360 zcmV-u0hj)XP)c>vdjOb)*#93teTB*{y5ZPy#e2Jo9gFyYf8d`Oq0OS#UBhFpX9Kd*ce z!0(DA1vJ71@Exb@pG`)VN!07l)r#ZZ9AKvQ@2@AEW0%1;rAs)}Y9+`9O%Tr@G6My1 z{qP$0piu0i+M4bqL|Op72MkZnB@-V1)311WPAKzZ;$bHVf>lNP6Gsf&*_zxOL_rw8 z{gzTNmH;*5Y$%L+%oe++W&?gl2m<9~Sqo;W*y&;(H^~~TZBY)gb(LBG0000``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{(nyw$B+ufvq9EEjE*92K582I9k6QfS2^lg zz`RgNVSxpk`G%(qI~a|GUbD3@h4pVtO@AXM8Eg7A?b+UUFDt(`#5=dB-Z5L%v4~w< zEN?}p;hcUZ6;lZ>6*0-xZf_?gX|LNVk@!u`VUz14#`_Pt<~-LBnlk_Gj&JKfr>vg; zmqTPjdF|^`jE$^L#!Fu|((8NU6_yvBni_4D@g%e+ zN%n<<=jO}vnwNBa`kE89`Vvc0+kqCnS6`VDmNs;q-(_Q8(7ZO7XGg5g2Rmjt8ABJo zh4B+Q7He`hpEz^%%;^t-?+UbzCQtFNIVQ&Z<>T}n%uD~c+6SzjnF0(722WQ%mvv4F FO#rO7jC241 literal 0 HcmV?d00001 diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/severity_count/critical.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/severity_count/critical.png new file mode 100644 index 0000000000000000000000000000000000000000..8b8a7e566bf91265176845a8d05e42ed1fd4bb84 GIT binary patch literal 385 zcmV-{0e=38P)n~|c7c}BIUfbq#ilO?@LG%Q4)wci7JCn%A48mi+ zCZz*(7Z(|`g)*GZ5ub^!Yc^B8eL!<`!FsjgJ-FdJd9L&cTX=u^VDMbuH?>WF1R6>G zG*j>Ox@4;J+vw8lKPGpD&<4LmAi;3DeiNZOS4)Q2KS9oZJKh`o*7cZHFg9yW_5;?4 zz^n@Yb#O)+MWkWG%gJe;@;09-%z>639?CPYQF2Jyu@Y#hJ3iOOs<3)DU|TKmhKeRu f&(i|$ik;OD8HZNFmMN5f00000NkvXXu0mjfs-~l5 literal 0 HcmV?d00001 diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/severity_count/high.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/severity_count/high.png new file mode 100644 index 0000000000000000000000000000000000000000..fc36e9291cf922d3e52f0e5db9aeda747c757b91 GIT binary patch literal 360 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{$EcQ$B+ufvy=S$7#&59?Z2LrW-LBqVy5~G zOQ-0*;0@}Ed&=fDA1U(; zI_TE-^}uuXfIs_>+`4YbUCwezv9V-{!=EP;R%!V?Ig?e>#64ZO4a0DCTl}KOp{?K@As1D4KGgv1_XnrtDnm{r-UW| DDG!lj literal 0 HcmV?d00001 diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/severity_count/low.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/severity_count/low.png new file mode 100644 index 0000000000000000000000000000000000000000..a0574bce17918585465932e870d194e6d4639690 GIT binary patch literal 349 zcmV-j0iyniP)L*Z6uk6r3@-2j;mrJ(kK1^%WLfLp z)vG}^^MzXF&(l0HaGVYdp$w|!U!)SB>iOr}FXNs+L>nYtmqz*wT(K!OF#QjD7szP3 v!bicf{{>Bs0Mt|pca*UQqet;U8*j-Oe(^@6d)#;Q00000NkvXXu0mjfHp!2? literal 0 HcmV?d00001 diff --git a/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/severity_count/medium.png b/ast-visual-studio-extension/CxExtension/Resources/DevAssist/Icons/Light/severity_count/medium.png new file mode 100644 index 0000000000000000000000000000000000000000..c0b5679fdd2fa2835cf73f9badfff90632e93509 GIT binary patch literal 359 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{vS^l$B+ufvq9E;O%4LJ63L5Om-cpjWVm(v z0p}OCN1k7}<{dcC`Gn;W?~64ZH$2l9_HsU8R?*wAXzsozsp;wI_k7aWf0#}w=RYVU z6`#I@OWrw^>-l1a$t7~NRn<$Tg|&FT%ZwDc{fuSCnnf+j`x^WGE(Ll@U648MbK&ac za>k-Yv1WGf1wrhqvU0Y&9c9|7cvrvhX&c*|vsWfdM4#iBccLssRxD?BXtq|u`pRvS zCA@Drvdo_>X3g>NQfX<+r_9D2cI_gjYEku_`nGrGDar8|DsJ~U$Y%X-JDa6UYE$2f z6aSmqGbJ><`OJ-8>qMo_{bW4rp#e|WyvO<<8NxH1UYnP4Yy^e_gQu&X%Q~loCIFhX BhMWKZ literal 0 HcmV?d00001 diff --git a/ast-visual-studio-extension/ast-visual-studio-extension.csproj b/ast-visual-studio-extension/ast-visual-studio-extension.csproj index 05fa58cf..be516ba9 100644 --- a/ast-visual-studio-extension/ast-visual-studio-extension.csproj +++ b/ast-visual-studio-extension/ast-visual-studio-extension.csproj @@ -143,6 +143,9 @@ DevAssistHoverPopup.xaml + + DevAssistHoverPopup.xaml + CxWindowControl.xaml @@ -158,6 +161,10 @@ + + + + @@ -210,6 +217,8 @@ 19.225.1 + + runtime; build; native; contentfiles; analyzers; buildtransitive @@ -350,6 +359,15 @@ + + + + + + + + + @@ -375,10 +393,9 @@ Designer MSBuild:Compile - - Designer - MSBuild:Compile - + + ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers.DevAssistHoverPopup.xaml + diff --git a/ast-visual-studio-extension/ast_visual_studio_extensionPackage.cs b/ast-visual-studio-extension/ast_visual_studio_extensionPackage.cs index 77e16c87..cdef08a5 100644 --- a/ast-visual-studio-extension/ast_visual_studio_extensionPackage.cs +++ b/ast-visual-studio-extension/ast_visual_studio_extensionPackage.cs @@ -1,4 +1,5 @@ -๏ปฟusing Microsoft.VisualStudio.Shell; +using ast_visual_studio_extension.CxExtension.Commands; +using Microsoft.VisualStudio.Shell; using System; using System.Runtime.InteropServices; using System.Threading; @@ -7,6 +8,7 @@ namespace ast_visual_studio_extension { [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] + [ProvideMenuResource("Menus1.ctmenu", 1)] [Guid(ast_visual_studio_extensionPackage.PackageGuidString)] public sealed class ast_visual_studio_extensionPackage : AsyncPackage { @@ -26,9 +28,10 @@ public sealed class ast_visual_studio_extensionPackage : AsyncPackage /// A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method. protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) { - // When initialized asynchronously, the current thread may be a background thread at this point. - // Do any initialization that requires the UI thread after switching to the UI thread. await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + // Register "Test DevAssist Hover Popup" so it appears under Tools (menu from Menus1.ctmenu). + await TestGutterIconsDirectCommand.InitializeAsync(this); } #endregion diff --git a/docs/DEVASSIST_POC_VS_DEFAULT_VS_CUSTOM.md b/docs/DEVASSIST_POC_VS_DEFAULT_VS_CUSTOM.md new file mode 100644 index 00000000..e2f1dc58 --- /dev/null +++ b/docs/DEVASSIST_POC_VS_DEFAULT_VS_CUSTOM.md @@ -0,0 +1,110 @@ +# DevAssist POC: What Visual Studio Gives vs What We Built Ourselves + +A short note in simple words: for each part of the POC, what Visual Studio already does, what it cannot do, and what we built ourselves (and why). + +--- + +## 1. Gutter icon (the small icons left of the code) + +**What Visual Studio already does:** +Visual Studio can show a small icon in the strip on the left of the code (the โ€œgutterโ€) when there is an error or warning on that line. It uses a fixed set of iconsโ€”for example, a red mark for errors and a yellow one for warnings. We could have used that and gotten one standard icon per line. + +**Why we did not use it:** +That built-in option only gives you the same error/warning style. You cannot use your own pictures (e.g. different icons for โ€œCriticalโ€, โ€œHighโ€, โ€œMediumโ€, โ€œLowโ€) or custom styling (like colours or layout). We wanted different icons for each severity so users can see at a glance how serious the finding is (like in JetBrains). So we built our own. + +**What we built instead:** +We built our own way to show icons in that strip. Our code decides which line gets which icon and uses our own picture files (e.g. skull for malicious, shield for high). The main pieces are: **DevAssistGlyphTag** (stores โ€œthis line has this severityโ€), **DevAssistGlyphTagger** (figures out which lines need an icon), and **DevAssistGlyphFactory** (draws our icon in the gutter). + +*Classes (for reference):* +VS provides: **IErrorTag** / **ErrorTag** (default gutter icon for errors), and **IGlyphFactory** / **IGlyphFactoryProvider** (the mechanism to draw in the gutter). We implement: **DevAssistGlyphTag**, **DevAssistGlyphTagger**, **DevAssistGlyphTaggerProvider**, **DevAssistGlyphFactory**, **DevAssistGlyphFactoryProvider**. + +**Why we did not use the older โ€œline markerโ€ API (IVsTextLineMarker):** +Visual Studio also has an older way to put marks in the editor (used e.g. in our ASCA feature): **IVsTextLineMarker**, **IVsTextLines.CreateLineMarker**, **IVsTextMarkerClient**. We did not use that for DevAssist because: (1) It is the old system; the newer one fits better with the rest of our code. (2) It does not support our own images or stylingโ€”you only get the built-in mark types. (3) Our squiggles and hover already use the newer system, so we kept the gutter on the same system for consistency. + +--- + +## 2. Underline (the squiggly line under the code) + +**What Visual Studio already does:** +Visual Studio can draw a squiggly line under text and show a small tooltip when you hover. We use that as-is. We do not draw the squiggle ourselves. + +**What we had to add:** +Visual Studio does not know where our โ€œvulnerabilitiesโ€ are. We had to tell it: โ€œtreat these lines as errors and show this text in the tooltip.โ€ So we wrote **DevAssistErrorTagger**: it feeds the line and the message to Visual Studio, and Visual Studio does the rest (squiggle + tooltip). + +*Classes (for reference):* +VS provides: **IErrorTag**, **ErrorTag** (VS draws the squiggle and tooltip from these). We implement: **DevAssistErrorTagger**, **DevAssistErrorTaggerProvider** (they return **ErrorTag** for each line that has a vulnerability). + +**In short:** We use the default squiggle and tooltip. We only added the logic that says which lines to underline and what text to show. + +--- + +## 3. Problem window (list of findings) + +**What Visual Studio already does:** +Visual Studio has an โ€œError Listโ€ window (View โ†’ Error List) that shows build errors and warnings. Other add-ins can add their items there so everything appears in one list. + +**Why we did not use it (for this POC):** +In this POC we wanted our own window so we can show findings in our own way (e.g. grouped by file, with our own columns and actions). So we built a separate โ€œDevAssist findingsโ€ window: **DevAssistFindingsWindow** and **DevAssistFindingsControl**. It is our own list/tree, not the built-in Error List. + +*Classes (for reference):* +VS provides: **Error List** window (extensions can add items via **IErrorList** and table sources). We implement: **DevAssistFindingsWindow**, **DevAssistFindingsControl**, **ShowFindingsWindowCommand** (our own tool window and UI). + +**In short:** We did not put DevAssist findings into the standard Error List. We built our own window to show them. + +--- + +## 4. Mouse hover popup (the box that appears when you hover) + +**What Visual Studio already does:** +When you hover over code, Visual Studio can show a small popup (Quick Info) with plain textโ€”for example, type or description. We do use that system so our content can appear when Quick Info is shown. + +**Why we also built our own popup:** +That default popup is good for simple text only. It does not reliably show our rich layout: logo, severity icon, coloured badge, clickable links (โ€œFix with Checkmarx One Assistโ€, โ€œView detailsโ€, etc.). So we added our own popup that appears on hover and shows our full design (**DevAssistHoverPopup**). That way users always see the logo, links, and layout we want. + +*Classes (for reference):* +VS provides: **IQuickInfoControllerProvider**, **IQuickInfoSourceProvider**, **IQuickInfoSource** (Quick Info session and default presenter). We implement: **DevAssistQuickInfoSource** (feeds content into Quick Info), **DevAssistQuickInfoController** (opens our popup on hover), **DevAssistQuickInfoSourceProvider**, **DevAssistQuickInfoControllerProvider**, **DevAssistHoverPopup** (our WPF popup with logo, links, layout). + +**In short:** We still use Quick Info for simple content, but we show our own popup as well so the rich hover (logo, links, layout) always appears correctly. + +--- + +## Summary (in one table) + +| What we built | What Visual Studio gives (VS classes) | What we implemented (our classes) | +|----------------------|---------------------------------------|---------------------------------------------------------| +| Gutter icon | **IErrorTag**, **IGlyphFactory**, **IGlyphFactoryProvider** (one standard icon; we didnโ€™t use for gutter) | **DevAssistGlyphTag**, **DevAssistGlyphTagger**, **DevAssistGlyphTaggerProvider**, **DevAssistGlyphFactory**, **DevAssistGlyphFactoryProvider** | +| Underline (squiggle) | **IErrorTag**, **ErrorTag** (VS draws squiggle and tooltip) | **DevAssistErrorTagger**, **DevAssistErrorTaggerProvider** (return ErrorTag per line) | +| Problem window | **Error List**, **IErrorList** (we didnโ€™t use) | **DevAssistFindingsWindow**, **DevAssistFindingsControl**, **ShowFindingsWindowCommand** | +| Hover popup | **IQuickInfoSourceProvider**, **IQuickInfoSource** (Quick Info; we use for simple content) | **DevAssistQuickInfoSource**, **DevAssistQuickInfoController**, **DevAssistQuickInfoControllerProvider**, **DevAssistHoverPopup** | + +--- + +## Where we already use Visual Studioโ€™s built-in features + +| Area | What we use from VS (inbuilt) | +|------|------------------------------| +| **Underline (squiggle)** | We use the built-in error layer fully. We only supply **IErrorTag** / **ErrorTag** (line + tooltip text); VS draws the squiggle and tooltip. No custom drawing. | +| **Gutter** | We use VSโ€™s gutter mechanism (**IGlyphFactory**). We only supply our tag and our icon image; VS hosts the margin and calls our factory. We do not draw the margin ourselves. | +| **Hover** | We use the **Quick Info** system (**IQuickInfoSource**). We push content into it so the default session can show something. We also show our own popup for the rich layout. | +| **Theme** | We use VS theme colours in code (**EnvironmentColors**) and apply them after load so the popup respects light/dark theme. | + +--- + +## Suggestions: where we could use more built-in features + +1. **Problem window โ†’ Error List (IErrorList)** + **Current:** We have our own โ€œDevAssist findingsโ€ window. + **Suggestion:** Also (or instead) add DevAssist findings to the **Error List** via **IErrorList** and a table source. Users would see findings in the same list as build errors, get โ€œgo to lineโ€ and filters for free, and might not need to open a separate window. We could keep our custom window for a richer view and use the Error List for a quick, built-in list. + +2. **Hover โ†’ Rely more on Quick Info** + **Current:** We use Quick Info and a custom WPF popup so the rich hover (logo, links) always shows. + **Suggestion:** If we ever need a simpler experience, we could try feeding only **ContainerElement** / **ClassifiedTextElement** (text + basic formatting) into Quick Info and rely on the default presenter onlyโ€”no custom popup. We would lose the custom logo and clickable links in the hover, but we would use the inbuilt feature only. + +3. **Gutter โ†’ Standard error/warning icon (only if we drop custom icons)** + **Current:** We use a custom glyph tag and factory so we can show different icons per severity. + **Suggestion:** If we ever donโ€™t need severity-specific icons, we could use the same **IErrorTag** we use for the squiggle and let VS show its default error/warning icon in the gutter. That would remove the need for **DevAssistGlyphTag** and **DevAssistGlyphFactory**, but weโ€™d lose custom images (e.g. skull, shield). + +4. **Navigating from Error List** + If we add items to the Error List (suggestion 1), we get built-in โ€œclick to go to lineโ€ and document opening. No extra code needed for that navigation. + +**Summary:** We already use built-in features for the squiggle (fully), the gutter mechanism, and Quick Info. The main place to use *more* built-in is the **Error List (IErrorList)** for the list of findings; the rest is already aligned with VS where possible, with custom parts only where we need custom icons or rich hover layout. From 92b8d6d30934fba812826255b4ba0b880c1c4025 Mon Sep 17 00:00:00 2001 From: Rahul Pidde <206018639+cx-rahul-pidde@users.noreply.github.com> Date: Fri, 13 Feb 2026 14:14:27 +0530 Subject: [PATCH 06/45] Added mockData to show the details --- .../Commands/ShowFindingsWindowCommand.cs | 81 +------ .../DevAssist/Core/DevAssistMockData.cs | 199 ++++++++++++++++++ .../DevAssistTextViewCreationListener.cs | 69 +----- .../DevAssistFindingsControl.xaml | 24 ++- .../ast-visual-studio-extension.csproj | 1 + 5 files changed, 232 insertions(+), 142 deletions(-) create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistMockData.cs diff --git a/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs b/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs index d67601f0..dc8c9780 100644 --- a/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs +++ b/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs @@ -7,6 +7,7 @@ using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Task = System.Threading.Tasks.Task; +using ast_visual_studio_extension.CxExtension.DevAssist.Core; using ast_visual_studio_extension.CxExtension.DevAssist.UI.FindingsWindow; namespace ast_visual_studio_extension.CxExtension.Commands @@ -84,81 +85,13 @@ private void PopulateTestData(DevAssistFindingsControl control) { if (control == null) return; - var fileNodes = new ObservableCollection(); + // Same common mock data as gutter, underline, and popup hover (one source of truth) + var vulnerabilities = DevAssistMockData.GetCommonVulnerabilities(DevAssistMockData.DefaultFilePath); + var fileNodes = DevAssistMockData.BuildFileNodesFromVulnerabilities( + vulnerabilities, + loadSeverityIcon: LoadSeverityIcon, + loadFileIcon: () => LoadIcon("document.png")); - // File 1: go.mod with High and Medium vulnerabilities - var file1 = new FileNode - { - FileName = "go.mod", - FilePath = "C:\\Projects\\TestProject\\go.mod", - FileIcon = LoadIcon("document.png") - }; - - file1.Vulnerabilities.Add(new VulnerabilityNode - { - Severity = "High", - SeverityIcon = LoadSeverityIcon("High"), - PackageName = "helm.sh/helm/v3", - PackageVersion = "v3.18.2", - Line = 38, - Column = 1, - FilePath = file1.FilePath - }); - - file1.Vulnerabilities.Add(new VulnerabilityNode - { - Severity = "Medium", - SeverityIcon = LoadSeverityIcon("Medium"), - PackageName = "github.com/docker/docker", - PackageVersion = "v20.10.7", - Line = 42, - Column = 1, - FilePath = file1.FilePath - }); - - // Add severity counts - file1.SeverityCounts.Add(new SeverityCount { Severity = "High", Count = 1, Icon = LoadSeverityIcon("High") }); - file1.SeverityCounts.Add(new SeverityCount { Severity = "Medium", Count = 1, Icon = LoadSeverityIcon("Medium") }); - - fileNodes.Add(file1); - - // File 2: package.json with Malicious and Low vulnerabilities - var file2 = new FileNode - { - FileName = "package.json", - FilePath = "C:\\Projects\\TestProject\\package.json", - FileIcon = LoadIcon("document.png") - }; - - file2.Vulnerabilities.Add(new VulnerabilityNode - { - Severity = "Malicious", - SeverityIcon = LoadSeverityIcon("Malicious"), - PackageName = "evil-package", - PackageVersion = "1.0.0", - Line = 15, - Column = 4, - FilePath = file2.FilePath - }); - - file2.Vulnerabilities.Add(new VulnerabilityNode - { - Severity = "Low", - SeverityIcon = LoadSeverityIcon("Low"), - PackageName = "old-library", - PackageVersion = "2.3.1", - Line = 23, - Column = 4, - FilePath = file2.FilePath - }); - - // Add severity counts - file2.SeverityCounts.Add(new SeverityCount { Severity = "Malicious", Count = 1, Icon = LoadSeverityIcon("Malicious") }); - file2.SeverityCounts.Add(new SeverityCount { Severity = "Low", Count = 1, Icon = LoadSeverityIcon("Low") }); - - fileNodes.Add(file2); - - // Use SetAllFileNodes to enable filtering control.SetAllFileNodes(fileNodes); } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistMockData.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistMockData.cs new file mode 100644 index 00000000..a46531b6 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistMockData.cs @@ -0,0 +1,199 @@ +using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; +using ast_visual_studio_extension.CxExtension.DevAssist.UI.FindingsWindow; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Windows.Media; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core +{ + /// + /// Common mock data used to demonstrate all four POC features: + /// underline (squiggle), gutter icon, problem window, and popup hover. + /// One source of truth so editor and findings window show the same data. + /// + public static class DevAssistMockData + { + /// Default file path used for mock vulnerabilities (editor and findings window). + public const string DefaultFilePath = "Program.cs"; + + /// + /// Returns the common list of mock vulnerabilities used for: + /// - Gutter icons (severity-specific icons on lines 1, 3, 5, 7, 9) + /// - Underline (squiggles on the same lines) + /// - Popup hover (hover over those lines to see rich popup with OSS/ASCA content) + /// - Problem window (when converted to FileNodes via BuildFileNodesFromVulnerabilities) + /// + /// Optional file path; if null or empty, uses DefaultFilePath. + public static List GetCommonVulnerabilities(string filePath = null) + { + var path = string.IsNullOrEmpty(filePath) ? DefaultFilePath : filePath; + + return new List + { + // Line 1 โ€“ Malicious (OSS) โ€“ shows in gutter, underline, hover, problem window + new Vulnerability + { + Id = "POC-001", + Title = "Malicious Package", + Description = "Test Malicious vulnerability โ€“ known malicious package in dependencies.", + Severity = SeverityLevel.Malicious, + Scanner = ScannerType.OSS, + LineNumber = 1, + ColumnNumber = 0, + FilePath = path, + PackageName = "node-ipc", + PackageVersion = "10.1.1", + RecommendedVersion = "10.2.0", + CveName = "CVE-Malicious-Example", + CvssScore = 9.8, + LearnMoreUrl = "https://example.com/cve" + }, + // Line 3 โ€“ Critical (ASCA) + new Vulnerability + { + Id = "POC-002", + Title = "SQL Injection", + Description = "Test Critical vulnerability โ€“ user input concatenated into SQL without sanitization.", + Severity = SeverityLevel.Critical, + Scanner = ScannerType.ASCA, + LineNumber = 3, + ColumnNumber = 0, + FilePath = path, + RuleName = "SQL_INJECTION", + RemediationAdvice = "Use parameterized queries or prepared statements." + }, + // Line 5 โ€“ High (OSS) โ€“ first of two on same line (severity count in popup) + new Vulnerability + { + Id = "POC-003", + Title = "High-Risk Package", + Description = "Test High vulnerability โ€“ vulnerable version of package.", + Severity = SeverityLevel.High, + Scanner = ScannerType.OSS, + LineNumber = 5, + ColumnNumber = 0, + FilePath = path, + PackageName = "lodash", + PackageVersion = "4.17.15", + RecommendedVersion = "4.17.21", + CveName = "CVE-2020-8203", + CvssScore = 7.4 + }, + // Line 5 โ€“ Medium (second on same line) + new Vulnerability + { + Id = "POC-004", + Title = "Medium Severity Finding", + Description = "Test Medium vulnerability on same line as High.", + Severity = SeverityLevel.Medium, + Scanner = ScannerType.ASCA, + LineNumber = 5, + ColumnNumber = 0, + FilePath = path, + RuleName = "WEAK_CRYPTO", + RemediationAdvice = "Use a stronger algorithm." + }, + // Line 7 โ€“ Medium (OSS) + new Vulnerability + { + Id = "POC-005", + Title = "Outdated Dependency", + Description = "Test Medium vulnerability โ€“ dependency has a known issue.", + Severity = SeverityLevel.Medium, + Scanner = ScannerType.OSS, + LineNumber = 7, + ColumnNumber = 0, + FilePath = path, + PackageName = "axios", + PackageVersion = "0.21.0", + RecommendedVersion = "0.27.0" + }, + // Line 9 โ€“ Low + new Vulnerability + { + Id = "POC-006", + Title = "Low Severity", + Description = "Test Low vulnerability โ€“ minor finding.", + Severity = SeverityLevel.Low, + Scanner = ScannerType.OSS, + LineNumber = 9, + ColumnNumber = 0, + FilePath = path, + PackageName = "debug", + PackageVersion = "2.6.9" + } + }; + } + + /// + /// Builds the Findings window tree (FileNode with VulnerabilityNodes) from the common vulnerability list. + /// Use the same list from GetCommonVulnerabilities so problem window shows the same data as gutter/underline/hover. + /// + /// Typically from GetCommonVulnerabilities(). + /// Callback to get severity icon (e.g. from ShowFindingsWindowCommand). Can be null; then SeverityIcon is not set. + /// Callback to get file icon. Can be null. + public static ObservableCollection BuildFileNodesFromVulnerabilities( + List vulnerabilities, + Func loadSeverityIcon = null, + Func loadFileIcon = null) + { + if (vulnerabilities == null || vulnerabilities.Count == 0) + return new ObservableCollection(); + + var fileIcon = loadFileIcon?.Invoke(); + var grouped = vulnerabilities + .GroupBy(v => string.IsNullOrEmpty(v.FilePath) ? DefaultFilePath : v.FilePath) + .OrderBy(g => g.Key); + + var fileNodes = new ObservableCollection(); + + foreach (var group in grouped) + { + var filePath = group.Key; + var fileName = System.IO.Path.GetFileName(filePath); + if (string.IsNullOrEmpty(fileName)) fileName = filePath; + + var fileNode = new FileNode + { + FileName = fileName, + FilePath = filePath, + FileIcon = fileIcon + }; + + foreach (var v in group) + { + var vulnNode = new VulnerabilityNode + { + Severity = v.Severity.ToString(), + SeverityIcon = loadSeverityIcon?.Invoke(v.Severity.ToString()), + Description = v.Title ?? v.Description, + PackageName = v.PackageName, + PackageVersion = v.PackageVersion, + Line = v.LineNumber, + Column = v.ColumnNumber, + FilePath = v.FilePath + }; + fileNode.Vulnerabilities.Add(vulnNode); + } + + // Severity counts for badges + var severityCounts = fileNode.Vulnerabilities + .GroupBy(n => n.Severity) + .Select(g => new SeverityCount + { + Severity = g.Key, + Count = g.Count(), + Icon = loadSeverityIcon?.Invoke(g.Key) + }); + foreach (var sc in severityCounts) + fileNode.SeverityCounts.Add(sc); + + fileNodes.Add(fileNode); + } + + return fileNodes; + } + } +} diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs index 23a903ff..85ddd0c6 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs @@ -3,7 +3,7 @@ using System.ComponentModel.Composition; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities; -using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; +using ast_visual_studio_extension.CxExtension.DevAssist.Core; using ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers; namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons @@ -56,73 +56,14 @@ public void TextViewCreated(IWpfTextView textView) if (glyphTagger != null && errorTagger != null) { - System.Diagnostics.Debug.WriteLine("DevAssist: Both taggers found, adding test vulnerabilities"); + System.Diagnostics.Debug.WriteLine("DevAssist: Both taggers found, adding common mock data (underline, gutter, hover, problem window)"); - // Create test vulnerabilities - var vulnerabilities = new List - { - new Vulnerability - { - Id = "TEST-001", - Severity = SeverityLevel.Malicious, - LineNumber = 1, - Description = "Test Malicious vulnerability" - }, - new Vulnerability - { - Id = "TEST-002", - Severity = SeverityLevel.Critical, - LineNumber = 3, - Description = "Test Critical vulnerability" - }, - new Vulnerability - { - Id = "TEST-003", - Severity = SeverityLevel.High, - LineNumber = 5, - Description = "Test High vulnerability" - }, - new Vulnerability - { - Id = "TEST-004", - Severity = SeverityLevel.Medium, - LineNumber = 7, - Description = "Test Medium vulnerability" - }, - new Vulnerability - { - Id = "TEST-005", - Severity = SeverityLevel.Low, - LineNumber = 9, - Description = "Test Low vulnerability" - }, - new Vulnerability - { - Id = "TEST-006", - Severity = SeverityLevel.Ok, - LineNumber = 11, - Description = "Test OK" - }, - new Vulnerability - { - Id = "TEST-007", - Severity = SeverityLevel.Unknown, - LineNumber = 13, - Description = "Test UnKnown" - }, - new Vulnerability - { - Id = "TEST-008", - Severity = SeverityLevel.Ignored, - LineNumber = 15, - Description = "Test Ignored" - } - }; + // One common mock data for all POC features: underline, gutter icon, popup hover, and problem window + var vulnerabilities = DevAssistMockData.GetCommonVulnerabilities(filePath: null); - // Update both taggers with the same vulnerabilities glyphTagger.UpdateVulnerabilities(vulnerabilities); errorTagger.UpdateVulnerabilities(vulnerabilities); - System.Diagnostics.Debug.WriteLine("DevAssist: Test vulnerabilities added to both taggers successfully"); + System.Diagnostics.Debug.WriteLine("DevAssist: Common mock vulnerabilities added to both taggers successfully"); } else { diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml b/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml index 0613cb55..3b3ceb29 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml +++ b/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml @@ -277,12 +277,17 @@ Background="{DynamicResource {x:Static vsui:EnvironmentColors.ToolWindowBackgroundBrushKey}}" Foreground="{DynamicResource {x:Static vsui:EnvironmentColors.ToolWindowTextBrushKey}}"> - + - + - + + + + + + @@ -291,11 +296,22 @@ - + + + + + + + + + + + + diff --git a/ast-visual-studio-extension/ast-visual-studio-extension.csproj b/ast-visual-studio-extension/ast-visual-studio-extension.csproj index be516ba9..8a0d8a25 100644 --- a/ast-visual-studio-extension/ast-visual-studio-extension.csproj +++ b/ast-visual-studio-extension/ast-visual-studio-extension.csproj @@ -166,6 +166,7 @@ + From 7f3debf12086b3f2bb77b4d86155e2669a0da217 Mon Sep 17 00:00:00 2001 From: Rahul Pidde <206018639+cx-rahul-pidde@users.noreply.github.com> Date: Mon, 16 Feb 2026 21:40:02 +0530 Subject: [PATCH 07/45] Changes related to problem window --- .../DevAssistErrorHandlerTests.cs | 99 ++++++++++ .../Commands/ShowFindingsWindowCommand.cs | 11 +- .../Core/DevAssistDisplayCoordinator.cs | 184 ++++++++++++++++++ .../DevAssist/Core/DevAssistErrorHandler.cs | 70 +++++++ .../Core/GutterIcons/DevAssistGlyphFactory.cs | 2 +- .../Core/GutterIcons/DevAssistGlyphTagger.cs | 74 ++++--- .../DevAssistTextViewCreationListener.cs | 22 ++- .../Core/Markers/DevAssistErrorTagger.cs | 75 ++++--- .../Markers/DevAssistQuickInfoController.cs | 113 ++++++----- .../DevAssistFindingsControl.xaml.cs | 57 +++++- .../ast-visual-studio-extension.csproj | 2 + 11 files changed, 597 insertions(+), 112 deletions(-) create mode 100644 ast-visual-studio-extension-tests/cx-unit-tests/cx-extension-tests/DevAssistErrorHandlerTests.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistDisplayCoordinator.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorHandler.cs diff --git a/ast-visual-studio-extension-tests/cx-unit-tests/cx-extension-tests/DevAssistErrorHandlerTests.cs b/ast-visual-studio-extension-tests/cx-unit-tests/cx-extension-tests/DevAssistErrorHandlerTests.cs new file mode 100644 index 00000000..be5ecb0e --- /dev/null +++ b/ast-visual-studio-extension-tests/cx-unit-tests/cx-extension-tests/DevAssistErrorHandlerTests.cs @@ -0,0 +1,99 @@ +using System; +using Xunit; +using ast_visual_studio_extension.CxExtension.DevAssist.Core; + +namespace ast_visual_studio_extension_tests.cx_unit_tests.cx_extension_tests +{ + /// + /// Unit tests for DevAssist error-handling scenarios. + /// Verifies that TryRun, TryGet, and LogAndSwallow never rethrow and behave correctly. + /// + public class DevAssistErrorHandlerTests + { + [Fact] + public void TryRun_ReturnsTrue_WhenActionSucceeds() + { + bool executed = false; + bool result = DevAssistErrorHandler.TryRun(() => { executed = true; }, "Test"); + + Assert.True(result); + Assert.True(executed); + } + + [Fact] + public void TryRun_ReturnsFalse_WhenActionThrows() + { + bool result = DevAssistErrorHandler.TryRun(() => throw new InvalidOperationException("Test exception"), "Test"); + + Assert.False(result); + } + + [Fact] + public void TryRun_DoesNotRethrow_WhenActionThrows() + { + var ex = Record.Exception(() => + DevAssistErrorHandler.TryRun(() => throw new InvalidOperationException("Test"), "Test")); + + Assert.Null(ex); + } + + [Fact] + public void TryRun_HandlesNullAction_WithoutThrowing() + { + bool result = DevAssistErrorHandler.TryRun(null, "Test"); + + Assert.True(result); + } + + [Fact] + public void TryGet_ReturnsValue_WhenFunctionSucceeds() + { + int value = DevAssistErrorHandler.TryGet(() => 42, "Test", 0); + + Assert.Equal(42, value); + } + + [Fact] + public void TryGet_ReturnsDefault_WhenFunctionThrows() + { + int value = DevAssistErrorHandler.TryGet(() => throw new InvalidOperationException("Test"), "Test", 99); + + Assert.Equal(99, value); + } + + [Fact] + public void TryGet_DoesNotRethrow_WhenFunctionThrows() + { + var ex = Record.Exception(() => + DevAssistErrorHandler.TryGet(() => throw new InvalidOperationException("Test"), "Test", 0)); + + Assert.Null(ex); + } + + [Fact] + public void TryGet_ReturnsDefaultT_WhenFunctionIsNull() + { + int value = DevAssistErrorHandler.TryGet(null, "Test", 7); + + Assert.Equal(7, value); + } + + [Fact] + public void LogAndSwallow_DoesNotThrow_WhenGivenException() + { + var ex = Record.Exception(() => + DevAssistErrorHandler.LogAndSwallow(new InvalidOperationException("Test"), "TestContext")); + + Assert.Null(ex); + } + + [Fact] + public void LogAndSwallow_DoesNotThrow_WhenGivenNull() + { + var ex = Record.Exception(() => + DevAssistErrorHandler.LogAndSwallow(null, "TestContext")); + + Assert.Null(ex); + } + } +} diff --git a/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs b/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs index dc8c9780..b7baea9a 100644 --- a/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs +++ b/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs @@ -85,13 +85,20 @@ private void PopulateTestData(DevAssistFindingsControl control) { if (control == null) return; - // Same common mock data as gutter, underline, and popup hover (one source of truth) + // Use coordinator's current findings (from last UpdateFindings) so problem window matches gutter/underline + var current = DevAssistDisplayCoordinator.GetCurrentFindings(); + if (current != null && current.Count > 0) + { + DevAssistDisplayCoordinator.RefreshProblemWindow(control, LoadSeverityIcon, () => LoadIcon("document.png")); + return; + } + + // No current findings (no file opened yet), show mock data so the window is not empty var vulnerabilities = DevAssistMockData.GetCommonVulnerabilities(DevAssistMockData.DefaultFilePath); var fileNodes = DevAssistMockData.BuildFileNodesFromVulnerabilities( vulnerabilities, loadSeverityIcon: LoadSeverityIcon, loadFileIcon: () => LoadIcon("document.png")); - control.SetAllFileNodes(fileNodes); } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistDisplayCoordinator.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistDisplayCoordinator.cs new file mode 100644 index 00000000..88a3a83f --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistDisplayCoordinator.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Reflection; +using Microsoft.VisualStudio.Text; +using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; +using ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons; +using ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers; +using ast_visual_studio_extension.CxExtension.DevAssist.UI.FindingsWindow; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core +{ + /// + /// Single coordinator for DevAssist display (Option B). + /// Takes one List<Vulnerability> and updates gutter, underline, and problem window in one go. + /// Stores issues per file (like JetBrains ProblemHolderService) and notifies via IssuesUpdated so the findings window can subscribe and stay in sync. + /// + public static class DevAssistDisplayCoordinator + { + private static readonly object _lock = new object(); + private static Dictionary> _fileToIssues = new Dictionary>(StringComparer.OrdinalIgnoreCase); + + /// + /// Normalizes a file path for use as the per-file map key (same file always maps to the same key). + /// + private static string NormalizePath(string path) + { + if (string.IsNullOrEmpty(path)) return path; + try + { + return Path.GetFullPath(path); + } + catch + { + return path; + } + } + + /// + /// Gets the file path for the buffer when it is backed by a file (e.g. for passing to mock data or scan). + /// Returns null if the buffer has no associated document. + /// + public static string GetFilePathForBuffer(ITextBuffer buffer) => TryGetFilePathFromBuffer(buffer); + + /// + /// Tries to get the file path for the buffer from ITextDocument (when the buffer is backed by a file). + /// Uses reflection so we don't require an extra assembly reference. + /// + private static string TryGetFilePathFromBuffer(ITextBuffer buffer) + { + if (buffer?.Properties == null) return null; + try + { + // ITextDocument is in Microsoft.VisualStudio.Text.Logic (or Text.Data); key is often the type + var docType = Type.GetType("Microsoft.VisualStudio.Text.ITextDocument, Microsoft.VisualStudio.Text.Logic", false) + ?? Type.GetType("Microsoft.VisualStudio.Text.ITextDocument, Microsoft.VisualStudio.Text.Data", false); + if (docType == null) return null; + if (!buffer.Properties.TryGetProperty(docType, out object doc) || doc == null) return null; + var pathProp = docType.GetProperty("FilePath", BindingFlags.Public | BindingFlags.Instance); + return pathProp?.GetValue(doc) as string; + } + catch + { + return null; + } + } + + /// + /// Raised when issues are updated (any file). Subscribers (e.g. findings window) can refresh to stay in sync (JetBrains ISSUE_TOPIC-like). + /// + public static event Action>> IssuesUpdated; + + /// + /// Gets all issues by file path (like JetBrains ProblemHolderService.GetAllIssues). + /// + public static IReadOnlyDictionary> GetAllIssuesByFile() + { + lock (_lock) + { + var copy = new Dictionary>(_fileToIssues.Count, StringComparer.OrdinalIgnoreCase); + foreach (var kv in _fileToIssues) + copy[kv.Key] = new List(kv.Value); + return copy; + } + } + + /// + /// Gets the current findings as a single flattened list (for backward compatibility and for BuildFileNodesFromVulnerabilities). + /// + public static List GetCurrentFindings() + { + lock (_lock) + { + if (_fileToIssues.Count == 0) return null; + var flat = new List(); + foreach (var list in _fileToIssues.Values) + flat.AddRange(list); + return flat; + } + } + + /// + /// 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 (JetBrains-like). + /// + /// Text buffer for the open file (used to get glyph and error taggers). + /// Findings to show; can be null or empty to clear for this file. + /// Optional. File path for per-file storage. If null, uses first vulnerability's FilePath when list is non-empty. + public static void UpdateFindings(ITextBuffer buffer, List vulnerabilities, string filePath = null) + { + if (buffer == null) + { + System.Diagnostics.Debug.WriteLine("DevAssistDisplayCoordinator: buffer is null"); + return; + } + + var list = vulnerabilities ?? new List(); + + // 1. Update gutter + var glyphTagger = DevAssistErrorHandler.TryGet(() => DevAssistGlyphTaggerProvider.GetTaggerForBuffer(buffer), "Coordinator.GetGlyphTagger", null); + if (glyphTagger != null) + DevAssistErrorHandler.TryRun(() => glyphTagger.UpdateVulnerabilities(list), "Coordinator.GlyphTagger.UpdateVulnerabilities"); + else + System.Diagnostics.Debug.WriteLine("DevAssistDisplayCoordinator: glyph tagger not found for buffer"); + + // 2. Update underline + var errorTagger = DevAssistErrorHandler.TryGet(() => DevAssistErrorTaggerProvider.GetTaggerForBuffer(buffer), "Coordinator.GetErrorTagger", null); + if (errorTagger != null) + DevAssistErrorHandler.TryRun(() => errorTagger.UpdateVulnerabilities(list), "Coordinator.ErrorTagger.UpdateVulnerabilities"); + else + System.Diagnostics.Debug.WriteLine("DevAssistDisplayCoordinator: error tagger not found for buffer"); + + // 3. Store per file and notify (JetBrains ProblemHolderService + ISSUE_TOPIC-like) + DevAssistErrorHandler.TryRun(() => + { + // Prefer explicit filePath, then path from buffer (so we can clear when list is empty), then first vulnerability + string resolvedPath = filePath ?? TryGetFilePathFromBuffer(buffer) ?? (list.Count > 0 ? list[0].FilePath : null); + string key = NormalizePath(resolvedPath); + if (string.IsNullOrEmpty(key)) return; + + IReadOnlyDictionary> snapshot; + lock (_lock) + { + if (list.Count == 0) + _fileToIssues.Remove(key); + else + _fileToIssues[key] = new List(list); + var copy = new Dictionary>(_fileToIssues.Count, StringComparer.OrdinalIgnoreCase); + foreach (var kv in _fileToIssues) + copy[kv.Key] = new List(kv.Value); + snapshot = copy; + } + IssuesUpdated?.Invoke(snapshot); + }, "Coordinator.StoreCurrentFindings"); + + System.Diagnostics.Debug.WriteLine($"DevAssistDisplayCoordinator: updated gutter, underline, and per-file findings ({list.Count} for file)"); + } + + /// + /// Updates the problem window control with the current findings (builds FileNodes and calls SetAllFileNodes). + /// Call this when the Findings window is shown so it displays the same data as gutter/underline. + /// + /// The DevAssist Findings control to update. + /// Optional; if null, severity icons are not set. + /// Optional; if null, file icon is not set. + public static void RefreshProblemWindow( + DevAssistFindingsControl findingsControl, + Func loadSeverityIcon = null, + Func loadFileIcon = null) + { + if (findingsControl == null) return; + + DevAssistErrorHandler.TryRun(() => + { + List current = GetCurrentFindings(); + ObservableCollection fileNodes = current != null && current.Count > 0 + ? DevAssistMockData.BuildFileNodesFromVulnerabilities(current, loadSeverityIcon, loadFileIcon) + : new ObservableCollection(); + findingsControl.SetAllFileNodes(fileNodes); + }, "Coordinator.RefreshProblemWindow"); + } + } +} diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorHandler.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorHandler.cs new file mode 100644 index 00000000..0547b784 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorHandler.cs @@ -0,0 +1,70 @@ +using System; +using System.Diagnostics; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core +{ + /// + /// Central error handling for DevAssist so third-party plugins or VS errors + /// do not crash gutter, underline, problem window, or hover. + /// We log and swallow exceptions at VS callback boundaries (GetTags, GenerateGlyph, etc.). + /// + internal static class DevAssistErrorHandler + { + private const string Category = "DevAssist"; + + /// + /// Logs the exception and returns without rethrowing. + /// Use at VS/extension callback boundaries (GetTags, GenerateGlyph, event handlers) + /// so our code never throws into VS or other extensions. + /// + /// The exception (can be from our code, third-party, or VS). + /// Short description of where it happened (e.g. "GlyphTagger.GetTags"). + public static void LogAndSwallow(Exception ex, string context) + { + if (ex == null) return; + try + { + Debug.WriteLine($"[{Category}] {context}: {ex.Message}"); + Debug.WriteLine($"[{Category}] {ex.StackTrace}"); + } + catch + { + // Do not throw from error handler + } + } + + /// + /// Wraps an action in try-catch; on exception logs and swallows (does not rethrow). + /// Returns true if the action ran without exception, false otherwise. + /// + public static bool TryRun(Action action, string context) + { + try + { + action?.Invoke(); + return true; + } + catch (Exception ex) + { + LogAndSwallow(ex, context); + return false; + } + } + + /// + /// Tries to run a function; on exception logs, swallows, and returns default(T). + /// + public static T TryGet(Func func, string context, T defaultValue = default) + { + try + { + return func != null ? func() : defaultValue; + } + catch (Exception ex) + { + LogAndSwallow(ex, context); + return defaultValue; + } + } + } +} diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphFactory.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphFactory.cs index 40958e18..7efdeb66 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphFactory.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphFactory.cs @@ -64,7 +64,7 @@ public UIElement GenerateGlyph(IWpfTextViewLine line, IGlyphTag tag) } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"DevAssist: Icon loading failed: {ex.Message}"); + DevAssistErrorHandler.LogAndSwallow(ex, "GlyphFactory.GenerateGlyph"); return null; } } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTagger.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTagger.cs index 0e8d91b4..fd0c14d2 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTagger.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTagger.cs @@ -30,49 +30,67 @@ public DevAssistGlyphTagger(ITextBuffer buffer) public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) { - System.Diagnostics.Debug.WriteLine($"DevAssist: GetTags called - spans count: {spans.Count}, vulnerabilities count: {_vulnerabilitiesByLine.Count}"); + var result = new List>(); + System.Diagnostics.Debug.WriteLine($"DevAssist: GetTags called - spans count: {spans?.Count ?? 0}, vulnerabilities count: {_vulnerabilitiesByLine.Count}"); - if (spans.Count == 0 || _vulnerabilitiesByLine.Count == 0) + if (spans == null || spans.Count == 0 || _vulnerabilitiesByLine.Count == 0) { System.Diagnostics.Debug.WriteLine($"DevAssist: GetTags returning early - no spans or vulnerabilities"); - yield break; + return result; } - var snapshot = spans[0].Snapshot; + ITextSnapshot snapshot = null; + try + { + snapshot = spans[0].Snapshot; + } + catch (Exception ex) + { + DevAssistErrorHandler.LogAndSwallow(ex, "GlyphTagger.GetTags (snapshot)"); + } + + if (snapshot == null) return result; int tagCount = 0; foreach (var span in spans) { - var startLine = snapshot.GetLineNumberFromPosition(span.Start); - var endLine = snapshot.GetLineNumberFromPosition(span.End); - - for (int lineNumber = startLine; lineNumber <= endLine; lineNumber++) + try { - if (_vulnerabilitiesByLine.TryGetValue(lineNumber, out var vulnerabilities)) + var startLine = snapshot.GetLineNumberFromPosition(span.Start); + var endLine = snapshot.GetLineNumberFromPosition(span.End); + + for (int lineNumber = startLine; lineNumber <= endLine; lineNumber++) { - // Get the most severe vulnerability for this line (for gutter icon) - var mostSevere = GetMostSevereVulnerability(vulnerabilities); - if (mostSevere != null) + if (_vulnerabilitiesByLine.TryGetValue(lineNumber, out var vulnerabilities)) { - var line = snapshot.GetLineFromLineNumber(lineNumber); - var lineSpan = new SnapshotSpan(snapshot, line.Start, line.Length); - - var tooltipText = BuildTooltipText(vulnerabilities); - var tag = new DevAssistGlyphTag( - mostSevere.Severity.ToString(), - tooltipText, - mostSevere.Id - ); - - tagCount++; - System.Diagnostics.Debug.WriteLine($"DevAssist: Creating tag #{tagCount} for line {lineNumber}, severity: {mostSevere.Severity}"); - yield return new TagSpan(lineSpan, tag); + var mostSevere = GetMostSevereVulnerability(vulnerabilities); + if (mostSevere != null) + { + var line = snapshot.GetLineFromLineNumber(lineNumber); + var lineSpan = new SnapshotSpan(snapshot, line.Start, line.Length); + + var tooltipText = BuildTooltipText(vulnerabilities); + var tag = new DevAssistGlyphTag( + mostSevere.Severity.ToString(), + tooltipText, + mostSevere.Id + ); + + tagCount++; + System.Diagnostics.Debug.WriteLine($"DevAssist: Creating tag #{tagCount} for line {lineNumber}, severity: {mostSevere.Severity}"); + result.Add(new TagSpan(lineSpan, tag)); + } } } } + catch (Exception ex) + { + DevAssistErrorHandler.LogAndSwallow(ex, "GlyphTagger.GetTags (span)"); + } } System.Diagnostics.Debug.WriteLine($"DevAssist: GetTags completed - returned {tagCount} tags"); + return result; } /// @@ -80,6 +98,11 @@ public IEnumerable> GetTags(NormalizedSnapshotSpanCo /// Based on JetBrains ProblemDecorator.decorateUI pattern /// public void UpdateVulnerabilities(List vulnerabilities) + { + DevAssistErrorHandler.TryRun(() => UpdateVulnerabilitiesCore(vulnerabilities), "GlyphTagger.UpdateVulnerabilities"); + } + + private void UpdateVulnerabilitiesCore(List vulnerabilities) { System.Diagnostics.Debug.WriteLine($"DevAssist: UpdateVulnerabilities called with {vulnerabilities?.Count ?? 0} vulnerabilities"); @@ -103,7 +126,6 @@ public void UpdateVulnerabilities(List vulnerabilities) System.Diagnostics.Debug.WriteLine($"DevAssist: Vulnerabilities stored in {_vulnerabilitiesByLine.Count} lines"); System.Diagnostics.Debug.WriteLine($"DevAssist: TagsChanged event has {(TagsChanged != null ? TagsChanged.GetInvocationList().Length : 0)} subscribers"); - // Notify that tags have changed var snapshot = _buffer.CurrentSnapshot; var entireSpan = new SnapshotSpan(snapshot, 0, snapshot.Length); diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs index 85ddd0c6..8dcb8e2d 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; +using System.Threading; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities; using ast_visual_studio_extension.CxExtension.DevAssist.Core; @@ -17,6 +18,8 @@ namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons [TextViewRole(PredefinedTextViewRoles.Document)] internal class DevAssistTextViewCreationListener : IWpfTextViewCreationListener { + private static int _fallbackDocumentCounter; + public void TextViewCreated(IWpfTextView textView) { System.Diagnostics.Debug.WriteLine("DevAssist: TextViewCreated - C# file opened"); @@ -56,14 +59,21 @@ public void TextViewCreated(IWpfTextView textView) if (glyphTagger != null && errorTagger != null) { - System.Diagnostics.Debug.WriteLine("DevAssist: Both taggers found, adding common mock data (underline, gutter, hover, problem window)"); + System.Diagnostics.Debug.WriteLine("DevAssist: Both taggers found, updating via coordinator (gutter, underline, problem window)"); - // One common mock data for all POC features: underline, gutter icon, popup hover, and problem window - var vulnerabilities = DevAssistMockData.GetCommonVulnerabilities(filePath: null); + // Single coordinator call: updates gutter, underline, and current findings for problem window (Option B) + var filePath = DevAssistDisplayCoordinator.GetFilePathForBuffer(buffer); + // When path is unknown (e.g. ITextDocument not available), use a unique key per buffer so multi-file doesn't overwrite with "Program.cs" + if (string.IsNullOrEmpty(filePath)) + { + var fallback = Interlocked.Increment(ref _fallbackDocumentCounter); + filePath = $"Document {fallback}"; + System.Diagnostics.Debug.WriteLine($"DevAssist: GetFilePathForBuffer returned null, using fallback: {filePath}"); + } + var vulnerabilities = DevAssistMockData.GetCommonVulnerabilities(filePath); + DevAssistDisplayCoordinator.UpdateFindings(buffer, vulnerabilities, filePath); - glyphTagger.UpdateVulnerabilities(vulnerabilities); - errorTagger.UpdateVulnerabilities(vulnerabilities); - System.Diagnostics.Debug.WriteLine("DevAssist: Common mock vulnerabilities added to both taggers successfully"); + System.Diagnostics.Debug.WriteLine("DevAssist: Coordinator updated gutter, underline, and findings successfully"); } else { diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs index 7145e180..7ef1ea35 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs @@ -26,49 +26,68 @@ public DevAssistErrorTagger(ITextBuffer buffer) public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) { - System.Diagnostics.Debug.WriteLine($"DevAssist Markers: GetTags called - spans count: {spans.Count}, vulnerabilities count: {_vulnerabilitiesByLine.Count}"); + var result = new List>(); + System.Diagnostics.Debug.WriteLine($"DevAssist Markers: GetTags called - spans count: {spans?.Count ?? 0}, vulnerabilities count: {_vulnerabilitiesByLine.Count}"); - if (spans.Count == 0 || _vulnerabilitiesByLine.Count == 0) + if (spans == null || spans.Count == 0 || _vulnerabilitiesByLine.Count == 0) { System.Diagnostics.Debug.WriteLine($"DevAssist Markers: GetTags returning early - no spans or vulnerabilities"); - yield break; + return result; } - var snapshot = spans[0].Snapshot; + ITextSnapshot snapshot = null; + try + { + snapshot = spans[0].Snapshot; + } + catch (Exception ex) + { + DevAssistErrorHandler.LogAndSwallow(ex, "ErrorTagger.GetTags (snapshot)"); + } + + if (snapshot == null) return result; int tagCount = 0; foreach (var span in spans) { - var startLine = snapshot.GetLineNumberFromPosition(span.Start); - var endLine = snapshot.GetLineNumberFromPosition(span.End); - - for (int lineNumber = startLine; lineNumber <= endLine; lineNumber++) + try { - if (_vulnerabilitiesByLine.TryGetValue(lineNumber, out var vulnerabilities)) + var startLine = snapshot.GetLineNumberFromPosition(span.Start); + var endLine = snapshot.GetLineNumberFromPosition(span.End); + + for (int lineNumber = startLine; lineNumber <= endLine; lineNumber++) { - foreach (var vulnerability in vulnerabilities) + if (_vulnerabilitiesByLine.TryGetValue(lineNumber, out var vulnerabilities)) { - if (!ShouldShowUnderline(vulnerability.Severity)) + foreach (var vulnerability in vulnerabilities) { - System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Skipping underline for {vulnerability.Severity} on line {lineNumber}"); - continue; - } + if (!ShouldShowUnderline(vulnerability.Severity)) + { + System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Skipping underline for {vulnerability.Severity} on line {lineNumber}"); + continue; + } - var line = snapshot.GetLineFromLineNumber(lineNumber); - var lineSpan = new SnapshotSpan(snapshot, line.Start, line.Length); + var line = snapshot.GetLineFromLineNumber(lineNumber); + var lineSpan = new SnapshotSpan(snapshot, line.Start, line.Length); - var tooltipText = BuildTooltipText(vulnerability); - IErrorTag tag = new ErrorTag("Error", tooltipText); + var tooltipText = BuildTooltipText(vulnerability); + IErrorTag tag = new ErrorTag("Error", tooltipText); - tagCount++; - System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Creating error tag #{tagCount} for line {lineNumber}, severity: {vulnerability.Severity}"); - yield return new TagSpan(lineSpan, tag); + tagCount++; + System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Creating error tag #{tagCount} for line {lineNumber}, severity: {vulnerability.Severity}"); + result.Add(new TagSpan(lineSpan, tag)); + } } } } + catch (Exception ex) + { + DevAssistErrorHandler.LogAndSwallow(ex, "ErrorTagger.GetTags (span)"); + } } System.Diagnostics.Debug.WriteLine($"DevAssist Markers: GetTags completed - returned {tagCount} error tags"); + return result; } /// @@ -112,6 +131,11 @@ private static string BuildTooltipText(Vulnerability vulnerability) /// Similar to JetBrains MarkupModel.removeAllHighlighters() + addRangeHighlighter() /// public void UpdateVulnerabilities(List vulnerabilities) + { + DevAssistErrorHandler.TryRun(() => UpdateVulnerabilitiesCore(vulnerabilities), "ErrorTagger.UpdateVulnerabilities"); + } + + private void UpdateVulnerabilitiesCore(List vulnerabilities) { System.Diagnostics.Debug.WriteLine($"DevAssist Markers: UpdateVulnerabilities called with {vulnerabilities?.Count ?? 0} vulnerabilities"); @@ -121,7 +145,6 @@ public void UpdateVulnerabilities(List vulnerabilities) { foreach (var vulnerability in vulnerabilities) { - // Convert 1-based line number to 0-based for Visual Studio int lineNumber = vulnerability.LineNumber - 1; if (!_vulnerabilitiesByLine.ContainsKey(lineNumber)) @@ -136,7 +159,6 @@ public void UpdateVulnerabilities(List vulnerabilities) System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Vulnerabilities stored in {_vulnerabilitiesByLine.Count} lines"); - // Notify that tags have changed var snapshot = _buffer.CurrentSnapshot; var entireSpan = new SnapshotSpan(snapshot, 0, snapshot.Length); @@ -160,9 +182,10 @@ public void ClearVulnerabilities() /// public IReadOnlyList GetVulnerabilitiesForLine(int zeroBasedLineNumber) { - if (_vulnerabilitiesByLine.TryGetValue(zeroBasedLineNumber, out var list)) - return list; - return Array.Empty(); + return DevAssistErrorHandler.TryGet( + () => _vulnerabilitiesByLine.TryGetValue(zeroBasedLineNumber, out var list) ? list : (IReadOnlyList)Array.Empty(), + "ErrorTagger.GetVulnerabilitiesForLine", + Array.Empty()); } } } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoController.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoController.cs index 8bb67be5..7830a31e 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoController.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoController.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; @@ -32,64 +33,78 @@ internal DevAssistQuickInfoController( private void OnTextViewMouseHover(object sender, MouseHoverEventArgs e) { - var point = _textView.BufferGraph.MapDownToFirstMatch( - new SnapshotPoint(_textView.TextSnapshot, e.Position), - PointTrackingMode.Positive, - snapshot => _subjectBuffers.Contains(snapshot.TextBuffer), - PositionAffinity.Predecessor); - - if (!point.HasValue) - return; - - var buffer = point.Value.Snapshot.TextBuffer; - int lineNumber = point.Value.Snapshot.GetLineNumberFromPosition(point.Value.Position); - - var tagger = DevAssistErrorTaggerProvider.GetTaggerForBuffer(buffer); - if (tagger == null) - return; - - var vulnerabilities = tagger.GetVulnerabilitiesForLine(lineNumber); - if (vulnerabilities == null || vulnerabilities.Count == 0) - { - CloseHoverPopup(); - return; - } - - // Trigger default Quick Info so our source adds ContainerElement/ClassifiedText content (description, links). - if (!_provider.QuickInfoBroker.IsQuickInfoActive(_textView)) + try { - var triggerPoint = point.Value.Snapshot.CreateTrackingPoint(point.Value.Position, PointTrackingMode.Positive); - _provider.QuickInfoBroker.TriggerQuickInfo(_textView, triggerPoint, trackMouse: true); + var point = _textView.BufferGraph.MapDownToFirstMatch( + new SnapshotPoint(_textView.TextSnapshot, e.Position), + PointTrackingMode.Positive, + snapshot => _subjectBuffers.Contains(snapshot.TextBuffer), + PositionAffinity.Predecessor); + + if (!point.HasValue) + return; + + var buffer = point.Value.Snapshot.TextBuffer; + int lineNumber = point.Value.Snapshot.GetLineNumberFromPosition(point.Value.Position); + + var tagger = DevAssistErrorTaggerProvider.GetTaggerForBuffer(buffer); + if (tagger == null) + return; + + var vulnerabilities = tagger.GetVulnerabilitiesForLine(lineNumber); + if (vulnerabilities == null || vulnerabilities.Count == 0) + { + CloseHoverPopup(); + return; + } + + if (!_provider.QuickInfoBroker.IsQuickInfoActive(_textView)) + { + var triggerPoint = point.Value.Snapshot.CreateTrackingPoint(point.Value.Position, PointTrackingMode.Positive); + _provider.QuickInfoBroker.TriggerQuickInfo(_textView, triggerPoint, trackMouse: true); + } + + var wpfView = _textView as IWpfTextView; + if (wpfView?.VisualElement != null) + { + var element = wpfView.VisualElement; + var vulnList = vulnerabilities; + Dispatcher.CurrentDispatcher.Invoke(() => ShowHoverPopup(element, vulnList)); + } } - - // Also show custom WPF popup (badge, full layout) as fallback/alternative. - var wpfView = _textView as IWpfTextView; - if (wpfView?.VisualElement != null) + catch (Exception ex) { - var element = wpfView.VisualElement; - var vulnList = vulnerabilities; - Dispatcher.CurrentDispatcher.Invoke(() => ShowHoverPopup(element, vulnList)); + DevAssistErrorHandler.LogAndSwallow(ex, "QuickInfoController.OnTextViewMouseHover"); + CloseHoverPopup(); } } private void ShowHoverPopup(FrameworkElement placementTarget, IReadOnlyList vulnerabilities) { - CloseHoverPopup(); - - var first = vulnerabilities[0]; - var content = new DevAssistHoverPopup(first, vulnerabilities); - - _hoverPopup = new Popup + try { - Child = content, - PlacementTarget = placementTarget, - Placement = PlacementMode.MousePoint, - StaysOpen = false, - AllowsTransparency = false - }; - - _hoverPopup.Closed += (s, args) => _hoverPopup = null; - _hoverPopup.IsOpen = true; + CloseHoverPopup(); + if (vulnerabilities == null || vulnerabilities.Count == 0) return; + + var first = vulnerabilities[0]; + var content = new DevAssistHoverPopup(first, vulnerabilities); + + _hoverPopup = new Popup + { + Child = content, + PlacementTarget = placementTarget, + Placement = PlacementMode.MousePoint, + StaysOpen = false, + AllowsTransparency = false + }; + + _hoverPopup.Closed += (s, args) => _hoverPopup = null; + _hoverPopup.IsOpen = true; + } + catch (Exception ex) + { + DevAssistErrorHandler.LogAndSwallow(ex, "QuickInfoController.ShowHoverPopup"); + } } private void CloseHoverPopup() diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml.cs b/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml.cs index 114e2783..3337df38 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; @@ -10,6 +11,7 @@ using System.Windows.Media.Imaging; using Microsoft.VisualStudio.Shell; using EnvDTE; +using ast_visual_studio_extension.CxExtension.DevAssist.Core; namespace ast_visual_studio_extension.CxExtension.DevAssist.UI.FindingsWindow { @@ -22,6 +24,7 @@ public partial class DevAssistFindingsControl : UserControl, INotifyPropertyChan private ObservableCollection _allFileNodes; // Store unfiltered data private string _statusBarText; private bool _isLoading; + private Action>> _onIssuesUpdated; public event PropertyChangedEventHandler PropertyChanged; @@ -66,8 +69,58 @@ public DevAssistFindingsControl() _allFileNodes = new ObservableCollection(); DataContext = this; - // Load filter icons after InitializeComponent so named controls are available - Loaded += (s, e) => LoadFilterIcons(); + // Load filter icons and subscribe to coordinator (JetBrains ISSUE_TOPIC-like: window stays in sync when issues change) + Loaded += OnLoaded; + Unloaded += OnUnloaded; + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + LoadFilterIcons(); + _onIssuesUpdated = OnIssuesUpdated; + DevAssistDisplayCoordinator.IssuesUpdated += _onIssuesUpdated; + // Initial refresh from current data + RefreshFromCoordinator(); + } + + private void OnUnloaded(object sender, RoutedEventArgs e) + { + if (_onIssuesUpdated != null) + { + DevAssistDisplayCoordinator.IssuesUpdated -= _onIssuesUpdated; + _onIssuesUpdated = null; + } + } + + private void OnIssuesUpdated(IReadOnlyDictionary> issuesByFile) + { + Dispatcher.BeginInvoke(new Action(() => RefreshFromCoordinator())); + } + + /// + /// Refreshes the tree from coordinator's current issues (used when IssuesUpdated fires or on load). + /// + private void RefreshFromCoordinator() + { + var current = DevAssistDisplayCoordinator.GetCurrentFindings(); + var fileNodes = current != null && current.Count > 0 + ? DevAssistMockData.BuildFileNodesFromVulnerabilities(current, LoadSeverityIconForTree, null) + : new ObservableCollection(); + SetAllFileNodes(fileNodes); + } + + /// + /// Load severity icon for tree items (same theme logic as filter icons). + /// + private System.Windows.Media.ImageSource LoadSeverityIconForTree(string severity) + { + try + { + string themeFolder = IsDarkTheme() ? "Dark" : "Light"; + string iconName = (severity ?? "unknown").ToLower() + ".png"; + return LoadIcon(themeFolder, iconName); + } + catch { return null; } } /// diff --git a/ast-visual-studio-extension/ast-visual-studio-extension.csproj b/ast-visual-studio-extension/ast-visual-studio-extension.csproj index 8a0d8a25..bc1b5773 100644 --- a/ast-visual-studio-extension/ast-visual-studio-extension.csproj +++ b/ast-visual-studio-extension/ast-visual-studio-extension.csproj @@ -167,6 +167,8 @@ + + From ca688101ba8373875e3ebdd05188e804a742bd49 Mon Sep 17 00:00:00 2001 From: Rahul Pidde <206018639+cx-rahul-pidde@users.noreply.github.com> Date: Wed, 18 Feb 2026 19:15:19 +0530 Subject: [PATCH 08/45] Code changes related to Error List --- .../CxExtension/CxWindowPackage.cs | 7 + .../DevAssist/Core/DevAssistErrorListSync.cs | 179 ++++++++++++ .../Core/GutterIcons/DevAssistGlyphFactory.cs | 2 +- .../Markers/DevAssistCompilerErrorsForLine.cs | 105 +++++++ .../Markers/DevAssistHoverPopup.Designer.cs | 14 + .../Core/Markers/DevAssistHoverPopup.xaml | 32 ++- .../Core/Markers/DevAssistHoverPopup.xaml.cs | 226 ++++++++++++++- .../Core/Markers/DevAssistQuickFixActions.cs | 143 ++++++++++ .../Markers/DevAssistQuickInfoController.cs | 10 +- .../DevAssistSuggestedActionsSource.cs | 85 ++++++ ...DevAssistSuggestedActionsSourceProvider.cs | 26 ++ .../ast-visual-studio-extension.csproj | 6 + ...R_UNDERLINE_PROBLEMWINDOW_POPUP_SUMMARY.md | 261 ++++++++++++++++++ docs/DEVASSIST_TESTING_ERROR_SCENARIOS.md | 226 +++++++++++++++ 14 files changed, 1301 insertions(+), 21 deletions(-) create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorListSync.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistCompilerErrorsForLine.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickFixActions.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSuggestedActionsSource.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSuggestedActionsSourceProvider.cs create mode 100644 docs/DEVASSIST_GUTTER_UNDERLINE_PROBLEMWINDOW_POPUP_SUMMARY.md create mode 100644 docs/DEVASSIST_TESTING_ERROR_SCENARIOS.md diff --git a/ast-visual-studio-extension/CxExtension/CxWindowPackage.cs b/ast-visual-studio-extension/CxExtension/CxWindowPackage.cs index ce342f5b..385ea7cf 100644 --- a/ast-visual-studio-extension/CxExtension/CxWindowPackage.cs +++ b/ast-visual-studio-extension/CxExtension/CxWindowPackage.cs @@ -1,4 +1,5 @@ using ast_visual_studio_extension.CxExtension.Commands; +using ast_visual_studio_extension.CxExtension.DevAssist.Core; using log4net; using log4net.Appender; using log4net.Config; @@ -48,6 +49,8 @@ public sealed class CxWindowPackage : AsyncPackage /// public const string PackageGuidString = "63d5f3b4-a254-4bef-974b-0733c306ed2c"; + private DevAssistErrorListSync _devAssistErrorListSync; + #region Package Members /// @@ -78,6 +81,10 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke // Show Findings Window Command (POC for AST-133228 - Custom Tool Window) // Command still works programmatically but not visible in menu await ShowFindingsWindowCommand.InitializeAsync(this); + + // Sync DevAssist findings to the built-in Error List (findings appear in both our window and Error List) + _devAssistErrorListSync = new DevAssistErrorListSync(); + _devAssistErrorListSync.Start(); } catch (Exception ex) { diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorListSync.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorListSync.cs new file mode 100644 index 00000000..ed9f599f --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorListSync.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using EnvDTE; +using EnvDTE80; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core +{ + /// + /// Syncs DevAssist findings to the built-in Error List so issues appear in both + /// the custom DevAssist findings window and the VS Error List. + /// + internal sealed class DevAssistErrorListSync + { + private ErrorListProvider _errorListProvider; + private bool _subscribed; + + public void Start() + { + if (_subscribed) return; + + ThreadHelper.ThrowIfNotOnUIThread(); + EnsureErrorListProvider(); + DevAssistDisplayCoordinator.IssuesUpdated += OnIssuesUpdated; + _subscribed = true; + + // Initial sync from current state + var snapshot = DevAssistDisplayCoordinator.GetAllIssuesByFile(); + if (snapshot != null && snapshot.Count > 0) + SyncToErrorList(snapshot); + } + + public void Stop() + { + if (!_subscribed) return; + + DevAssistDisplayCoordinator.IssuesUpdated -= OnIssuesUpdated; + _subscribed = false; + + ThreadHelper.JoinableTaskFactory.Run(async () => + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + _errorListProvider?.Tasks.Clear(); + }); + } + + private void OnIssuesUpdated(IReadOnlyDictionary> snapshot) + { + ThreadHelper.JoinableTaskFactory.RunAsync(async () => + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + SyncToErrorList(snapshot); + }); + } + + private void EnsureErrorListProvider() + { + ThreadHelper.ThrowIfNotOnUIThread(); + if (_errorListProvider != null) return; + + _errorListProvider = new ErrorListProvider(ServiceProvider.GlobalProvider) + { + ProviderName = "DevAssist" + }; + } + + private void SyncToErrorList(IReadOnlyDictionary> issuesByFile) + { + ThreadHelper.ThrowIfNotOnUIThread(); + EnsureErrorListProvider(); + + _errorListProvider.Tasks.Clear(); + + if (issuesByFile == null || issuesByFile.Count == 0) + return; + + var dte = Package.GetGlobalService(typeof(DTE)) as DTE2; + if (dte?.Documents == null) return; + + foreach (var kv in issuesByFile) + { + string filePath = kv.Key; + var list = kv.Value; + if (list == null) continue; + + Document document = null; + try + { + document = dte.Documents.Cast().FirstOrDefault(doc => + string.Equals(doc.FullName, filePath, StringComparison.OrdinalIgnoreCase)); + } + catch + { + // Document may not be open + } + + foreach (var v in list) + { + string severityLabel = v.Severity.ToString(); + var task = new ErrorTask + { + Category = TaskCategory.CodeSense, + ErrorCategory = GetErrorCategory(v.Severity), + Text = $"[DevAssist] [{severityLabel}] {v.Title}", + Document = filePath, + Line = Math.Max(0, v.LineNumber - 1), + Column = Math.Max(0, v.ColumnNumber), + HierarchyItem = document != null ? GetHierarchyItem(document) : null + }; + + task.Navigate += (s, e) => NavigateToVulnerability(v); + _errorListProvider.Tasks.Add(task); + } + } + } + + private static TaskErrorCategory GetErrorCategory(SeverityLevel severity) + { + switch (severity) + { + case SeverityLevel.Malicious: + case SeverityLevel.Critical: + case SeverityLevel.High: + return TaskErrorCategory.Error; + case SeverityLevel.Medium: + return TaskErrorCategory.Warning; + case SeverityLevel.Low: + case SeverityLevel.Info: + case SeverityLevel.Unknown: + case SeverityLevel.Ok: + case SeverityLevel.Ignored: + default: + return TaskErrorCategory.Message; + } + } + + private static IVsHierarchy GetHierarchyItem(Document document) + { + ThreadHelper.ThrowIfNotOnUIThread(); + if (document?.ProjectItem?.ContainingProject == null) return null; + + var serviceProvider = ServiceProvider.GlobalProvider; + var solution = serviceProvider.GetService(typeof(SVsSolution)) as IVsSolution; + if (solution == null) return null; + + solution.GetProjectOfUniqueName(document.ProjectItem.ContainingProject.UniqueName, out IVsHierarchy hierarchy); + return hierarchy; + } + + private static void NavigateToVulnerability(Vulnerability v) + { + ThreadHelper.ThrowIfNotOnUIThread(); + if (string.IsNullOrEmpty(v?.FilePath)) return; + + var dte = Package.GetGlobalService(typeof(DTE)) as DTE2; + if (dte == null) return; + + try + { + var window = dte.ItemOperations.OpenFile(v.FilePath, EnvDTE.Constants.vsViewKindCode); + Document doc = window.Document; + if (doc?.Object("TextDocument") is TextDocument textDoc) + { + var selection = textDoc.Selection; + int line = Math.Max(1, v.LineNumber); + selection.MoveToLineAndOffset(line, 1); + selection.SelectLine(); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"DevAssistErrorListSync: Navigate failed: {ex.Message}"); + } + } + } +} diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphFactory.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphFactory.cs index 7efdeb66..0038d3c0 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphFactory.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphFactory.cs @@ -21,7 +21,7 @@ namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons /// internal class DevAssistGlyphFactory : IGlyphFactory { - private const double GlyphSize = 16.0; + private const double GlyphSize = 14.0; public UIElement GenerateGlyph(IWpfTextViewLine line, IGlyphTag tag) { diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistCompilerErrorsForLine.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistCompilerErrorsForLine.cs new file mode 100644 index 00000000..7684e534 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistCompilerErrorsForLine.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.IO; +using EnvDTE; +using EnvDTE80; +using Microsoft.VisualStudio.Shell; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +{ + /// + /// Gets compiler / VS Error List messages for a given file and line so the custom popup can show + /// "Also on this line (Compiler / VS):" combined with DevAssist findings. + /// + internal static class DevAssistCompilerErrorsForLine + { + /// + /// Returns Error List (compiler/VS) messages for the given file and line. + /// Line is 1-based (Error List uses 1-based line numbers). + /// Returns empty list if DTE/Error List is unavailable or on error. + /// + public static IReadOnlyList GetErrorsForLine(string filePath, int line1Based) + { + if (string.IsNullOrEmpty(filePath) || line1Based < 1) + return Array.Empty(); + + try + { + var dte = Package.GetGlobalService(typeof(DTE)) as DTE; + if (dte == null) return Array.Empty(); + + var dte2 = dte as DTE2; + if (dte2 == null) return Array.Empty(); + + ErrorList errorList = dte2.ToolWindows?.ErrorList; + if (errorList == null) return Array.Empty(); + + ErrorItems errorItems = errorList.ErrorItems; + if (errorItems == null) return Array.Empty(); + + int count = errorItems.Count; + if (count <= 0) return Array.Empty(); + + string normalizedPath = NormalizePath(filePath); + string normalizedFileName = Path.GetFileName(normalizedPath); + var list = new List(); + + // ErrorItems.Item is 1-based + for (int i = 1; i <= count; i++) + { + try + { + ErrorItem item = errorItems.Item(i); + if (item == null) continue; + + string fileName = item.FileName ?? ""; + if (string.IsNullOrEmpty(fileName)) continue; + if (!PathsMatch(fileName, normalizedPath, normalizedFileName)) + continue; + + int itemLine = item.Line; + if (itemLine != line1Based) continue; + + string description = item.Description ?? ""; + if (string.IsNullOrEmpty(description)) description = "Compiler / VS error"; + list.Add(description); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"DevAssist: GetErrorsForLine item {i}: {ex.Message}"); + } + } + + return list; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"DevAssist: GetErrorsForLine failed: {ex.Message}"); + return Array.Empty(); + } + } + + private static string NormalizePath(string path) + { + if (string.IsNullOrEmpty(path)) return path; + try { return Path.GetFullPath(path.Trim()); } + catch { return path.Trim(); } + } + + /// Match document path with Error List item path (full path or filename only). + private static bool PathsMatch(string errorItemFileName, string normalizedDocPath, string normalizedDocFileName) + { + if (string.IsNullOrEmpty(errorItemFileName)) return false; + string normalizedError = NormalizePath(errorItemFileName); + if (string.Compare(normalizedError, normalizedDocPath, StringComparison.OrdinalIgnoreCase) == 0) + return true; + if (string.Compare(Path.GetFileName(normalizedError), normalizedDocFileName, StringComparison.OrdinalIgnoreCase) == 0) + return true; + if (normalizedDocPath.EndsWith(errorItemFileName.Trim(), StringComparison.OrdinalIgnoreCase)) + return true; + if (normalizedError.EndsWith(normalizedDocFileName, StringComparison.OrdinalIgnoreCase)) + return true; + return false; + } + } +} diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.Designer.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.Designer.cs index 34b32c84..0bb8fac9 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.Designer.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.Designer.cs @@ -16,6 +16,10 @@ public partial class DevAssistHoverPopup // Header: Checkmarx One Assist logo image (not text/label) internal Image HeaderLogoImage; internal Button MoreOptionsButton; + internal StackPanel MultipleIssuesPanel; + internal TextBlock MultipleIssuesHeader; + internal StackPanel MultipleIssuesCards; + internal StackPanel SingleIssuePanel; // Main line internal Image SeverityIcon; internal TextBlock TitleText; @@ -51,6 +55,9 @@ public partial class DevAssistHoverPopup internal System.Windows.Documents.Hyperlink LearnMoreLink; internal TextBlock ApplyFixLinkBlock; internal System.Windows.Documents.Hyperlink ApplyFixLink; + internal StackPanel CompilerErrorsPanel; + internal TextBlock CompilerErrorsTitle; + internal StackPanel CompilerErrorsList; private void InitializeComponent() { @@ -67,6 +74,10 @@ private void InitializeComponent() Content = root; HeaderLogoImage = (Image)root.FindName("HeaderLogoImage"); MoreOptionsButton = (Button)root.FindName("MoreOptionsButton"); + MultipleIssuesPanel = (StackPanel)root.FindName("MultipleIssuesPanel"); + MultipleIssuesHeader = (TextBlock)root.FindName("MultipleIssuesHeader"); + MultipleIssuesCards = (StackPanel)root.FindName("MultipleIssuesCards"); + SingleIssuePanel = (StackPanel)root.FindName("SingleIssuePanel"); SeverityIcon = (Image)root.FindName("SeverityIcon"); TitleText = (TextBlock)root.FindName("TitleText"); ScannerBadge = (Border)root.FindName("ScannerBadge"); @@ -99,6 +110,9 @@ private void InitializeComponent() LearnMoreLink = (System.Windows.Documents.Hyperlink)root.FindName("LearnMoreLink"); ApplyFixLinkBlock = (TextBlock)root.FindName("ApplyFixLinkBlock"); ApplyFixLink = (System.Windows.Documents.Hyperlink)root.FindName("ApplyFixLink"); + CompilerErrorsPanel = (StackPanel)root.FindName("CompilerErrorsPanel"); + CompilerErrorsTitle = (TextBlock)root.FindName("CompilerErrorsTitle"); + CompilerErrorsList = (StackPanel)root.FindName("CompilerErrorsList"); } } } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml index 8e7ab185..9c7d8b14 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml @@ -1,15 +1,15 @@ + + CornerRadius="3" + Padding="10,8"> + - - + + @@ -32,6 +32,17 @@ Cursor="Hand"/> + + + + + + + + @@ -223,6 +234,15 @@ + + + + + + + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml.cs index 2b7c753b..dc45c385 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml.cs @@ -21,17 +21,24 @@ public partial class DevAssistHoverPopup : UserControl { private readonly Vulnerability _vulnerability; private readonly IReadOnlyList _allForLine; + private readonly IReadOnlyList _compilerErrorsOnLine; public DevAssistHoverPopup(Vulnerability vulnerability) - : this(vulnerability, new[] { vulnerability }) + : this(vulnerability, new[] { vulnerability }, null) { } public DevAssistHoverPopup(Vulnerability first, IReadOnlyList allForLine) + : this(first, allForLine, null) + { + } + + public DevAssistHoverPopup(Vulnerability first, IReadOnlyList allForLine, IReadOnlyList compilerErrorsOnLine) { InitializeComponent(); _vulnerability = first ?? throw new ArgumentNullException(nameof(first)); _allForLine = allForLine ?? new[] { first }; + _compilerErrorsOnLine = compilerErrorsOnLine ?? Array.Empty(); PopulateContent(); } @@ -39,6 +46,19 @@ private void PopulateContent() { if (TitleText == null) return; // XAML failed to load from embedded resource + + // JetBrains-style: when multiple vulnerabilities on same line, show "N issues detected" + one card per vulnerability + if (_allForLine != null && _allForLine.Count > 1 && MultipleIssuesPanel != null && MultipleIssuesCards != null) + { + BuildMultipleIssuesContent(); + return; + } + + if (SingleIssuePanel != null) + SingleIssuePanel.Visibility = Visibility.Visible; + if (MultipleIssuesPanel != null) + MultipleIssuesPanel.Visibility = Visibility.Collapsed; + // DevAssist logo (JetBrains-style at top of tooltip) SetDevAssistIcon(); @@ -102,6 +122,9 @@ private void PopulateContent() if (MoreOptionsButton != null) MoreOptionsButton.Click += MoreOptionsButton_Click; + // Combined: show compiler/VS errors on this line in the same popup + BuildCompilerErrorsSection(); + // Apply VS theme so popup respects light/dark (was always dark due to hardcoded XAML colors) ApplyVsTheme(); } @@ -135,6 +158,8 @@ private void ApplyVsTheme() SetResourceRef(NavigateToCodeLink, TextElement.ForegroundProperty, EnvironmentColors.ControlLinkTextBrushKey); SetResourceRef(LearnMoreLink, TextElement.ForegroundProperty, EnvironmentColors.ControlLinkTextBrushKey); SetResourceRef(ApplyFixLink, TextElement.ForegroundProperty, EnvironmentColors.ControlLinkTextBrushKey); + if (MultipleIssuesHeader != null) SetResourceRef(MultipleIssuesHeader, TextElement.ForegroundProperty, EnvironmentColors.ToolTipTextBrushKey); + if (ScannerText != null) SetResourceRef(ScannerText, TextElement.ForegroundProperty, EnvironmentColors.ToolTipTextBrushKey); if (SeparatorLine1 != null) SeparatorLine1.SetResourceReference(Border.BackgroundProperty, EnvironmentColors.ToolTipBorderBrushKey); if (SeparatorLine2 != null) SeparatorLine2.SetResourceReference(Border.BackgroundProperty, EnvironmentColors.ToolTipBorderBrushKey); } @@ -508,6 +533,168 @@ private void BuildSeverityCountRow() SeverityCountPanel.Visibility = Visibility.Visible; } + /// + /// JetBrains-style: "N issues detected on this line Checkmarx One Assist" plus one card per vulnerability + /// (each card: severity icon, title, description, Fix / View details / Ignore this). + /// + private void BuildMultipleIssuesContent() + { + SetDevAssistIcon(); + SingleIssuePanel.Visibility = Visibility.Collapsed; + MultipleIssuesPanel.Visibility = Visibility.Visible; + + int n = _allForLine.Count; + MultipleIssuesHeader.Text = $"{n} issue{(n == 1 ? "" : "s")} detected on this line Checkmarx One Assist"; + try + { + MultipleIssuesHeader.SetResourceReference(TextBlock.ForegroundProperty, EnvironmentColors.ToolTipTextBrushKey); + } + catch { /* theme not available */ } + + MultipleIssuesCards.Children.Clear(); + foreach (var v in _allForLine) + AddVulnerabilityCard(v); + + if (MoreOptionsButton != null) + MoreOptionsButton.Click += MoreOptionsButton_Click; + BuildCompilerErrorsSection(); + ApplyVsTheme(); + } + + /// + /// Shows "Also on this line (Compiler / VS):" with Error List messages when there are compiler errors on the same line. + /// + private void BuildCompilerErrorsSection() + { + try + { + if (_compilerErrorsOnLine == null || _compilerErrorsOnLine.Count == 0 || + CompilerErrorsPanel == null || CompilerErrorsList == null) + return; + + CompilerErrorsList.Children.Clear(); + foreach (string message in _compilerErrorsOnLine) + { + var tb = new TextBlock + { + Text = message, + TextWrapping = TextWrapping.Wrap, + FontSize = 11, + Margin = new Thickness(0, 2, 0, 2) + }; + try { tb.SetResourceReference(TextBlock.ForegroundProperty, EnvironmentColors.ToolTipTextBrushKey); } + catch { tb.Foreground = new SolidColorBrush(Color.FromRgb(0xDC, 0xDC, 0xDC)); } + CompilerErrorsList.Children.Add(tb); + } + if (CompilerErrorsTitle != null) + { + try { CompilerErrorsTitle.SetResourceReference(TextBlock.ForegroundProperty, EnvironmentColors.ToolTipTextBrushKey); } + catch { } + } + CompilerErrorsPanel.Visibility = Visibility.Visible; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"DevAssist Hover: BuildCompilerErrorsSection failed: {ex.Message}"); + } + } + + /// + /// Adds one vulnerability card (severity icon, title, description, action links) to MultipleIssuesCards. + /// + private void AddVulnerabilityCard(Vulnerability v) + { + // Separator between cards (skip before first card) + if (MultipleIssuesCards.Children.Count > 0) + { + var sep = new Border { Height = 1, Margin = new Thickness(0, 0, 0, 8) }; + try { sep.SetResourceReference(Border.BackgroundProperty, EnvironmentColors.ToolTipBorderBrushKey); } + catch { sep.Background = new SolidColorBrush(Color.FromRgb(0x3F, 0x3F, 0x46)); } + MultipleIssuesCards.Children.Add(sep); + } + + var theme = GetCurrentTheme(); + string iconFileName = GetSeverityIconFileName(v.Severity); + var iconSource = !string.IsNullOrEmpty(iconFileName) ? LoadIconFromAssembly(theme, iconFileName) : null; + + var card = new StackPanel { Margin = new Thickness(0, 0, 0, 12) }; + + // Row: severity icon + title (bold) + var titleRow = new Grid(); + titleRow.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(28) }); + titleRow.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); + if (iconSource != null) + { + var img = new Image + { + Source = iconSource, + Width = 20, + Height = 20, + VerticalAlignment = VerticalAlignment.Top, + Margin = new Thickness(0, 0, 8, 0) + }; + Grid.SetColumn(img, 0); + titleRow.Children.Add(img); + } + string titlePart = !string.IsNullOrEmpty(v.Title) ? v.Title : (!string.IsNullOrEmpty(v.RuleName) ? v.RuleName : v.Description); + var titleBlock = new TextBlock + { + Text = titlePart ?? "", + FontWeight = FontWeights.SemiBold, + FontSize = 13, + TextWrapping = TextWrapping.Wrap + }; + try { titleBlock.SetResourceReference(TextBlock.ForegroundProperty, EnvironmentColors.ToolTipTextBrushKey); } + catch { titleBlock.Foreground = new SolidColorBrush(Color.FromRgb(0xDC, 0xDC, 0xDC)); } + Grid.SetColumn(titleBlock, 1); + titleRow.Children.Add(titleBlock); + card.Children.Add(titleRow); + + // Description + var descText = !string.IsNullOrEmpty(v.Description) ? v.Description : "Vulnerability detected by " + v.Scanner + "."; + if (v.Scanner == ScannerType.IaC) + descText += " IaC vulnerability"; + else if (v.Scanner == ScannerType.ASCA) + descText += " SAST vulnerability"; + var descBlock = new TextBlock + { + Text = descText, + TextWrapping = TextWrapping.Wrap, + FontSize = 12, + Margin = new Thickness(0, 4, 0, 6) + }; + try { descBlock.SetResourceReference(TextBlock.ForegroundProperty, EnvironmentColors.ToolTipTextBrushKey); } + catch { descBlock.Foreground = new SolidColorBrush(Color.FromRgb(0xDC, 0xDC, 0xDC)); } + card.Children.Add(descBlock); + + // Action links: Fix with Checkmarx One Assist | View details | Ignore this vulnerability + var linksBlock = new TextBlock { Margin = new Thickness(0, 0, 0, 0) }; + var fixLink = new System.Windows.Documents.Hyperlink(new Run("Fix with Checkmarx One Assist")) { Foreground = new SolidColorBrush(Color.FromRgb(0x37, 0x94, 0xFF)) }; + fixLink.Click += (s, e) => DoFixWithCxOneAssist(v); + fixLink.PreviewMouseDown += (s, e) => DoFixWithCxOneAssist(v); + var viewLink = new System.Windows.Documents.Hyperlink(new Run("View details")) { Foreground = new SolidColorBrush(Color.FromRgb(0x37, 0x94, 0xFF)) }; + viewLink.Click += (s, e) => DoViewDetails(v); + viewLink.PreviewMouseDown += (s, e) => DoViewDetails(v); + var ignoreLink = new System.Windows.Documents.Hyperlink(new Run("Ignore this vulnerability")) { Foreground = new SolidColorBrush(Color.FromRgb(0x37, 0x94, 0xFF)) }; + ignoreLink.Click += (s, e) => DoIgnoreThis(v); + ignoreLink.PreviewMouseDown += (s, e) => DoIgnoreThis(v); + try + { + fixLink.SetResourceReference(TextElement.ForegroundProperty, EnvironmentColors.ControlLinkTextBrushKey); + viewLink.SetResourceReference(TextElement.ForegroundProperty, EnvironmentColors.ControlLinkTextBrushKey); + ignoreLink.SetResourceReference(TextElement.ForegroundProperty, EnvironmentColors.ControlLinkTextBrushKey); + } + catch { } + linksBlock.Inlines.Add(fixLink); + linksBlock.Inlines.Add(new Run(" ")); + linksBlock.Inlines.Add(viewLink); + linksBlock.Inlines.Add(new Run(" ")); + linksBlock.Inlines.Add(ignoreLink); + card.Children.Add(linksBlock); + + MultipleIssuesCards.Children.Add(card); + } + private void SetScannerBadge() { // Set scanner text @@ -586,37 +773,54 @@ private string GetCurrentTheme() return "Dark"; // Default to Dark for now } - private void FixWithCxOneAssistLink_Click(object sender, RoutedEventArgs e) + private void DoFixWithCxOneAssist(Vulnerability v) { - // Static implementation for now: copy prompt / open AI assist - System.Diagnostics.Debug.WriteLine($"Fix with Checkmarx One Assist for: {_vulnerability.Id}"); + if (v == null) return; + System.Diagnostics.Debug.WriteLine($"Fix with Checkmarx One Assist for: {v.Id}"); MessageBox.Show( - $"Fix with Checkmarx One Assist\nVulnerability: {_vulnerability.Title}\nID: {_vulnerability.Id}", + $"Fix with Checkmarx One Assist\nVulnerability: {v.Title}\nID: {v.Id}", "DevAssist", MessageBoxButton.OK, MessageBoxImage.Information); } - private void ViewDetailsLink_Click(object sender, RoutedEventArgs e) + private void DoViewDetails(Vulnerability v) { - System.Diagnostics.Debug.WriteLine($"View details clicked for vulnerability: {_vulnerability.Id}"); + if (v == null) return; + System.Diagnostics.Debug.WriteLine($"View details clicked for vulnerability: {v.Id}"); MessageBox.Show( - $"{_vulnerability.Title}\n\n{_vulnerability.Description}\n\nScanner: {_vulnerability.Scanner} | Severity: {_vulnerability.Severity}", + $"{v.Title}\n\n{v.Description}\n\nScanner: {v.Scanner} | Severity: {v.Severity}", "View details", MessageBoxButton.OK, MessageBoxImage.Information); } - private void IgnoreThisLink_Click(object sender, RoutedEventArgs e) + private void DoIgnoreThis(Vulnerability v) { - System.Diagnostics.Debug.WriteLine($"Ignore this vulnerability: {_vulnerability.Id}"); + if (v == null) return; + System.Diagnostics.Debug.WriteLine($"Ignore this vulnerability: {v.Id}"); MessageBox.Show( - $"Ignore this vulnerability: {_vulnerability.Title}\n(Static demo โ€“ ignore not persisted yet)", + $"Ignore this vulnerability: {v.Title}\n(Static demo โ€“ ignore not persisted yet)", "DevAssist", MessageBoxButton.OK, MessageBoxImage.Information); } + private void FixWithCxOneAssistLink_Click(object sender, RoutedEventArgs e) + { + DoFixWithCxOneAssist(_vulnerability); + } + + private void ViewDetailsLink_Click(object sender, RoutedEventArgs e) + { + DoViewDetails(_vulnerability); + } + + private void IgnoreThisLink_Click(object sender, RoutedEventArgs e) + { + DoIgnoreThis(_vulnerability); + } + private void IgnoreAllOfThisTypeLink_Click(object sender, RoutedEventArgs e) { System.Diagnostics.Debug.WriteLine($"Ignore all of this type: {_vulnerability.Id}"); diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickFixActions.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickFixActions.cs new file mode 100644 index 00000000..5f6dd0e0 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickFixActions.cs @@ -0,0 +1,143 @@ +using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; +using Microsoft.VisualStudio.Imaging.Interop; +using Microsoft.VisualStudio.Language.Intellisense; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +{ + /// + /// Quick Fix action: "Fix with Checkmarx One Assist" (same behavior as hover popup link). + /// + internal sealed class FixWithCxOneAssistSuggestedAction : ISuggestedAction + { + private readonly Vulnerability _vulnerability; + + public FixWithCxOneAssistSuggestedAction(Vulnerability vulnerability) + { + _vulnerability = vulnerability ?? throw new ArgumentNullException(nameof(vulnerability)); + } + + public string DisplayText => "Fix with Checkmarx One Assist"; + + public string IconAutomationText => null; + + public ImageMoniker IconMoniker => default; + + public string InputGestureText => null; + + public bool HasPreview => false; + + public bool HasActionSets => false; + + public void Dispose() + { + } + + public Task> GetActionSetsAsync(CancellationToken cancellationToken) + { + return Task.FromResult>(null); + } + + public Task GetPreviewAsync(CancellationToken cancellationToken) + { + return Task.FromResult(null); + } + + public void Invoke(CancellationToken cancellationToken) + { + if (_vulnerability == null) return; + var v = _vulnerability; + System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => + { + try + { + MessageBox.Show( + $"Fix with Checkmarx One Assist\nVulnerability: {v.Title}\nID: {v.Id}", + "DevAssist", + MessageBoxButton.OK, + MessageBoxImage.Information); + } + catch (Exception ex) + { + DevAssistErrorHandler.LogAndSwallow(ex, "FixWithCxOneAssistSuggestedAction.Invoke"); + } + })); + } + + public bool TryGetTelemetryId(out Guid telemetryId) + { + telemetryId = Guid.Empty; + return false; + } + } + + /// + /// Quick Fix action: "View details" (same behavior as hover popup link). + /// + internal sealed class ViewDetailsSuggestedAction : ISuggestedAction + { + private readonly Vulnerability _vulnerability; + + public ViewDetailsSuggestedAction(Vulnerability vulnerability) + { + _vulnerability = vulnerability ?? throw new ArgumentNullException(nameof(vulnerability)); + } + + public string DisplayText => "View details"; + + public string IconAutomationText => null; + + public ImageMoniker IconMoniker => default; + + public string InputGestureText => null; + + public bool HasPreview => false; + + public bool HasActionSets => false; + + public void Dispose() + { + } + + public Task> GetActionSetsAsync(CancellationToken cancellationToken) + { + return Task.FromResult>(null); + } + + public Task GetPreviewAsync(CancellationToken cancellationToken) + { + return Task.FromResult(null); + } + + public void Invoke(CancellationToken cancellationToken) + { + if (_vulnerability == null) return; + var v = _vulnerability; + System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => + { + try + { + MessageBox.Show( + $"{v.Title}\n\n{v.Description}\n\nScanner: {v.Scanner} | Severity: {v.Severity}", + "View details", + MessageBoxButton.OK, + MessageBoxImage.Information); + } + catch (Exception ex) + { + DevAssistErrorHandler.LogAndSwallow(ex, "ViewDetailsSuggestedAction.Invoke"); + } + })); + } + + public bool TryGetTelemetryId(out Guid telemetryId) + { + telemetryId = Guid.Empty; + return false; + } + } +} diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoController.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoController.cs index 7830a31e..9079f125 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoController.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoController.cs @@ -6,6 +6,7 @@ using System.Windows; using System.Windows.Controls.Primitives; using System.Windows.Threading; +using ast_visual_studio_extension.CxExtension.DevAssist.Core; namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers { @@ -69,7 +70,10 @@ private void OnTextViewMouseHover(object sender, MouseHoverEventArgs e) { var element = wpfView.VisualElement; var vulnList = vulnerabilities; - Dispatcher.CurrentDispatcher.Invoke(() => ShowHoverPopup(element, vulnList)); + Dispatcher.CurrentDispatcher.Invoke(() => + { + ShowHoverPopup(element, vulnList, compilerErrorsOnLine: null); + }); } } catch (Exception ex) @@ -79,7 +83,7 @@ private void OnTextViewMouseHover(object sender, MouseHoverEventArgs e) } } - private void ShowHoverPopup(FrameworkElement placementTarget, IReadOnlyList vulnerabilities) + private void ShowHoverPopup(FrameworkElement placementTarget, IReadOnlyList vulnerabilities, IReadOnlyList compilerErrorsOnLine = null) { try { @@ -87,7 +91,7 @@ private void ShowHoverPopup(FrameworkElement placementTarget, IReadOnlyList + /// Suggested actions source for DevAssist: shows Quick Fix (light bulb) when the caret is on a line that has at least one vulnerability. + /// + internal class DevAssistSuggestedActionsSource : ISuggestedActionsSource + { + private readonly ITextView _textView; + private readonly ITextBuffer _textBuffer; + + public DevAssistSuggestedActionsSource(ITextView textView, ITextBuffer textBuffer) + { + _textView = textView ?? throw new ArgumentNullException(nameof(textView)); + _textBuffer = textBuffer ?? throw new ArgumentNullException(nameof(textBuffer)); + } + + public event EventHandler SuggestedActionsChanged; + + public void Dispose() + { + } + + public bool TryGetTelemetryId(out Guid telemetryId) + { + telemetryId = Guid.Empty; + return false; + } + + public Task HasSuggestedActionsAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) + { + return Task.Run(() => + { + try + { + int lineNumber = range.Snapshot.GetLineNumberFromPosition(range.Start); + var tagger = DevAssistErrorTaggerProvider.GetTaggerForBuffer(_textBuffer); + if (tagger == null) return false; + var list = tagger.GetVulnerabilitiesForLine(lineNumber); + return list != null && list.Count > 0; + } + catch (Exception ex) + { + DevAssistErrorHandler.LogAndSwallow(ex, "DevAssistSuggestedActionsSource.HasSuggestedActionsAsync"); + return false; + } + }, cancellationToken); + } + + public IEnumerable GetSuggestedActions(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) + { + try + { + int lineNumber = range.Snapshot.GetLineNumberFromPosition(range.Start); + var tagger = DevAssistErrorTaggerProvider.GetTaggerForBuffer(_textBuffer); + if (tagger == null) return Enumerable.Empty(); + var list = tagger.GetVulnerabilitiesForLine(lineNumber); + if (list == null || list.Count == 0) return Enumerable.Empty(); + + // Use first vulnerability for the two Quick Fix actions (same as hover popup when multiple on line) + var vulnerability = list[0]; + var actions = new List + { + new FixWithCxOneAssistSuggestedAction(vulnerability), + new ViewDetailsSuggestedAction(vulnerability) + }; + return new[] { new SuggestedActionSet(actions) }; + } + catch (Exception ex) + { + DevAssistErrorHandler.LogAndSwallow(ex, "DevAssistSuggestedActionsSource.GetSuggestedActions"); + return Enumerable.Empty(); + } + } + } +} diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSuggestedActionsSourceProvider.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSuggestedActionsSourceProvider.cs new file mode 100644 index 00000000..d5136b58 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSuggestedActionsSourceProvider.cs @@ -0,0 +1,26 @@ +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; +using System.ComponentModel.Composition; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +{ + /// + /// Provides Quick Fix (light bulb) suggested actions for DevAssist findings: + /// "Fix with Checkmarx One Assist" and "View details" when the caret is on a line with a vulnerability. + /// + [Export(typeof(ISuggestedActionsSourceProvider))] + [Name("DevAssist Quick Fix")] + [ContentType("code")] + [ContentType("text")] + internal class DevAssistSuggestedActionsSourceProvider : ISuggestedActionsSourceProvider + { + public ISuggestedActionsSource CreateSuggestedActionsSource(ITextView textView, ITextBuffer textBuffer) + { + if (textBuffer == null || textView == null) + return null; + return new DevAssistSuggestedActionsSource(textView, textBuffer); + } + } +} diff --git a/ast-visual-studio-extension/ast-visual-studio-extension.csproj b/ast-visual-studio-extension/ast-visual-studio-extension.csproj index bc1b5773..07b6396f 100644 --- a/ast-visual-studio-extension/ast-visual-studio-extension.csproj +++ b/ast-visual-studio-extension/ast-visual-studio-extension.csproj @@ -165,9 +165,14 @@ + + + + + @@ -197,6 +202,7 @@ + diff --git a/docs/DEVASSIST_GUTTER_UNDERLINE_PROBLEMWINDOW_POPUP_SUMMARY.md b/docs/DEVASSIST_GUTTER_UNDERLINE_PROBLEMWINDOW_POPUP_SUMMARY.md new file mode 100644 index 00000000..8296ca13 --- /dev/null +++ b/docs/DEVASSIST_GUTTER_UNDERLINE_PROBLEMWINDOW_POPUP_SUMMARY.md @@ -0,0 +1,261 @@ +# DevAssist POC: What Microsoft Visual Studio Gives vs What We Built Ourselves + +A short explanation, in simple words, of what Visual Studio already provides, what it cannot do, and what we built ourselves (and why). + +--- + +## 1. Gutter icon (small icons left of the code) + +### What Visual Studio already does + +Visual Studio can show a small icon in the left margin (the "gutter") when there is an error or warning on that line. It uses fixed built-in icons (e.g. red for error, yellow for warning). + +If we used only that, we would get: + +- One standard icon per line +- Standard error/warning look + +The extension point is the **glyph margin** plus **IGlyphTag**, **IGlyphFactory**, **IGlyphFactoryProvider** (Microsoft.VisualStudio.Text.Editor, Microsoft.VisualStudio.Text.Tagging). The platform does **not** ship a built-in tag type that shows different icons per severity; diagnostics typically use the squiggle and Error List only. + +### Why we did not use it directly + +The built-in option: + +- Does not allow our own custom images +- Does not support different icons per severity (Critical, High, Medium, Low, Malicious) +- Does not allow custom styling + +We wanted: + +- Different icons (e.g. per severity) +- Clear visual difference by severity +- A JetBrains-style experience + +### What we built instead + +We used Visual Studio's gutter mechanism, but plugged in our own logic and icons. + +**We implemented:** + +- **DevAssistGlyphTag** โ€“ marks "this line has this severity" +- **DevAssistGlyphTagger** โ€“ decides which lines get icons +- **DevAssistGlyphFactory** โ€“ draws our custom icon (16ร—16 per severity) +- **DevAssistGlyphTaggerProvider** / **DevAssistGlyphFactoryProvider** โ€“ MEF exports so VS discovers our tagger and factory + +**Visual Studio provides:** + +- **IGlyphTag**, **IGlyphFactory**, **IGlyphFactoryProvider** +- Glyph margin (hosts the icons) + +We use the mechanism, but control the icon. + +### Why we did not use the old line marker API + +Visual Studio has an older API: + +- **IVsTextLineMarker** +- **IVsTextLines.CreateLineMarker** +- **IVsTextMarkerClient** + +We did not use it because: + +- It is the old system. +- It does not support custom images. +- Our squiggles already use the newer tagging system. +- We wanted consistency. + +### In short + +We use the VS gutter mechanism; we only supply the custom tag type and icon. + +**Key files:** `DevAssistGlyphTagger.cs`, `DevAssistGlyphTaggerProvider.cs`, `DevAssistGlyphFactory.cs`, `DevAssistGlyphTag` (in same file). + +--- + +## 2. Underline (squiggly line under the code) + +### What Visual Studio already does + +Visual Studio can: + +- Draw squiggly lines under text +- Show a tooltip on hover + +We use this fully as-is. + +**Visual Studio provides:** the **built-in error layer** plus **IErrorTag** / **ErrorTag** (Microsoft.VisualStudio.Text.Tagging). Any tagger that returns **ErrorTag** (error type + optional tooltip) gets a squiggle and tooltip drawn by VS. Same mechanism as compiler errors. + +### What we added + +Visual Studio does not know where our vulnerabilities are. We had to tell it: + +- "This line is an error. Show this message." + +**We implemented:** + +- **DevAssistErrorTagger** โ€“ produces **ErrorTag** per line with a finding (tooltip = title/description) +- **DevAssistErrorTaggerProvider** โ€“ MEF export, creates/caches tagger per buffer + +Visual Studio then: + +- Draws the squiggle +- Shows the tooltip + +### In short + +We did not draw anything ourselves. We only tell Visual Studio where and what. + +**Key files:** `DevAssistErrorTagger.cs`, `DevAssistErrorTaggerProvider.cs`. + +--- + +## 3. Problem window (list of findings) + +### What Visual Studio already provides + +Visual Studio has the **Error List** window (View โ†’ Error List): + +- Shows build errors, warnings, diagnostics +- Supports filtering +- Supports "click to go to line" + +Extensions can add items via **EnvDTE80.ErrorList**, **ErrorItems**, **ErrorItem**, or **IVsErrorList** / **ErrorListProvider**. + +### Why we did not use it (in this POC) + +We wanted: + +- Custom grouping (per file, JetBrains-like) +- Custom columns and layout +- Our own tool window (Checkmarx โ†’ DevAssist tab) +- List driven by our in-memory model and **IssuesUpdated** events + +So we built: + +- **DevAssistFindingsWindow** โ€“ our Checkmarx tool window with DevAssist tab (`ToolWindowPane`) +- **DevAssistFindingsControl** โ€“ custom WPF tree/list (FileNode, VulnerabilityNode; subscribes to **IssuesUpdated**; click to navigate) +- **DevAssistDisplayCoordinator** โ€“ holds per-file findings, raises **IssuesUpdated**, provides **GetAllIssuesByFile()** + +This is our own tool window. + +**Note:** We **do** use the Error List (EnvDTE80) in **DevAssistCompilerErrorsForLine** only to show compiler errors in the hover popup ("Also on this line (Compiler / VS)"), not for the DevAssist findings list. + +### In short + +We built our own list instead of using the built-in Error List. + +**Key files:** `DevAssistFindingsWindow.cs`, `DevAssistFindingsControl.xaml(.cs)`, `DevAssistDisplayCoordinator.cs`. + +--- + +## 4. Mouse hover popup + +### What Visual Studio already does + +Visual Studio has **Quick Info**: + +- Shows a small popup when hovering +- Good for plain text +- Supports basic formatting (ContainerElement, ClassifiedTextElement, ClassifiedTextRun) + +**Provided by:** **IQuickInfoSource**, **IQuickInfoSourceProvider**, **IQuickInfoBroker**, **IIntellisenseControllerProvider**. + +We use this system for integration (e.g. trigger, session). + +### Why we also built our own popup + +Default Quick Info: + +- Is mainly for text +- Does not reliably support rich layout +- Does not easily support: logos, severity badges, clickable links, custom layout, multiple cards, "Also on this line (Compiler / VS)" section + +So we added: + +- **DevAssistHoverPopup** โ€“ custom UserControl (XAML): logo, severity icon, single/multiple cards, compiler errors section, action links (Fix, View details, Ignore, etc.). Theme-aware. +- **DevAssistQuickInfoController** โ€“ subscribes to MouseHover; gets vulnerabilities from **DevAssistErrorTagger**; shows a **Popup** with **DevAssistHoverPopup** as content. +- **DevAssistQuickInfoSource** โ€“ optional simple text fallback via ContainerElement. +- **DevAssistSuggestedActionsSource** + **FixWithCxOneAssistSuggestedAction** / **ViewDetailsSuggestedAction** โ€“ light bulb actions. +- **DevAssistCompilerErrorsForLine** โ€“ reads **EnvDTE80.ErrorList** for file+line and passes compiler errors into the popup. + +This gives us: logo, severity icon, styled layout, clickable actions ("Fix", "View details"), and compiler errors on the same line. + +### In short + +We use Quick Info for basic integration. We use our own popup for rich UI. + +**Key files:** `DevAssistQuickInfoController.cs`, `DevAssistQuickInfoControllerProvider.cs`, `DevAssistHoverPopup.xaml` / `.xaml.cs`, `DevAssistQuickInfoSource.cs`, `DevAssistSuggestedActionsSource.cs`, `DevAssistQuickFixActions.cs`, `DevAssistCompilerErrorsForLine.cs`. + +--- + +## Summary table + +| Feature | What Visual Studio gives | What we implemented | +|---------------|---------------------------------------------------------------|-------------------------------------------------------------------------------------| +| **Gutter icon** | IGlyphTag, IGlyphFactory, IGlyphFactoryProvider, glyph margin | DevAssistGlyphTag, DevAssistGlyphTagger, DevAssistGlyphFactory (+ providers) | +| **Squiggle** | IErrorTag / ErrorTag (VS draws it) | DevAssistErrorTagger, DevAssistErrorTaggerProvider | +| **Problem list** | Error List, EnvDTE80.ErrorList, IErrorList | DevAssistFindingsWindow, DevAssistFindingsControl, DevAssistDisplayCoordinator | +| **Hover** | Quick Info system (IQuickInfoSource, IQuickInfoBroker, etc.) | DevAssistHoverPopup, DevAssistQuickInfoController, DevAssistQuickInfoSource (+ light bulb) | + +--- + +## Where we already use built-in features + +| Area | Built-in usage | +|----------|---------------------------------------------------------------------------------| +| **Squiggle** | Fully built-in. We only provide ErrorTag; VS draws the squiggle and tooltip. | +| **Gutter** | Use VS gutter mechanism; we only supply custom tag and icon. | +| **Hover** | Use Quick Info system; extend with custom popup. | +| **Theme** | Use EnvironmentColors so UI follows light/dark theme. | + +--- + +## Suggestions: where we could use more built-in features + +### 1. Use Error List (IErrorList / EnvDTE80) for findings too + +Instead of only using our custom findings window, we could: + +- Add DevAssist findings into the built-in Error List +- Get filtering and navigation for free +- Let users see DevAssist issues together with build errors + +We could: keep our rich window **and** feed the Error List. + +### 2. Use only Quick Info (simpler hover) + +If we accept: no logo, no clickable links, simpler formatting, we could: + +- Use only **ContainerElement** / **ClassifiedTextElement** / **ClassifiedTextRun** +- Remove the custom popup + +This would reduce complexity but we would lose the rich UI. + +### 3. Use standard gutter icons (if custom icons not needed) + +If we stop needing severity-based custom icons, we could: + +- Return only **IErrorTag** (or use a single built-in glyph type) +- Let Visual Studio draw its default error/warning icon + +We would remove: DevAssistGlyphTag, DevAssistGlyphFactory (and custom icon set). +We would lose: custom severity visuals. + +--- + +## Final summary + +We already use built-in features from Microsoft Visual Studio wherever possible: + +- **Squiggles** โ†’ fully built-in +- **Gutter mechanism** โ†’ built-in hosting, custom icon +- **Hover** โ†’ built-in Quick Info + custom UI +- **Theme** โ†’ built-in theme system + +The only major area where we **completely replaced** a built-in feature is the **Error List**, where we chose to build our own findings window for flexibility and richer UI. + +--- + +## Single update path (how the four surfaces stay in sync) + +All four surfaces are updated from **one place**: **DevAssistDisplayCoordinator.UpdateFindings(buffer, vulnerabilities, filePath)**. The coordinator updates the glyph tagger and error tagger (gutter and underline), and updates its per-file map and raises **IssuesUpdated** (problem window). The popup and Quick Fix read from the same tagger data when the user hovers or invokes the light bulb. One logical source of truth; no separate update path per surface. diff --git a/docs/DEVASSIST_TESTING_ERROR_SCENARIOS.md b/docs/DEVASSIST_TESTING_ERROR_SCENARIOS.md new file mode 100644 index 00000000..e1fb8aa6 --- /dev/null +++ b/docs/DEVASSIST_TESTING_ERROR_SCENARIOS.md @@ -0,0 +1,226 @@ +# How to Test DevAssist Error-Handling Scenarios + +This document describes how to verify that **third-party or VS errors** do not crash gutter, underline, problem window, or hover. Use a mix of **unit tests**, **manual tests**, and (optional) **test commands** that force exceptions. + +--- + +## 1. Unit tests (automated) + +### 1.1 DevAssistErrorHandler + +Run the **DevAssistErrorHandlerTests** (see `ast-visual-studio-extension-tests/cx-unit-tests/cx-extension-tests/DevAssistErrorHandlerTests.cs`): + +- **TryRun** โ€“ When the action throws, `TryRun` returns `false` and does not rethrow. When the action succeeds, it returns `true`. +- **TryGet** โ€“ When the function throws, `TryGet` returns the given default and does not rethrow. When it succeeds, it returns the function result. +- **LogAndSwallow** โ€“ When given a non-null exception, it does not throw (e.g. call with null and with a real exception; verify no throw). + +Run from Visual Studio: **Test Explorer** โ†’ run **DevAssistErrorHandlerTests**. + +Or from repo root (when the solution builds successfully): + +```powershell +dotnet test ast-visual-studio-extension-tests\ast-visual-studio-extension-tests.csproj --filter "FullyQualifiedName~DevAssistErrorHandlerTests" +``` + +--- + +## 2. Manual tests (in the VS Experimental Instance) + +Goal: confirm that when something goes wrong (or is simulated), the IDE stays stable and other features still work. + +### 2.1 Gutter and underline (normal path) + +1. Deploy the extension (F5 or install VSIX in Experimental Instance). +2. Open a **C# file** (e.g. any `.cs` in the solution). +3. After ~1 second you should see **gutter icons** and **underlines** (mock data). +4. **Verify:** No crashes; Output window may show `DevAssist:` debug lines but no unhandled exceptions. + +### 2.2 Problem window + +1. In the same instance, open **Checkmarx** tool window and switch to **DevAssist** tab (or use the command that shows DevAssist findings). +2. **Verify:** Findings list appears (mock or from last opened file). If you open the window before opening a C# file, mock data is shown; if after, data from the coordinator is shown. +3. **Verify:** No crash when opening the window or switching tabs. + +### 2.3 Hover + +1. Hover the mouse over a line that has a **gutter icon** / underline (e.g. line 1, 3, 5 in the mock data). +2. **Verify:** Rich hover popup appears; closing it or moving away does not crash. +3. Hover over a line **without** a finding. **Verify:** No popup, no crash. + +### 2.4 Coordinator: one failure does not block others + +This is hard to simulate without code changes. Option: temporarily make the **glyph tagger** return null for a buffer (or skip calling it in the coordinator) and confirm that **underline** and **problem window** still update when you open a file. Alternatively, rely on unit tests and code review for the coordinatorโ€™s per-step `TryRun` usage. + +--- + +## 3. Optional: test command that forces exceptions (debug builds) + +To simulate **third-party/VS-like** failures and confirm they are swallowed: + +1. Add a **test menu command** (e.g. โ€œDevAssist โ€“ Test error handlingโ€) that: + - Calls **DevAssistDisplayCoordinator.UpdateFindings** with a **null buffer** (expect no crash; coordinator logs and returns). + - Or, in a **test/debug-only** code path, temporarily **throw** inside a callback (e.g. in `GetTags` or `GenerateGlyph`) and confirm: + - The exception is caught and logged (e.g. in Debug Output with `[DevAssist]`). + - The editor and other DevAssist features (other taggers, problem window, hover) continue to work. + +2. Run the command from the Experimental Instance and check: + - **Output** window (Show output from: Debug) for `[DevAssist]` messages. + - That VS does **not** show an unhandled exception dialog and does not crash. + +Example (pseudo-code) for a test command: + +```csharp +// In a test command (e.g. TestDevAssistErrorHandlingCommand): +// 1. UpdateFindings(null, someList) โ†’ should not throw +DevAssistDisplayCoordinator.UpdateFindings(null, new List()); +// 2. RefreshProblemWindow(null, ...) โ†’ should not throw +DevAssistDisplayCoordinator.RefreshProblemWindow(null); +// Show a message box: "If you see this, error handling did not crash." +``` + +--- + +## 4. What to check in Debug Output + +When testing, open **Output** (View โ†’ Output), set โ€œShow output fromโ€ to **Debug**. Look for: + +- **Normal:** `DevAssist:`, `DevAssist Markers:`, `DevAssistDisplayCoordinator:` messages. +- **After a swallowed exception:** `[DevAssist] : ` and stack trace. No unhandled exception dialog should appear. + +--- + +## 5. Manual testing: path normalization and buffer-derived path + +These checks verify the **file path handling** improvements (normalized key, path from buffer, real path in mock). + +### 5.1 Real file path in findings + +1. Deploy the extension (F5) and open a **C# file** from your solution (e.g. `SomeFolder\MyFile.cs`). +2. After ~1 second, gutter icons and underlines appear (mock data). +3. Open **Checkmarx** โ†’ **DevAssist** tab. +4. **Verify:** The findings list shows the **actual path** of the file you opened (e.g. full path to `MyFile.cs`), not a generic default path. This confirms the listener uses `GetFilePathForBuffer(buffer)` and passes it to mock data. + +### 5.2 Multiple files (per-file storage) + +1. Open **first** C# file (e.g. `FileA.cs`). Wait for mock findings. +2. Open **second** C# file (e.g. `FileB.cs`). Wait for mock findings. +3. Open **DevAssist** tab. +4. **Verify:** The problem window shows findings for **both** files (two file nodes). Closing one file does not clear the other fileโ€™s findings from the in-memory list until you call `UpdateFindings` with an empty list for that file (e.g. when real scan is integrated). + +### 5.3 Optional: clear-on-empty (when integrated) + +When the real scan is wired: after opening a file and showing findings, if the scan is re-run and returns **no issues**, the code should call `UpdateFindings(buffer, new List(), filePath: null)`. The coordinator will use the path from the buffer and **remove** that file from the per-file map, and the problem window will refresh via `IssuesUpdated`. Manual test: simulate that call (e.g. with a test command) and confirm the file disappears from the findings list. + +--- + +## 6. Manual testing: DevAssist (Checkmarx) vs VS/compiler errors (hover) + +Use these steps to verify **Checkmarx plugin findings** and **VS/compiler errors** separately and together. + +### 6.1 Scenario A: Only DevAssist (Checkmarx) finding + +**Goal:** See only our finding on a line (no C# error). + +1. Deploy the extension (F5) and open any **C# file** (e.g. `DevAssistGlyphTagger.cs`). +2. Wait ~1 second. Mock data adds findings on **lines 1, 3, 5, 7, 9** (1-based). Pick a line that has **no** red squiggle from the compiler (e.g. line 5 if itโ€™s valid code). +3. **Hover** over that line (or over the **gutter icon** / **our squiggle**). +4. **Verify:** + - **Our custom popup** appears (DevAssist logo, severity, โ€œFix with Checkmarx One Assistโ€, โ€œView detailsโ€). + - Optional: hover **only** over our squiggle โ†’ our **ErrorTag** tooltip (short text). + - No compiler error tooltip (no red squiggle on that line). + +### 6.2 Scenario B: Only VS/compiler error + +**Goal:** See only a C# compiler error on a line (no DevAssist finding). + +1. In the same Experimental Instance, open a C# file (or use another file). +2. On a line that does **not** have a DevAssist mock finding (e.g. **line 2** or **line 10**), add a **compile error**, e.g.: + - `NotARealType x = null;` (CS0246 โ€“ type not found), or + - `int a = undefinedVar;` (CS0103 โ€“ name not found). +3. Save the file. You should see a **red squiggle** on that line (and in Error List). +4. **Hover** over the **red squiggle** (or the error text). +5. **Verify:** + - **Compiler/VS tooltip** appears (e.g. โ€œCS0246: The type or namespace name โ€˜NotARealTypeโ€™ could not be foundโ€). + - No DevAssist popup (that line has no DevAssist finding). + +### 6.3 Scenario C: Same line โ€“ both DevAssist finding and VS error + +**Goal:** One line has both; verify you can see both (our popup + compiler tooltip). + +**Reproduce (exact steps):** + +1. **Start the extension** + Press **F5** to launch the Experimental Instance. + +2. **Open a C# file** + e.g. **`DevAssistGlyphTagger.cs`** (or any `.cs` in the solution). + Wait **about 1 second** so mock data loads. You should see **gutter icons** and **squiggles** on **lines 1, 3, 5, 7, 9**. + +3. **Put a compiler error on line 5** + - Go to **line 5** (Ctrl+G, type 5, Enter). + - **Replace the whole line** with this (invalid type so the compiler reports an error): + ```csharp + NotARealType x = null; + ``` + So line 5 now has: **our mock finding** (from step 2) **and** a **C# error** (CS0246). + +4. **Save the file** (Ctrl+S). + You should see **two** underlines on line 5 (or one combined): our DevAssist squiggle and the **red** compiler squiggle. The **Error List** may show CS0246 for that line. + +5. **Hover over line 5** (over the text or the left part of the line): + - **Check:** The **DevAssist custom popup** appears (logo, severity, โ€œFix with Checkmarx One Assistโ€, โ€œView detailsโ€). That is the Checkmarx finding. + +6. **Hover over the red squiggle** (move the mouse onto the underlined `NotARealType` or the red underline): + - **Check:** The **compiler tooltip** appears, e.g. *โ€œCS0246: The type or namespace name 'NotARealType' could not be foundโ€*. That is the VS error. + +7. **Summary** + On the same line you get: **our popup** for the DevAssist finding and **hover the red squiggle** for the compiler message. There is **no** single Quick Info bubble that shows both; the two are shown separately as above. + +--- + +## 8. Manual testing: Multiple vulnerabilities on same line (JetBrains-style hover) + +Use this to verify the **multi-issue hover UI**: when several findings are on the same line, the popup shows โ€œN issues detected on this lineโ€ and **one card per vulnerability** (icon, title, description, Fix / View details / Ignore this). + +**Steps:** + +1. **Run the extension** + Press **F5** to start the Experimental Instance. + +2. **Open any C# file** + e.g. `DevAssistGlyphTagger.cs` or `Program.cs`. + Wait **~1 second** so mock data loads. You should see **gutter icons** and **underlines** on lines **1, 3, 5, 7, 9**. + Mock data has **two vulnerabilities on line 5**: โ€œHigh-Risk Packageโ€ (OSS) and โ€œMedium Severity Findingโ€ (ASCA). + +3. **Hover over line 5** + Move the mouse over **line 5** (text or gutter). + +4. **Verify the multi-issue popup** + - Header: **โ€œ2 issues detected on this line Checkmarx One Assistโ€**. + - **First card:** severity icon, โ€œHigh-Risk Packageโ€, description (e.g. Test High vulnerabilityโ€ฆ), links: Fix with Checkmarx One Assist | View details | Ignore this vulnerability. + - **Second card:** severity icon, โ€œMedium Severity Findingโ€, description (e.g. Test Medium vulnerabilityโ€ฆ), same three links. + - Each cardโ€™s โ€œFixโ€ / โ€œView detailsโ€ / โ€œIgnore thisโ€ act on **that** vulnerability (e.g. View details shows the correct title/description). + +5. **Single-issue line (sanity check)** + Hover over **line 1** or **line 7** (only one finding each). + **Verify:** The **single-issue** layout appears (one severity icon, one title, one description, one set of links), not the โ€œN issues detectedโ€ header. + +**Summary:** Line 5 triggers the JetBrains-style multi-vulnerability UI; other lines trigger the single-issue UI. + +--- + +## 9. Summary + +| Scenario | How to test (manual) | +|----------|----------------------| +| **Gutter / underline / problem window / hover** | Open C# file, open DevAssist tab, hover over lines with findings; confirm no crash. | +| **DevAssist only (Scenario A)** | Line with only mock finding (e.g. line 5, valid code); hover โ†’ our custom popup. | +| **VS/compiler only (Scenario B)** | Line with only C# error (e.g. line 2); hover red squiggle โ†’ compiler tooltip. | +| **Both on same line (Scenario C)** | Line 5 with mock + compile error; hover line โ†’ our popup; hover red squiggle โ†’ compiler tooltip. | +| **Multiple issues on same line (Section 8)** | Hover **line 5** โ†’ โ€œ2 issues detected on this lineโ€ + two full cards; hover line 1 or 7 โ†’ single-issue layout. | +| **Real file path in mock** | Open a specific C# file, open DevAssist tab; confirm path in list matches opened file. | +| **Per-file storage (multiple files)** | Open two C# files; confirm both appear in DevAssist findings. | +| **Coordinator with null/invalid input** | Optional test command: call `UpdateFindings(null, ...)`, `RefreshProblemWindow(null)`; confirm no throw. | +| **Simulated exception in callback** | Optional test command that throws in a guarded path; confirm log in Output and no crash. | + +If all of the above pass, error handling for **gutter, underline, problem window, and hover** is behaving as intended when third-party or VS code causes (or simulates) errors, **path normalization and buffer-derived path** behave as designed, and the **multiple-vulnerability hover UI** matches the JetBrains-style design. From bde3b2c13f37e4e63ae6c9408bfd64b142ed06f8 Mon Sep 17 00:00:00 2001 From: Rahul Pidde <206018639+cx-rahul-pidde@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:08:52 +0530 Subject: [PATCH 09/45] Added oclick logic in problem windows --- .../Core/DevAssistDisplayCoordinator.cs | 37 ++++++++++++ .../DevAssist/Core/DevAssistErrorListSync.cs | 28 ++++++++- .../DevAssistTextViewCreationListener.cs | 21 +++++-- .../DevAssistFindingsControl.xaml.cs | 59 ++++++++++--------- 4 files changed, 112 insertions(+), 33 deletions(-) diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistDisplayCoordinator.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistDisplayCoordinator.cs index 88a3a83f..4a36ab14 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistDisplayCoordinator.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistDisplayCoordinator.cs @@ -8,6 +8,7 @@ using ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons; using ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers; using ast_visual_studio_extension.CxExtension.DevAssist.UI.FindingsWindow; +using System.Linq; namespace ast_visual_studio_extension.CxExtension.DevAssist.Core { @@ -100,6 +101,42 @@ public static List GetCurrentFindings() } } + /// + /// Finds a vulnerability by Id in the current findings (e.g. from Error List task HelpKeyword). + /// + public static Vulnerability FindVulnerabilityById(string id) + { + if (string.IsNullOrEmpty(id)) return null; + lock (_lock) + { + foreach (var list in _fileToIssues.Values) + { + var v = list?.FirstOrDefault(x => string.Equals(x.Id, id, StringComparison.OrdinalIgnoreCase)); + if (v != null) return v; + } + return null; + } + } + + /// + /// Finds the first vulnerability at the given location (for Error List selection by document + line). + /// + /// Full path of the file (normalized for comparison). + /// 0-based line number (Error List uses 0-based). + public static Vulnerability FindVulnerabilityByLocation(string documentPath, int zeroBasedLine) + { + if (string.IsNullOrEmpty(documentPath)) return null; + string key; + try { key = Path.GetFullPath(documentPath); } + catch { key = documentPath; } + int line1Based = zeroBasedLine + 1; + lock (_lock) + { + if (!_fileToIssues.TryGetValue(key, out var list) || list == null) return null; + return list.FirstOrDefault(v => v.LineNumber == line1Based); + } + } + /// /// 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 (JetBrains-like). diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorListSync.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorListSync.cs index ed9f599f..9aef19a1 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorListSync.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorListSync.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using EnvDTE; using EnvDTE80; @@ -15,6 +16,9 @@ namespace ast_visual_studio_extension.CxExtension.DevAssist.Core /// internal sealed class DevAssistErrorListSync { + /// Prefix stored in ErrorTask.HelpKeyword so we can identify DevAssist tasks and recover vulnerability Id. + public const string HelpKeywordPrefix = "DevAssist:"; + private ErrorListProvider _errorListProvider; private bool _subscribed; @@ -100,15 +104,18 @@ private void SyncToErrorList(IReadOnlyDictionary> is foreach (var v in list) { string severityLabel = v.Severity.ToString(); + string docPath = GetDocumentPath(v.FilePath, filePath); + string helpKeyword = HelpKeywordPrefix + v.Id; var task = new ErrorTask { Category = TaskCategory.CodeSense, ErrorCategory = GetErrorCategory(v.Severity), Text = $"[DevAssist] [{severityLabel}] {v.Title}", - Document = filePath, + Document = docPath, Line = Math.Max(0, v.LineNumber - 1), Column = Math.Max(0, v.ColumnNumber), - HierarchyItem = document != null ? GetHierarchyItem(document) : null + HierarchyItem = document != null ? GetHierarchyItem(document) : null, + HelpKeyword = helpKeyword }; task.Navigate += (s, e) => NavigateToVulnerability(v); @@ -117,6 +124,23 @@ private void SyncToErrorList(IReadOnlyDictionary> is } } + /// + /// Returns a normalized full path for the Error List so VS shows the actual file name instead of "Document 1". + /// + private static string GetDocumentPath(string vulnerabilityFilePath, string fallbackFilePath) + { + string path = !string.IsNullOrEmpty(vulnerabilityFilePath) ? vulnerabilityFilePath : fallbackFilePath; + if (string.IsNullOrEmpty(path)) return null; + try + { + return Path.GetFullPath(path); + } + catch + { + return path; + } + } + private static TaskErrorCategory GetErrorCategory(SeverityLevel severity) { switch (severity) diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs index 8dcb8e2d..9bb323e6 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs @@ -2,6 +2,9 @@ using System.Collections.Generic; using System.ComponentModel.Composition; using System.Threading; +using EnvDTE; +using EnvDTE80; +using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities; using ast_visual_studio_extension.CxExtension.DevAssist.Core; @@ -63,12 +66,22 @@ public void TextViewCreated(IWpfTextView textView) // Single coordinator call: updates gutter, underline, and current findings for problem window (Option B) var filePath = DevAssistDisplayCoordinator.GetFilePathForBuffer(buffer); - // When path is unknown (e.g. ITextDocument not available), use a unique key per buffer so multi-file doesn't overwrite with "Program.cs" + // When path is unknown (e.g. ITextDocument not available), try active document so problem window shows real file name if (string.IsNullOrEmpty(filePath)) { - var fallback = Interlocked.Increment(ref _fallbackDocumentCounter); - filePath = $"Document {fallback}"; - System.Diagnostics.Debug.WriteLine($"DevAssist: GetFilePathForBuffer returned null, using fallback: {filePath}"); + try + { + var dte = Package.GetGlobalService(typeof(DTE)) as DTE2; + if (!string.IsNullOrEmpty(dte?.ActiveDocument?.FullName)) + filePath = dte.ActiveDocument.FullName; + } + catch { } + if (string.IsNullOrEmpty(filePath)) + { + var fallback = Interlocked.Increment(ref _fallbackDocumentCounter); + filePath = $"Document {fallback}"; + System.Diagnostics.Debug.WriteLine($"DevAssist: GetFilePathForBuffer returned null, using fallback: {filePath}"); + } } var vulnerabilities = DevAssistMockData.GetCommonVulnerabilities(filePath); DevAssistDisplayCoordinator.UpdateFindings(buffer, vulnerabilities, filePath); diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml.cs b/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml.cs index 3337df38..fa4d40fb 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml.cs @@ -255,38 +255,39 @@ private void UpdateStatusBar() /// private void TreeViewItem_MouseDoubleClick(object sender, MouseButtonEventArgs e) { - ThreadHelper.ThrowIfNotOnUIThread(); - var item = sender as TreeViewItem; if (item?.DataContext is VulnerabilityNode vulnerability) { - NavigateToVulnerability(vulnerability); e.Handled = true; + Microsoft.VisualStudio.Shell.ThreadHelper.JoinableTaskFactory.RunAsync(async () => + { + await Microsoft.VisualStudio.Shell.ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + NavigateToVulnerability(vulnerability); + }); } } /// - /// Navigate to vulnerability location in code + /// Navigate to vulnerability location in code (same approach as Error List navigation). /// private void NavigateToVulnerability(VulnerabilityNode vulnerability) { ThreadHelper.ThrowIfNotOnUIThread(); + if (string.IsNullOrEmpty(vulnerability?.FilePath)) return; try { var dte = Microsoft.VisualStudio.Shell.ServiceProvider.GlobalProvider.GetService(typeof(DTE)) as DTE; - if (dte != null && !string.IsNullOrEmpty(vulnerability.FilePath)) + if (dte == null) return; + + var window = dte.ItemOperations.OpenFile(vulnerability.FilePath, EnvDTE.Constants.vsViewKindCode); + Document doc = window?.Document; + if (doc?.Object("TextDocument") is TextDocument textDoc) { - // Open the file - var window = dte.ItemOperations.OpenFile(vulnerability.FilePath); - - // Navigate to line and column - if (dte.ActiveDocument != null) - { - var textDocument = (TextDocument)dte.ActiveDocument.Object("TextDocument"); - textDocument.Selection.MoveToLineAndOffset(vulnerability.Line, vulnerability.Column); - textDocument.Selection.SelectLine(); - } + int line = Math.Max(1, vulnerability.Line); + int column = Math.Max(1, vulnerability.Column); + textDoc.Selection.MoveToLineAndOffset(line, column); + textDoc.Selection.SelectLine(); } } catch (Exception ex) @@ -516,27 +517,31 @@ private void CopyMenuItem_Click(object sender, RoutedEventArgs e) /// private void NavigateMenuItem_Click(object sender, RoutedEventArgs e) { - ThreadHelper.ThrowIfNotOnUIThread(); - var selectedItem = FindingsTreeView.SelectedItem; if (selectedItem is VulnerabilityNode vulnNode) { - NavigateToVulnerability(vulnNode); + ThreadHelper.JoinableTaskFactory.RunAsync(async () => + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + NavigateToVulnerability(vulnNode); + }); } else if (selectedItem is FileNode fileNode && !string.IsNullOrEmpty(fileNode.FilePath)) { - try + ThreadHelper.JoinableTaskFactory.RunAsync(async () => { - var dte = Microsoft.VisualStudio.Shell.ServiceProvider.GlobalProvider.GetService(typeof(DTE)) as DTE; - if (dte != null) + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + try { - dte.ItemOperations.OpenFile(fileNode.FilePath); + var dte = Microsoft.VisualStudio.Shell.ServiceProvider.GlobalProvider.GetService(typeof(DTE)) as DTE; + if (dte != null) + dte.ItemOperations.OpenFile(fileNode.FilePath, EnvDTE.Constants.vsViewKindCode); } - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"Error navigating to file: {ex.Message}"); - } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Error navigating to file: {ex.Message}"); + } + }); } } From eccf9a8cc36dabbec5dc86cc817c8a8b7c5c5d61 Mon Sep 17 00:00:00 2001 From: Rahul Pidde <206018639+cx-rahul-pidde@users.noreply.github.com> Date: Mon, 23 Feb 2026 18:08:50 +0530 Subject: [PATCH 10/45] POC of pophover --- .../DevAssist/Core/DevAssistMockData.cs | 76 ++- .../Markers/DevAssistAsyncQuickInfoSource.cs | 117 ++++ .../DevAssistAsyncQuickInfoSourceProvider.cs | 20 + .../Core/Markers/DevAssistErrorTagger.cs | 7 +- .../Core/Markers/DevAssistHoverPopup.xaml.cs | 3 - .../Core/Markers/DevAssistQuickFixActions.cs | 3 +- .../DevAssistQuickInfoClassifications.cs | 55 ++ .../Markers/DevAssistQuickInfoController.cs | 14 +- .../DevAssistQuickInfoControllerProvider.cs | 2 +- .../Core/Markers/DevAssistQuickInfoSource.cs | 607 +++++++++++++++++- .../DevAssistQuickInfoSourceProvider.cs | 7 +- .../ast-visual-studio-extension.csproj | 4 + 12 files changed, 871 insertions(+), 44 deletions(-) create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistAsyncQuickInfoSource.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistAsyncQuickInfoSourceProvider.cs create mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoClassifications.cs diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistMockData.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistMockData.cs index a46531b6..5aa87436 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistMockData.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistMockData.cs @@ -16,7 +16,10 @@ namespace ast_visual_studio_extension.CxExtension.DevAssist.Core public static class DevAssistMockData { /// Default file path used for mock vulnerabilities (editor and findings window). - public const string DefaultFilePath = "Program.cs"; + public const string DefaultFilePath = "Program.cs" + + /// Vulnerability Id that uses standard Quick Info popup only (no custom hover popup). + public const string QuickInfoOnlyVulnerabilityId = "POC-007"; /// /// Returns the common list of mock vulnerabilities used for: @@ -123,6 +126,77 @@ public static List GetCommonVulnerabilities(string filePath = nul FilePath = path, PackageName = "debug", PackageVersion = "2.6.9" + }, + // Line 11 โ€“ Quick Info only (no custom hover popup): shows standard VS Quick Info with rich text and links + new Vulnerability + { + Id = QuickInfoOnlyVulnerabilityId, + Title = "High Severity Finding", + Description = "This finding uses the standard Quick Info popup: Checkmarx One Assist badge, rich severity name, description, and action links (Fix with Checkmarx Assist, View Details, Ignore vulnerability).", + Severity = SeverityLevel.High, + Scanner = ScannerType.ASCA, + LineNumber = 11, + ColumnNumber = 0, + FilePath = path, + RuleName = "QUICK_INFO_DEMO", + RemediationAdvice = "Use the Quick Info links to fix, view details, or ignore." + }, + // Line 13 โ€“ Quick Info only: 2 vulnerabilities on same line (no custom popup; hover shows Quick Info for first) + new Vulnerability + { + Id = QuickInfoOnlyVulnerabilityId, + Title = "First finding on line (Critical)", + Description = "First of two Quick-Info-only findings on this line. Critical severity โ€“ sensitive data exposure risk.", + Severity = SeverityLevel.Critical, + Scanner = ScannerType.ASCA, + LineNumber = 13, + ColumnNumber = 0, + FilePath = path, + RuleName = "SENSITIVE_DATA", + RemediationAdvice = "Avoid logging or exposing sensitive data." + }, + new Vulnerability + { + Id = QuickInfoOnlyVulnerabilityId, + Title = "Second finding on line (Medium)", + Description = "Second of two Quick-Info-only findings on this line. Medium severity โ€“ weak cryptographic usage.", + Severity = SeverityLevel.Medium, + Scanner = ScannerType.ASCA, + LineNumber = 13, + ColumnNumber = 0, + FilePath = path, + RuleName = "WEAK_CRYPTO", + RemediationAdvice = "Use a stronger algorithm." + }, + // Line 15 โ€“ Quick Info only (single finding) + new Vulnerability + { + Id = QuickInfoOnlyVulnerabilityId, + Title = "Quick Info โ€“ Outdated dependency", + Description = "Quick-Info-only demo: outdated package with known CVE. Use standard Quick Info links to fix or view details.", + Severity = SeverityLevel.Medium, + Scanner = ScannerType.OSS, + LineNumber = 15, + ColumnNumber = 0, + FilePath = path, + PackageName = "minimist", + PackageVersion = "1.2.0", + RecommendedVersion = "1.2.6", + CveName = "CVE-2022-21803" + }, + // Line 17 โ€“ Quick Info only (single finding) + new Vulnerability + { + Id = QuickInfoOnlyVulnerabilityId, + Title = "Quick Info โ€“ Low severity", + Description = "Quick-Info-only demo: low-severity finding. Only the standard Quick Info popup is shown here.", + Severity = SeverityLevel.Low, + Scanner = ScannerType.ASCA, + LineNumber = 17, + ColumnNumber = 0, + FilePath = path, + RuleName = "LOW_SEVERITY_DEMO", + RemediationAdvice = "Consider addressing in next sprint." } }; } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistAsyncQuickInfoSource.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistAsyncQuickInfoSource.cs new file mode 100644 index 00000000..5474dad1 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistAsyncQuickInfoSource.cs @@ -0,0 +1,117 @@ +using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +{ + /// + /// Async Quick Info source so the modern presenter can wire navigation callbacks + /// (legacy IQuickInfoSource presenter ignores per-ClassifiedTextRun actions). + /// Only one content block is shown per session even when multiple subject buffers exist. + /// + internal class DevAssistAsyncQuickInfoSource : IAsyncQuickInfoSource + { + private static readonly HashSet _sessionsWithDevAssistContent = new HashSet(); + private static readonly object _sessionLock = new object(); + + private readonly ITextBuffer _buffer; + private bool _disposed; + + public DevAssistAsyncQuickInfoSource(ITextBuffer buffer) + { + _buffer = buffer; + } + + public async Task GetQuickInfoItemAsync(IAsyncQuickInfoSession session, CancellationToken cancellationToken) + { + if (!DevAssistQuickInfoSource.UseRichHover) + return null; + + SnapshotPoint? triggerPoint = session.GetTriggerPoint(_buffer.CurrentSnapshot); + if (!triggerPoint.HasValue && session.TextView != null) + { + var viewSnapshot = session.TextView.TextSnapshot; + var viewTrigger = session.GetTriggerPoint(viewSnapshot); + if (viewTrigger.HasValue && viewTrigger.Value.Snapshot.TextBuffer != _buffer) + { + var mapped = session.TextView.BufferGraph.MapDownToFirstMatch( + viewTrigger.Value, + PointTrackingMode.Positive, + sb => sb == _buffer, + PositionAffinity.Predecessor); + if (mapped.HasValue) + triggerPoint = mapped.Value; + } + else if (viewTrigger.HasValue && viewTrigger.Value.Snapshot.TextBuffer == _buffer) + { + triggerPoint = viewTrigger; + } + } + + if (!triggerPoint.HasValue) + return null; + + await Task.Yield(); + cancellationToken.ThrowIfCancellationRequested(); + + var snapshot = triggerPoint.Value.Snapshot; + int lineNumber = snapshot.GetLineNumberFromPosition(triggerPoint.Value.Position); + + var tagger = DevAssistErrorTaggerProvider.GetTaggerForBuffer(_buffer); + if (tagger == null) + return null; + + var vulnerabilities = tagger.GetVulnerabilitiesForLine(lineNumber); + if (vulnerabilities == null || vulnerabilities.Count == 0) + return null; + + // Only one of our sources (per session) should contribute; avoid duplicate blocks when multiple subject buffers exist. + lock (_sessionLock) + { + if (_sessionsWithDevAssistContent.Contains(session)) + return null; + _sessionsWithDevAssistContent.Add(session); + } + + void OnSessionStateChanged(object sender, QuickInfoSessionStateChangedEventArgs e) + { + if (e.NewState == QuickInfoSessionState.Dismissed) + { + lock (_sessionLock) + { + _sessionsWithDevAssistContent.Remove(session); + } + session.StateChanged -= OnSessionStateChanged; + } + } + + session.StateChanged += OnSessionStateChanged; + + object content = DevAssistQuickInfoSource.BuildQuickInfoContentForLine(vulnerabilities); + if (content == null) + { + lock (_sessionLock) + { + _sessionsWithDevAssistContent.Remove(session); + } + session.StateChanged -= OnSessionStateChanged; + return null; + } + + var line = snapshot.GetLineFromLineNumber(lineNumber); + var applicableToSpan = snapshot.CreateTrackingSpan(line.Extent, SpanTrackingMode.EdgeInclusive); + return new QuickInfoItem(applicableToSpan, content); + } + + public void Dispose() + { + if (_disposed) + return; + _disposed = true; + } + } +} diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistAsyncQuickInfoSourceProvider.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistAsyncQuickInfoSourceProvider.cs new file mode 100644 index 00000000..023d3e00 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistAsyncQuickInfoSourceProvider.cs @@ -0,0 +1,20 @@ +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Utilities; +using System.ComponentModel.Composition; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +{ + [Export(typeof(IAsyncQuickInfoSourceProvider))] + [Name("DevAssist Async QuickInfo Source")] + [Order(Before = "Default Quick Info Presenter")] + [ContentType("code")] + [ContentType("text")] + internal class DevAssistAsyncQuickInfoSourceProvider : IAsyncQuickInfoSourceProvider + { + public IAsyncQuickInfoSource TryCreateQuickInfoSource(ITextBuffer textBuffer) + { + return new DevAssistAsyncQuickInfoSource(textBuffer); + } + } +} diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs index 7ef1ea35..f1648d0e 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs @@ -1,3 +1,4 @@ +using ast_visual_studio_extension.CxExtension.DevAssist.Core; using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Tagging; @@ -118,12 +119,12 @@ private bool ShouldShowUnderline(SeverityLevel severity) } /// - /// Builds tooltip text for ErrorTag. Minimal so the rich Quick Info popup (inserted first) is the main hover content. + /// Builds tooltip text for ErrorTag. Use minimal text so the rich Quick Info (async source) is the single place + /// for full content; avoids duplicate "Checkmarx One Assist" block from ErrorTag tooltip in the same popup. /// private static string BuildTooltipText(Vulnerability vulnerability) { - if (vulnerability == null) return string.Empty; - return "Hover for details"; + return string.Empty; } /// diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml.cs index dc45c385..0a369017 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml.cs @@ -59,10 +59,7 @@ private void PopulateContent() if (MultipleIssuesPanel != null) MultipleIssuesPanel.Visibility = Visibility.Collapsed; - // DevAssist logo (JetBrains-style at top of tooltip) SetDevAssistIcon(); - - // Set severity icon SetSeverityIcon(); // Severity count row when multiple findings on same line (JetBrains buildVulnerabilitySection) diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickFixActions.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickFixActions.cs index 5f6dd0e0..5c464463 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickFixActions.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickFixActions.cs @@ -18,7 +18,8 @@ internal sealed class FixWithCxOneAssistSuggestedAction : ISuggestedAction public FixWithCxOneAssistSuggestedAction(Vulnerability vulnerability) { - _vulnerability = vulnerability ?? throw new ArgumentNullException(nameof(vulnerability)); + _vulnerability = vulnerability ?? throw new ArgumentNullException(nameof(vulnerability)) + } public string DisplayText => "Fix with Checkmarx One Assist"; diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoClassifications.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoClassifications.cs new file mode 100644 index 00000000..35d2e0b0 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoClassifications.cs @@ -0,0 +1,55 @@ +using System.ComponentModel.Composition; +using System.Windows.Media; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Utilities; + +namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +{ + /// + /// Classification type names used in Quick Info content. Must match the names used in + /// [ClassificationType(ClassificationTypeNames = ...)] on the format definitions. + /// + internal static class DevAssistQuickInfoClassificationNames + { + public const string Header = "DevAssist.QuickInfo.Header"; + public const string Link = "DevAssist.QuickInfo.Link"; + } + + /// + /// Format for "Checkmarx One Assist" and severity (e.g. "High") in Quick Info: bold + colored. + /// Applied when ClassifiedTextRun uses DevAssistQuickInfoClassificationNames.Header. + /// + [Export(typeof(EditorFormatDefinition))] + [ClassificationType(ClassificationTypeNames = DevAssistQuickInfoClassificationNames.Header)] + [Name("DevAssist Quick Info Header")] + [UserVisible(true)] + [Order(After = Priority.High)] + internal sealed class DevAssistQuickInfoHeaderFormat : ClassificationFormatDefinition + { + public DevAssistQuickInfoHeaderFormat() + { + DisplayName = "DevAssist Quick Info Header"; + IsBold = true; + ForegroundColor = Color.FromRgb(0x56, 0x9C, 0xD6); // light blue, readable on dark theme + } + } + + /// + /// Format for action links in Quick Info: link-like color + underline. + /// Applied when ClassifiedTextRun uses DevAssistQuickInfoClassificationNames.Link. + /// + [Export(typeof(EditorFormatDefinition))] + [ClassificationType(ClassificationTypeNames = DevAssistQuickInfoClassificationNames.Link)] + [Name("DevAssist Quick Info Link")] + [UserVisible(true)] + [Order(After = Priority.High)] + internal sealed class DevAssistQuickInfoLinkFormat : ClassificationFormatDefinition + { + public DevAssistQuickInfoLinkFormat() + { + DisplayName = "DevAssist Quick Info Link"; + IsBold = false; + ForegroundColor = Color.FromRgb(0x37, 0x94, 0xFF); // link blue (underline comes from ClassifiedTextRunStyle.Underline) + } + } +} diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoController.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoController.cs index 9079f125..e59ccaa7 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoController.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoController.cs @@ -1,12 +1,15 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; -using System.Collections.Generic; using System.Windows; using System.Windows.Controls.Primitives; using System.Windows.Threading; using ast_visual_studio_extension.CxExtension.DevAssist.Core; +using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers { @@ -59,12 +62,17 @@ private void OnTextViewMouseHover(object sender, MouseHoverEventArgs e) return; } - if (!_provider.QuickInfoBroker.IsQuickInfoActive(_textView)) + if (!_provider.AsyncQuickInfoBroker.IsQuickInfoActive(_textView)) { var triggerPoint = point.Value.Snapshot.CreateTrackingPoint(point.Value.Position, PointTrackingMode.Positive); - _provider.QuickInfoBroker.TriggerQuickInfo(_textView, triggerPoint, trackMouse: true); + _ = _provider.AsyncQuickInfoBroker.TriggerQuickInfoAsync(_textView, triggerPoint, QuickInfoSessionOptions.None, CancellationToken.None); } + // POC-007: standard Quick Info only (no custom popup) + bool useQuickInfoOnly = vulnerabilities.All(v => v.Id == DevAssistMockData.QuickInfoOnlyVulnerabilityId); + if (useQuickInfoOnly) + return; + var wpfView = _textView as IWpfTextView; if (wpfView?.VisualElement != null) { diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoControllerProvider.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoControllerProvider.cs index 34505897..5c5ccd81 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoControllerProvider.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoControllerProvider.cs @@ -14,7 +14,7 @@ namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers internal class DevAssistQuickInfoControllerProvider : IIntellisenseControllerProvider { [Import] - internal IQuickInfoBroker QuickInfoBroker { get; set; } + internal IAsyncQuickInfoBroker AsyncQuickInfoBroker { get; set; } public IIntellisenseController TryCreateIntellisenseController(ITextView textView, IList subjectBuffers) { diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSource.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSource.cs index cd702f02..6766502e 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSource.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSource.cs @@ -1,9 +1,22 @@ using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Language.StandardClassification; +using Microsoft.VisualStudio.PlatformUI; +using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Adornments; using System; using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Threading; namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers { @@ -65,9 +78,7 @@ public void AugmentQuickInfoSession(IQuickInfoSession session, IList qiC if (vulnerabilities == null || vulnerabilities.Count == 0) return; - var first = vulnerabilities[0]; - - object content = BuildQuickInfoContent(first); + object content = BuildQuickInfoContentForLine(vulnerabilities); if (content == null) return; @@ -77,45 +88,583 @@ public void AugmentQuickInfoSession(IQuickInfoSession session, IList qiC } /// - /// Builds content using official Quick Info types: ContainerElement, ClassifiedTextElement, ClassifiedTextRun (with navigation). - /// Default presenter resolves these via IViewElementFactoryService for description, links, and theming. + /// Builds Quick Info content for all vulnerabilities on the line (e.g. line 13 with 2 findings shows both). + /// Shared for use by DevAssistAsyncQuickInfoSource (IAsyncQuickInfoSource). /// - private static object BuildQuickInfoContent(Vulnerability v) + internal static object BuildQuickInfoContentForLine(IReadOnlyList vulnerabilities) + { + if (vulnerabilities == null || vulnerabilities.Count == 0) + return null; + if (vulnerabilities.Count == 1) + return BuildQuickInfoContent(vulnerabilities[0]); + + var elements = new List(); + + // Single header at top + var headerRow = CreateHeaderRow(); + if (headerRow != null) + elements.Add(headerRow); + else + elements.Add(new ClassifiedTextElement( + new ClassifiedTextRun(PredefinedClassificationTypeNames.Keyword, "Checkmarx One Assist", ClassifiedTextRunStyle.UseClassificationStyle | ClassifiedTextRunStyle.UseClassificationFont) + )); + + for (int i = 0; i < vulnerabilities.Count; i++) + { + var v = vulnerabilities[i]; + var title = !string.IsNullOrEmpty(v.Title) ? v.Title : (!string.IsNullOrEmpty(v.RuleName) ? v.RuleName : v.Description); + var description = !string.IsNullOrEmpty(v.Description) ? v.Description : "Vulnerability detected by " + v.Scanner + "."; + var severityName = GetRichSeverityName(v.Severity); + + if (i > 0) + { + var betweenSeparator = CreateHorizontalSeparator(); + if (betweenSeparator != null) + elements.Add(betweenSeparator); + // Header (Checkmarx One Assist) for each additional finding + var headerForFinding = CreateHeaderRow(); + if (headerForFinding != null) + elements.Add(headerForFinding); + else + elements.Add(new ClassifiedTextElement( + new ClassifiedTextRun(PredefinedClassificationTypeNames.Keyword, "Checkmarx One Assist", ClassifiedTextRunStyle.UseClassificationStyle | ClassifiedTextRunStyle.UseClassificationFont) + )); + } + + var severityTitleRow = CreateSeverityTitleRow(v.Severity, title ?? "", severityName); + if (severityTitleRow != null) + elements.Add(severityTitleRow); + else + { + elements.Add(new ClassifiedTextElement( + new ClassifiedTextRun(PredefinedClassificationTypeNames.Keyword, severityName, ClassifiedTextRunStyle.UseClassificationStyle | ClassifiedTextRunStyle.UseClassificationFont) + )); + elements.Add(new ClassifiedTextElement( + new ClassifiedTextRun(PredefinedClassificationTypeNames.Text, title ?? "", ClassifiedTextRunStyle.UseClassificationFont) + )); + } + + var descriptionBlock = CreateDescriptionBlock(description); + if (descriptionBlock != null) + elements.Add(descriptionBlock); + else + elements.Add(new ClassifiedTextElement( + new ClassifiedTextRun(PredefinedClassificationTypeNames.Text, description, ClassifiedTextRunStyle.UseClassificationFont) + )); + + var linksRow = CreateActionLinksRow(v); + if (linksRow != null) + elements.Add(linksRow); + else + { + const string urlClassification = "url"; + elements.Add(new ClassifiedTextElement( + new ClassifiedTextRun(urlClassification, "Fix with Checkmarx Assist", () => RunFixWithAssist(v), "Fix with Checkmarx Assist", ClassifiedTextRunStyle.Underline), + new ClassifiedTextRun(PredefinedClassificationTypeNames.Text, " ", ClassifiedTextRunStyle.UseClassificationFont), + new ClassifiedTextRun(urlClassification, "View Details", () => RunViewDetails(v), "View Details", ClassifiedTextRunStyle.Underline), + new ClassifiedTextRun(PredefinedClassificationTypeNames.Text, " ", ClassifiedTextRunStyle.UseClassificationFont), + new ClassifiedTextRun(urlClassification, "Ignore vulnerability", () => RunIgnoreVulnerability(v), "Ignore vulnerability", ClassifiedTextRunStyle.Underline) + )); + } + } + + var separator = CreateHorizontalSeparator(); + if (separator != null) + elements.Add(separator); + + return new ContainerElement(ContainerElementStyle.Stacked, elements); + } + + /// + /// Builds content for a single vulnerability (header + severity+title + description + links + separator). + /// + internal static object BuildQuickInfoContent(Vulnerability v) { if (v == null) return null; var title = !string.IsNullOrEmpty(v.Title) ? v.Title : (!string.IsNullOrEmpty(v.RuleName) ? v.RuleName : v.Description); var description = !string.IsNullOrEmpty(v.Description) ? v.Description : "Vulnerability detected by " + v.Scanner + "."; + var severityName = GetRichSeverityName(v.Severity); var elements = new List(); - elements.Add(new ClassifiedTextElement( - new ClassifiedTextRun("keyword", "DevAssist", ClassifiedTextRunStyle.UseClassificationFont), - new ClassifiedTextRun("plain text", " โ€ข ", ClassifiedTextRunStyle.UseClassificationFont), - new ClassifiedTextRun("keyword", v.Scanner.ToString(), ClassifiedTextRunStyle.UseClassificationFont), - new ClassifiedTextRun("plain text", " โ€ข ", ClassifiedTextRunStyle.UseClassificationFont), - new ClassifiedTextRun("keyword", v.Severity.ToString(), ClassifiedTextRunStyle.UseClassificationFont) - )); - - elements.Add(new ClassifiedTextElement( - new ClassifiedTextRun("plain text", title ?? "", ClassifiedTextRunStyle.UseClassificationFont) - )); - - elements.Add(new ClassifiedTextElement( - new ClassifiedTextRun("plain text", description, ClassifiedTextRunStyle.UseClassificationFont) - )); - - elements.Add(new ClassifiedTextElement( - new ClassifiedTextRun("plain text", "Fix with Checkmarx One Assist", ClassifiedTextRunStyle.UseClassificationFont), - new ClassifiedTextRun("plain text", " | ", ClassifiedTextRunStyle.UseClassificationFont), - new ClassifiedTextRun("plain text", "View details", ClassifiedTextRunStyle.UseClassificationFont), - new ClassifiedTextRun("plain text", " | ", ClassifiedTextRunStyle.UseClassificationFont), - new ClassifiedTextRun("plain text", "Ignore this vulnerability", ClassifiedTextRunStyle.UseClassificationFont) - )); + // Row 1 โ€“ Header (badge + "Checkmarx One Assist") โ€“ custom-popup style + var headerRow = CreateHeaderRow(); + if (headerRow != null) + elements.Add(headerRow); + else + elements.Add(new ClassifiedTextElement( + new ClassifiedTextRun(PredefinedClassificationTypeNames.Keyword, "Checkmarx One Assist", ClassifiedTextRunStyle.UseClassificationStyle | ClassifiedTextRunStyle.UseClassificationFont) + )); + + // Row 2 โ€“ Severity icon + title on one line โ€“ custom-popup style + var severityTitleRow = CreateSeverityTitleRow(v.Severity, title ?? "", severityName); + if (severityTitleRow != null) + elements.Add(severityTitleRow); + else + { + elements.Add(new ClassifiedTextElement( + new ClassifiedTextRun(PredefinedClassificationTypeNames.Keyword, severityName, ClassifiedTextRunStyle.UseClassificationStyle | ClassifiedTextRunStyle.UseClassificationFont) + )); + elements.Add(new ClassifiedTextElement( + new ClassifiedTextRun(PredefinedClassificationTypeNames.Text, title ?? "", ClassifiedTextRunStyle.UseClassificationFont) + )); + } + + // Description with extra line spacing between lines + var descriptionBlock = CreateDescriptionBlock(description); + if (descriptionBlock != null) + elements.Add(descriptionBlock); + else + elements.Add(new ClassifiedTextElement( + new ClassifiedTextRun(PredefinedClassificationTypeNames.Text, description, ClassifiedTextRunStyle.UseClassificationFont) + )); + + // Action links: underline only on mouse hover + var linksRow = CreateActionLinksRow(v); + if (linksRow != null) + elements.Add(linksRow); + else + { + const string urlClassification = "url"; + elements.Add(new ClassifiedTextElement( + new ClassifiedTextRun(urlClassification, "Fix with Checkmarx Assist", () => RunFixWithAssist(v), "Fix with Checkmarx Assist", ClassifiedTextRunStyle.Underline), + new ClassifiedTextRun(PredefinedClassificationTypeNames.Text, " ", ClassifiedTextRunStyle.UseClassificationFont), + new ClassifiedTextRun(urlClassification, "View Details", () => RunViewDetails(v), "View Details", ClassifiedTextRunStyle.Underline), + new ClassifiedTextRun(PredefinedClassificationTypeNames.Text, " ", ClassifiedTextRunStyle.UseClassificationFont), + new ClassifiedTextRun(urlClassification, "Ignore vulnerability", () => RunIgnoreVulnerability(v), "Ignore vulnerability", ClassifiedTextRunStyle.Underline) + )); + } + + // Horizontal separator line after our details + var separator = CreateHorizontalSeparator(); + if (separator != null) + elements.Add(separator); return new ContainerElement(ContainerElementStyle.Stacked, elements); } + internal static void RunFixWithAssist(Vulnerability v) + { + RunOnUiThread(() => MessageBox.Show($"Fix with Checkmarx One Assist:\n{v?.Title ?? v?.Description ?? "โ€”"}", "Checkmarx One Assist")); + } + + internal static void RunViewDetails(Vulnerability v) + { + var url = !string.IsNullOrEmpty(v?.LearnMoreUrl) ? v.LearnMoreUrl : v?.FixLink; + if (!string.IsNullOrEmpty(url)) + { + RunOnUiThread(() => + { + try + { + Process.Start(new ProcessStartInfo { FileName = url, UseShellExecute = true }); + } + catch (Exception ex) + { + Debug.WriteLine($"DevAssist QuickInfo View Details: {ex.Message}"); + MessageBox.Show($"Could not open link: {url}", "Checkmarx One Assist"); + } + }); + } + else + { + RunOnUiThread(() => MessageBox.Show($"View Details:\n{v?.Title ?? ""}\n{v?.Description ?? ""}\nSeverity: {v?.Severity}", "Checkmarx One Assist")); + } + } + + internal static void RunIgnoreVulnerability(Vulnerability v) + { + RunOnUiThread(() => MessageBox.Show($"Ignore vulnerability:\n{v?.Title ?? v?.Description ?? "โ€”"}", "Checkmarx One Assist")); + } + + internal static void RunOnUiThread(Action action) + { + if (action == null) return; + try + { + System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke(action, DispatcherPriority.Send); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"DevAssist QuickInfo link: {ex.Message}"); + } + } + + /// + /// Description text with extra line spacing between lines. + /// + private static System.Windows.UIElement CreateDescriptionBlock(string description) + { + if (string.IsNullOrEmpty(description)) return null; + try + { + return ThreadHelper.JoinableTaskFactory.Run(async () => + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + return new TextBlock + { + Text = description, + TextWrapping = TextWrapping.Wrap, + LineHeight = 20, + LineStackingStrategy = LineStackingStrategy.BlockLineHeight, + Foreground = new SolidColorBrush(Color.FromRgb(0xF0, 0xF0, 0xF0)), + FontSize = 12, + Margin = new Thickness(0, 0, 0, 6) + }; + }); + } + catch (Exception ex) + { + Debug.WriteLine($"DevAssist QuickInfo CreateDescriptionBlock: {ex.Message}"); + return null; + } + } + + /// + /// Action links row: underline only on mouse hover (not by default). + /// + private static System.Windows.UIElement CreateActionLinksRow(Vulnerability v) + { + if (v == null) return null; + try + { + return ThreadHelper.JoinableTaskFactory.Run(async () => + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var linkBrush = new SolidColorBrush(Color.FromRgb(0x56, 0x9C, 0xD6)); + var panel = new StackPanel { Orientation = Orientation.Horizontal }; + + void AddLink(string text, Action clickAction) + { + var block = new TextBlock + { + Text = text, + Foreground = linkBrush, + Cursor = System.Windows.Input.Cursors.Hand, + Margin = new Thickness(0, 0, 12, 0) + }; + block.MouseEnter += (s, _) => { block.TextDecorations = TextDecorations.Underline; }; + block.MouseLeave += (s, _) => { block.TextDecorations = null; }; + block.MouseLeftButtonDown += (s, _) => { RunOnUiThread(clickAction); }; + panel.Children.Add(block); + } + + AddLink("Fix with Checkmarx Assist", () => RunFixWithAssist(v)); + AddLink("View Details", () => RunViewDetails(v)); + AddLink("Ignore vulnerability", () => RunIgnoreVulnerability(v)); + + return (System.Windows.UIElement)panel; + }); + } + catch (Exception ex) + { + Debug.WriteLine($"DevAssist QuickInfo CreateActionLinksRow: {ex.Message}"); + return null; + } + } + + /// + /// Creates a thin horizontal line (separator) to show after our Quick Info details. + /// + private static System.Windows.UIElement CreateHorizontalSeparator() + { + try + { + return ThreadHelper.JoinableTaskFactory.Run(async () => + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + return new Border + { + Height = 1, + Margin = new Thickness(0, 6, 0, 0), + Background = new SolidColorBrush(Color.FromRgb(0x55, 0x55, 0x55)) + }; + }); + } + catch (Exception ex) + { + Debug.WriteLine($"DevAssist QuickInfo CreateHorizontalSeparator: {ex.Message}"); + return null; + } + } + + /// + /// Returns current VS theme folder name for icon paths: "Dark" or "Light". + /// Uses VSColorTheme so badge and severity icons follow IDE theme dynamically. + /// + private static string GetCurrentTheme() + { + try + { + var backgroundColor = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowBackgroundColorKey); + double brightness = (0.299 * backgroundColor.R + 0.587 * backgroundColor.G + 0.114 * backgroundColor.B) / 255.0; + return brightness < 0.5 ? "Dark" : "Light"; + } + catch + { + return "Dark"; + } + } + + /// + /// Header row: badge + "Checkmarx One Assist" text (custom-popup style, no custom popup). + /// + private static System.Windows.UIElement CreateHeaderRow() + { + string theme = GetCurrentTheme(); + var source = LoadIconFromAssembly(theme, "cxone_assist.png"); + if (source == null && theme != "Dark") + source = LoadIconFromAssembly("Dark", "cxone_assist.png"); + if (source == null) + return null; + try + { + return ThreadHelper.JoinableTaskFactory.Run(async () => + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var image = new Image + { + Source = source, + Width = 150, + Height = 32, + Stretch = Stretch.Uniform, + VerticalAlignment = VerticalAlignment.Center, + Margin = new Thickness(0, 0, 8, 0) + }; + var panel = new StackPanel + { + Orientation = Orientation.Horizontal, + Margin = new Thickness(0, 0, 0, 6) + }; + panel.Children.Add(image); + return (System.Windows.UIElement)panel; + }); + } + catch (Exception ex) + { + Debug.WriteLine($"DevAssist QuickInfo CreateHeaderRow: {ex.Message}"); + return null; + } + } + + /// + /// Severity + title row: icon + finding title on one line (custom-popup style). + /// + private static System.Windows.UIElement CreateSeverityTitleRow(SeverityLevel severity, string title, string severityName) + { + string theme = GetCurrentTheme(); + string fileName = GetSeverityIconFileName(severity); + ImageSource severitySource = null; + if (!string.IsNullOrEmpty(fileName)) + { + severitySource = LoadIconFromAssembly(theme, fileName); + if (severitySource == null && theme != "Dark") + severitySource = LoadIconFromAssembly("Dark", fileName); + } + try + { + return ThreadHelper.JoinableTaskFactory.Run(async () => + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var panel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 0, 0, 4) }; + if (severitySource != null) + { + var image = new Image + { + Source = severitySource, + Width = 16, + Height = 16, + Stretch = Stretch.Uniform, + VerticalAlignment = VerticalAlignment.Center, + Margin = new Thickness(0, 0, 6, 0) + }; + panel.Children.Add(image); + } + var text = new TextBlock + { + Text = string.IsNullOrEmpty(title) ? severityName : title, + FontSize = 12, + FontWeight = FontWeights.SemiBold, + Foreground = new SolidColorBrush(Color.FromRgb(0xF0, 0xF0, 0xF0)), + VerticalAlignment = VerticalAlignment.Center, + TextWrapping = TextWrapping.Wrap + }; + panel.Children.Add(text); + return (System.Windows.UIElement)panel; + }); + } + catch (Exception ex) + { + Debug.WriteLine($"DevAssist QuickInfo CreateSeverityTitleRow: {ex.Message}"); + return null; + } + } + + /// + /// Creates a WPF Image for the Checkmarx One Assist badge only (theme-based). Used when not using header row. + /// + private static System.Windows.UIElement CreateDevAssistBadgeImage() + { + string theme = GetCurrentTheme(); + string fileName = "cxone_assist.png"; + var source = LoadIconFromAssembly(theme, fileName); + if (source == null && theme != "Dark") + source = LoadIconFromAssembly("Dark", fileName); + if (source == null) + return null; + try + { + return ThreadHelper.JoinableTaskFactory.Run(async () => + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var image = new Image + { + Source = source, + Width = 150, + Height = 32, + Stretch = Stretch.Uniform, + HorizontalAlignment = HorizontalAlignment.Left + }; + return (System.Windows.UIElement)image; + }); + } + catch (Exception ex) + { + Debug.WriteLine($"DevAssist QuickInfo badge: create Image on main thread: {ex.Message}"); + return null; + } + } + + /// + /// Creates a WPF Image for the severity icon (theme-based, dynamic by SeverityLevel). + /// + private static System.Windows.UIElement CreateSeverityImage(SeverityLevel severity) + { + string theme = GetCurrentTheme(); + string fileName = GetSeverityIconFileName(severity); + if (string.IsNullOrEmpty(fileName)) + return null; + var source = LoadIconFromAssembly(theme, fileName); + if (source == null && theme != "Dark") + source = LoadIconFromAssembly("Dark", fileName); + if (source == null) + return null; + try + { + return ThreadHelper.JoinableTaskFactory.Run(async () => + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var image = new Image + { + Source = source, + Width = 16, + Height = 16, + Stretch = Stretch.Uniform, + HorizontalAlignment = HorizontalAlignment.Left + }; + return (System.Windows.UIElement)image; + }); + } + catch (Exception ex) + { + Debug.WriteLine($"DevAssist QuickInfo severity icon: {ex.Message}"); + return null; + } + } + + private static string GetSeverityIconFileName(SeverityLevel severity) + { + switch (severity) + { + case SeverityLevel.Malicious: return "malicious.png"; + case SeverityLevel.Critical: return "critical.png"; + case SeverityLevel.High: return "high.png"; + case SeverityLevel.Medium: return "medium.png"; + case SeverityLevel.Low: + case SeverityLevel.Info: return "low.png"; + default: return null; + } + } + + /// + /// Loads a PNG from DevAssist Icons by theme (Dark/Light). Used for badge and severity icons. + /// + private static BitmapImage LoadIconFromAssembly(string theme, string fileName) + { + var packPath = $"pack://application:,,,/ast-visual-studio-extension;component/CxExtension/Resources/DevAssist/Icons/{theme}/{fileName}"; + try + { + var streamInfo = System.Windows.Application.GetResourceStream(new Uri(packPath, UriKind.Absolute)); + if (streamInfo?.Stream != null) + { + using (var ms = new MemoryStream()) + { + streamInfo.Stream.CopyTo(ms); + ms.Position = 0; + var img = new BitmapImage(); + img.BeginInit(); + img.StreamSource = ms; + img.CacheOption = BitmapCacheOption.OnLoad; + img.EndInit(); + img.Freeze(); + return img; + } + } + } + catch (Exception ex) + { + Debug.WriteLine($"DevAssist QuickInfo icon: pack load failed for {fileName}: {ex.Message}"); + } + + try + { + var asm = Assembly.GetExecutingAssembly(); + var resourceName = asm.GetManifestResourceNames() + .FirstOrDefault(n => n.Replace('\\', '/').EndsWith($"DevAssist/Icons/{theme}/{fileName}", StringComparison.OrdinalIgnoreCase) + || n.Replace('\\', '.').EndsWith($"DevAssist.Icons.{theme}.{fileName}", StringComparison.OrdinalIgnoreCase)); + if (resourceName != null) + { + using (var stream = asm.GetManifestResourceStream(resourceName)) + { + if (stream != null) + { + var img = new BitmapImage(); + img.BeginInit(); + img.StreamSource = stream; + img.CacheOption = BitmapCacheOption.OnLoad; + img.EndInit(); + img.Freeze(); + return img; + } + } + } + } + catch (Exception ex) + { + Debug.WriteLine($"DevAssist QuickInfo icon: manifest load failed for {fileName}: {ex.Message}"); + } + + return null; + } + + internal static string GetRichSeverityName(SeverityLevel severity) + { + switch (severity) + { + case SeverityLevel.Critical: return "Critical"; + case SeverityLevel.High: return "High"; + case SeverityLevel.Medium: return "Medium"; + case SeverityLevel.Low: return "Low"; + case SeverityLevel.Info: return "Info"; + case SeverityLevel.Malicious: return "Malicious"; + case SeverityLevel.Unknown: return "Unknown"; + case SeverityLevel.Ok: return "Ok"; + case SeverityLevel.Ignored: return "Ignored"; + default: return severity.ToString(); + } + } + public void Dispose() { if (_disposed) diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSourceProvider.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSourceProvider.cs index 63c07a7d..694e5091 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSourceProvider.cs +++ b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSourceProvider.cs @@ -5,9 +5,10 @@ namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers { - [Export(typeof(IQuickInfoSourceProvider))] - [Name("DevAssist QuickInfo Source")] - [Order(Before = "Default Quick Info Presenter")] + // Legacy sync provider disabled: built-in presenter ignores per-ClassifiedTextRun navigation callbacks. + // Quick Info is now provided by DevAssistAsyncQuickInfoSourceProvider (IAsyncQuickInfoSource). + // [Export(typeof(IQuickInfoSourceProvider))] + [Name("DevAssist QuickInfo Source (legacy, disabled)")] [ContentType("code")] [ContentType("text")] internal class DevAssistQuickInfoSourceProvider : IQuickInfoSourceProvider diff --git a/ast-visual-studio-extension/ast-visual-studio-extension.csproj b/ast-visual-studio-extension/ast-visual-studio-extension.csproj index 07b6396f..ae8b30ec 100644 --- a/ast-visual-studio-extension/ast-visual-studio-extension.csproj +++ b/ast-visual-studio-extension/ast-visual-studio-extension.csproj @@ -163,8 +163,11 @@ + + + @@ -227,6 +230,7 @@ 19.225.1 + From a4dda98ad3e4718c36ab4e8e33b0a1a013808fd1 Mon Sep 17 00:00:00 2001 From: Rahul Pidde <206018639+cx-rahul-pidde@users.noreply.github.com> Date: Mon, 23 Feb 2026 18:55:07 +0530 Subject: [PATCH 11/45] Renamed file and folder name --- ...rTests.cs => CxAssistErrorHandlerTests.cs} | 26 +- .../Commands/ShowFindingsWindowCommand.cs | 32 +- .../TestErrorListCustomizationCommand.cs | 8 +- .../Commands/TestGutterIconsDirectCommand.cs | 40 +- .../Core/CxAssistDisplayCoordinator.cs} | 40 +- .../Core/CxAssistErrorHandler.cs} | 8 +- .../Core/CxAssistErrorListSync.cs} | 26 +- .../Core/CxAssistMockData.cs} | 10 +- .../Core/GutterIcons/CxAssistGlyphFactory.cs} | 56 +- .../Core/GutterIcons/CxAssistGlyphTagger.cs} | 44 +- .../CxAssistGlyphTaggerProvider.cs} | 48 +- .../CxAssistTextViewCreationListener.cs} | 40 +- .../Markers/CxAssistAsyncQuickInfoSource.cs} | 24 +- .../CxAssistAsyncQuickInfoSourceProvider.cs} | 8 +- .../Markers/CxAssistCompilerErrorsForLine.cs} | 10 +- .../Core/Markers/CxAssistErrorTagger.cs} | 42 +- .../Markers/CxAssistErrorTaggerProvider.cs} | 32 +- .../Core/Markers/CxAssistQuickFixActions.cs} | 12 +- .../CxAssistQuickInfoClassifications.cs} | 32 +- .../Markers/CxAssistQuickInfoController.cs | 78 ++ .../CxAssistQuickInfoControllerProvider.cs} | 8 +- .../Core/Markers/CxAssistQuickInfoSource.cs} | 46 +- .../CxAssistQuickInfoSourceProvider.cs} | 10 +- .../CxAssistSuggestedActionsSource.cs} | 18 +- ...CxAssistSuggestedActionsSourceProvider.cs} | 10 +- .../Core/Markers/CxAssistTestHelper.cs} | 8 +- .../Core/Models/ScannerType.cs | 4 +- .../Core/Models/SeverityLevel.cs | 2 +- .../Core/Models/Vulnerability.cs | 4 +- .../CxAssistFindingsControl.xaml} | 4 +- .../CxAssistFindingsControl.xaml.cs} | 34 +- .../FindingsWindow/CxAssistFindingsWindow.cs} | 12 +- .../UI/FindingsWindow/FindingsTreeNode.cs | 2 +- .../CxExtension/CxWindowControl.xaml | 14 +- .../CxExtension/CxWindowControl.xaml.cs | 32 +- .../CxExtension/CxWindowPackage.cs | 14 +- .../CxExtension/CxWindowPackage.vsct | 22 +- .../Markers/DevAssistHoverPopup.Designer.cs | 119 --- .../Core/Markers/DevAssistHoverPopup.xaml | 248 ----- .../Core/Markers/DevAssistHoverPopup.xaml.cs | 884 ------------------ .../Markers/DevAssistQuickInfoController.cs | 144 --- .../Icons/Dark/container.png | Bin .../Icons/Dark/critical.png | Bin .../Icons/Dark/critical.svg | 0 .../Icons/Dark/cxone_assist.png | Bin .../Icons/Dark/high.png | Bin .../Icons/Dark/high.svg | 0 .../Icons/Dark/ignored.svg | 0 .../Icons/Dark/low.png | Bin .../Icons/Dark/low.svg | 0 .../Icons/Dark/malicious.png | Bin .../Icons/Dark/malicious.svg | 0 .../Icons/Dark/medium.png | Bin .../Icons/Dark/medium.svg | 0 .../{DevAssist => CxAssist}/Icons/Dark/ok.svg | 0 .../Icons/Dark/package.png | Bin .../Icons/Dark/severity_count/critical.png | Bin .../Icons/Dark/severity_count/high.png | Bin .../Icons/Dark/severity_count/low.png | Bin .../Icons/Dark/severity_count/medium.png | Bin .../Icons/Dark/unknown.svg | 0 .../Icons/Light/container.png | Bin .../Icons/Light/critical.png | Bin .../Icons/Light/critical.svg | 0 .../Icons/Light/cxone_assist.png | Bin .../Icons/Light/high.png | Bin .../Icons/Light/high.svg | 0 .../Icons/Light/ignored.svg | 0 .../Icons/Light/low.png | Bin .../Icons/Light/low.svg | 0 .../Icons/Light/malicious.png | Bin .../Icons/Light/malicious.svg | 0 .../Icons/Light/medium.png | Bin .../Icons/Light/medium.svg | 0 .../Icons/Light/ok.svg | 0 .../Icons/Light/package.png | Bin .../Icons/Light/severity_count/critical.png | Bin .../Icons/Light/severity_count/high.png | Bin .../Icons/Light/severity_count/low.png | Bin .../Icons/Light/severity_count/medium.png | Bin .../Icons/Light/unknown.svg | 0 .../ast-visual-studio-extension.csproj | 159 ++-- .../ast_visual_studio_extensionPackage.cs | 2 +- 83 files changed, 545 insertions(+), 1871 deletions(-) rename ast-visual-studio-extension-tests/cx-unit-tests/cx-extension-tests/{DevAssistErrorHandlerTests.cs => CxAssistErrorHandlerTests.cs} (61%) rename ast-visual-studio-extension/CxExtension/{DevAssist/Core/DevAssistDisplayCoordinator.cs => CxAssist/Core/CxAssistDisplayCoordinator.cs} (82%) rename ast-visual-studio-extension/CxExtension/{DevAssist/Core/DevAssistErrorHandler.cs => CxAssist/Core/CxAssistErrorHandler.cs} (89%) rename ast-visual-studio-extension/CxExtension/{DevAssist/Core/DevAssistErrorListSync.cs => CxAssist/Core/CxAssistErrorListSync.cs} (87%) rename ast-visual-studio-extension/CxExtension/{DevAssist/Core/DevAssistMockData.cs => CxAssist/Core/CxAssistMockData.cs} (97%) rename ast-visual-studio-extension/CxExtension/{DevAssist/Core/GutterIcons/DevAssistGlyphFactory.cs => CxAssist/Core/GutterIcons/CxAssistGlyphFactory.cs} (75%) rename ast-visual-studio-extension/CxExtension/{DevAssist/Core/GutterIcons/DevAssistGlyphTagger.cs => CxAssist/Core/GutterIcons/CxAssistGlyphTagger.cs} (74%) rename ast-visual-studio-extension/CxExtension/{DevAssist/Core/GutterIcons/DevAssistGlyphTaggerProvider.cs => CxAssist/Core/GutterIcons/CxAssistGlyphTaggerProvider.cs} (65%) rename ast-visual-studio-extension/CxExtension/{DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs => CxAssist/Core/GutterIcons/CxAssistTextViewCreationListener.cs} (68%) rename ast-visual-studio-extension/CxExtension/{DevAssist/Core/Markers/DevAssistAsyncQuickInfoSource.cs => CxAssist/Core/Markers/CxAssistAsyncQuickInfoSource.cs} (81%) rename ast-visual-studio-extension/CxExtension/{DevAssist/Core/Markers/DevAssistAsyncQuickInfoSourceProvider.cs => CxAssist/Core/Markers/CxAssistAsyncQuickInfoSourceProvider.cs} (61%) rename ast-visual-studio-extension/CxExtension/{DevAssist/Core/Markers/DevAssistCompilerErrorsForLine.cs => CxAssist/Core/Markers/CxAssistCompilerErrorsForLine.cs} (90%) rename ast-visual-studio-extension/CxExtension/{DevAssist/Core/Markers/DevAssistErrorTagger.cs => CxAssist/Core/Markers/CxAssistErrorTagger.cs} (74%) rename ast-visual-studio-extension/CxExtension/{DevAssist/Core/Markers/DevAssistErrorTaggerProvider.cs => CxAssist/Core/Markers/CxAssistErrorTaggerProvider.cs} (67%) rename ast-visual-studio-extension/CxExtension/{DevAssist/Core/Markers/DevAssistQuickFixActions.cs => CxAssist/Core/Markers/CxAssistQuickFixActions.cs} (90%) rename ast-visual-studio-extension/CxExtension/{DevAssist/Core/Markers/DevAssistQuickInfoClassifications.cs => CxAssist/Core/Markers/CxAssistQuickInfoClassifications.cs} (52%) create mode 100644 ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistQuickInfoController.cs rename ast-visual-studio-extension/CxExtension/{DevAssist/Core/Markers/DevAssistQuickInfoControllerProvider.cs => CxAssist/Core/Markers/CxAssistQuickInfoControllerProvider.cs} (67%) rename ast-visual-studio-extension/CxExtension/{DevAssist/Core/Markers/DevAssistQuickInfoSource.cs => CxAssist/Core/Markers/CxAssistQuickInfoSource.cs} (93%) rename ast-visual-studio-extension/CxExtension/{DevAssist/Core/Markers/DevAssistQuickInfoSourceProvider.cs => CxAssist/Core/Markers/CxAssistQuickInfoSourceProvider.cs} (56%) rename ast-visual-studio-extension/CxExtension/{DevAssist/Core/Markers/DevAssistSuggestedActionsSource.cs => CxAssist/Core/Markers/CxAssistSuggestedActionsSource.cs} (75%) rename ast-visual-studio-extension/CxExtension/{DevAssist/Core/Markers/DevAssistSuggestedActionsSourceProvider.cs => CxAssist/Core/Markers/CxAssistSuggestedActionsSourceProvider.cs} (65%) rename ast-visual-studio-extension/CxExtension/{DevAssist/Core/Markers/DevAssistTestHelper.cs => CxAssist/Core/Markers/CxAssistTestHelper.cs} (96%) rename ast-visual-studio-extension/CxExtension/{DevAssist => CxAssist}/Core/Models/ScannerType.cs (65%) rename ast-visual-studio-extension/CxExtension/{DevAssist => CxAssist}/Core/Models/SeverityLevel.cs (89%) rename ast-visual-studio-extension/CxExtension/{DevAssist => CxAssist}/Core/Models/Vulnerability.cs (93%) rename ast-visual-studio-extension/CxExtension/{DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml => CxAssist/UI/FindingsWindow/CxAssistFindingsControl.xaml} (99%) rename ast-visual-studio-extension/CxExtension/{DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml.cs => CxAssist/UI/FindingsWindow/CxAssistFindingsControl.xaml.cs} (93%) rename ast-visual-studio-extension/CxExtension/{DevAssist/UI/FindingsWindow/DevAssistFindingsWindow.cs => CxAssist/UI/FindingsWindow/CxAssistFindingsWindow.cs} (72%) rename ast-visual-studio-extension/CxExtension/{DevAssist => CxAssist}/UI/FindingsWindow/FindingsTreeNode.cs (98%) delete mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.Designer.cs delete mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml delete mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml.cs delete mode 100644 ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoController.cs rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Dark/container.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Dark/critical.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Dark/critical.svg (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Dark/cxone_assist.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Dark/high.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Dark/high.svg (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Dark/ignored.svg (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Dark/low.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Dark/low.svg (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Dark/malicious.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Dark/malicious.svg (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Dark/medium.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Dark/medium.svg (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Dark/ok.svg (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Dark/package.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Dark/severity_count/critical.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Dark/severity_count/high.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Dark/severity_count/low.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Dark/severity_count/medium.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Dark/unknown.svg (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Light/container.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Light/critical.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Light/critical.svg (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Light/cxone_assist.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Light/high.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Light/high.svg (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Light/ignored.svg (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Light/low.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Light/low.svg (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Light/malicious.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Light/malicious.svg (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Light/medium.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Light/medium.svg (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Light/ok.svg (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Light/package.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Light/severity_count/critical.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Light/severity_count/high.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Light/severity_count/low.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Light/severity_count/medium.png (100%) rename ast-visual-studio-extension/CxExtension/Resources/{DevAssist => CxAssist}/Icons/Light/unknown.svg (100%) diff --git a/ast-visual-studio-extension-tests/cx-unit-tests/cx-extension-tests/DevAssistErrorHandlerTests.cs b/ast-visual-studio-extension-tests/cx-unit-tests/cx-extension-tests/CxAssistErrorHandlerTests.cs similarity index 61% rename from ast-visual-studio-extension-tests/cx-unit-tests/cx-extension-tests/DevAssistErrorHandlerTests.cs rename to ast-visual-studio-extension-tests/cx-unit-tests/cx-extension-tests/CxAssistErrorHandlerTests.cs index be5ecb0e..84273a48 100644 --- a/ast-visual-studio-extension-tests/cx-unit-tests/cx-extension-tests/DevAssistErrorHandlerTests.cs +++ b/ast-visual-studio-extension-tests/cx-unit-tests/cx-extension-tests/CxAssistErrorHandlerTests.cs @@ -1,20 +1,20 @@ using System; using Xunit; -using ast_visual_studio_extension.CxExtension.DevAssist.Core; +using ast_visual_studio_extension.CxExtension.CxAssist.Core; namespace ast_visual_studio_extension_tests.cx_unit_tests.cx_extension_tests { /// - /// Unit tests for DevAssist error-handling scenarios. + /// Unit tests for CxAssist error-handling scenarios. /// Verifies that TryRun, TryGet, and LogAndSwallow never rethrow and behave correctly. /// - public class DevAssistErrorHandlerTests + public class CxAssistErrorHandlerTests { [Fact] public void TryRun_ReturnsTrue_WhenActionSucceeds() { bool executed = false; - bool result = DevAssistErrorHandler.TryRun(() => { executed = true; }, "Test"); + bool result = CxAssistErrorHandler.TryRun(() => { executed = true; }, "Test"); Assert.True(result); Assert.True(executed); @@ -23,7 +23,7 @@ public void TryRun_ReturnsTrue_WhenActionSucceeds() [Fact] public void TryRun_ReturnsFalse_WhenActionThrows() { - bool result = DevAssistErrorHandler.TryRun(() => throw new InvalidOperationException("Test exception"), "Test"); + bool result = CxAssistErrorHandler.TryRun(() => throw new InvalidOperationException("Test exception"), "Test"); Assert.False(result); } @@ -32,7 +32,7 @@ public void TryRun_ReturnsFalse_WhenActionThrows() public void TryRun_DoesNotRethrow_WhenActionThrows() { var ex = Record.Exception(() => - DevAssistErrorHandler.TryRun(() => throw new InvalidOperationException("Test"), "Test")); + CxAssistErrorHandler.TryRun(() => throw new InvalidOperationException("Test"), "Test")); Assert.Null(ex); } @@ -40,7 +40,7 @@ public void TryRun_DoesNotRethrow_WhenActionThrows() [Fact] public void TryRun_HandlesNullAction_WithoutThrowing() { - bool result = DevAssistErrorHandler.TryRun(null, "Test"); + bool result = CxAssistErrorHandler.TryRun(null, "Test"); Assert.True(result); } @@ -48,7 +48,7 @@ public void TryRun_HandlesNullAction_WithoutThrowing() [Fact] public void TryGet_ReturnsValue_WhenFunctionSucceeds() { - int value = DevAssistErrorHandler.TryGet(() => 42, "Test", 0); + int value = CxAssistErrorHandler.TryGet(() => 42, "Test", 0); Assert.Equal(42, value); } @@ -56,7 +56,7 @@ public void TryGet_ReturnsValue_WhenFunctionSucceeds() [Fact] public void TryGet_ReturnsDefault_WhenFunctionThrows() { - int value = DevAssistErrorHandler.TryGet(() => throw new InvalidOperationException("Test"), "Test", 99); + int value = CxAssistErrorHandler.TryGet(() => throw new InvalidOperationException("Test"), "Test", 99); Assert.Equal(99, value); } @@ -65,7 +65,7 @@ public void TryGet_ReturnsDefault_WhenFunctionThrows() public void TryGet_DoesNotRethrow_WhenFunctionThrows() { var ex = Record.Exception(() => - DevAssistErrorHandler.TryGet(() => throw new InvalidOperationException("Test"), "Test", 0)); + CxAssistErrorHandler.TryGet(() => throw new InvalidOperationException("Test"), "Test", 0)); Assert.Null(ex); } @@ -73,7 +73,7 @@ public void TryGet_DoesNotRethrow_WhenFunctionThrows() [Fact] public void TryGet_ReturnsDefaultT_WhenFunctionIsNull() { - int value = DevAssistErrorHandler.TryGet(null, "Test", 7); + int value = CxAssistErrorHandler.TryGet(null, "Test", 7); Assert.Equal(7, value); } @@ -82,7 +82,7 @@ public void TryGet_ReturnsDefaultT_WhenFunctionIsNull() public void LogAndSwallow_DoesNotThrow_WhenGivenException() { var ex = Record.Exception(() => - DevAssistErrorHandler.LogAndSwallow(new InvalidOperationException("Test"), "TestContext")); + CxAssistErrorHandler.LogAndSwallow(new InvalidOperationException("Test"), "TestContext")); Assert.Null(ex); } @@ -91,7 +91,7 @@ public void LogAndSwallow_DoesNotThrow_WhenGivenException() public void LogAndSwallow_DoesNotThrow_WhenGivenNull() { var ex = Record.Exception(() => - DevAssistErrorHandler.LogAndSwallow(null, "TestContext")); + CxAssistErrorHandler.LogAndSwallow(null, "TestContext")); Assert.Null(ex); } diff --git a/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs b/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs index b7baea9a..ad265ada 100644 --- a/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs +++ b/ast-visual-studio-extension/CxExtension/Commands/ShowFindingsWindowCommand.cs @@ -7,13 +7,13 @@ using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Task = System.Threading.Tasks.Task; -using ast_visual_studio_extension.CxExtension.DevAssist.Core; -using ast_visual_studio_extension.CxExtension.DevAssist.UI.FindingsWindow; +using ast_visual_studio_extension.CxExtension.CxAssist.Core; +using ast_visual_studio_extension.CxExtension.CxAssist.UI.FindingsWindow; namespace ast_visual_studio_extension.CxExtension.Commands { /// - /// Command handler to show and populate the DevAssist Findings window + /// Command handler to show and populate the CxAssist Findings window /// internal sealed class ShowFindingsWindowCommand { @@ -50,7 +50,7 @@ private void Execute(object sender, EventArgs e) try { - // Show the existing Checkmarx window (not the standalone DevAssistFindingsWindow) + // Show the existing Checkmarx window (not the standalone CxAssistFindingsWindow) ToolWindowPane window = this.package.FindToolWindow(typeof(CxWindow), 0, true); if ((null == window) || (null == window.Frame)) { @@ -60,15 +60,15 @@ private void Execute(object sender, EventArgs e) IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame; Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show()); - // Get the CxWindowControl and switch to DevAssist tab + // Get the CxWindowControl and switch to CxAssist tab var cxWindow = window as CxWindow; if (cxWindow != null && cxWindow.Content is CxWindowControl cxWindowControl) { - // Switch to the DevAssist Findings tab - cxWindowControl.SwitchToDevAssistTab(); + // Switch to the CxAssist Findings tab + cxWindowControl.SwitchToCxAssistTab(); - // Get the DevAssist Findings Control and populate with test data - var findingsControl = cxWindowControl.GetDevAssistFindingsControl(); + // Get the CxAssist Findings Control and populate with test data + var findingsControl = cxWindowControl.GetCxAssistFindingsControl(); if (findingsControl != null) { PopulateTestData(findingsControl); @@ -77,25 +77,25 @@ private void Execute(object sender, EventArgs e) } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"Error showing DevAssist findings: {ex.Message}"); + System.Diagnostics.Debug.WriteLine($"Error showing CxAssist findings: {ex.Message}"); } } - private void PopulateTestData(DevAssistFindingsControl control) + private void PopulateTestData(CxAssistFindingsControl control) { if (control == null) return; // Use coordinator's current findings (from last UpdateFindings) so problem window matches gutter/underline - var current = DevAssistDisplayCoordinator.GetCurrentFindings(); + var current = CxAssistDisplayCoordinator.GetCurrentFindings(); if (current != null && current.Count > 0) { - DevAssistDisplayCoordinator.RefreshProblemWindow(control, LoadSeverityIcon, () => LoadIcon("document.png")); + CxAssistDisplayCoordinator.RefreshProblemWindow(control, LoadSeverityIcon, () => LoadIcon("document.png")); return; } // No current findings (no file opened yet), show mock data so the window is not empty - var vulnerabilities = DevAssistMockData.GetCommonVulnerabilities(DevAssistMockData.DefaultFilePath); - var fileNodes = DevAssistMockData.BuildFileNodesFromVulnerabilities( + var vulnerabilities = CxAssistMockData.GetCommonVulnerabilities(CxAssistMockData.DefaultFilePath); + var fileNodes = CxAssistMockData.BuildFileNodesFromVulnerabilities( vulnerabilities, loadSeverityIcon: LoadSeverityIcon, loadFileIcon: () => LoadIcon("document.png")); @@ -140,7 +140,7 @@ private System.Windows.Media.ImageSource LoadSeverityIcon(string severity) // Build the icon path string iconName = severity.ToLower(); - string iconPath = $"pack://application:,,,/ast-visual-studio-extension;component/CxExtension/Resources/DevAssist/Icons/{themeFolder}/{iconName}.png"; + string iconPath = $"pack://application:,,,/ast-visual-studio-extension;component/CxExtension/Resources/CxAssist/Icons/{themeFolder}/{iconName}.png"; // Load the PNG image var bitmap = new BitmapImage(); diff --git a/ast-visual-studio-extension/CxExtension/Commands/TestErrorListCustomizationCommand.cs b/ast-visual-studio-extension/CxExtension/Commands/TestErrorListCustomizationCommand.cs index ead04e42..1ce4eff1 100644 --- a/ast-visual-studio-extension/CxExtension/Commands/TestErrorListCustomizationCommand.cs +++ b/ast-visual-studio-extension/CxExtension/Commands/TestErrorListCustomizationCommand.cs @@ -50,14 +50,14 @@ private void Execute(object sender, EventArgs e) try { - System.Diagnostics.Debug.WriteLine("DevAssist: Testing Error List customization..."); + System.Diagnostics.Debug.WriteLine("CxAssist: Testing Error List customization..."); // Initialize ErrorListProvider if (_errorListProvider == null) { _errorListProvider = new ErrorListProvider(Microsoft.VisualStudio.Shell.ServiceProvider.GlobalProvider) { - ProviderName = "Checkmarx DevAssist", + ProviderName = "Checkmarx CxAssist", ProviderGuid = new Guid("12345678-1234-1234-1234-123456789ABC") }; } @@ -102,7 +102,7 @@ private void Execute(object sender, EventArgs e) } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"DevAssist: Error in Error List test: {ex.Message}\n{ex.StackTrace}"); + System.Diagnostics.Debug.WriteLine($"CxAssist: Error in Error List test: {ex.Message}\n{ex.StackTrace}"); VsShellUtilities.ShowMessageBox( this.package, $"Error: {ex.Message}", @@ -121,7 +121,7 @@ private void AddTestTask(string severity, TaskErrorCategory category, string des { Category = TaskCategory.CodeSense, ErrorCategory = category, - Text = $"[{severity}] {description} (DevAssist)", + Text = $"[{severity}] {description} (CxAssist)", Document = file, Line = line - 1, Column = column, diff --git a/ast-visual-studio-extension/CxExtension/Commands/TestGutterIconsDirectCommand.cs b/ast-visual-studio-extension/CxExtension/Commands/TestGutterIconsDirectCommand.cs index 5a0737f3..ae03abaf 100644 --- a/ast-visual-studio-extension/CxExtension/Commands/TestGutterIconsDirectCommand.cs +++ b/ast-visual-studio-extension/CxExtension/Commands/TestGutterIconsDirectCommand.cs @@ -5,9 +5,9 @@ using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Tagging; using Microsoft.VisualStudio.ComponentModelHost; -using ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons; -using ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers; -using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; +using ast_visual_studio_extension.CxExtension.CxAssist.Core.GutterIcons; +using ast_visual_studio_extension.CxExtension.CxAssist.Core.Markers; +using ast_visual_studio_extension.CxExtension.CxAssist.Core.Models; using Task = System.Threading.Tasks.Task; using Microsoft.VisualStudio.Shell.Interop; @@ -52,7 +52,7 @@ private void Execute(object sender, EventArgs e) try { - System.Diagnostics.Debug.WriteLine("DevAssist: TestGutterIconsDirectCommand - Starting DIRECT test (no MEF)"); + System.Diagnostics.Debug.WriteLine("CxAssist: TestGutterIconsDirectCommand - Starting DIRECT test (no MEF)"); var textView = GetActiveTextView(); if (textView == null) @@ -72,33 +72,33 @@ private void Execute(object sender, EventArgs e) // Use the same taggers the editor and Quick Info use (from MEF providers). // Creating new taggers and storing in buffer.Properties would not be used by // the error layer or Quick Info source, so the rich hover would never see data. - var glyphTagger = DevAssistGlyphTaggerProvider.GetTaggerForBuffer(buffer); - var errorTagger = DevAssistErrorTaggerProvider.GetTaggerForBuffer(buffer); + var glyphTagger = CxAssistGlyphTaggerProvider.GetTaggerForBuffer(buffer); + var errorTagger = CxAssistErrorTaggerProvider.GetTaggerForBuffer(buffer); if (glyphTagger == null || errorTagger == null) { VsShellUtilities.ShowMessageBox( this.package, - "DevAssist taggers not ready for this buffer. Ensure the code file is open and focused, then run this command again.", - "Test DevAssist Hover Popup", + "CxAssist taggers not ready for this buffer. Ensure the code file is open and focused, then run this command again.", + "Test CxAssist Hover Popup", OLEMSGICON.OLEMSGICON_WARNING, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); return; } - System.Diagnostics.Debug.WriteLine("DevAssist: Using provider taggers for glyph and error (same as editor and Quick Info)"); + System.Diagnostics.Debug.WriteLine("CxAssist: Using provider taggers for glyph and error (same as editor and Quick Info)"); // Create test vulnerabilities: Critical, High, Medium, Low for colored marker verification (AST-133227) var vulnerabilities = new List { // Scanner-specific vulnerabilities (cover Critical, High, Medium) - DevAssistTestHelper.CreateOssVulnerability(), // Line 5: High (red) - DevAssistTestHelper.CreateLowSeverityVulnerability(), // Line 7: Low (green) - visual distinction - DevAssistTestHelper.CreateAscaVulnerability(), // Line 42: Critical (dark red) - DevAssistTestHelper.CreateIacVulnerability(), // Line 28: High (red) - DevAssistTestHelper.CreateSecretsVulnerability(), // Line 12: Critical (dark red) - DevAssistTestHelper.CreateContainersVulnerability(), // Line 1: Medium (orange) + CxAssistTestHelper.CreateOssVulnerability(), // Line 5: High (red) + CxAssistTestHelper.CreateLowSeverityVulnerability(), // Line 7: Low (green) - visual distinction + CxAssistTestHelper.CreateAscaVulnerability(), // Line 42: Critical (dark red) + CxAssistTestHelper.CreateIacVulnerability(), // Line 28: High (red) + CxAssistTestHelper.CreateSecretsVulnerability(), // Line 12: Critical (dark red) + CxAssistTestHelper.CreateContainersVulnerability(), // Line 1: Medium (orange) // Line 5: second finding on same line -> popup shows severity count row (Critical + High) new Vulnerability { Id = "TEST-005B", Title = "Second finding on line 5", Severity = SeverityLevel.Critical, LineNumber = 5, Scanner = ScannerType.ASCA, Description = "Multiple findings on one line test." }, @@ -109,19 +109,19 @@ private void Execute(object sender, EventArgs e) new Vulnerability { Id = "TEST-008", Severity = SeverityLevel.Ignored, LineNumber = 15, Description = "Test Ignored vulnerability", Scanner = ScannerType.IaC } }; - System.Diagnostics.Debug.WriteLine($"DevAssist: Adding {vulnerabilities.Count} test vulnerabilities to both taggers"); + System.Diagnostics.Debug.WriteLine($"CxAssist: Adding {vulnerabilities.Count} test vulnerabilities to both taggers"); // Update both taggers with the same vulnerabilities glyphTagger.UpdateVulnerabilities(vulnerabilities); errorTagger.UpdateVulnerabilities(vulnerabilities); // Force the text view to refresh - System.Diagnostics.Debug.WriteLine("DevAssist: Forcing text view refresh"); + System.Diagnostics.Debug.WriteLine("CxAssist: Forcing text view refresh"); textView.VisualElement.InvalidateVisual(); VsShellUtilities.ShowMessageBox( this.package, - $"โœ… DevAssist Hover Popup Test - Scanner-Specific Data!\n\n" + + $"โœ… CxAssist Hover Popup Test - Scanner-Specific Data!\n\n" + $"Added {vulnerabilities.Count} test vulnerabilities with rich scanner-specific data:\n\n" + $"SCANNER-SPECIFIC VULNERABILITIES:\n" + $"๐ŸŸข Line 5: OSS - Package vulnerability\n" + @@ -142,14 +142,14 @@ private void Execute(object sender, EventArgs e) $"โœ… Action links (View Details, Navigate, Learn More, Apply Fix)\n" + $"โœ… Theme-aware styling\n" + $"โœ… Similar to JetBrains implementation", - "Test DevAssist Hover Popup - Scanner-Specific Content", + "Test CxAssist Hover Popup - Scanner-Specific Content", OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"DevAssist: Error in direct test: {ex.Message}\n{ex.StackTrace}"); + System.Diagnostics.Debug.WriteLine($"CxAssist: Error in direct test: {ex.Message}\n{ex.StackTrace}"); VsShellUtilities.ShowMessageBox( this.package, $"Error: {ex.Message}", diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistDisplayCoordinator.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/CxAssistDisplayCoordinator.cs similarity index 82% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistDisplayCoordinator.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/CxAssistDisplayCoordinator.cs index 4a36ab14..8286cf65 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistDisplayCoordinator.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/CxAssistDisplayCoordinator.cs @@ -4,20 +4,20 @@ using System.IO; using System.Reflection; using Microsoft.VisualStudio.Text; -using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; -using ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons; -using ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers; -using ast_visual_studio_extension.CxExtension.DevAssist.UI.FindingsWindow; +using ast_visual_studio_extension.CxExtension.CxAssist.Core.Models; +using ast_visual_studio_extension.CxExtension.CxAssist.Core.GutterIcons; +using ast_visual_studio_extension.CxExtension.CxAssist.Core.Markers; +using ast_visual_studio_extension.CxExtension.CxAssist.UI.FindingsWindow; using System.Linq; -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core { /// - /// Single coordinator for DevAssist display (Option B). + /// Single coordinator for CxAssist display (Option B). /// Takes one List<Vulnerability> and updates gutter, underline, and problem window in one go. /// Stores issues per file (like JetBrains ProblemHolderService) and notifies via IssuesUpdated so the findings window can subscribe and stay in sync. /// - public static class DevAssistDisplayCoordinator + public static class CxAssistDisplayCoordinator { private static readonly object _lock = new object(); private static Dictionary> _fileToIssues = new Dictionary>(StringComparer.OrdinalIgnoreCase); @@ -148,28 +148,28 @@ public static void UpdateFindings(ITextBuffer buffer, List vulner { if (buffer == null) { - System.Diagnostics.Debug.WriteLine("DevAssistDisplayCoordinator: buffer is null"); + System.Diagnostics.Debug.WriteLine("CxAssistDisplayCoordinator: buffer is null"); return; } var list = vulnerabilities ?? new List(); // 1. Update gutter - var glyphTagger = DevAssistErrorHandler.TryGet(() => DevAssistGlyphTaggerProvider.GetTaggerForBuffer(buffer), "Coordinator.GetGlyphTagger", null); + var glyphTagger = CxAssistErrorHandler.TryGet(() => CxAssistGlyphTaggerProvider.GetTaggerForBuffer(buffer), "Coordinator.GetGlyphTagger", null); if (glyphTagger != null) - DevAssistErrorHandler.TryRun(() => glyphTagger.UpdateVulnerabilities(list), "Coordinator.GlyphTagger.UpdateVulnerabilities"); + CxAssistErrorHandler.TryRun(() => glyphTagger.UpdateVulnerabilities(list), "Coordinator.GlyphTagger.UpdateVulnerabilities"); else - System.Diagnostics.Debug.WriteLine("DevAssistDisplayCoordinator: glyph tagger not found for buffer"); + System.Diagnostics.Debug.WriteLine("CxAssistDisplayCoordinator: glyph tagger not found for buffer"); // 2. Update underline - var errorTagger = DevAssistErrorHandler.TryGet(() => DevAssistErrorTaggerProvider.GetTaggerForBuffer(buffer), "Coordinator.GetErrorTagger", null); + var errorTagger = CxAssistErrorHandler.TryGet(() => CxAssistErrorTaggerProvider.GetTaggerForBuffer(buffer), "Coordinator.GetErrorTagger", null); if (errorTagger != null) - DevAssistErrorHandler.TryRun(() => errorTagger.UpdateVulnerabilities(list), "Coordinator.ErrorTagger.UpdateVulnerabilities"); + CxAssistErrorHandler.TryRun(() => errorTagger.UpdateVulnerabilities(list), "Coordinator.ErrorTagger.UpdateVulnerabilities"); else - System.Diagnostics.Debug.WriteLine("DevAssistDisplayCoordinator: error tagger not found for buffer"); + System.Diagnostics.Debug.WriteLine("CxAssistDisplayCoordinator: error tagger not found for buffer"); // 3. Store per file and notify (JetBrains ProblemHolderService + ISSUE_TOPIC-like) - DevAssistErrorHandler.TryRun(() => + CxAssistErrorHandler.TryRun(() => { // Prefer explicit filePath, then path from buffer (so we can clear when list is empty), then first vulnerability string resolvedPath = filePath ?? TryGetFilePathFromBuffer(buffer) ?? (list.Count > 0 ? list[0].FilePath : null); @@ -191,28 +191,28 @@ public static void UpdateFindings(ITextBuffer buffer, List vulner IssuesUpdated?.Invoke(snapshot); }, "Coordinator.StoreCurrentFindings"); - System.Diagnostics.Debug.WriteLine($"DevAssistDisplayCoordinator: updated gutter, underline, and per-file findings ({list.Count} for file)"); + System.Diagnostics.Debug.WriteLine($"CxAssistDisplayCoordinator: updated gutter, underline, and per-file findings ({list.Count} for file)"); } /// /// Updates the problem window control with the current findings (builds FileNodes and calls SetAllFileNodes). /// Call this when the Findings window is shown so it displays the same data as gutter/underline. /// - /// The DevAssist Findings control to update. + /// The CxAssist Findings control to update. /// Optional; if null, severity icons are not set. /// Optional; if null, file icon is not set. public static void RefreshProblemWindow( - DevAssistFindingsControl findingsControl, + CxAssistFindingsControl findingsControl, Func loadSeverityIcon = null, Func loadFileIcon = null) { if (findingsControl == null) return; - DevAssistErrorHandler.TryRun(() => + CxAssistErrorHandler.TryRun(() => { List current = GetCurrentFindings(); ObservableCollection fileNodes = current != null && current.Count > 0 - ? DevAssistMockData.BuildFileNodesFromVulnerabilities(current, loadSeverityIcon, loadFileIcon) + ? CxAssistMockData.BuildFileNodesFromVulnerabilities(current, loadSeverityIcon, loadFileIcon) : new ObservableCollection(); findingsControl.SetAllFileNodes(fileNodes); }, "Coordinator.RefreshProblemWindow"); diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorHandler.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/CxAssistErrorHandler.cs similarity index 89% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorHandler.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/CxAssistErrorHandler.cs index 0547b784..9675120b 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorHandler.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/CxAssistErrorHandler.cs @@ -1,16 +1,16 @@ using System; using System.Diagnostics; -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core { /// - /// Central error handling for DevAssist so third-party plugins or VS errors + /// Central error handling for CxAssist so third-party plugins or VS errors /// do not crash gutter, underline, problem window, or hover. /// We log and swallow exceptions at VS callback boundaries (GetTags, GenerateGlyph, etc.). /// - internal static class DevAssistErrorHandler + internal static class CxAssistErrorHandler { - private const string Category = "DevAssist"; + private const string Category = "CxAssist"; /// /// Logs the exception and returns without rethrowing. diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorListSync.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/CxAssistErrorListSync.cs similarity index 87% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorListSync.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/CxAssistErrorListSync.cs index 9aef19a1..af5bb416 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistErrorListSync.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/CxAssistErrorListSync.cs @@ -6,18 +6,18 @@ using EnvDTE80; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; -using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; +using ast_visual_studio_extension.CxExtension.CxAssist.Core.Models; -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core { /// - /// Syncs DevAssist findings to the built-in Error List so issues appear in both - /// the custom DevAssist findings window and the VS Error List. + /// Syncs CxAssist findings to the built-in Error List so issues appear in both + /// the custom CxAssist findings window and the VS Error List. /// - internal sealed class DevAssistErrorListSync + internal sealed class CxAssistErrorListSync { - /// Prefix stored in ErrorTask.HelpKeyword so we can identify DevAssist tasks and recover vulnerability Id. - public const string HelpKeywordPrefix = "DevAssist:"; + /// Prefix stored in ErrorTask.HelpKeyword so we can identify CxAssist tasks and recover vulnerability Id. + public const string HelpKeywordPrefix = "CxAssist:"; private ErrorListProvider _errorListProvider; private bool _subscribed; @@ -28,11 +28,11 @@ public void Start() ThreadHelper.ThrowIfNotOnUIThread(); EnsureErrorListProvider(); - DevAssistDisplayCoordinator.IssuesUpdated += OnIssuesUpdated; + CxAssistDisplayCoordinator.IssuesUpdated += OnIssuesUpdated; _subscribed = true; // Initial sync from current state - var snapshot = DevAssistDisplayCoordinator.GetAllIssuesByFile(); + var snapshot = CxAssistDisplayCoordinator.GetAllIssuesByFile(); if (snapshot != null && snapshot.Count > 0) SyncToErrorList(snapshot); } @@ -41,7 +41,7 @@ public void Stop() { if (!_subscribed) return; - DevAssistDisplayCoordinator.IssuesUpdated -= OnIssuesUpdated; + CxAssistDisplayCoordinator.IssuesUpdated -= OnIssuesUpdated; _subscribed = false; ThreadHelper.JoinableTaskFactory.Run(async () => @@ -67,7 +67,7 @@ private void EnsureErrorListProvider() _errorListProvider = new ErrorListProvider(ServiceProvider.GlobalProvider) { - ProviderName = "DevAssist" + ProviderName = "CxAssist" }; } @@ -110,7 +110,7 @@ private void SyncToErrorList(IReadOnlyDictionary> is { Category = TaskCategory.CodeSense, ErrorCategory = GetErrorCategory(v.Severity), - Text = $"[DevAssist] [{severityLabel}] {v.Title}", + Text = $"[CxAssist] [{severityLabel}] {v.Title}", Document = docPath, Line = Math.Max(0, v.LineNumber - 1), Column = Math.Max(0, v.ColumnNumber), @@ -196,7 +196,7 @@ private static void NavigateToVulnerability(Vulnerability v) } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"DevAssistErrorListSync: Navigate failed: {ex.Message}"); + System.Diagnostics.Debug.WriteLine($"CxAssistErrorListSync: Navigate failed: {ex.Message}"); } } } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistMockData.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/CxAssistMockData.cs similarity index 97% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistMockData.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/CxAssistMockData.cs index 5aa87436..537ba630 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/DevAssistMockData.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/CxAssistMockData.cs @@ -1,22 +1,22 @@ -using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; -using ast_visual_studio_extension.CxExtension.DevAssist.UI.FindingsWindow; +using ast_visual_studio_extension.CxExtension.CxAssist.Core.Models; +using ast_visual_studio_extension.CxExtension.CxAssist.UI.FindingsWindow; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Windows.Media; -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core { /// /// Common mock data used to demonstrate all four POC features: /// underline (squiggle), gutter icon, problem window, and popup hover. /// One source of truth so editor and findings window show the same data. /// - public static class DevAssistMockData + public static class CxAssistMockData { /// Default file path used for mock vulnerabilities (editor and findings window). - public const string DefaultFilePath = "Program.cs" + public const string DefaultFilePath = "Program.cs"; /// Vulnerability Id that uses standard Quick Info popup only (no custom hover popup). public const string QuickInfoOnlyVulnerabilityId = "POC-007"; diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphFactory.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/GutterIcons/CxAssistGlyphFactory.cs similarity index 75% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphFactory.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/GutterIcons/CxAssistGlyphFactory.cs index 0038d3c0..ef05149d 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphFactory.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/GutterIcons/CxAssistGlyphFactory.cs @@ -12,29 +12,29 @@ using SharpVectors.Converters; using SharpVectors.Renderers.Wpf; -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core.GutterIcons { /// - /// Factory for creating custom gutter glyphs for DevAssist vulnerabilities + /// Factory for creating custom gutter glyphs for CxAssist vulnerabilities /// Based on JetBrains GutterIconRenderer pattern adapted for Visual Studio MEF /// Uses IGlyphFactory to display custom severity icons in the gutter margin /// - internal class DevAssistGlyphFactory : IGlyphFactory + internal class CxAssistGlyphFactory : IGlyphFactory { private const double GlyphSize = 14.0; public UIElement GenerateGlyph(IWpfTextViewLine line, IGlyphTag tag) { - System.Diagnostics.Debug.WriteLine($"DevAssist: GenerateGlyph called - tag type: {tag?.GetType().Name}"); + System.Diagnostics.Debug.WriteLine($"CxAssist: GenerateGlyph called - tag type: {tag?.GetType().Name}"); - if (tag == null || !(tag is DevAssistGlyphTag)) + if (tag == null || !(tag is CxAssistGlyphTag)) { - System.Diagnostics.Debug.WriteLine($"DevAssist: Tag is null or not DevAssistGlyphTag"); + System.Diagnostics.Debug.WriteLine($"CxAssist: Tag is null or not CxAssistGlyphTag"); return null; } - var glyphTag = (DevAssistGlyphTag)tag; - System.Diagnostics.Debug.WriteLine($"DevAssist: Generating glyph for severity: {glyphTag.Severity}"); + var glyphTag = (CxAssistGlyphTag)tag; + System.Diagnostics.Debug.WriteLine($"CxAssist: Generating glyph for severity: {glyphTag.Severity}"); try { @@ -42,7 +42,7 @@ public UIElement GenerateGlyph(IWpfTextViewLine line, IGlyphTag tag) var iconSource = GetIconForSeverity(glyphTag.Severity); if (iconSource == null) { - System.Diagnostics.Debug.WriteLine($"DevAssist: Icon source is null for severity: {glyphTag.Severity}"); + System.Diagnostics.Debug.WriteLine($"CxAssist: Icon source is null for severity: {glyphTag.Severity}"); return null; } @@ -59,12 +59,12 @@ public UIElement GenerateGlyph(IWpfTextViewLine line, IGlyphTag tag) image.ToolTip = glyphTag.TooltipText; } - System.Diagnostics.Debug.WriteLine($"DevAssist: Successfully created glyph image for severity: {glyphTag.Severity}"); + System.Diagnostics.Debug.WriteLine($"CxAssist: Successfully created glyph image for severity: {glyphTag.Severity}"); return image; } catch (Exception ex) { - DevAssistErrorHandler.LogAndSwallow(ex, "GlyphFactory.GenerateGlyph"); + CxAssistErrorHandler.LogAndSwallow(ex, "GlyphFactory.GenerateGlyph"); return null; } } @@ -129,7 +129,7 @@ private ImageSource GetIconForSeverity(string severity) /// /// Loads a themed icon from the organized folder structure /// Detects Visual Studio theme (Light/Dark) and loads appropriate icon - /// Path: CxExtension/Resources/DevAssist/Icons/{Light|Dark}/{iconName}.svg + /// Path: CxExtension/Resources/CxAssist/Icons/{Light|Dark}/{iconName}.svg /// Uses SharpVectors to render SVG files in WPF /// private ImageSource LoadThemedIcon(string iconName) @@ -139,7 +139,7 @@ private ImageSource LoadThemedIcon(string iconName) string themeFolder = isDarkTheme ? "Dark" : "Light"; // Build path to themed SVG icon - var iconUri = new Uri($"pack://application:,,,/ast-visual-studio-extension;component/CxExtension/Resources/DevAssist/Icons/{themeFolder}/{iconName}.svg"); + var iconUri = new Uri($"pack://application:,,,/ast-visual-studio-extension;component/CxExtension/Resources/CxAssist/Icons/{themeFolder}/{iconName}.svg"); try { @@ -147,7 +147,7 @@ private ImageSource LoadThemedIcon(string iconName) var streamInfo = System.Windows.Application.GetResourceStream(iconUri); if (streamInfo == null) { - System.Diagnostics.Debug.WriteLine($"DevAssist: Failed to load SVG icon {iconName} - resource not found"); + System.Diagnostics.Debug.WriteLine($"CxAssist: Failed to load SVG icon {iconName} - resource not found"); return null; } @@ -172,12 +172,12 @@ private ImageSource LoadThemedIcon(string iconName) } } - System.Diagnostics.Debug.WriteLine($"DevAssist: Failed to load SVG icon {iconName} - drawing is null"); + System.Diagnostics.Debug.WriteLine($"CxAssist: Failed to load SVG icon {iconName} - drawing is null"); return null; } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"DevAssist: Failed to load SVG icon {iconName}: {ex.Message}"); + System.Diagnostics.Debug.WriteLine($"CxAssist: Failed to load SVG icon {iconName}: {ex.Message}"); return null; } } @@ -208,42 +208,42 @@ private bool IsVsDarkTheme() } /// - /// MEF export for DevAssist glyph factory provider - /// Registers the factory for the "DevAssist" glyph tag type + /// MEF export for CxAssist glyph factory provider + /// Registers the factory for the "CxAssist" glyph tag type /// [Export(typeof(IGlyphFactoryProvider))] - [Name("DevAssistGlyph")] + [Name("CxAssistGlyph")] [Order(After = "VsTextMarker")] [ContentType("code")] [ContentType("text")] - [TagType(typeof(DevAssistGlyphTag))] + [TagType(typeof(CxAssistGlyphTag))] [TextViewRole(PredefinedTextViewRoles.Document)] [TextViewRole(PredefinedTextViewRoles.Editable)] - internal sealed class DevAssistGlyphFactoryProvider : IGlyphFactoryProvider + internal sealed class CxAssistGlyphFactoryProvider : IGlyphFactoryProvider { - public DevAssistGlyphFactoryProvider() + public CxAssistGlyphFactoryProvider() { - System.Diagnostics.Debug.WriteLine("DevAssist: DevAssistGlyphFactoryProvider constructor called - MEF is loading glyph factory provider"); + System.Diagnostics.Debug.WriteLine("CxAssist: CxAssistGlyphFactoryProvider constructor called - MEF is loading glyph factory provider"); } public IGlyphFactory GetGlyphFactory(IWpfTextView view, IWpfTextViewMargin margin) { - System.Diagnostics.Debug.WriteLine($"DevAssist: GetGlyphFactory called for margin: {margin?.GetType().Name}"); - return new DevAssistGlyphFactory(); + System.Diagnostics.Debug.WriteLine($"CxAssist: GetGlyphFactory called for margin: {margin?.GetType().Name}"); + return new CxAssistGlyphFactory(); } } /// - /// Custom glyph tag for DevAssist vulnerabilities + /// Custom glyph tag for CxAssist vulnerabilities /// Based on JetBrains GutterIconRenderer pattern /// - internal class DevAssistGlyphTag : IGlyphTag + internal class CxAssistGlyphTag : IGlyphTag { public string Severity { get; } public string TooltipText { get; } public string VulnerabilityId { get; } - public DevAssistGlyphTag(string severity, string tooltipText, string vulnerabilityId) + public CxAssistGlyphTag(string severity, string tooltipText, string vulnerabilityId) { Severity = severity; TooltipText = tooltipText; diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTagger.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/GutterIcons/CxAssistGlyphTagger.cs similarity index 74% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTagger.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/GutterIcons/CxAssistGlyphTagger.cs index fd0c14d2..679b7002 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTagger.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/GutterIcons/CxAssistGlyphTagger.cs @@ -6,36 +6,36 @@ using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Tagging; using Microsoft.VisualStudio.Utilities; -using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; +using ast_visual_studio_extension.CxExtension.CxAssist.Core.Models; -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core.GutterIcons { /// - /// Tagger that provides glyph tags for DevAssist vulnerabilities + /// Tagger that provides glyph tags for CxAssist vulnerabilities /// Based on JetBrains MarkupModel.addRangeHighlighter pattern /// Manages the lifecycle of gutter icons in the text view /// - internal class DevAssistGlyphTagger : ITagger + internal class CxAssistGlyphTagger : ITagger { private readonly ITextBuffer _buffer; private readonly Dictionary> _vulnerabilitiesByLine; public event EventHandler TagsChanged; - public DevAssistGlyphTagger(ITextBuffer buffer) + public CxAssistGlyphTagger(ITextBuffer buffer) { _buffer = buffer; _vulnerabilitiesByLine = new Dictionary>(); } - public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) + public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) { - var result = new List>(); - System.Diagnostics.Debug.WriteLine($"DevAssist: GetTags called - spans count: {spans?.Count ?? 0}, vulnerabilities count: {_vulnerabilitiesByLine.Count}"); + var result = new List>(); + System.Diagnostics.Debug.WriteLine($"CxAssist: GetTags called - spans count: {spans?.Count ?? 0}, vulnerabilities count: {_vulnerabilitiesByLine.Count}"); if (spans == null || spans.Count == 0 || _vulnerabilitiesByLine.Count == 0) { - System.Diagnostics.Debug.WriteLine($"DevAssist: GetTags returning early - no spans or vulnerabilities"); + System.Diagnostics.Debug.WriteLine($"CxAssist: GetTags returning early - no spans or vulnerabilities"); return result; } @@ -46,7 +46,7 @@ public IEnumerable> GetTags(NormalizedSnapshotSpanCo } catch (Exception ex) { - DevAssistErrorHandler.LogAndSwallow(ex, "GlyphTagger.GetTags (snapshot)"); + CxAssistErrorHandler.LogAndSwallow(ex, "GlyphTagger.GetTags (snapshot)"); } if (snapshot == null) return result; @@ -70,26 +70,26 @@ public IEnumerable> GetTags(NormalizedSnapshotSpanCo var lineSpan = new SnapshotSpan(snapshot, line.Start, line.Length); var tooltipText = BuildTooltipText(vulnerabilities); - var tag = new DevAssistGlyphTag( + var tag = new CxAssistGlyphTag( mostSevere.Severity.ToString(), tooltipText, mostSevere.Id ); tagCount++; - System.Diagnostics.Debug.WriteLine($"DevAssist: Creating tag #{tagCount} for line {lineNumber}, severity: {mostSevere.Severity}"); - result.Add(new TagSpan(lineSpan, tag)); + System.Diagnostics.Debug.WriteLine($"CxAssist: Creating tag #{tagCount} for line {lineNumber}, severity: {mostSevere.Severity}"); + result.Add(new TagSpan(lineSpan, tag)); } } } } catch (Exception ex) { - DevAssistErrorHandler.LogAndSwallow(ex, "GlyphTagger.GetTags (span)"); + CxAssistErrorHandler.LogAndSwallow(ex, "GlyphTagger.GetTags (span)"); } } - System.Diagnostics.Debug.WriteLine($"DevAssist: GetTags completed - returned {tagCount} tags"); + System.Diagnostics.Debug.WriteLine($"CxAssist: GetTags completed - returned {tagCount} tags"); return result; } @@ -99,12 +99,12 @@ public IEnumerable> GetTags(NormalizedSnapshotSpanCo /// public void UpdateVulnerabilities(List vulnerabilities) { - DevAssistErrorHandler.TryRun(() => UpdateVulnerabilitiesCore(vulnerabilities), "GlyphTagger.UpdateVulnerabilities"); + CxAssistErrorHandler.TryRun(() => UpdateVulnerabilitiesCore(vulnerabilities), "GlyphTagger.UpdateVulnerabilities"); } private void UpdateVulnerabilitiesCore(List vulnerabilities) { - System.Diagnostics.Debug.WriteLine($"DevAssist: UpdateVulnerabilities called with {vulnerabilities?.Count ?? 0} vulnerabilities"); + System.Diagnostics.Debug.WriteLine($"CxAssist: UpdateVulnerabilities called with {vulnerabilities?.Count ?? 0} vulnerabilities"); _vulnerabilitiesByLine.Clear(); @@ -123,15 +123,15 @@ private void UpdateVulnerabilitiesCore(List vulnerabilities) } } - System.Diagnostics.Debug.WriteLine($"DevAssist: Vulnerabilities stored in {_vulnerabilitiesByLine.Count} lines"); - System.Diagnostics.Debug.WriteLine($"DevAssist: TagsChanged event has {(TagsChanged != null ? TagsChanged.GetInvocationList().Length : 0)} subscribers"); + System.Diagnostics.Debug.WriteLine($"CxAssist: Vulnerabilities stored in {_vulnerabilitiesByLine.Count} lines"); + System.Diagnostics.Debug.WriteLine($"CxAssist: TagsChanged event has {(TagsChanged != null ? TagsChanged.GetInvocationList().Length : 0)} subscribers"); var snapshot = _buffer.CurrentSnapshot; var entireSpan = new SnapshotSpan(snapshot, 0, snapshot.Length); - System.Diagnostics.Debug.WriteLine($"DevAssist: Raising TagsChanged event for span: {entireSpan.Start} to {entireSpan.End}"); + System.Diagnostics.Debug.WriteLine($"CxAssist: Raising TagsChanged event for span: {entireSpan.Start} to {entireSpan.End}"); TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(entireSpan)); - System.Diagnostics.Debug.WriteLine($"DevAssist: TagsChanged event raised"); + System.Diagnostics.Debug.WriteLine($"CxAssist: TagsChanged event raised"); } /// @@ -192,7 +192,7 @@ private string BuildTooltipText(List vulnerabilities) if (vulnerabilities.Count == 1) { var vuln = vulnerabilities[0]; - return $"{vuln.Severity} - {vuln.Title}\n{vuln.Description}\n(DevAssist - {vuln.Scanner})"; + return $"{vuln.Severity} - {vuln.Title}\n{vuln.Description}\n(CxAssist - {vuln.Scanner})"; } else { diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTaggerProvider.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/GutterIcons/CxAssistGlyphTaggerProvider.cs similarity index 65% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTaggerProvider.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/GutterIcons/CxAssistGlyphTaggerProvider.cs index 7288f061..a9d54cec 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistGlyphTaggerProvider.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/GutterIcons/CxAssistGlyphTaggerProvider.cs @@ -6,10 +6,10 @@ using Microsoft.VisualStudio.Text.Tagging; using Microsoft.VisualStudio.Utilities; -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core.GutterIcons { /// - /// MEF provider for DevAssist glyph tagger + /// MEF provider for CxAssist glyph tagger /// Based on JetBrains EditorFactoryListener pattern adapted for Visual Studio /// Creates and manages tagger instances per buffer (not per view) /// IMPORTANT: Uses ITaggerProvider (not IViewTaggerProvider) for glyph tags @@ -17,27 +17,27 @@ namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons [Export(typeof(ITaggerProvider))] [ContentType("code")] [ContentType("text")] - [TagType(typeof(DevAssistGlyphTag))] + [TagType(typeof(CxAssistGlyphTag))] [TextViewRole(PredefinedTextViewRoles.Document)] [TextViewRole(PredefinedTextViewRoles.Editable)] - internal class DevAssistGlyphTaggerProvider : ITaggerProvider + internal class CxAssistGlyphTaggerProvider : ITaggerProvider { // Static instance for external access - private static DevAssistGlyphTaggerProvider _instance; + private static CxAssistGlyphTaggerProvider _instance; // Cache taggers per buffer to ensure single instance per buffer - private readonly Dictionary _taggers = - new Dictionary(); + private readonly Dictionary _taggers = + new Dictionary(); - public DevAssistGlyphTaggerProvider() + public CxAssistGlyphTaggerProvider() { - System.Diagnostics.Debug.WriteLine("DevAssist: DevAssistGlyphTaggerProvider constructor called - MEF is loading this provider"); + System.Diagnostics.Debug.WriteLine("CxAssist: CxAssistGlyphTaggerProvider constructor called - MEF is loading this provider"); _instance = this; } public ITagger CreateTagger(ITextBuffer buffer) where T : ITag { - System.Diagnostics.Debug.WriteLine($"DevAssist: CreateTagger called - buffer: {buffer != null}"); + System.Diagnostics.Debug.WriteLine($"CxAssist: CreateTagger called - buffer: {buffer != null}"); if (buffer == null) return null; @@ -47,19 +47,19 @@ public ITagger CreateTagger(ITextBuffer buffer) where T : ITag { if (!_taggers.TryGetValue(buffer, out var tagger)) { - System.Diagnostics.Debug.WriteLine($"DevAssist: CreateTagger - creating NEW tagger for buffer"); - tagger = new DevAssistGlyphTagger(buffer); + System.Diagnostics.Debug.WriteLine($"CxAssist: CreateTagger - creating NEW tagger for buffer"); + tagger = new CxAssistGlyphTagger(buffer); _taggers[buffer] = tagger; // Store tagger in buffer properties for external access try { - buffer.Properties.AddProperty(typeof(DevAssistGlyphTagger), tagger); - System.Diagnostics.Debug.WriteLine($"DevAssist: CreateTagger - tagger stored in buffer properties"); + buffer.Properties.AddProperty(typeof(CxAssistGlyphTagger), tagger); + System.Diagnostics.Debug.WriteLine($"CxAssist: CreateTagger - tagger stored in buffer properties"); } catch { - System.Diagnostics.Debug.WriteLine($"DevAssist: CreateTagger - tagger already in buffer properties"); + System.Diagnostics.Debug.WriteLine($"CxAssist: CreateTagger - tagger already in buffer properties"); } // Clean up when buffer is closed @@ -68,13 +68,13 @@ public ITagger CreateTagger(ITextBuffer buffer) where T : ITag lock (_taggers) { _taggers.Remove(buffer); - buffer.Properties.RemoveProperty(typeof(DevAssistGlyphTagger)); + buffer.Properties.RemoveProperty(typeof(CxAssistGlyphTagger)); } })); } else { - System.Diagnostics.Debug.WriteLine($"DevAssist: CreateTagger - returning EXISTING tagger"); + System.Diagnostics.Debug.WriteLine($"CxAssist: CreateTagger - returning EXISTING tagger"); } return tagger as ITagger; @@ -83,24 +83,24 @@ public ITagger CreateTagger(ITextBuffer buffer) where T : ITag /// /// Gets the tagger for a specific buffer (for external access) - /// Allows DevAssistPOC or other components to update vulnerabilities + /// Allows CxAssistPOC or other components to update vulnerabilities /// IMPORTANT: Only returns taggers created by MEF through CreateTagger() /// This ensures Visual Studio is properly subscribed to TagsChanged events /// - public static DevAssistGlyphTagger GetTaggerForBuffer(ITextBuffer buffer) + public static CxAssistGlyphTagger GetTaggerForBuffer(ITextBuffer buffer) { if (buffer == null) { - System.Diagnostics.Debug.WriteLine("DevAssist: GetTaggerForBuffer - buffer is null"); + System.Diagnostics.Debug.WriteLine("CxAssist: GetTaggerForBuffer - buffer is null"); return null; } // ONLY get tagger from buffer properties - do NOT create it directly // The tagger MUST be created by MEF through CreateTagger() so that // Visual Studio subscribes to the TagsChanged event - if (buffer.Properties.TryGetProperty(typeof(DevAssistGlyphTagger), out DevAssistGlyphTagger tagger)) + if (buffer.Properties.TryGetProperty(typeof(CxAssistGlyphTagger), out CxAssistGlyphTagger tagger)) { - System.Diagnostics.Debug.WriteLine("DevAssist: GetTaggerForBuffer - found tagger in buffer properties"); + System.Diagnostics.Debug.WriteLine("CxAssist: GetTaggerForBuffer - found tagger in buffer properties"); return tagger; } @@ -111,13 +111,13 @@ public static DevAssistGlyphTagger GetTaggerForBuffer(ITextBuffer buffer) { if (_instance._taggers.TryGetValue(buffer, out tagger)) { - System.Diagnostics.Debug.WriteLine("DevAssist: GetTaggerForBuffer - found tagger in instance cache"); + System.Diagnostics.Debug.WriteLine("CxAssist: GetTaggerForBuffer - found tagger in instance cache"); return tagger; } } } - System.Diagnostics.Debug.WriteLine("DevAssist: GetTaggerForBuffer - tagger NOT found (MEF hasn't created it yet)"); + System.Diagnostics.Debug.WriteLine("CxAssist: GetTaggerForBuffer - tagger NOT found (MEF hasn't created it yet)"); return null; } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/GutterIcons/CxAssistTextViewCreationListener.cs similarity index 68% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/GutterIcons/CxAssistTextViewCreationListener.cs index 9bb323e6..ba422d21 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/GutterIcons/DevAssistTextViewCreationListener.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/GutterIcons/CxAssistTextViewCreationListener.cs @@ -7,10 +7,10 @@ using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities; -using ast_visual_studio_extension.CxExtension.DevAssist.Core; -using ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers; +using ast_visual_studio_extension.CxExtension.CxAssist.Core; +using ast_visual_studio_extension.CxExtension.CxAssist.Core.Markers; -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core.GutterIcons { /// /// Listens for text view creation and automatically adds test gutter icons and colored markers @@ -19,13 +19,13 @@ namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.GutterIcons [Export(typeof(IWpfTextViewCreationListener))] [ContentType("CSharp")] [TextViewRole(PredefinedTextViewRoles.Document)] - internal class DevAssistTextViewCreationListener : IWpfTextViewCreationListener + internal class CxAssistTextViewCreationListener : IWpfTextViewCreationListener { private static int _fallbackDocumentCounter; public void TextViewCreated(IWpfTextView textView) { - System.Diagnostics.Debug.WriteLine("DevAssist: TextViewCreated - C# file opened"); + System.Diagnostics.Debug.WriteLine("CxAssist: TextViewCreated - C# file opened"); // Wait for MEF to create the taggers, then add test vulnerabilities // We need to wait because the taggers are created asynchronously by MEF @@ -37,35 +37,35 @@ public void TextViewCreated(IWpfTextView textView) { await Microsoft.VisualStudio.Shell.ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - System.Diagnostics.Debug.WriteLine("DevAssist: Attempting to add test vulnerabilities to C# file"); + System.Diagnostics.Debug.WriteLine("CxAssist: Attempting to add test vulnerabilities to C# file"); var buffer = textView.TextBuffer; // Try to get the glyph tagger - it should have been created by MEF by now - DevAssistGlyphTagger glyphTagger = null; - DevAssistErrorTagger errorTagger = null; + CxAssistGlyphTagger glyphTagger = null; + CxAssistErrorTagger errorTagger = null; // Try multiple times with delays in case MEF is still loading for (int i = 0; i < 8; i++) { - glyphTagger = DevAssistGlyphTaggerProvider.GetTaggerForBuffer(buffer); - errorTagger = DevAssistErrorTaggerProvider.GetTaggerForBuffer(buffer); + glyphTagger = CxAssistGlyphTaggerProvider.GetTaggerForBuffer(buffer); + errorTagger = CxAssistErrorTaggerProvider.GetTaggerForBuffer(buffer); if (glyphTagger != null && errorTagger != null) { - System.Diagnostics.Debug.WriteLine($"DevAssist: Both taggers found on attempt {i + 1}"); + System.Diagnostics.Debug.WriteLine($"CxAssist: Both taggers found on attempt {i + 1}"); break; } - System.Diagnostics.Debug.WriteLine($"DevAssist: Taggers not found, attempt {i + 1}/8, waiting..."); + System.Diagnostics.Debug.WriteLine($"CxAssist: Taggers not found, attempt {i + 1}/8, waiting..."); await System.Threading.Tasks.Task.Delay(200); } if (glyphTagger != null && errorTagger != null) { - System.Diagnostics.Debug.WriteLine("DevAssist: Both taggers found, updating via coordinator (gutter, underline, problem window)"); + System.Diagnostics.Debug.WriteLine("CxAssist: Both taggers found, updating via coordinator (gutter, underline, problem window)"); // Single coordinator call: updates gutter, underline, and current findings for problem window (Option B) - var filePath = DevAssistDisplayCoordinator.GetFilePathForBuffer(buffer); + var filePath = CxAssistDisplayCoordinator.GetFilePathForBuffer(buffer); // When path is unknown (e.g. ITextDocument not available), try active document so problem window shows real file name if (string.IsNullOrEmpty(filePath)) { @@ -80,23 +80,23 @@ public void TextViewCreated(IWpfTextView textView) { var fallback = Interlocked.Increment(ref _fallbackDocumentCounter); filePath = $"Document {fallback}"; - System.Diagnostics.Debug.WriteLine($"DevAssist: GetFilePathForBuffer returned null, using fallback: {filePath}"); + System.Diagnostics.Debug.WriteLine($"CxAssist: GetFilePathForBuffer returned null, using fallback: {filePath}"); } } - var vulnerabilities = DevAssistMockData.GetCommonVulnerabilities(filePath); - DevAssistDisplayCoordinator.UpdateFindings(buffer, vulnerabilities, filePath); + var vulnerabilities = CxAssistMockData.GetCommonVulnerabilities(filePath); + CxAssistDisplayCoordinator.UpdateFindings(buffer, vulnerabilities, filePath); - System.Diagnostics.Debug.WriteLine("DevAssist: Coordinator updated gutter, underline, and findings successfully"); + System.Diagnostics.Debug.WriteLine("CxAssist: Coordinator updated gutter, underline, and findings successfully"); } else { - System.Diagnostics.Debug.WriteLine("DevAssist: Taggers are NULL - MEF hasn't created them yet"); + System.Diagnostics.Debug.WriteLine("CxAssist: Taggers are NULL - MEF hasn't created them yet"); } }); } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"DevAssist: Error adding test vulnerabilities: {ex.Message}"); + System.Diagnostics.Debug.WriteLine($"CxAssist: Error adding test vulnerabilities: {ex.Message}"); } }); } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistAsyncQuickInfoSource.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistAsyncQuickInfoSource.cs similarity index 81% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistAsyncQuickInfoSource.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistAsyncQuickInfoSource.cs index 5474dad1..f4f0c77b 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistAsyncQuickInfoSource.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistAsyncQuickInfoSource.cs @@ -1,4 +1,4 @@ -using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; +using ast_visual_studio_extension.CxExtension.CxAssist.Core.Models; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; using System; @@ -6,29 +6,29 @@ using System.Threading; using System.Threading.Tasks; -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core.Markers { /// /// Async Quick Info source so the modern presenter can wire navigation callbacks /// (legacy IQuickInfoSource presenter ignores per-ClassifiedTextRun actions). /// Only one content block is shown per session even when multiple subject buffers exist. /// - internal class DevAssistAsyncQuickInfoSource : IAsyncQuickInfoSource + internal class CxAssistAsyncQuickInfoSource : IAsyncQuickInfoSource { - private static readonly HashSet _sessionsWithDevAssistContent = new HashSet(); + private static readonly HashSet _sessionsWithCxAssistContent = new HashSet(); private static readonly object _sessionLock = new object(); private readonly ITextBuffer _buffer; private bool _disposed; - public DevAssistAsyncQuickInfoSource(ITextBuffer buffer) + public CxAssistAsyncQuickInfoSource(ITextBuffer buffer) { _buffer = buffer; } public async Task GetQuickInfoItemAsync(IAsyncQuickInfoSession session, CancellationToken cancellationToken) { - if (!DevAssistQuickInfoSource.UseRichHover) + if (!CxAssistQuickInfoSource.UseRichHover) return null; SnapshotPoint? triggerPoint = session.GetTriggerPoint(_buffer.CurrentSnapshot); @@ -61,7 +61,7 @@ public async Task GetQuickInfoItemAsync(IAsyncQuickInfoSession se var snapshot = triggerPoint.Value.Snapshot; int lineNumber = snapshot.GetLineNumberFromPosition(triggerPoint.Value.Position); - var tagger = DevAssistErrorTaggerProvider.GetTaggerForBuffer(_buffer); + var tagger = CxAssistErrorTaggerProvider.GetTaggerForBuffer(_buffer); if (tagger == null) return null; @@ -72,9 +72,9 @@ public async Task GetQuickInfoItemAsync(IAsyncQuickInfoSession se // Only one of our sources (per session) should contribute; avoid duplicate blocks when multiple subject buffers exist. lock (_sessionLock) { - if (_sessionsWithDevAssistContent.Contains(session)) + if (_sessionsWithCxAssistContent.Contains(session)) return null; - _sessionsWithDevAssistContent.Add(session); + _sessionsWithCxAssistContent.Add(session); } void OnSessionStateChanged(object sender, QuickInfoSessionStateChangedEventArgs e) @@ -83,7 +83,7 @@ void OnSessionStateChanged(object sender, QuickInfoSessionStateChangedEventArgs { lock (_sessionLock) { - _sessionsWithDevAssistContent.Remove(session); + _sessionsWithCxAssistContent.Remove(session); } session.StateChanged -= OnSessionStateChanged; } @@ -91,12 +91,12 @@ void OnSessionStateChanged(object sender, QuickInfoSessionStateChangedEventArgs session.StateChanged += OnSessionStateChanged; - object content = DevAssistQuickInfoSource.BuildQuickInfoContentForLine(vulnerabilities); + object content = CxAssistQuickInfoSource.BuildQuickInfoContentForLine(vulnerabilities); if (content == null) { lock (_sessionLock) { - _sessionsWithDevAssistContent.Remove(session); + _sessionsWithCxAssistContent.Remove(session); } session.StateChanged -= OnSessionStateChanged; return null; diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistAsyncQuickInfoSourceProvider.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistAsyncQuickInfoSourceProvider.cs similarity index 61% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistAsyncQuickInfoSourceProvider.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistAsyncQuickInfoSourceProvider.cs index 023d3e00..7555d2d0 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistAsyncQuickInfoSourceProvider.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistAsyncQuickInfoSourceProvider.cs @@ -3,18 +3,18 @@ using Microsoft.VisualStudio.Utilities; using System.ComponentModel.Composition; -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core.Markers { [Export(typeof(IAsyncQuickInfoSourceProvider))] - [Name("DevAssist Async QuickInfo Source")] + [Name("CxAssist Async QuickInfo Source")] [Order(Before = "Default Quick Info Presenter")] [ContentType("code")] [ContentType("text")] - internal class DevAssistAsyncQuickInfoSourceProvider : IAsyncQuickInfoSourceProvider + internal class CxAssistAsyncQuickInfoSourceProvider : IAsyncQuickInfoSourceProvider { public IAsyncQuickInfoSource TryCreateQuickInfoSource(ITextBuffer textBuffer) { - return new DevAssistAsyncQuickInfoSource(textBuffer); + return new CxAssistAsyncQuickInfoSource(textBuffer); } } } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistCompilerErrorsForLine.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistCompilerErrorsForLine.cs similarity index 90% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistCompilerErrorsForLine.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistCompilerErrorsForLine.cs index 7684e534..1f5cb17f 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistCompilerErrorsForLine.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistCompilerErrorsForLine.cs @@ -5,13 +5,13 @@ using EnvDTE80; using Microsoft.VisualStudio.Shell; -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core.Markers { /// /// Gets compiler / VS Error List messages for a given file and line so the custom popup can show - /// "Also on this line (Compiler / VS):" combined with DevAssist findings. + /// "Also on this line (Compiler / VS):" combined with CxAssist findings. /// - internal static class DevAssistCompilerErrorsForLine + internal static class CxAssistCompilerErrorsForLine { /// /// Returns Error List (compiler/VS) messages for the given file and line. @@ -66,7 +66,7 @@ public static IReadOnlyList GetErrorsForLine(string filePath, int line1B } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"DevAssist: GetErrorsForLine item {i}: {ex.Message}"); + System.Diagnostics.Debug.WriteLine($"CxAssist: GetErrorsForLine item {i}: {ex.Message}"); } } @@ -74,7 +74,7 @@ public static IReadOnlyList GetErrorsForLine(string filePath, int line1B } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"DevAssist: GetErrorsForLine failed: {ex.Message}"); + System.Diagnostics.Debug.WriteLine($"CxAssist: GetErrorsForLine failed: {ex.Message}"); return Array.Empty(); } } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistErrorTagger.cs similarity index 74% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistErrorTagger.cs index f1648d0e..6fa1d81d 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTagger.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistErrorTagger.cs @@ -1,25 +1,25 @@ -using ast_visual_studio_extension.CxExtension.DevAssist.Core; -using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; +using ast_visual_studio_extension.CxExtension.CxAssist.Core; +using ast_visual_studio_extension.CxExtension.CxAssist.Core.Models; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Tagging; using System; using System.Collections.Generic; using System.Linq; -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core.Markers { /// - /// Tagger that provides IErrorTag for DevAssist vulnerabilities. + /// Tagger that provides IErrorTag for CxAssist vulnerabilities. /// Uses VS built-in ErrorTag only; no custom tag. VS draws squiggles and shows tooltip. /// - internal class DevAssistErrorTagger : ITagger + internal class CxAssistErrorTagger : ITagger { private readonly ITextBuffer _buffer; private readonly Dictionary> _vulnerabilitiesByLine; public event EventHandler TagsChanged; - public DevAssistErrorTagger(ITextBuffer buffer) + public CxAssistErrorTagger(ITextBuffer buffer) { _buffer = buffer; _vulnerabilitiesByLine = new Dictionary>(); @@ -28,11 +28,11 @@ public DevAssistErrorTagger(ITextBuffer buffer) public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) { var result = new List>(); - System.Diagnostics.Debug.WriteLine($"DevAssist Markers: GetTags called - spans count: {spans?.Count ?? 0}, vulnerabilities count: {_vulnerabilitiesByLine.Count}"); + System.Diagnostics.Debug.WriteLine($"CxAssist Markers: GetTags called - spans count: {spans?.Count ?? 0}, vulnerabilities count: {_vulnerabilitiesByLine.Count}"); if (spans == null || spans.Count == 0 || _vulnerabilitiesByLine.Count == 0) { - System.Diagnostics.Debug.WriteLine($"DevAssist Markers: GetTags returning early - no spans or vulnerabilities"); + System.Diagnostics.Debug.WriteLine($"CxAssist Markers: GetTags returning early - no spans or vulnerabilities"); return result; } @@ -43,7 +43,7 @@ public IEnumerable> GetTags(NormalizedSnapshotSpanCollection } catch (Exception ex) { - DevAssistErrorHandler.LogAndSwallow(ex, "ErrorTagger.GetTags (snapshot)"); + CxAssistErrorHandler.LogAndSwallow(ex, "ErrorTagger.GetTags (snapshot)"); } if (snapshot == null) return result; @@ -64,7 +64,7 @@ public IEnumerable> GetTags(NormalizedSnapshotSpanCollection { if (!ShouldShowUnderline(vulnerability.Severity)) { - System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Skipping underline for {vulnerability.Severity} on line {lineNumber}"); + System.Diagnostics.Debug.WriteLine($"CxAssist Markers: Skipping underline for {vulnerability.Severity} on line {lineNumber}"); continue; } @@ -75,7 +75,7 @@ public IEnumerable> GetTags(NormalizedSnapshotSpanCollection IErrorTag tag = new ErrorTag("Error", tooltipText); tagCount++; - System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Creating error tag #{tagCount} for line {lineNumber}, severity: {vulnerability.Severity}"); + System.Diagnostics.Debug.WriteLine($"CxAssist Markers: Creating error tag #{tagCount} for line {lineNumber}, severity: {vulnerability.Severity}"); result.Add(new TagSpan(lineSpan, tag)); } } @@ -83,11 +83,11 @@ public IEnumerable> GetTags(NormalizedSnapshotSpanCollection } catch (Exception ex) { - DevAssistErrorHandler.LogAndSwallow(ex, "ErrorTagger.GetTags (span)"); + CxAssistErrorHandler.LogAndSwallow(ex, "ErrorTagger.GetTags (span)"); } } - System.Diagnostics.Debug.WriteLine($"DevAssist Markers: GetTags completed - returned {tagCount} error tags"); + System.Diagnostics.Debug.WriteLine($"CxAssist Markers: GetTags completed - returned {tagCount} error tags"); return result; } @@ -133,12 +133,12 @@ private static string BuildTooltipText(Vulnerability vulnerability) /// public void UpdateVulnerabilities(List vulnerabilities) { - DevAssistErrorHandler.TryRun(() => UpdateVulnerabilitiesCore(vulnerabilities), "ErrorTagger.UpdateVulnerabilities"); + CxAssistErrorHandler.TryRun(() => UpdateVulnerabilitiesCore(vulnerabilities), "ErrorTagger.UpdateVulnerabilities"); } private void UpdateVulnerabilitiesCore(List vulnerabilities) { - System.Diagnostics.Debug.WriteLine($"DevAssist Markers: UpdateVulnerabilities called with {vulnerabilities?.Count ?? 0} vulnerabilities"); + System.Diagnostics.Debug.WriteLine($"CxAssist Markers: UpdateVulnerabilities called with {vulnerabilities?.Count ?? 0} vulnerabilities"); _vulnerabilitiesByLine.Clear(); @@ -154,18 +154,18 @@ private void UpdateVulnerabilitiesCore(List vulnerabilities) } _vulnerabilitiesByLine[lineNumber].Add(vulnerability); - System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Added vulnerability {vulnerability.Id} to line {lineNumber}"); + System.Diagnostics.Debug.WriteLine($"CxAssist Markers: Added vulnerability {vulnerability.Id} to line {lineNumber}"); } } - System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Vulnerabilities stored in {_vulnerabilitiesByLine.Count} lines"); + System.Diagnostics.Debug.WriteLine($"CxAssist Markers: Vulnerabilities stored in {_vulnerabilitiesByLine.Count} lines"); var snapshot = _buffer.CurrentSnapshot; var entireSpan = new SnapshotSpan(snapshot, 0, snapshot.Length); - System.Diagnostics.Debug.WriteLine($"DevAssist Markers: Raising TagsChanged event for span: {entireSpan.Start} to {entireSpan.End}"); + System.Diagnostics.Debug.WriteLine($"CxAssist Markers: Raising TagsChanged event for span: {entireSpan.Start} to {entireSpan.End}"); TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(entireSpan)); - System.Diagnostics.Debug.WriteLine($"DevAssist Markers: TagsChanged event raised"); + System.Diagnostics.Debug.WriteLine($"CxAssist Markers: TagsChanged event raised"); } /// @@ -174,7 +174,7 @@ private void UpdateVulnerabilitiesCore(List vulnerabilities) /// public void ClearVulnerabilities() { - System.Diagnostics.Debug.WriteLine("DevAssist Markers: ClearVulnerabilities called"); + System.Diagnostics.Debug.WriteLine("CxAssist Markers: ClearVulnerabilities called"); UpdateVulnerabilities(null); } @@ -183,7 +183,7 @@ public void ClearVulnerabilities() /// public IReadOnlyList GetVulnerabilitiesForLine(int zeroBasedLineNumber) { - return DevAssistErrorHandler.TryGet( + return CxAssistErrorHandler.TryGet( () => _vulnerabilitiesByLine.TryGetValue(zeroBasedLineNumber, out var list) ? list : (IReadOnlyList)Array.Empty(), "ErrorTagger.GetVulnerabilitiesForLine", Array.Empty()); diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTaggerProvider.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistErrorTaggerProvider.cs similarity index 67% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTaggerProvider.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistErrorTaggerProvider.cs index fca8bcff..5c8d386b 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistErrorTaggerProvider.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistErrorTaggerProvider.cs @@ -5,10 +5,10 @@ using System.Collections.Generic; using System.ComponentModel.Composition; -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core.Markers { /// - /// MEF provider for DevAssist error tagger + /// MEF provider for CxAssist error tagger /// Based on JetBrains EditorFactoryListener pattern adapted for Visual Studio /// Creates and manages error tagger instances per buffer (not per view). /// Exports IErrorTag so VS built-in error layer draws squiggles using IErrorType (CompilerError / syntax error colour). @@ -19,24 +19,24 @@ namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers [TagType(typeof(IErrorTag))] [TextViewRole(PredefinedTextViewRoles.Document)] [TextViewRole(PredefinedTextViewRoles.Editable)] - internal class DevAssistErrorTaggerProvider : ITaggerProvider + internal class CxAssistErrorTaggerProvider : ITaggerProvider { // Static instance for external access - private static DevAssistErrorTaggerProvider _instance; + private static CxAssistErrorTaggerProvider _instance; // Cache taggers per buffer to ensure single instance per buffer - private readonly Dictionary _taggers = - new Dictionary(); + private readonly Dictionary _taggers = + new Dictionary(); - public DevAssistErrorTaggerProvider() + public CxAssistErrorTaggerProvider() { - System.Diagnostics.Debug.WriteLine("DevAssist Markers: DevAssistErrorTaggerProvider constructor called - MEF is loading error tagger provider"); + System.Diagnostics.Debug.WriteLine("CxAssist Markers: CxAssistErrorTaggerProvider constructor called - MEF is loading error tagger provider"); _instance = this; } public ITagger CreateTagger(ITextBuffer buffer) where T : ITag { - System.Diagnostics.Debug.WriteLine($"DevAssist Markers: CreateTagger called - buffer: {buffer != null}"); + System.Diagnostics.Debug.WriteLine($"CxAssist Markers: CreateTagger called - buffer: {buffer != null}"); if (buffer == null) return null; @@ -46,12 +46,12 @@ public ITagger CreateTagger(ITextBuffer buffer) where T : ITag { if (_taggers.TryGetValue(buffer, out var existingTagger)) { - System.Diagnostics.Debug.WriteLine("DevAssist Markers: Returning existing error tagger from cache"); + System.Diagnostics.Debug.WriteLine("CxAssist Markers: Returning existing error tagger from cache"); return existingTagger as ITagger; } - System.Diagnostics.Debug.WriteLine("DevAssist Markers: Creating new error tagger"); - var tagger = new DevAssistErrorTagger(buffer); + System.Diagnostics.Debug.WriteLine("CxAssist Markers: Creating new error tagger"); + var tagger = new CxAssistErrorTagger(buffer); _taggers[buffer] = tagger; // Clean up when buffer is disposed @@ -73,7 +73,7 @@ public ITagger CreateTagger(ITextBuffer buffer) where T : ITag /// Used by external components to update vulnerability markers /// Similar to JetBrains MarkupModel access pattern /// - public static DevAssistErrorTagger GetTaggerForBuffer(ITextBuffer buffer) + public static CxAssistErrorTagger GetTaggerForBuffer(ITextBuffer buffer) { if (_instance == null || buffer == null) return null; @@ -89,14 +89,14 @@ public static DevAssistErrorTagger GetTaggerForBuffer(ITextBuffer buffer) /// Gets all active error taggers /// Useful for debugging and diagnostics /// - public static IEnumerable GetAllTaggers() + public static IEnumerable GetAllTaggers() { if (_instance == null) - return new List(); + return new List(); lock (_instance._taggers) { - return new List(_instance._taggers.Values); + return new List(_instance._taggers.Values); } } } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickFixActions.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistQuickFixActions.cs similarity index 90% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickFixActions.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistQuickFixActions.cs index 5c464463..2468902e 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickFixActions.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistQuickFixActions.cs @@ -1,4 +1,4 @@ -using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; +using ast_visual_studio_extension.CxExtension.CxAssist.Core.Models; using Microsoft.VisualStudio.Imaging.Interop; using Microsoft.VisualStudio.Language.Intellisense; using System; @@ -7,7 +7,7 @@ using System.Threading.Tasks; using System.Windows; -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core.Markers { /// /// Quick Fix action: "Fix with Checkmarx One Assist" (same behavior as hover popup link). @@ -18,7 +18,7 @@ internal sealed class FixWithCxOneAssistSuggestedAction : ISuggestedAction public FixWithCxOneAssistSuggestedAction(Vulnerability vulnerability) { - _vulnerability = vulnerability ?? throw new ArgumentNullException(nameof(vulnerability)) + _vulnerability = vulnerability ?? throw new ArgumentNullException(nameof(vulnerability)); } @@ -58,13 +58,13 @@ public void Invoke(CancellationToken cancellationToken) { MessageBox.Show( $"Fix with Checkmarx One Assist\nVulnerability: {v.Title}\nID: {v.Id}", - "DevAssist", + "CxAssist", MessageBoxButton.OK, MessageBoxImage.Information); } catch (Exception ex) { - DevAssistErrorHandler.LogAndSwallow(ex, "FixWithCxOneAssistSuggestedAction.Invoke"); + CxAssistErrorHandler.LogAndSwallow(ex, "FixWithCxOneAssistSuggestedAction.Invoke"); } })); } @@ -130,7 +130,7 @@ public void Invoke(CancellationToken cancellationToken) } catch (Exception ex) { - DevAssistErrorHandler.LogAndSwallow(ex, "ViewDetailsSuggestedAction.Invoke"); + CxAssistErrorHandler.LogAndSwallow(ex, "ViewDetailsSuggestedAction.Invoke"); } })); } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoClassifications.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistQuickInfoClassifications.cs similarity index 52% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoClassifications.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistQuickInfoClassifications.cs index 35d2e0b0..84a647e2 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoClassifications.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistQuickInfoClassifications.cs @@ -3,32 +3,32 @@ using Microsoft.VisualStudio.Text.Classification; using Microsoft.VisualStudio.Utilities; -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core.Markers { /// /// Classification type names used in Quick Info content. Must match the names used in /// [ClassificationType(ClassificationTypeNames = ...)] on the format definitions. /// - internal static class DevAssistQuickInfoClassificationNames + internal static class CxAssistQuickInfoClassificationNames { - public const string Header = "DevAssist.QuickInfo.Header"; - public const string Link = "DevAssist.QuickInfo.Link"; + public const string Header = "CxAssist.QuickInfo.Header"; + public const string Link = "CxAssist.QuickInfo.Link"; } /// /// Format for "Checkmarx One Assist" and severity (e.g. "High") in Quick Info: bold + colored. - /// Applied when ClassifiedTextRun uses DevAssistQuickInfoClassificationNames.Header. + /// Applied when ClassifiedTextRun uses CxAssistQuickInfoClassificationNames.Header. /// [Export(typeof(EditorFormatDefinition))] - [ClassificationType(ClassificationTypeNames = DevAssistQuickInfoClassificationNames.Header)] - [Name("DevAssist Quick Info Header")] + [ClassificationType(ClassificationTypeNames = CxAssistQuickInfoClassificationNames.Header)] + [Name("CxAssist Quick Info Header")] [UserVisible(true)] [Order(After = Priority.High)] - internal sealed class DevAssistQuickInfoHeaderFormat : ClassificationFormatDefinition + internal sealed class CxAssistQuickInfoHeaderFormat : ClassificationFormatDefinition { - public DevAssistQuickInfoHeaderFormat() + public CxAssistQuickInfoHeaderFormat() { - DisplayName = "DevAssist Quick Info Header"; + DisplayName = "CxAssist Quick Info Header"; IsBold = true; ForegroundColor = Color.FromRgb(0x56, 0x9C, 0xD6); // light blue, readable on dark theme } @@ -36,18 +36,18 @@ public DevAssistQuickInfoHeaderFormat() /// /// Format for action links in Quick Info: link-like color + underline. - /// Applied when ClassifiedTextRun uses DevAssistQuickInfoClassificationNames.Link. + /// Applied when ClassifiedTextRun uses CxAssistQuickInfoClassificationNames.Link. /// [Export(typeof(EditorFormatDefinition))] - [ClassificationType(ClassificationTypeNames = DevAssistQuickInfoClassificationNames.Link)] - [Name("DevAssist Quick Info Link")] + [ClassificationType(ClassificationTypeNames = CxAssistQuickInfoClassificationNames.Link)] + [Name("CxAssist Quick Info Link")] [UserVisible(true)] [Order(After = Priority.High)] - internal sealed class DevAssistQuickInfoLinkFormat : ClassificationFormatDefinition + internal sealed class CxAssistQuickInfoLinkFormat : ClassificationFormatDefinition { - public DevAssistQuickInfoLinkFormat() + public CxAssistQuickInfoLinkFormat() { - DisplayName = "DevAssist Quick Info Link"; + DisplayName = "CxAssist Quick Info Link"; IsBold = false; ForegroundColor = Color.FromRgb(0x37, 0x94, 0xFF); // link blue (underline comes from ClassifiedTextRunStyle.Underline) } diff --git a/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistQuickInfoController.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistQuickInfoController.cs new file mode 100644 index 00000000..c0ad0043 --- /dev/null +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistQuickInfoController.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using ast_visual_studio_extension.CxExtension.CxAssist.Core; + +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core.Markers +{ + /// + /// Triggers the default Quick Info popup on mouse hover over lines with CxAssist vulnerabilities. + /// Content is provided by CxAssistAsyncQuickInfoSource (no custom popup). + /// + internal class CxAssistQuickInfoController : IIntellisenseController + { + private readonly ITextView _textView; + private readonly IList _subjectBuffers; + private readonly CxAssistQuickInfoControllerProvider _provider; + + internal CxAssistQuickInfoController( + ITextView textView, + IList subjectBuffers, + CxAssistQuickInfoControllerProvider provider) + { + _textView = textView; + _subjectBuffers = subjectBuffers; + _provider = provider; + _textView.MouseHover += OnTextViewMouseHover; + } + + private void OnTextViewMouseHover(object sender, MouseHoverEventArgs e) + { + try + { + var point = _textView.BufferGraph.MapDownToFirstMatch( + new SnapshotPoint(_textView.TextSnapshot, e.Position), + PointTrackingMode.Positive, + snapshot => _subjectBuffers.Contains(snapshot.TextBuffer), + PositionAffinity.Predecessor); + + if (!point.HasValue) + return; + + var buffer = point.Value.Snapshot.TextBuffer; + int lineNumber = point.Value.Snapshot.GetLineNumberFromPosition(point.Value.Position); + + var tagger = CxAssistErrorTaggerProvider.GetTaggerForBuffer(buffer); + if (tagger == null) + return; + + var vulnerabilities = tagger.GetVulnerabilitiesForLine(lineNumber); + if (vulnerabilities == null || vulnerabilities.Count == 0) + return; + + if (!_provider.AsyncQuickInfoBroker.IsQuickInfoActive(_textView)) + { + var triggerPoint = point.Value.Snapshot.CreateTrackingPoint(point.Value.Position, PointTrackingMode.Positive); + _ = _provider.AsyncQuickInfoBroker.TriggerQuickInfoAsync(_textView, triggerPoint, QuickInfoSessionOptions.None, CancellationToken.None); + } + } + catch (Exception ex) + { + CxAssistErrorHandler.LogAndSwallow(ex, "QuickInfoController.OnTextViewMouseHover"); + } + } + + public void Detach(ITextView textView) + { + if (_textView == textView) + _textView.MouseHover -= OnTextViewMouseHover; + } + + public void ConnectSubjectBuffer(ITextBuffer subjectBuffer) { } + + public void DisconnectSubjectBuffer(ITextBuffer subjectBuffer) { } + } +} diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoControllerProvider.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistQuickInfoControllerProvider.cs similarity index 67% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoControllerProvider.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistQuickInfoControllerProvider.cs index 5c5ccd81..6004d86a 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoControllerProvider.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistQuickInfoControllerProvider.cs @@ -5,20 +5,20 @@ using System.Collections.Generic; using System.ComponentModel.Composition; -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core.Markers { [Export(typeof(IIntellisenseControllerProvider))] - [Name("DevAssist QuickInfo Controller")] + [Name("CxAssist QuickInfo Controller")] [ContentType("code")] [ContentType("text")] - internal class DevAssistQuickInfoControllerProvider : IIntellisenseControllerProvider + internal class CxAssistQuickInfoControllerProvider : IIntellisenseControllerProvider { [Import] internal IAsyncQuickInfoBroker AsyncQuickInfoBroker { get; set; } public IIntellisenseController TryCreateIntellisenseController(ITextView textView, IList subjectBuffers) { - return new DevAssistQuickInfoController(textView, subjectBuffers, this); + return new CxAssistQuickInfoController(textView, subjectBuffers, this); } } } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSource.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistQuickInfoSource.cs similarity index 93% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSource.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistQuickInfoSource.cs index 6766502e..ad05290d 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSource.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistQuickInfoSource.cs @@ -1,4 +1,4 @@ -using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; +using ast_visual_studio_extension.CxExtension.CxAssist.Core.Models; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Language.StandardClassification; using Microsoft.VisualStudio.PlatformUI; @@ -18,21 +18,21 @@ using System.Windows.Media.Imaging; using System.Windows.Threading; -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core.Markers { /// /// Quick Info source using official content types (ContainerElement, ClassifiedTextElement, ClassifiedTextRun) /// so the default Quick Info presenter shows description, links, and optional image. /// - internal class DevAssistQuickInfoSource : IQuickInfoSource + internal class CxAssistQuickInfoSource : IQuickInfoSource { internal const bool UseRichHover = true; - private readonly DevAssistQuickInfoSourceProvider _provider; + private readonly CxAssistQuickInfoSourceProvider _provider; private readonly ITextBuffer _buffer; private bool _disposed; - public DevAssistQuickInfoSource(DevAssistQuickInfoSourceProvider provider, ITextBuffer buffer) + public CxAssistQuickInfoSource(CxAssistQuickInfoSourceProvider provider, ITextBuffer buffer) { _provider = provider; _buffer = buffer; @@ -70,7 +70,7 @@ public void AugmentQuickInfoSession(IQuickInfoSession session, IList qiC var snapshot = triggerPoint.Value.Snapshot; int lineNumber = snapshot.GetLineNumberFromPosition(triggerPoint.Value.Position); - var tagger = DevAssistErrorTaggerProvider.GetTaggerForBuffer(_buffer); + var tagger = CxAssistErrorTaggerProvider.GetTaggerForBuffer(_buffer); if (tagger == null) return; @@ -89,7 +89,7 @@ public void AugmentQuickInfoSession(IQuickInfoSession session, IList qiC /// /// Builds Quick Info content for all vulnerabilities on the line (e.g. line 13 with 2 findings shows both). - /// Shared for use by DevAssistAsyncQuickInfoSource (IAsyncQuickInfoSource). + /// Shared for use by CxAssistAsyncQuickInfoSource (IAsyncQuickInfoSource). /// internal static object BuildQuickInfoContentForLine(IReadOnlyList vulnerabilities) { @@ -262,7 +262,7 @@ internal static void RunViewDetails(Vulnerability v) } catch (Exception ex) { - Debug.WriteLine($"DevAssist QuickInfo View Details: {ex.Message}"); + Debug.WriteLine($"CxAssist QuickInfo View Details: {ex.Message}"); MessageBox.Show($"Could not open link: {url}", "Checkmarx One Assist"); } }); @@ -287,7 +287,7 @@ internal static void RunOnUiThread(Action action) } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"DevAssist QuickInfo link: {ex.Message}"); + System.Diagnostics.Debug.WriteLine($"CxAssist QuickInfo link: {ex.Message}"); } } @@ -316,7 +316,7 @@ private static System.Windows.UIElement CreateDescriptionBlock(string descriptio } catch (Exception ex) { - Debug.WriteLine($"DevAssist QuickInfo CreateDescriptionBlock: {ex.Message}"); + Debug.WriteLine($"CxAssist QuickInfo CreateDescriptionBlock: {ex.Message}"); return null; } } @@ -359,7 +359,7 @@ void AddLink(string text, Action clickAction) } catch (Exception ex) { - Debug.WriteLine($"DevAssist QuickInfo CreateActionLinksRow: {ex.Message}"); + Debug.WriteLine($"CxAssist QuickInfo CreateActionLinksRow: {ex.Message}"); return null; } } @@ -384,7 +384,7 @@ private static System.Windows.UIElement CreateHorizontalSeparator() } catch (Exception ex) { - Debug.WriteLine($"DevAssist QuickInfo CreateHorizontalSeparator: {ex.Message}"); + Debug.WriteLine($"CxAssist QuickInfo CreateHorizontalSeparator: {ex.Message}"); return null; } } @@ -443,7 +443,7 @@ private static System.Windows.UIElement CreateHeaderRow() } catch (Exception ex) { - Debug.WriteLine($"DevAssist QuickInfo CreateHeaderRow: {ex.Message}"); + Debug.WriteLine($"CxAssist QuickInfo CreateHeaderRow: {ex.Message}"); return null; } } @@ -496,7 +496,7 @@ private static System.Windows.UIElement CreateSeverityTitleRow(SeverityLevel sev } catch (Exception ex) { - Debug.WriteLine($"DevAssist QuickInfo CreateSeverityTitleRow: {ex.Message}"); + Debug.WriteLine($"CxAssist QuickInfo CreateSeverityTitleRow: {ex.Message}"); return null; } } @@ -504,7 +504,7 @@ private static System.Windows.UIElement CreateSeverityTitleRow(SeverityLevel sev /// /// Creates a WPF Image for the Checkmarx One Assist badge only (theme-based). Used when not using header row. /// - private static System.Windows.UIElement CreateDevAssistBadgeImage() + private static System.Windows.UIElement CreateCxAssistBadgeImage() { string theme = GetCurrentTheme(); string fileName = "cxone_assist.png"; @@ -531,7 +531,7 @@ private static System.Windows.UIElement CreateDevAssistBadgeImage() } catch (Exception ex) { - Debug.WriteLine($"DevAssist QuickInfo badge: create Image on main thread: {ex.Message}"); + Debug.WriteLine($"CxAssist QuickInfo badge: create Image on main thread: {ex.Message}"); return null; } } @@ -568,7 +568,7 @@ private static System.Windows.UIElement CreateSeverityImage(SeverityLevel severi } catch (Exception ex) { - Debug.WriteLine($"DevAssist QuickInfo severity icon: {ex.Message}"); + Debug.WriteLine($"CxAssist QuickInfo severity icon: {ex.Message}"); return null; } } @@ -588,11 +588,11 @@ private static string GetSeverityIconFileName(SeverityLevel severity) } /// - /// Loads a PNG from DevAssist Icons by theme (Dark/Light). Used for badge and severity icons. + /// Loads a PNG from CxAssist Icons by theme (Dark/Light). Used for badge and severity icons. /// private static BitmapImage LoadIconFromAssembly(string theme, string fileName) { - var packPath = $"pack://application:,,,/ast-visual-studio-extension;component/CxExtension/Resources/DevAssist/Icons/{theme}/{fileName}"; + var packPath = $"pack://application:,,,/ast-visual-studio-extension;component/CxExtension/Resources/CxAssist/Icons/{theme}/{fileName}"; try { var streamInfo = System.Windows.Application.GetResourceStream(new Uri(packPath, UriKind.Absolute)); @@ -614,15 +614,15 @@ private static BitmapImage LoadIconFromAssembly(string theme, string fileName) } catch (Exception ex) { - Debug.WriteLine($"DevAssist QuickInfo icon: pack load failed for {fileName}: {ex.Message}"); + Debug.WriteLine($"CxAssist QuickInfo icon: pack load failed for {fileName}: {ex.Message}"); } try { var asm = Assembly.GetExecutingAssembly(); var resourceName = asm.GetManifestResourceNames() - .FirstOrDefault(n => n.Replace('\\', '/').EndsWith($"DevAssist/Icons/{theme}/{fileName}", StringComparison.OrdinalIgnoreCase) - || n.Replace('\\', '.').EndsWith($"DevAssist.Icons.{theme}.{fileName}", StringComparison.OrdinalIgnoreCase)); + .FirstOrDefault(n => n.Replace('\\', '/').EndsWith($"CxAssist/Icons/{theme}/{fileName}", StringComparison.OrdinalIgnoreCase) + || n.Replace('\\', '.').EndsWith($"CxAssist.Icons.{theme}.{fileName}", StringComparison.OrdinalIgnoreCase)); if (resourceName != null) { using (var stream = asm.GetManifestResourceStream(resourceName)) @@ -642,7 +642,7 @@ private static BitmapImage LoadIconFromAssembly(string theme, string fileName) } catch (Exception ex) { - Debug.WriteLine($"DevAssist QuickInfo icon: manifest load failed for {fileName}: {ex.Message}"); + Debug.WriteLine($"CxAssist QuickInfo icon: manifest load failed for {fileName}: {ex.Message}"); } return null; diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSourceProvider.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistQuickInfoSourceProvider.cs similarity index 56% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSourceProvider.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistQuickInfoSourceProvider.cs index 694e5091..47e4c59b 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistQuickInfoSourceProvider.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistQuickInfoSourceProvider.cs @@ -3,19 +3,19 @@ using Microsoft.VisualStudio.Utilities; using System.ComponentModel.Composition; -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core.Markers { // Legacy sync provider disabled: built-in presenter ignores per-ClassifiedTextRun navigation callbacks. - // Quick Info is now provided by DevAssistAsyncQuickInfoSourceProvider (IAsyncQuickInfoSource). + // Quick Info is now provided by CxAssistAsyncQuickInfoSourceProvider (IAsyncQuickInfoSource). // [Export(typeof(IQuickInfoSourceProvider))] - [Name("DevAssist QuickInfo Source (legacy, disabled)")] + [Name("CxAssist QuickInfo Source (legacy, disabled)")] [ContentType("code")] [ContentType("text")] - internal class DevAssistQuickInfoSourceProvider : IQuickInfoSourceProvider + internal class CxAssistQuickInfoSourceProvider : IQuickInfoSourceProvider { public IQuickInfoSource TryCreateQuickInfoSource(ITextBuffer textBuffer) { - return new DevAssistQuickInfoSource(this, textBuffer); + return new CxAssistQuickInfoSource(this, textBuffer); } } } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSuggestedActionsSource.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistSuggestedActionsSource.cs similarity index 75% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSuggestedActionsSource.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistSuggestedActionsSource.cs index 58f212d6..c7814978 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSuggestedActionsSource.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistSuggestedActionsSource.cs @@ -1,4 +1,4 @@ -using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; +using ast_visual_studio_extension.CxExtension.CxAssist.Core.Models; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; @@ -8,17 +8,17 @@ using System.Threading; using System.Threading.Tasks; -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core.Markers { /// - /// Suggested actions source for DevAssist: shows Quick Fix (light bulb) when the caret is on a line that has at least one vulnerability. + /// Suggested actions source for CxAssist: shows Quick Fix (light bulb) when the caret is on a line that has at least one vulnerability. /// - internal class DevAssistSuggestedActionsSource : ISuggestedActionsSource + internal class CxAssistSuggestedActionsSource : ISuggestedActionsSource { private readonly ITextView _textView; private readonly ITextBuffer _textBuffer; - public DevAssistSuggestedActionsSource(ITextView textView, ITextBuffer textBuffer) + public CxAssistSuggestedActionsSource(ITextView textView, ITextBuffer textBuffer) { _textView = textView ?? throw new ArgumentNullException(nameof(textView)); _textBuffer = textBuffer ?? throw new ArgumentNullException(nameof(textBuffer)); @@ -43,14 +43,14 @@ public Task HasSuggestedActionsAsync(ISuggestedActionCategorySet requested try { int lineNumber = range.Snapshot.GetLineNumberFromPosition(range.Start); - var tagger = DevAssistErrorTaggerProvider.GetTaggerForBuffer(_textBuffer); + var tagger = CxAssistErrorTaggerProvider.GetTaggerForBuffer(_textBuffer); if (tagger == null) return false; var list = tagger.GetVulnerabilitiesForLine(lineNumber); return list != null && list.Count > 0; } catch (Exception ex) { - DevAssistErrorHandler.LogAndSwallow(ex, "DevAssistSuggestedActionsSource.HasSuggestedActionsAsync"); + CxAssistErrorHandler.LogAndSwallow(ex, "CxAssistSuggestedActionsSource.HasSuggestedActionsAsync"); return false; } }, cancellationToken); @@ -61,7 +61,7 @@ public IEnumerable GetSuggestedActions(ISuggestedActionCateg try { int lineNumber = range.Snapshot.GetLineNumberFromPosition(range.Start); - var tagger = DevAssistErrorTaggerProvider.GetTaggerForBuffer(_textBuffer); + var tagger = CxAssistErrorTaggerProvider.GetTaggerForBuffer(_textBuffer); if (tagger == null) return Enumerable.Empty(); var list = tagger.GetVulnerabilitiesForLine(lineNumber); if (list == null || list.Count == 0) return Enumerable.Empty(); @@ -77,7 +77,7 @@ public IEnumerable GetSuggestedActions(ISuggestedActionCateg } catch (Exception ex) { - DevAssistErrorHandler.LogAndSwallow(ex, "DevAssistSuggestedActionsSource.GetSuggestedActions"); + CxAssistErrorHandler.LogAndSwallow(ex, "CxAssistSuggestedActionsSource.GetSuggestedActions"); return Enumerable.Empty(); } } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSuggestedActionsSourceProvider.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistSuggestedActionsSourceProvider.cs similarity index 65% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSuggestedActionsSourceProvider.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistSuggestedActionsSourceProvider.cs index d5136b58..dde953b3 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistSuggestedActionsSourceProvider.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistSuggestedActionsSourceProvider.cs @@ -4,23 +4,23 @@ using Microsoft.VisualStudio.Utilities; using System.ComponentModel.Composition; -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core.Markers { /// - /// Provides Quick Fix (light bulb) suggested actions for DevAssist findings: + /// Provides Quick Fix (light bulb) suggested actions for CxAssist findings: /// "Fix with Checkmarx One Assist" and "View details" when the caret is on a line with a vulnerability. /// [Export(typeof(ISuggestedActionsSourceProvider))] - [Name("DevAssist Quick Fix")] + [Name("CxAssist Quick Fix")] [ContentType("code")] [ContentType("text")] - internal class DevAssistSuggestedActionsSourceProvider : ISuggestedActionsSourceProvider + internal class CxAssistSuggestedActionsSourceProvider : ISuggestedActionsSourceProvider { public ISuggestedActionsSource CreateSuggestedActionsSource(ITextView textView, ITextBuffer textBuffer) { if (textBuffer == null || textView == null) return null; - return new DevAssistSuggestedActionsSource(textView, textBuffer); + return new CxAssistSuggestedActionsSource(textView, textBuffer); } } } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistTestHelper.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistTestHelper.cs similarity index 96% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistTestHelper.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistTestHelper.cs index 6af74e04..01556f4c 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistTestHelper.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Markers/CxAssistTestHelper.cs @@ -1,13 +1,13 @@ -using ast_visual_studio_extension.CxExtension.DevAssist.Core.Models; +using ast_visual_studio_extension.CxExtension.CxAssist.Core.Models; using System.Collections.Generic; -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core.Markers { /// - /// Helper class for testing DevAssist hover popups with mock data + /// Helper class for testing CxAssist hover popups with mock data /// Use this to generate sample vulnerabilities for each scanner type /// - public static class DevAssistTestHelper + public static class CxAssistTestHelper { /// /// Creates a sample OSS vulnerability with package information diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/ScannerType.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Models/ScannerType.cs similarity index 65% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/ScannerType.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/Models/ScannerType.cs index 0c6006dc..ba8f4333 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/ScannerType.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Models/ScannerType.cs @@ -1,7 +1,7 @@ -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Models +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core.Models { /// - /// Scanner types for DevAssist + /// Scanner types for CxAssist /// Based on JetBrains ScanEngine enum /// public enum ScannerType diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/SeverityLevel.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Models/SeverityLevel.cs similarity index 89% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/SeverityLevel.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/Models/SeverityLevel.cs index 085c14e6..ff138037 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/SeverityLevel.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Models/SeverityLevel.cs @@ -1,4 +1,4 @@ -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Models +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core.Models { /// /// Severity levels for vulnerabilities diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/Vulnerability.cs b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Models/Vulnerability.cs similarity index 93% rename from ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/Vulnerability.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/Core/Models/Vulnerability.cs index 7ad37563..f2ac8706 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Models/Vulnerability.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/Core/Models/Vulnerability.cs @@ -1,7 +1,7 @@ -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Models +namespace ast_visual_studio_extension.CxExtension.CxAssist.Core.Models { /// - /// Represents a vulnerability found by DevAssist scanners + /// Represents a vulnerability found by CxAssist scanners /// Based on JetBrains ScanIssue model with scanner-specific fields /// public class Vulnerability diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml b/ast-visual-studio-extension/CxExtension/CxAssist/UI/FindingsWindow/CxAssistFindingsControl.xaml similarity index 99% rename from ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml rename to ast-visual-studio-extension/CxExtension/CxAssist/UI/FindingsWindow/CxAssistFindingsControl.xaml index 3b3ceb29..697cfa75 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsControl.xaml +++ b/ast-visual-studio-extension/CxExtension/CxAssist/UI/FindingsWindow/CxAssistFindingsControl.xaml @@ -1,9 +1,9 @@ - - /// Interaction logic for DevAssistFindingsControl.xaml + /// Interaction logic for CxAssistFindingsControl.xaml /// - public partial class DevAssistFindingsControl : UserControl, INotifyPropertyChanged + public partial class CxAssistFindingsControl : UserControl, INotifyPropertyChanged { private ObservableCollection _fileNodes; private ObservableCollection _allFileNodes; // Store unfiltered data @@ -62,7 +62,7 @@ public bool IsLoading } } - public DevAssistFindingsControl() + public CxAssistFindingsControl() { InitializeComponent(); FileNodes = new ObservableCollection(); @@ -78,7 +78,7 @@ private void OnLoaded(object sender, RoutedEventArgs e) { LoadFilterIcons(); _onIssuesUpdated = OnIssuesUpdated; - DevAssistDisplayCoordinator.IssuesUpdated += _onIssuesUpdated; + CxAssistDisplayCoordinator.IssuesUpdated += _onIssuesUpdated; // Initial refresh from current data RefreshFromCoordinator(); } @@ -87,7 +87,7 @@ private void OnUnloaded(object sender, RoutedEventArgs e) { if (_onIssuesUpdated != null) { - DevAssistDisplayCoordinator.IssuesUpdated -= _onIssuesUpdated; + CxAssistDisplayCoordinator.IssuesUpdated -= _onIssuesUpdated; _onIssuesUpdated = null; } } @@ -102,9 +102,9 @@ private void OnIssuesUpdated(IReadOnlyDictionary private void RefreshFromCoordinator() { - var current = DevAssistDisplayCoordinator.GetCurrentFindings(); + var current = CxAssistDisplayCoordinator.GetCurrentFindings(); var fileNodes = current != null && current.Count > 0 - ? DevAssistMockData.BuildFileNodesFromVulnerabilities(current, LoadSeverityIconForTree, null) + ? CxAssistMockData.BuildFileNodesFromVulnerabilities(current, LoadSeverityIconForTree, null) : new ObservableCollection(); SetAllFileNodes(fileNodes); } @@ -152,7 +152,7 @@ private ImageSource LoadIcon(string themeFolder, string iconName) { try { - string iconPath = $"pack://application:,,,/ast-visual-studio-extension;component/CxExtension/Resources/DevAssist/Icons/{themeFolder}/{iconName}"; + string iconPath = $"pack://application:,,,/ast-visual-studio-extension;component/CxExtension/Resources/CxAssist/Icons/{themeFolder}/{iconName}"; var bitmap = new BitmapImage(); bitmap.BeginInit(); bitmap.UriSource = new Uri(iconPath, UriKind.Absolute); @@ -181,7 +181,7 @@ public static ImageSource GetFileTypeIcon(string fileName) // In future, can add specific icons for .go, .json, .xml, .cs, etc. try { - string iconPath = "pack://application:,,,/ast-visual-studio-extension;component/CxExtension/Resources/DevAssist/Icons/document.png"; + string iconPath = "pack://application:,,,/ast-visual-studio-extension;component/CxExtension/Resources/CxAssist/Icons/document.png"; var bitmap = new BitmapImage(); bitmap.BeginInit(); bitmap.UriSource = new Uri(iconPath, UriKind.Absolute); @@ -304,7 +304,7 @@ private void FixWithCxOneAssist_Click(object sender, RoutedEventArgs e) if (vulnerability != null) { MessageBox.Show($"Fix with CxOne Assist:\n{vulnerability.DisplayText}", - "Checkmarx DevAssist", MessageBoxButton.OK, MessageBoxImage.Information); + "Checkmarx CxAssist", MessageBoxButton.OK, MessageBoxImage.Information); // TODO: Implement actual fix logic } } @@ -315,7 +315,7 @@ private void ViewDetails_Click(object sender, RoutedEventArgs e) if (vulnerability != null) { MessageBox.Show($"View Details:\n{vulnerability.DisplayText}\n\nSeverity: {vulnerability.Severity}\nFile: {vulnerability.FilePath}\nLine: {vulnerability.Line}, Column: {vulnerability.Column}", - "Checkmarx DevAssist", MessageBoxButton.OK, MessageBoxImage.Information); + "Checkmarx CxAssist", MessageBoxButton.OK, MessageBoxImage.Information); // TODO: Implement actual details view } } @@ -326,11 +326,11 @@ private void Ignore_Click(object sender, RoutedEventArgs e) if (vulnerability != null) { var result = MessageBox.Show($"Ignore this vulnerability?\n{vulnerability.DisplayText}", - "Checkmarx DevAssist", MessageBoxButton.YesNo, MessageBoxImage.Question); + "Checkmarx CxAssist", MessageBoxButton.YesNo, MessageBoxImage.Question); if (result == MessageBoxResult.Yes) { // TODO: Implement ignore logic - MessageBox.Show("Vulnerability ignored.", "Checkmarx DevAssist", MessageBoxButton.OK, MessageBoxImage.Information); + MessageBox.Show("Vulnerability ignored.", "Checkmarx CxAssist", MessageBoxButton.OK, MessageBoxImage.Information); } } } @@ -341,11 +341,11 @@ private void IgnoreAll_Click(object sender, RoutedEventArgs e) if (vulnerability != null) { var result = MessageBox.Show($"Ignore all vulnerabilities of this type?\n{vulnerability.Description}", - "Checkmarx DevAssist", MessageBoxButton.YesNo, MessageBoxImage.Warning); + "Checkmarx CxAssist", MessageBoxButton.YesNo, MessageBoxImage.Warning); if (result == MessageBoxResult.Yes) { // TODO: Implement ignore all logic - MessageBox.Show("All vulnerabilities of this type ignored.", "Checkmarx DevAssist", MessageBoxButton.OK, MessageBoxImage.Information); + MessageBox.Show("All vulnerabilities of this type ignored.", "Checkmarx CxAssist", MessageBoxButton.OK, MessageBoxImage.Information); } } } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsWindow.cs b/ast-visual-studio-extension/CxExtension/CxAssist/UI/FindingsWindow/CxAssistFindingsWindow.cs similarity index 72% rename from ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsWindow.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/UI/FindingsWindow/CxAssistFindingsWindow.cs index b5b82a92..8822f339 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/DevAssistFindingsWindow.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/UI/FindingsWindow/CxAssistFindingsWindow.cs @@ -2,7 +2,7 @@ using System.Runtime.InteropServices; using Microsoft.VisualStudio.Shell; -namespace ast_visual_studio_extension.CxExtension.DevAssist.UI.FindingsWindow +namespace ast_visual_studio_extension.CxExtension.CxAssist.UI.FindingsWindow { /// /// This class implements the tool window exposed by this package and hosts a user control. @@ -16,25 +16,25 @@ namespace ast_visual_studio_extension.CxExtension.DevAssist.UI.FindingsWindow /// /// [Guid("8F3E8B6A-1234-4567-89AB-CDEF01234567")] - public class DevAssistFindingsWindow : ToolWindowPane + public class CxAssistFindingsWindow : ToolWindowPane { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public DevAssistFindingsWindow() : base(null) + public CxAssistFindingsWindow() : base(null) { this.Caption = "Checkmarx Findings"; // This is the user control hosted by the tool window; Note that, even if this class implements IDisposable, // we are not calling Dispose on this object. This is because ToolWindowPane calls Dispose on // the object returned by the Content property. - this.Content = new DevAssistFindingsControl(); + this.Content = new CxAssistFindingsControl(); } /// /// Get the control hosted in this tool window /// - public DevAssistFindingsControl FindingsControl => this.Content as DevAssistFindingsControl; + public CxAssistFindingsControl FindingsControl => this.Content as CxAssistFindingsControl; } } diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/FindingsTreeNode.cs b/ast-visual-studio-extension/CxExtension/CxAssist/UI/FindingsWindow/FindingsTreeNode.cs similarity index 98% rename from ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/FindingsTreeNode.cs rename to ast-visual-studio-extension/CxExtension/CxAssist/UI/FindingsWindow/FindingsTreeNode.cs index e9bf75fb..b28121d3 100644 --- a/ast-visual-studio-extension/CxExtension/DevAssist/UI/FindingsWindow/FindingsTreeNode.cs +++ b/ast-visual-studio-extension/CxExtension/CxAssist/UI/FindingsWindow/FindingsTreeNode.cs @@ -2,7 +2,7 @@ using System.ComponentModel; using System.Windows.Media; -namespace ast_visual_studio_extension.CxExtension.DevAssist.UI.FindingsWindow +namespace ast_visual_studio_extension.CxExtension.CxAssist.UI.FindingsWindow { /// /// Base class for tree nodes in the Findings window diff --git a/ast-visual-studio-extension/CxExtension/CxWindowControl.xaml b/ast-visual-studio-extension/CxExtension/CxWindowControl.xaml index 95824177..591cf145 100644 --- a/ast-visual-studio-extension/CxExtension/CxWindowControl.xaml +++ b/ast-visual-studio-extension/CxExtension/CxWindowControl.xaml @@ -1,4 +1,4 @@ -๏ปฟ - + @@ -865,8 +865,8 @@ - - + + @@ -876,7 +876,7 @@ - + @@ -893,7 +893,7 @@ - - /// Gets the DevAssist Findings Control from the DevAssist tab + /// Gets the CxAssist Findings Control from the CxAssist tab /// - public DevAssist.UI.FindingsWindow.DevAssistFindingsControl GetDevAssistFindingsControl() + public CxAssist.UI.FindingsWindow.CxAssistFindingsControl GetCxAssistFindingsControl() { - return DevAssistFindingsControl; + return CxAssistFindingsControl; } /// - /// Switches to the DevAssist Findings tab + /// Switches to the CxAssist Findings tab /// - public void SwitchToDevAssistTab() + public void SwitchToCxAssistTab() { - MainTabControl.SelectedItem = DevAssistFindingsTab; + MainTabControl.SelectedItem = CxAssistFindingsTab; } /// @@ -116,21 +116,21 @@ public void SwitchToScanResultsTab() } /// - /// Event handler for tab selection changed - populates DevAssist data when tab is first shown + /// Event handler for tab selection changed - populates CxAssist data when tab is first shown /// private void MainTabControl_SelectionChanged(object sender, SelectionChangedEventArgs e) { - if (MainTabControl.SelectedItem == DevAssistFindingsTab && !_devAssistDataLoaded) + if (MainTabControl.SelectedItem == CxAssistFindingsTab && !_CxAssistDataLoaded) { - _devAssistDataLoaded = true; - PopulateDevAssistTestData(); + _CxAssistDataLoaded = true; + PopulateCxAssistTestData(); } } /// - /// Populates the DevAssist Findings tab with test data + /// Populates the CxAssist Findings tab with test data /// - private void PopulateDevAssistTestData() + private void PopulateCxAssistTestData() { try { @@ -146,7 +146,7 @@ private void PopulateDevAssistTestData() } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"Error populating DevAssist test data: {ex.Message}"); + System.Diagnostics.Debug.WriteLine($"Error populating CxAssist test data: {ex.Message}"); } } diff --git a/ast-visual-studio-extension/CxExtension/CxWindowPackage.cs b/ast-visual-studio-extension/CxExtension/CxWindowPackage.cs index 385ea7cf..c8b8aba7 100644 --- a/ast-visual-studio-extension/CxExtension/CxWindowPackage.cs +++ b/ast-visual-studio-extension/CxExtension/CxWindowPackage.cs @@ -1,5 +1,5 @@ using ast_visual_studio_extension.CxExtension.Commands; -using ast_visual_studio_extension.CxExtension.DevAssist.Core; +using ast_visual_studio_extension.CxExtension.CxAssist.Core; using log4net; using log4net.Appender; using log4net.Config; @@ -40,7 +40,7 @@ namespace ast_visual_studio_extension.CxExtension [ProvideAutoLoad(UIContextGuids80.NoSolution, PackageAutoLoadFlags.BackgroundLoad)] [ProvideAutoLoad(UIContextGuids80.SolutionExists, PackageAutoLoadFlags.BackgroundLoad)] [ProvideToolWindow(typeof(CxWindow),Style = VsDockStyle.Tabbed,Orientation = ToolWindowOrientation.Right,Window = EnvDTE.Constants.vsWindowKindOutput,Transient = false)] - [ProvideToolWindow(typeof(DevAssist.UI.FindingsWindow.DevAssistFindingsWindow), Style = VsDockStyle.Tabbed, Orientation = ToolWindowOrientation.Bottom, Window = EnvDTE.Constants.vsWindowKindOutput, Transient = false)] + [ProvideToolWindow(typeof(CxAssist.UI.FindingsWindow.CxAssistFindingsWindow), Style = VsDockStyle.Tabbed, Orientation = ToolWindowOrientation.Bottom, Window = EnvDTE.Constants.vsWindowKindOutput, Transient = false)] [Guid(PackageGuidString)] public sealed class CxWindowPackage : AsyncPackage { @@ -49,7 +49,7 @@ public sealed class CxWindowPackage : AsyncPackage /// public const string PackageGuidString = "63d5f3b4-a254-4bef-974b-0733c306ed2c"; - private DevAssistErrorListSync _devAssistErrorListSync; + private CxAssistErrorListSync _CxAssistErrorListSync; #region Package Members @@ -73,7 +73,7 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke // Command to create Checkmarx extension main window await CxWindowCommand.InitializeAsync(this); - // Test DevAssist Hover Popup is registered in ast_visual_studio_extensionPackage so it appears under Tools. + // Test CxAssist Hover Popup is registered in ast_visual_studio_extensionPackage so it appears under Tools. // Test Error List Customization Command (POC for AST-133228) await TestErrorListCustomizationCommand.InitializeAsync(this); @@ -82,9 +82,9 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke // Command still works programmatically but not visible in menu await ShowFindingsWindowCommand.InitializeAsync(this); - // Sync DevAssist findings to the built-in Error List (findings appear in both our window and Error List) - _devAssistErrorListSync = new DevAssistErrorListSync(); - _devAssistErrorListSync.Start(); + // Sync CxAssist findings to the built-in Error List (findings appear in both our window and Error List) + _CxAssistErrorListSync = new CxAssistErrorListSync(); + _CxAssistErrorListSync.Start(); } catch (Exception ex) { diff --git a/ast-visual-studio-extension/CxExtension/CxWindowPackage.vsct b/ast-visual-studio-extension/CxExtension/CxWindowPackage.vsct index 497bcb68..7d05d02c 100644 --- a/ast-visual-studio-extension/CxExtension/CxWindowPackage.vsct +++ b/ast-visual-studio-extension/CxExtension/CxWindowPackage.vsct @@ -15,11 +15,11 @@ Checkmarx extension toolbar - + - DevAssist - DevAssist + CxAssist + CxAssist @@ -28,10 +28,10 @@ - - + + - DevAssist + CxAssist @@ -57,12 +57,12 @@ --> - + @@ -78,8 +78,8 @@ - - + + diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.Designer.cs b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.Designer.cs deleted file mode 100644 index 0bb8fac9..00000000 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.Designer.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System.Reflection; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Documents; -using System.Windows.Markup; -using System.Windows.Media; - -namespace ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers -{ - /// - /// Designer partial: declares XAML-named elements and loads the XAML from embedded resource - /// so that the project compiles without relying on MSBuild-generated .g.cs. - /// - public partial class DevAssistHoverPopup - { - // Header: Checkmarx One Assist logo image (not text/label) - internal Image HeaderLogoImage; - internal Button MoreOptionsButton; - internal StackPanel MultipleIssuesPanel; - internal TextBlock MultipleIssuesHeader; - internal StackPanel MultipleIssuesCards; - internal StackPanel SingleIssuePanel; - // Main line - internal Image SeverityIcon; - internal TextBlock TitleText; - internal Border ScannerBadge; - internal TextBlock ScannerText; - internal StackPanel SeverityCountPanel; - // Description and panels - internal TextBlock DescriptionText; - internal StackPanel PackageInfoPanel; - internal TextBlock PackageNameText; - internal TextBlock CveText; - internal TextBlock CvssScoreText; - internal StackPanel RecommendedVersionPanel; - internal TextBlock RecommendedVersionText; - internal StackPanel RemediationPanel; - internal TextBlock RemediationText; - internal StackPanel IacValuesPanel; - internal TextBlock ExpectedValueText; - internal TextBlock ActualValueText; - // Location and links - internal TextBlock LocationTextBlock; - internal Run LocationText; - internal Border SeparatorLine1; - internal Border SeparatorLine2; - internal System.Windows.Documents.Hyperlink FixWithCxOneAssistLink; - internal System.Windows.Documents.Hyperlink ViewDetailsLink; - internal System.Windows.Documents.Hyperlink IgnoreThisLink; - internal TextBlock IgnoreAllOfThisTypeBlock; - internal System.Windows.Documents.Hyperlink IgnoreAllOfThisTypeLink; - internal StackPanel SecondaryLinksPanel; - internal System.Windows.Documents.Hyperlink NavigateToCodeLink; - internal TextBlock LearnMoreLinkBlock; - internal System.Windows.Documents.Hyperlink LearnMoreLink; - internal TextBlock ApplyFixLinkBlock; - internal System.Windows.Documents.Hyperlink ApplyFixLink; - internal StackPanel CompilerErrorsPanel; - internal TextBlock CompilerErrorsTitle; - internal StackPanel CompilerErrorsList; - - private void InitializeComponent() - { - const string resourceName = "ast_visual_studio_extension.CxExtension.DevAssist.Core.Markers.DevAssistHoverPopup.xaml"; - var asm = Assembly.GetExecutingAssembly(); - using (var stream = asm.GetManifestResourceStream(resourceName)) - { - if (stream == null) - { - System.Diagnostics.Debug.WriteLine($"DevAssistHoverPopup: Embedded resource not found: {resourceName}"); - return; - } - var root = (FrameworkElement)XamlReader.Load(stream); - Content = root; - HeaderLogoImage = (Image)root.FindName("HeaderLogoImage"); - MoreOptionsButton = (Button)root.FindName("MoreOptionsButton"); - MultipleIssuesPanel = (StackPanel)root.FindName("MultipleIssuesPanel"); - MultipleIssuesHeader = (TextBlock)root.FindName("MultipleIssuesHeader"); - MultipleIssuesCards = (StackPanel)root.FindName("MultipleIssuesCards"); - SingleIssuePanel = (StackPanel)root.FindName("SingleIssuePanel"); - SeverityIcon = (Image)root.FindName("SeverityIcon"); - TitleText = (TextBlock)root.FindName("TitleText"); - ScannerBadge = (Border)root.FindName("ScannerBadge"); - ScannerText = (TextBlock)root.FindName("ScannerText"); - SeverityCountPanel = (StackPanel)root.FindName("SeverityCountPanel"); - DescriptionText = (TextBlock)root.FindName("DescriptionText"); - PackageInfoPanel = (StackPanel)root.FindName("PackageInfoPanel"); - PackageNameText = (TextBlock)root.FindName("PackageNameText"); - CveText = (TextBlock)root.FindName("CveText"); - CvssScoreText = (TextBlock)root.FindName("CvssScoreText"); - RecommendedVersionPanel = (StackPanel)root.FindName("RecommendedVersionPanel"); - RecommendedVersionText = (TextBlock)root.FindName("RecommendedVersionText"); - RemediationPanel = (StackPanel)root.FindName("RemediationPanel"); - RemediationText = (TextBlock)root.FindName("RemediationText"); - IacValuesPanel = (StackPanel)root.FindName("IacValuesPanel"); - ExpectedValueText = (TextBlock)root.FindName("ExpectedValueText"); - ActualValueText = (TextBlock)root.FindName("ActualValueText"); - LocationTextBlock = (TextBlock)root.FindName("LocationTextBlock"); - LocationText = (Run)root.FindName("LocationText"); - SeparatorLine1 = (Border)root.FindName("SeparatorLine1"); - SeparatorLine2 = (Border)root.FindName("SeparatorLine2"); - FixWithCxOneAssistLink = (System.Windows.Documents.Hyperlink)root.FindName("FixWithCxOneAssistLink"); - ViewDetailsLink = (System.Windows.Documents.Hyperlink)root.FindName("ViewDetailsLink"); - IgnoreThisLink = (System.Windows.Documents.Hyperlink)root.FindName("IgnoreThisLink"); - IgnoreAllOfThisTypeBlock = (TextBlock)root.FindName("IgnoreAllOfThisTypeBlock"); - IgnoreAllOfThisTypeLink = (System.Windows.Documents.Hyperlink)root.FindName("IgnoreAllOfThisTypeLink"); - SecondaryLinksPanel = (StackPanel)root.FindName("SecondaryLinksPanel"); - NavigateToCodeLink = (System.Windows.Documents.Hyperlink)root.FindName("NavigateToCodeLink"); - LearnMoreLinkBlock = (TextBlock)root.FindName("LearnMoreLinkBlock"); - LearnMoreLink = (System.Windows.Documents.Hyperlink)root.FindName("LearnMoreLink"); - ApplyFixLinkBlock = (TextBlock)root.FindName("ApplyFixLinkBlock"); - ApplyFixLink = (System.Windows.Documents.Hyperlink)root.FindName("ApplyFixLink"); - CompilerErrorsPanel = (StackPanel)root.FindName("CompilerErrorsPanel"); - CompilerErrorsTitle = (TextBlock)root.FindName("CompilerErrorsTitle"); - CompilerErrorsList = (StackPanel)root.FindName("CompilerErrorsList"); - } - } - } -} diff --git a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml b/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml deleted file mode 100644 index 9c7d8b14..00000000 --- a/ast-visual-studio-extension/CxExtension/DevAssist/Core/Markers/DevAssistHoverPopup.xaml +++ /dev/null @@ -1,248 +0,0 @@ - - - - - - - - - - - - - - - - -