Skip to content
This repository has been archived by the owner on May 4, 2023. It is now read-only.

Rosie text highlights and fixes #5

Merged
merged 9 commits into from
Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ jobs:
run: msbuild Extension.sln /restore /v:m /property:Configuration=${{ inputs.config }}

- name: Install NUnit.ConsoleRunner
run: nuget install NUnit.ConsoleRunner -Version 3.13.0 -DirectDownload -OutputDirectory .
run: nuget install NUnit.ConsoleRunner -Version 3.16.0 -DirectDownload -OutputDirectory .

- name: Run UnitTests
run: ./NUnit.ConsoleRunner.3.13.0/tools/nunit3-console.exe src\Tests\bin\${{ inputs.config }}\net48\Tests.dll
run: ./NUnit.ConsoleRunner.3.16.0/tools/nunit3-console.exe src\Tests\bin\${{ inputs.config }}\net48\Tests.dll

- name: Publish test results
uses: EnricoMi/publish-unit-test-result-action/composite@v2
Expand Down
122 changes: 112 additions & 10 deletions DEVELOPMENT.md

Large diffs are not rendered by default.

Binary file modified images/project-structure.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions images/tagging-flow-during-editing.drawio.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<mxfile host="app.diagrams.net" modified="2022-11-30T10:01:00.434Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" etag="lvogCwi11RcuYZyPfP5i" version="20.0.3"><diagram id="EbkYADztwSF3Mh8ZUwno" name="Page-1">7Vrdd6I4FP9rfKwHiIg+qu1s55zOObNjd2f6GCFCdiOhIVSdv34TiHxItHQKutp5gtx8Xe7H797c0AOz1eYPBqPgC/UQ6VmGt+mB255lmbZpiIekbDOKMzAzgs+wpwYVhDn+iRRRzfMT7KG4MpBTSjiOqkSXhiFyeYUGGaPr6rAlJdVdI+ijGmHuQlKnfsceDzKqBcC46LhH2A/U1gAYivMV3I1WhDiAHl2XSOCuB2aMUp69rTYzRKT0doLJ5n060JtzxlDIm0z4ktxMoj+jkeXhjbPZjKPZ5OHGtLJlXiBJ1Ccrbvl2JwNGk9BDchWjB6brAHM0j6Are9dC64IW8BURLVO8LmnIP8EVJlLh94i8II5dKDrUPohxtDn4BWYuF2FRiK4QZ1sxRE0AO6tQxmQC1V4XqjFtRQtKWsmJUJmDn69dCEy8KJm9QX7jCxKfY1XFl1tmSXwDoBPfuCvxjS5YfObZpWfXhIU8gV6qSRkPqE9DSO4K6rQqzmLMA6WREuI/iPOtgmKYcFoVMdpg/qP0/iSX6tuqdbtRK6eNrWq8ohbJ9XGliI+kCXPRMSRT0QEyH/Ej4yy9khkikOOXKh+ta8ys2fsMEhL3pGkNCVeikoEKqo8dPicyQEwfEhd7UPTMaBhTocq8R7z58vn5EW34NFkuEet/pTGfBTD0habVwoLfbO1stBT7C0r3EuM8gpjkPWyFk280xuhvTKVEafgIfV+ufoCNg+5uNnB3TMiMEsrSuUB8+tB1BT3mjP6LSj2eM14YRjs4sA8DjiYIjTU44HQFA+bgnDhQ+P5TqUePA0LCbFuaJJtP5b5iWto6JX5YDfFjeE78qGdr39BzgmIepzmwh+SmQovbGMcX6FoA2FXfGmp8y9D41qAr3wKXEmIPuZb5FteqQftZIvQBGzmNhw00GenxeFjgUS0Qfoc49cwlldGVB9I9kzgNtULn0qyERajlF2w3i28jHPr5NCh3JQjG8mkbxipuGkul1h7gQhzJK+YFCfZD8e4KDQpewFT6rOCfTFTHCnteZskoxj/hIl1PGktEcchTidvTnn3b2Hw0oKAO7GrxXh5Vy4Z12CMPIsiN0RfBV6FIY4tRy32Vn1caQpfLWFjqvknlu74jZF9M6l7gSiVgFyBzxpA9bAooht5m3mYfE8bgtjRAOUPdfJQpDsZ7x217rz7z2nhlxoXpZRy0aohDzYHEDZA2nRDyihPCryGx0JU+TptYWKMPhQAdZxZGUyRwzoEEjnnUs18d7xi9zoFgt2UJCT6HL8Ij2ylOPEI/blyQoMvTlyEqOVocwXDHR0ZZB+kWhkAvdIH4Z9eKFnYd/xwN/g07K1qANhPt2X6sapYgC4Hy7nJjCb9xhsRmTcEhDdGeNexIv5pSv+OcrbtI0YVD0Jk51BORvyIPcqlVIvdfJGQhN3alL9dTkP+/Jlu+jRgN9zy64VVYZxrMrzk/RhWy44TGaZrQjN+Z0LzPax3N8aG9+wzoT3yfIR9yGZuHcCVDKdGG9rw7ndqfQi5OMV4l6zAORIU2Lz3uhaOlcDXJcGqeKfpq7j6cgdWvho5BPZEAI7uOO53dwFvOb9xpD3fGDXHHss6KO/XfLtrDnSOO3J8n4hARc+Qp8mtHmitwcFNTKjmxh9dvzfPckItTXVos//D54OgXK1zdJYRnrXFfGTBneNsEmA/cj5zoftr6nRDqy0/z5wT7PkHX9jdMPWBYmkv7EweMem2p5fTggFr7TcqcV6BioKkXtaVi0Sz+ic6q28Wv5eDuPw==</diagram></mxfile>
Binary file added images/tagging-flow-during-editing.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions images/tagging-flow-initial.drawio.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<mxfile host="app.diagrams.net" modified="2022-11-30T09:28:08.487Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" etag="_5ULwbQeA7Yn275ZxKg2" version="20.0.3"><diagram id="_s4aj7_37aYGRvY7WVtE" name="Page-1">7Vpbd9o4EP41nNM+hINtMPAYSNL2bNuTs8lmk0dhC6NWSK4sbv31HdnyDdvESQBDdp9sje4z+r6RRmpZ4/n6k0D+7Bt3MW2ZHXfdsq5apmn0Bn34KMkmkvS7RiTwBHF1oVRwR35jLexo6YK4OMgVlJxTSfy80OGMYUfmZEgIvsoXm3Ka79VHHi4I7hxEi9J/iStnkdS0rGGa8RkTb6a7tqyOHvkcxaW1IJghl68yIuu6ZY0F5zL6m6/HmCrtxYqJ6t1U5CYjE5jJOhX+efxrc/F0697P1vTHZoSv7r9/vzC1fQK5iaeMXdCATnIhZ9zjDNHrVDoSfMFcrJrtQCot85VzH4QGCH9gKTfanGghOYhmck51Ll4T+Zj5f1JNtXs6dbXWLYeJTZxgUmwes4lMLZVMq4WpuN6UM3mD5oQqwdeFQ1wE8x1zFvBwMtH81aQr9apFAV8IB+9QZrxAkfCw3KV0OzE/AAfzOYYBQ0WBKZJkmR8I0ivYS8qlNoYfbeYXmFy3u0R0oXtqmTaF8Y58+PFkqJNIoHSnUIP0tO1fC7VaRwU1JjlxC3/zgOAHwtWEOLtHnofFreBLQLOIW5+I7f5gPlGXkdgRGElAPyihus5+x1gxEmV9wjyKJWftTJmMxrZQlGJELfLVjEh850djXAFV5vEwJZSOOeUirGtNp9h2HJAHUvCfOJPj9oeTTifpb4mFxOvd67a4zHSFga2JaRNTrk6vMkTX1bJZhuN6nUOtTPPsyOii0+7YLyOkMHWLBQGlYXF8ljJrstSwSZIyK0lK4z1dIzGsJ8j56YWr4cKJEHOpemFEEkQj2CcE8Ha6uPu1IB4QwiupLRYHPmJvnk09QvyM6RJL4qBi1VpEWzGDSnscUt/1xvQSFcAXzRUts0mgPqV0X9YXiCMj7sURPEMDp+Eo+iWOYnhMR2GfnZ843U2rVdMdVKyR47iDszmlnKDhzCYNZz3nx1/C0Q93qQsEoyl/VUHOXx4IXm275s4buv52fRNGGeY+Z2CpoNIpvDv2t/tb7G8V2d/olbC/fSj2N3sli+r49HBEqA/rcrTVJNaHBbO028nuKd1P+jCWKRfz0GCL6bRgO1irMq9qgLvH4N8Bzakj00itaMApvdQZc+K6kVVxQH6jSdiUsqvPiUIrtNsbtXpXqi0wZBDZ1ChghwG6t4AWi2pZ+01IM6zBFtR6JVAzS6DWPdiJvNGdVupsn7K+9lx3WkbtrVa3SRgb1T7bz3tiwKI+NvJl2dlXIQfylOudc6H+Q2iDquDAFBTLExadQONiE2CH3FnvnMNtRs/aQne3V0S3dcxjVHIzcQLoNmqiOwX0UzbvBNDdrYvufqPo7j6P7jGiNLu53leIPR/DaX/CEv6CDx+fj7CcNfDtLbfeLXr15NbwOLg3Cmr8H/evxn2/Lu4HjeK+XwP3aRiYueFxl1LsyBJPfYDLtwoSUKNZEhSOaS+U9AX6uvQ8gT0k1dZEh31pcVAyHFSU/UwkPBc6niirwuGEEuYdgEX/4+xpGDXps38w+mz0mvK90eegLn02eh8ZD7MefR5w47SDJicowIq2+X6I8gUM/T6Zxix5EFHKNAcLv5hWo0yT4Zn6F115pjFOiGnOI4pqFMOoBaa5IoFP0UaBXs5UXCXQBKEkH6LNW/JMUhPCTMVoPr5brNqNY3XQJFZfdynd7sCx4LweL9k1Qdzs6yW7BoYXIjofEBZIxCQJ3Wz4zGf7tOXoeEz8+mdv3n1MCVhAHSUuGeMyHMH7P1EkVyi7Hj6WXbO8IiADyfSFd5iXeShvXf8B</diagram></mxfile>
Binary file added images/tagging-flow-initial.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 13 additions & 1 deletion src/Extension/Extension.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,21 @@
<Compile Include="Caching\SnippetCache.cs" />
<Compile Include="Caching\TextViewCreationListener.cs" />
<Compile Include="Logging\ExtensionLogger.cs" />
<Compile Include="Rosie\Annotation\RosieViolationSquiggleTag.cs" />
<Compile Include="Rosie\Annotation\RosieViolationSquiggleTaggerProvider.cs" />
<Compile Include="Rosie\Annotation\RosieViolationTag.cs" />
<Compile Include="Rosie\Annotation\RosieViolationSquiggleTagger.cs" />
<Compile Include="Rosie\Annotation\RosieViolationTagger.cs" />
<Compile Include="Rosie\Annotation\RosieViolationTaggerProvider.cs" />
<Compile Include="Rosie\Annotation\StringUtils.cs" />
<Compile Include="Rosie\RosieClientProvider.cs" />
<Compile Include="Rosie\CodigaCodeAnalysisConfig.cs" />
<Compile Include="Rosie\CodigaConfigFileUtil.cs" />
<Compile Include="Rosie\Annotation\ApplyRosieFixSuggestedAction.cs" />
<Compile Include="Rosie\Annotation\DisableRosieAnalysisSuggestedAction.cs" />
<Compile Include="Rosie\Annotation\OpenOnCodigaHubSuggestedAction.cs" />
<Compile Include="Rosie\Annotation\RosieHighlightActionsSourceProvider.cs" />
<Compile Include="Rosie\Model\RosieAnnotation.cs" />
<Compile Include="Rosie\Model\RosieAnnotationJetBrains.cs" />
<Compile Include="Rosie\Model\RosiePosition.cs" />
<Compile Include="Rosie\Model\RosieRequest.cs" />
<Compile Include="Rosie\Model\RosieResponse.cs" />
Expand All @@ -82,8 +92,10 @@
<Compile Include="Rosie\Model\RosieViolationFixEdit.cs" />
<Compile Include="Rosie\IRosieClient.cs" />
<Compile Include="Rosie\RosieClient.cs" />
<Compile Include="Rosie\RosieEditTypes.cs" />
<Compile Include="Rosie\RosieRulesCache.cs" />
<Compile Include="Rosie\RosieRulesCacheValue.cs" />
<Compile Include="Rosie\RosieSeverities.cs" />
<Compile Include="Rosie\RosieUtils.cs" />
<Compile Include="SnippetSearch\Preview\PreviewClassifier.cs" />
<Compile Include="SnippetSearch\Preview\PreviewClassifierFormat.cs" />
Expand Down
37 changes: 4 additions & 33 deletions src/Extension/ExtensionPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
using Extension.Rosie;
using Task = System.Threading.Tasks.Task;
using Extension.SnippetSearch;
using Microsoft.VisualStudio.Shell.Events;
using Microsoft.VisualStudio;
using System.Threading.Tasks;
using SolutionEvents = Microsoft.VisualStudio.Shell.Events.SolutionEvents;

namespace Extension
{
Expand All @@ -29,8 +28,6 @@ public sealed class ExtensionPackage : AsyncPackage
/// </summary>
public const string PackageGuidString = "e8d2d8f8-96dc-4c92-bb81-346b4d2318e4";

private CancellationToken _cancellationToken;

/// <summary>
/// Initializes a new instance of the <see cref="ExtensionPackage"/> class.
/// </summary>
Expand All @@ -49,34 +46,15 @@ public ExtensionPackage()
/// <returns>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.</returns>
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
{
_cancellationToken = cancellationToken;
var isSolutionLoaded = await IsSolutionLoadedAsync();

if (isSolutionLoaded)
InitializeRulesCache();

//Inits the cache only after a solution is loaded completely
SolutionEvents.OnAfterBackgroundSolutionLoadComplete += InitializeRulesCache;
SolutionEvents.OnAfterCloseSolution += DisposeRulesCache;
SolutionEvents.OnAfterCloseSolution += CleanupCachesAndServices;

// 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);
await SnippetSearchMenuCommand.InitializeAsync(this);
}

//See https://github.com/madskristensen/SolutionLoadSample
private async Task<bool> IsSolutionLoadedAsync()
{
await JoinableTaskFactory.SwitchToMainThreadAsync();
var vsSolution = await GetServiceAsync(typeof(SVsSolution)) as IVsSolution;

ErrorHandler.ThrowOnFailure(vsSolution.GetProperty((int)__VSPROPID.VSPROPID_IsSolutionOpen, out object value));

return value is bool isSolutionOpen && isSolutionOpen;
}

public override IVsAsyncToolWindowFactory GetAsyncToolWindowFactory(Guid toolWindowType)
public override IVsAsyncToolWindowFactory GetAsyncToolWindowFactory(Guid toolWindowType)
{
ThreadHelper.ThrowIfNotOnUIThread();
if (toolWindowType == typeof(SnippetSearch.SearchWindow).GUID)
Expand All @@ -97,14 +75,7 @@ protected override string GetToolWindowTitle(Type toolWindowType, int id)
return base.GetToolWindowTitle(toolWindowType, id);
}

private async void InitializeRulesCache(object sender = null, EventArgs e = null)
{
//Switching back to main thread due to RosieRulesCache.StartPolling()
await JoinableTaskFactory.SwitchToMainThreadAsync(_cancellationToken);
RosieRulesCache.Initialize();
}

private static void DisposeRulesCache(object sender, EventArgs e)
private static void CleanupCachesAndServices(object sender, EventArgs e)
{
RosieRulesCache.Dispose();
}
Expand Down
144 changes: 144 additions & 0 deletions src/Extension/Rosie/Annotation/ApplyRosieFixSuggestedAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Imaging.Interop;
using Microsoft.VisualStudio.Language.Intellisense;
using Extension.Rosie.Model;
using Microsoft.VisualStudio.Text;
using Span = Microsoft.VisualStudio.Text.Span;

namespace Extension.Rosie.Annotation
{
/// <summary>
/// Applies a fix with a series of edits on the code.
/// </summary>
public class ApplyRosieFixSuggestedAction : ISuggestedAction
{
private readonly ITextBuffer _textBuffer;
private readonly IList<RosieViolationFixEdit> _edits;
private readonly string _displayText;

public ApplyRosieFixSuggestedAction(ITextBuffer textBuffer, RosieViolationFix fix)
{
_textBuffer = textBuffer;
_edits = fix.Edits;
_displayText = $"Fix: {fix.Description}";
}

public void Invoke(CancellationToken cancellationToken)
{
if (HasInvalidEditOffset())
return;

foreach (var edit in _edits)
{
//Apply code insertion/addition
if (StringUtils.AreEqualIgnoreCase(edit.EditType, RosieEditTypes.Add))
{
_textBuffer.Insert(edit.Start.GetOffset(_textBuffer), edit.Content);
}

//Apply code replacement/update
if (StringUtils.AreEqualIgnoreCase(edit.EditType, RosieEditTypes.Update))
{
var replacementSpan =
Span.FromBounds(edit.Start.GetOffset(_textBuffer), edit.End.GetOffset(_textBuffer));
_textBuffer.Replace(replacementSpan, edit.Content);
}

//Apply code removal
if (StringUtils.AreEqualIgnoreCase(edit.EditType, RosieEditTypes.Remove))
{
var removalSpan =
Span.FromBounds(edit.Start.GetOffset(_textBuffer), edit.End.GetOffset(_textBuffer));
_textBuffer.Delete(removalSpan);
}
}
}

/// <summary>
/// If the start offset for additions, or the start/end offset for removals and updates, received from the rule configuration,
/// is either null or is outside the current file's range, we don't apply the fix.
/// </summary>
internal bool HasInvalidEditOffset()
{
var hasInvalidOffset = true;
try
{
var documentLastPosition = _textBuffer.CurrentSnapshot.Length - 1;
hasInvalidOffset = _edits
.Any(edit =>
{
int? startPosition;
if (StringUtils.AreEqualIgnoreCase(edit.EditType, RosieEditTypes.Add))
{
startPosition = edit.Start?.GetOffset(_textBuffer);
return startPosition == null || startPosition < 0 || startPosition > documentLastPosition;
}

startPosition = edit.Start?.GetOffset(_textBuffer);
int? endPosition = edit.End?.GetOffset(_textBuffer);

return startPosition == null ||
endPosition == null ||
startPosition < 0 ||
endPosition < 0 ||
startPosition > documentLastPosition ||
endPosition > documentLastPosition;
});
}
catch (IndexOutOfRangeException)
{
//Let it through, no edit will happen.
}

return hasInvalidOffset;
}

#region Action sets and preview

public Task<IEnumerable<SuggestedActionSet>> GetActionSetsAsync(CancellationToken cancellationToken)
{
return Task.FromResult<IEnumerable<SuggestedActionSet>>(null);
}

public Task<object> GetPreviewAsync(CancellationToken cancellationToken)
{
return Task.FromResult<object>(null);
}

#endregion

#region Disposal

public void Dispose()
{
}

#endregion

#region Properties

public bool TryGetTelemetryId(out Guid telemetryId)
{
telemetryId = Guid.Empty;
return false;
}

public bool HasActionSets => false;

public string DisplayText => _displayText;

public ImageMoniker IconMoniker => default;

public string IconAutomationText => null;

public string InputGestureText => null;

public bool HasPreview => false;

#endregion
}
}
Loading