Skip to content

Commit a7be263

Browse files
committed
Code analyzer fix
Moved generation of suggestions from the code analyzer to the code fix to alleviate excessive CPU usage by the code analyzer. Fixes #305.
1 parent 745ebe1 commit a7be263

File tree

13 files changed

+215
-106
lines changed

13 files changed

+215
-106
lines changed

Docs/Content/VersionHistory/VersionHistory.aml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ project. Select a version below to see a description of its changes.</para>
99
<section>
1010
<content>
1111
<list class="bullet">
12+
<listItem>
13+
<para>
14+
<link xlink:href="9b0ee624-54c6-4020-b546-03e6f5c96b0e" />
15+
</para>
16+
</listItem>
17+
1218
<listItem>
1319
<para>
1420
<link xlink:href="6c7012c2-63ee-41d4-8036-df1054b0b714" />
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<topic id="9b0ee624-54c6-4020-b546-03e6f5c96b0e" revisionNumber="1">
3+
<developerConceptualDocument xmlns="http://ddue.schemas.microsoft.com/authoring/2003/5" xmlns:xlink="http://www.w3.org/1999/xlink">
4+
<introduction>
5+
<para>Changes made in this release:</para>
6+
</introduction>
7+
8+
<section>
9+
<content>
10+
<list class="bullet">
11+
<listItem>
12+
<para>Moved generation of suggestions for code analyzer misspellings to the code fix provider which
13+
should reduce excessive CPU usage by the code analyzer.</para>
14+
</listItem>
15+
16+
<listItem>
17+
<para>Fixed an issue with the classifier factory throwing an exception with ASPX files.</para>
18+
</listItem>
19+
20+
<listItem>
21+
<para>Fixed a couple of issues when adding and removing sections in the configuration editor.</para>
22+
</listItem>
23+
24+
<listItem>
25+
<para>Fixed handling of all uppercase words in the word splitter when they are being spell checked.</para>
26+
</listItem>
27+
28+
<listItem>
29+
<para>Changed type identifier renaming to only rename the containing file if the filename matches the
30+
misspelled identifier name.</para>
31+
</listItem>
32+
33+
</list>
34+
35+
</content>
36+
</section>
37+
38+
<relatedTopics>
39+
<link xlink:href="548dc6d7-6d08-4006-82b3-d5830be96f04" />
40+
</relatedTopics>
41+
42+
</developerConceptualDocument>
43+
</topic>

Docs/ContentLayout.content

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,12 @@
164164
<HelpKeywords>
165165
<HelpKeyword index="K" term="version, history" />
166166
</HelpKeywords>
167-
<Topic id="6c7012c2-63ee-41d4-8036-df1054b0b714" visible="True" isSelected="true" title="Version 2023.5.15.0">
167+
<Topic id="9b0ee624-54c6-4020-b546-03e6f5c96b0e" visible="True" isSelected="true" title="Version 2023.12.29.0">
168+
<HelpKeywords>
169+
<HelpKeyword index="K" term="versions, 2023.12.29.0" />
170+
</HelpKeywords>
171+
</Topic>
172+
<Topic id="6c7012c2-63ee-41d4-8036-df1054b0b714" visible="True" title="Version 2023.5.15.0">
168173
<HelpKeywords>
169174
<HelpKeyword index="K" term="versions, 2023.5.15.0" />
170175
</HelpKeywords>

Docs/VSSpellCheckerDocs.shfbproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
</NamespaceSummaries>
6666
<ComponentConfigurations>
6767
</ComponentConfigurations>
68-
<HelpFileVersion>2023.5.15.0</HelpFileVersion>
68+
<HelpFileVersion>2023.12.29.0</HelpFileVersion>
6969
<SaveComponentCacheCapacity>0</SaveComponentCacheCapacity>
7070
<SourceCodeBasePath>..\Source\</SourceCodeBasePath>
7171
<WarnOnMissingSourceContext>True</WarnOnMissingSourceContext>
@@ -219,6 +219,7 @@
219219
<None Include="Content\VersionHistory\v2022.1.3.0.aml" />
220220
<None Include="Content\VersionHistory\v2022.12.29.0.aml" />
221221
<None Include="Content\VersionHistory\v2022.9.6.0.aml" />
222+
<None Include="Content\VersionHistory\v2023.12.29.0.aml" />
222223
<None Include="Content\VersionHistory\v2023.3.4.0.aml" />
223224
<None Include="Content\VersionHistory\v2023.5.15.0.aml" />
224225
<None Include="Content\VersionHistory\VersionHistory.aml" />

Source/SpellCheckCodeAnalyzer.CodeFixes2022AndLater/SpellCheckCodeFixProvider.cs

Lines changed: 90 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// System : Visual Studio Spell Checker Package
33
// File : SpellCheckCodeFixProvider.cs
44
// Author : Eric Woodruff (Eric@EWoodruff.us)
5-
// Updated : 10/27/2023
5+
// Updated : 12/29/2023
66
// Note : Copyright 2023, Eric Woodruff, All rights reserved
77
//
88
// This file contains a class used to provide the spell check code fixes
@@ -17,9 +17,13 @@
1717
// 01/29/2023 EFW Created the code
1818
//===============================================================================================================
1919

20+
// Ignore Spelling: welldone
21+
2022
using System;
23+
using System.Collections.Generic;
2124
using System.Collections.Immutable;
2225
using System.Composition;
26+
using System.Globalization;
2327
using System.IO;
2428
using System.Linq;
2529
using System.Threading;
@@ -34,6 +38,7 @@
3438
using Microsoft.CodeAnalysis.Rename;
3539

3640
using VisualStudio.SpellChecker.CodeAnalyzer;
41+
using VisualStudio.SpellChecker.Common;
3742

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

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

78-
if(diagnostic.Properties.TryGetValue("Suggestions", out string suggestions))
85+
suggestions.Clear();
86+
87+
if(!diagnostic.Properties.TryGetValue("Suggestions", out string suggestedReplacements))
7988
{
80-
// If the misspelling is a sub-span, the prefix and suffix will contain the surrounding text
81-
// used to create the full identifier.
82-
_ = diagnostic.Properties.TryGetValue("Prefix", out string prefix);
83-
_ = diagnostic.Properties.TryGetValue("Suffix", out string suffix);
89+
if(diagnostic.Properties.TryGetValue("Languages", out string languages) &&
90+
diagnostic.Properties.TryGetValue("TextToCheck", out suggestedReplacements))
91+
{
92+
// Getting suggestions is expensive so it's done here when actually needed rather
93+
// than in the code analyzer. Dictionaries should exist at this point since the
94+
// code analyzer will have created them.
95+
var globalDictionaries = languages.Split(separators,
96+
StringSplitOptions.RemoveEmptyEntries).Select(l =>
97+
GlobalDictionary.CreateGlobalDictionary(new CultureInfo(l), null,
98+
Enumerable.Empty<string>(), false)).Where(d => d != null).Distinct().ToList();
99+
100+
if(globalDictionaries.Count != 0)
101+
{
102+
var dictionary = new SpellingDictionary(globalDictionaries, null);
103+
104+
suggestions.AddRange(CheckSuggestions(
105+
dictionary.SuggestCorrections(suggestedReplacements).Select(ss => ss.Suggestion)));
106+
}
107+
}
108+
}
109+
else
110+
suggestions.AddRange(suggestedReplacements.Split(separators, StringSplitOptions.RemoveEmptyEntries));
84111

112+
if(suggestedReplacements != null)
113+
{
85114
ImmutableArray<CodeAction> replacements;
86115

87-
if(!String.IsNullOrWhiteSpace(suggestions))
116+
if(suggestions.Count != 0)
88117
{
89-
replacements = suggestions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(
118+
// If the misspelling is a sub-span, the prefix and suffix will contain the surrounding text
119+
// used to create the full identifier.
120+
_ = diagnostic.Properties.TryGetValue("Prefix", out string prefix);
121+
_ = diagnostic.Properties.TryGetValue("Suffix", out string suffix);
122+
123+
replacements = suggestions.Select(
90124
s =>
91125
{
92126
string replacement = (String.IsNullOrWhiteSpace(prefix) &&
@@ -124,6 +158,54 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
124158
}
125159
}
126160

161+
/// <summary>
162+
/// This is used to filter and adjust the suggestions used to fix a misspelling in an identifier
163+
/// </summary>
164+
/// <param name="suggestions">The suggestions that should replace the misspelling</param>
165+
/// <returns>An enumerable list of valid suggestions, if any.</returns>
166+
/// <remarks>Some suggestions include spaces or punctuation. Those are altered to remove the punctuation
167+
/// and return the suggestion in camel case.</remarks>
168+
private static HashSet<string> CheckSuggestions(IEnumerable<string> suggestions)
169+
{
170+
var validSuggestions = new HashSet<string>();
171+
172+
foreach(string s in suggestions)
173+
{
174+
var wordChars = s.ToArray();
175+
176+
if(wordChars.All(c => Char.IsLetter(c)))
177+
validSuggestions.Add(s);
178+
else
179+
{
180+
// Certain misspellings may return suggestions with spaces or punctuation. For example:
181+
// welldone suggests "well done" and "well-done". Return those as a camel case suggestion:
182+
// wellDone.
183+
bool caseChanged = false;
184+
185+
for(int idx = 0; idx < wordChars.Length; idx++)
186+
{
187+
if(!Char.IsLetter(wordChars[idx]))
188+
{
189+
while(idx < wordChars.Length && !Char.IsLetter(wordChars[idx]))
190+
idx++;
191+
192+
if(idx < wordChars.Length)
193+
{
194+
wordChars[idx] = Char.ToUpperInvariant(wordChars[idx]);
195+
caseChanged = true;
196+
}
197+
}
198+
}
199+
200+
if(caseChanged)
201+
validSuggestions.Add(new String(wordChars.Where(c => Char.IsLetter(c)).ToArray()));
202+
}
203+
}
204+
205+
return validSuggestions;
206+
}
207+
208+
127209
/// <summary>
128210
/// Create the solution used to correct a spelling error
129211
/// </summary>

0 commit comments

Comments
 (0)