Skip to content

Commit df04a6a

Browse files
committed
Code analyzer fixes
Added a workaround to allow the code analyzer assembly to find its dependencies due to a change in where the hosting process loads it from. Fixes #316.
1 parent f3ba5d6 commit df04a6a

2 files changed

Lines changed: 78 additions & 7 deletions

File tree

Docs/Content/VersionHistory/v2024.12.14.0.aml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
<list class="bullet">
1111
<listItem>
1212
<para>An updated was required due to a change in the Visual Studio SDK that forced the spell checker
13-
assemblies to be marked as not CLS compliant. This fixes the failure to load in Visual Studio 2022 (17.12 and
13+
assemblies to be marked as not CLS compliant. Also, when running out of process, the hosting process now makes a
14+
copy of the code analyzer assembly in a cache location without any of its dependencies. This required coming up
15+
with a workaround to let it find them. These changes fix the failure to load in Visual Studio 2022 (17.12 and
1416
later).</para>
1517
</listItem>
1618

Source/SpellCheckCodeAnalyzer2022AndLater/CSharpSpellCheckCodeAnalyzer.cs

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
// System : Visual Studio Spell Checker Package
33
// File : CSharpSpellCheckCodeAnalyzer.cs
44
// Author : Eric Woodruff (Eric@EWoodruff.us)
5-
// Updated : 12/29/2023
6-
// Note : Copyright 2023, Eric Woodruff, All rights reserved
5+
// Updated : 12/14/2024
6+
// Note : Copyright 2023-2024, Eric Woodruff, All rights reserved
77
//
88
// This file contains the class used to implement the C# spell check code analyzer
99
//
@@ -94,6 +94,36 @@ public class CSharpSpellCheckCodeAnalyzer : DiagnosticAnalyzer
9494
IgnoreWordDescription,
9595
"https://ewsoftware.github.io/VSSpellChecker/html/83ff9063-294f-4a18-b765-1510c86ad0d4.htm");
9696

97+
// The path to our dependencies
98+
private static readonly string codeAnalyzerPath;
99+
100+
#endregion
101+
102+
#region Properties
103+
//=====================================================================
104+
105+
// I don't have a choice here so I'm going to use Environment and file I/O
106+
#pragma warning disable RS1035
107+
/// <summary>
108+
/// This read-only property returns the global configuration file path
109+
/// </summary>
110+
/// <value>We need this to get the analyzer code path so that we can find our dependencies because
111+
/// Visual Studio makes a copy of this assembly in some other cache location without them.</value>
112+
public static string GlobalConfigurationFilePath
113+
{
114+
get
115+
{
116+
string configPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
117+
@"EWSoftware\Visual Studio Spell Checker");
118+
119+
if(!Directory.Exists(configPath))
120+
Directory.CreateDirectory(configPath);
121+
122+
return configPath;
123+
}
124+
}
125+
#pragma warning restore RS1035
126+
97127
#endregion
98128

99129
#region Constructor
@@ -107,26 +137,65 @@ public class CSharpSpellCheckCodeAnalyzer : DiagnosticAnalyzer
107137
/// resolver to load them.</remarks>
108138
static CSharpSpellCheckCodeAnalyzer()
109139
{
140+
// This is a pain but for some reason starting in v17.12, Visual Studio started running the actual
141+
// code analysis in a separate copy of the assembly in a cache folder and we can no longer find our
142+
// dependencies. The odd thing is that it loads a copy to initialize it from the expected location.
143+
// As such, I'm forced to store a copy of the real path in a location the other copy can get to.
144+
// I haven't been able to find a better way around this. If you know of one, let me know.
145+
string analyzerCodePath = Path.Combine(GlobalConfigurationFilePath, "AnalyzerCodePath.txt");
146+
147+
var analyzerPaths = new List<string>();
148+
149+
if(File.Exists(analyzerCodePath))
150+
analyzerPaths.AddRange(File.ReadAllLines(analyzerCodePath));
151+
152+
var asm = Assembly.GetExecutingAssembly();
153+
string analyzerPath = analyzerPaths.FirstOrDefault(p => p.StartsWith(asm.FullName, StringComparison.Ordinal));
154+
155+
if(String.IsNullOrWhiteSpace(analyzerPath))
156+
{
157+
string dependencyPath = Path.GetDirectoryName(asm.Location);
158+
159+
if(File.Exists(Path.Combine(dependencyPath, "VisualStudio.SpellChecker.Common.dll")))
160+
{
161+
analyzerPaths.Add($"{asm.FullName}|{dependencyPath}");
162+
File.WriteAllLines(analyzerCodePath, analyzerPaths);
163+
codeAnalyzerPath = dependencyPath;
164+
}
165+
}
166+
else
167+
{
168+
int pos = analyzerPath.IndexOf('|');
169+
170+
if(pos != -1)
171+
codeAnalyzerPath = analyzerPath.Substring(pos + 1);
172+
}
173+
110174
AppDomain.CurrentDomain.AssemblyResolve += (s, e) =>
111175
{
112176
Assembly refAsm = null;
113177

114178
// We do see our own assembly name come through here so we'll ignore it
115-
if(!String.IsNullOrWhiteSpace(e.Name) &&
116-
!e.Name.StartsWith(Assembly.GetExecutingAssembly().GetName().Name, StringComparison.OrdinalIgnoreCase) &&
179+
if(!String.IsNullOrWhiteSpace(e.Name) && !String.IsNullOrWhiteSpace(codeAnalyzerPath) &&
180+
!e.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase) &&
181+
!e.Name.StartsWith(asm.GetName().Name, StringComparison.OrdinalIgnoreCase) &&
117182
!referenceAssemblies.TryGetValue(e.Name, out refAsm))
118183
{
119184
int pos = e.Name.IndexOf(',');
120185

121186
if(pos != -1)
122187
{
123-
string assemblyName = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
124-
e.Name.Substring(0, pos) + ".dll");
188+
string assemblyName = Path.Combine(codeAnalyzerPath, e.Name.Substring(0, pos) + ".dll");
125189

126190
// If not found it's probably looking for a resources assembly that doesn't exist so
127191
// we'll ignore it.
128192
if(File.Exists(assemblyName))
193+
{
129194
refAsm = Assembly.LoadFile(assemblyName);
195+
196+
if(refAsm.FullName != e.Name)
197+
refAsm = null;
198+
}
130199
}
131200

132201
referenceAssemblies[e.Name] = refAsm;

0 commit comments

Comments
 (0)