Skip to content

Commit

Permalink
Code analyzer fix
Browse files Browse the repository at this point in the history
Moved generation of suggestions from the code analyzer to the code fix to alleviate excessive CPU usage by the code analyzer.  Fixes #305.
  • Loading branch information
EWSoftware committed Dec 29, 2023
1 parent 745ebe1 commit a7be263
Show file tree
Hide file tree
Showing 13 changed files with 215 additions and 106 deletions.
6 changes: 6 additions & 0 deletions Docs/Content/VersionHistory/VersionHistory.aml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ project. Select a version below to see a description of its changes.</para>
<section>
<content>
<list class="bullet">
<listItem>
<para>
<link xlink:href="9b0ee624-54c6-4020-b546-03e6f5c96b0e" />
</para>
</listItem>

<listItem>
<para>
<link xlink:href="6c7012c2-63ee-41d4-8036-df1054b0b714" />
Expand Down
43 changes: 43 additions & 0 deletions Docs/Content/VersionHistory/v2023.12.29.0.aml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<topic id="9b0ee624-54c6-4020-b546-03e6f5c96b0e" revisionNumber="1">
<developerConceptualDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink">
<introduction>
<para>Changes made in this release:</para>
</introduction>

<section>
<content>
<list class="bullet">
<listItem>
<para>Moved generation of suggestions for code analyzer misspellings to the code fix provider which
should reduce excessive CPU usage by the code analyzer.</para>
</listItem>

<listItem>
<para>Fixed an issue with the classifier factory throwing an exception with ASPX files.</para>
</listItem>

<listItem>
<para>Fixed a couple of issues when adding and removing sections in the configuration editor.</para>
</listItem>

<listItem>
<para>Fixed handling of all uppercase words in the word splitter when they are being spell checked.</para>
</listItem>

<listItem>
<para>Changed type identifier renaming to only rename the containing file if the filename matches the
misspelled identifier name.</para>
</listItem>

</list>

</content>
</section>

<relatedTopics>
<link xlink:href="548dc6d7-6d08-4006-82b3-d5830be96f04" />
</relatedTopics>

</developerConceptualDocument>
</topic>
7 changes: 6 additions & 1 deletion Docs/ContentLayout.content
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,12 @@
<HelpKeywords>
<HelpKeyword index="K" term="version, history" />
</HelpKeywords>
<Topic id="6c7012c2-63ee-41d4-8036-df1054b0b714" visible="True" isSelected="true" title="Version 2023.5.15.0">
<Topic id="9b0ee624-54c6-4020-b546-03e6f5c96b0e" visible="True" isSelected="true" title="Version 2023.12.29.0">
<HelpKeywords>
<HelpKeyword index="K" term="versions, 2023.12.29.0" />
</HelpKeywords>
</Topic>
<Topic id="6c7012c2-63ee-41d4-8036-df1054b0b714" visible="True" title="Version 2023.5.15.0">
<HelpKeywords>
<HelpKeyword index="K" term="versions, 2023.5.15.0" />
</HelpKeywords>
Expand Down
3 changes: 2 additions & 1 deletion Docs/VSSpellCheckerDocs.shfbproj
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
</NamespaceSummaries>
<ComponentConfigurations>
</ComponentConfigurations>
<HelpFileVersion>2023.5.15.0</HelpFileVersion>
<HelpFileVersion>2023.12.29.0</HelpFileVersion>
<SaveComponentCacheCapacity>0</SaveComponentCacheCapacity>
<SourceCodeBasePath>..\Source\</SourceCodeBasePath>
<WarnOnMissingSourceContext>True</WarnOnMissingSourceContext>
Expand Down Expand Up @@ -219,6 +219,7 @@
<None Include="Content\VersionHistory\v2022.1.3.0.aml" />
<None Include="Content\VersionHistory\v2022.12.29.0.aml" />
<None Include="Content\VersionHistory\v2022.9.6.0.aml" />
<None Include="Content\VersionHistory\v2023.12.29.0.aml" />
<None Include="Content\VersionHistory\v2023.3.4.0.aml" />
<None Include="Content\VersionHistory\v2023.5.15.0.aml" />
<None Include="Content\VersionHistory\VersionHistory.aml" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// System : Visual Studio Spell Checker Package
// File : SpellCheckCodeFixProvider.cs
// Author : Eric Woodruff (Eric@EWoodruff.us)
// Updated : 10/27/2023
// Updated : 12/29/2023
// Note : Copyright 2023, Eric Woodruff, All rights reserved
//
// This file contains a class used to provide the spell check code fixes
Expand All @@ -17,9 +17,13 @@
// 01/29/2023 EFW Created the code
//===============================================================================================================

// Ignore Spelling: welldone

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
Expand All @@ -34,6 +38,7 @@
using Microsoft.CodeAnalysis.Rename;

using VisualStudio.SpellChecker.CodeAnalyzer;
using VisualStudio.SpellChecker.Common;

namespace VisualStudio.SpellChecker.CodeFixes
{
Expand Down Expand Up @@ -65,6 +70,8 @@ public sealed override FixAllProvider GetFixAllProvider()
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var suggestions = new List<string>();
var separators = new[] { ',' };

foreach(var diagnostic in context.Diagnostics)
{
Expand All @@ -75,18 +82,45 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
// Find the identifier for the diagnostic
var syntaxToken = root.FindToken(diagnosticSpan.Start);

if(diagnostic.Properties.TryGetValue("Suggestions", out string suggestions))
suggestions.Clear();

if(!diagnostic.Properties.TryGetValue("Suggestions", out string suggestedReplacements))
{
// If the misspelling is a sub-span, the prefix and suffix will contain the surrounding text
// used to create the full identifier.
_ = diagnostic.Properties.TryGetValue("Prefix", out string prefix);
_ = diagnostic.Properties.TryGetValue("Suffix", out string suffix);
if(diagnostic.Properties.TryGetValue("Languages", out string languages) &&
diagnostic.Properties.TryGetValue("TextToCheck", out suggestedReplacements))
{
// Getting suggestions is expensive so it's done here when actually needed rather
// than in the code analyzer. Dictionaries should exist at this point since the
// code analyzer will have created them.
var globalDictionaries = languages.Split(separators,
StringSplitOptions.RemoveEmptyEntries).Select(l =>
GlobalDictionary.CreateGlobalDictionary(new CultureInfo(l), null,
Enumerable.Empty<string>(), false)).Where(d => d != null).Distinct().ToList();

if(globalDictionaries.Count != 0)
{
var dictionary = new SpellingDictionary(globalDictionaries, null);

suggestions.AddRange(CheckSuggestions(
dictionary.SuggestCorrections(suggestedReplacements).Select(ss => ss.Suggestion)));
}
}
}
else
suggestions.AddRange(suggestedReplacements.Split(separators, StringSplitOptions.RemoveEmptyEntries));

if(suggestedReplacements != null)
{
ImmutableArray<CodeAction> replacements;

if(!String.IsNullOrWhiteSpace(suggestions))
if(suggestions.Count != 0)
{
replacements = suggestions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(
// If the misspelling is a sub-span, the prefix and suffix will contain the surrounding text
// used to create the full identifier.
_ = diagnostic.Properties.TryGetValue("Prefix", out string prefix);
_ = diagnostic.Properties.TryGetValue("Suffix", out string suffix);

replacements = suggestions.Select(
s =>
{
string replacement = (String.IsNullOrWhiteSpace(prefix) &&
Expand Down Expand Up @@ -124,6 +158,54 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
}
}

/// <summary>
/// This is used to filter and adjust the suggestions used to fix a misspelling in an identifier
/// </summary>
/// <param name="suggestions">The suggestions that should replace the misspelling</param>
/// <returns>An enumerable list of valid suggestions, if any.</returns>
/// <remarks>Some suggestions include spaces or punctuation. Those are altered to remove the punctuation
/// and return the suggestion in camel case.</remarks>
private static HashSet<string> CheckSuggestions(IEnumerable<string> suggestions)
{
var validSuggestions = new HashSet<string>();

foreach(string s in suggestions)
{
var wordChars = s.ToArray();

if(wordChars.All(c => Char.IsLetter(c)))
validSuggestions.Add(s);
else
{
// Certain misspellings may return suggestions with spaces or punctuation. For example:
// welldone suggests "well done" and "well-done". Return those as a camel case suggestion:
// wellDone.
bool caseChanged = false;

for(int idx = 0; idx < wordChars.Length; idx++)
{
if(!Char.IsLetter(wordChars[idx]))
{
while(idx < wordChars.Length && !Char.IsLetter(wordChars[idx]))
idx++;

if(idx < wordChars.Length)
{
wordChars[idx] = Char.ToUpperInvariant(wordChars[idx]);
caseChanged = true;
}
}
}

if(caseChanged)
validSuggestions.Add(new String(wordChars.Where(c => Char.IsLetter(c)).ToArray()));
}
}

return validSuggestions;
}


/// <summary>
/// Create the solution used to correct a spelling error
/// </summary>
Expand Down

0 comments on commit a7be263

Please sign in to comment.