Skip to content

Commit e38e16b

Browse files
thomhurstclaude
andauthored
fix: Cache SemanticModel and improve null handling patterns (#1639, #1572) (#1678)
- Cache GetSemanticModelAsync result in AsyncModuleCodeFixProvider to avoid duplicate async calls in the loop (fixes #1572) - Convert helper methods to static since they no longer need instance state - Make EscapeXmlComment and EscapeIdentifier accept nullable strings for consistent null handling (fixes #1639) - Return string.Empty instead of returning null from EscapeIdentifier 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent ef92749 commit e38e16b

2 files changed

Lines changed: 18 additions & 9 deletions

File tree

src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.CodeFixes/AsyncModuleCodeFixProvider.cs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,15 @@ private async Task<Document> AddAsync(CodeFixContext context, MethodDeclarationS
6161

6262
editor.SetModifiers(methodDeclarationSyntax, DeclarationModifiers.Override | DeclarationModifiers.Async);
6363

64+
// Cache the semantic model to avoid duplicate async calls
65+
var semanticModel = await context.Document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
66+
6467
foreach (var returnStatement in GetReturnStatements(methodDeclarationSyntax))
6568
{
6669
var expressionSyntax = returnStatement.ChildNodes().OfType<ExpressionSyntax>().First()!;
6770

68-
if (await IsTaskFromResult(expressionSyntax, context)
69-
|| await IsAsTaskExtension(expressionSyntax, context))
71+
if (IsTaskFromResult(expressionSyntax, semanticModel)
72+
|| IsAsTaskExtension(expressionSyntax, semanticModel))
7073
{
7174
var firstInnerExpression = expressionSyntax.ChildNodes().OfType<ArgumentListSyntax>().First().Arguments.First().Expression;
7275

@@ -97,14 +100,17 @@ private static ReturnStatementSyntax[] GetReturnStatements(MethodDeclarationSynt
97100
?? Array.Empty<ReturnStatementSyntax>();
98101
}
99102

100-
private async Task<bool> IsTaskFromResult(ExpressionSyntax expressionSyntax, CodeFixContext context)
103+
private static bool IsTaskFromResult(ExpressionSyntax expressionSyntax, SemanticModel? semanticModel)
101104
{
102105
if (expressionSyntax is not InvocationExpressionSyntax invocationExpressionSyntax)
103106
{
104107
return false;
105108
}
106109

107-
var semanticModel = await context.Document.GetSemanticModelAsync();
110+
if (semanticModel is null)
111+
{
112+
return false;
113+
}
108114

109115
var symbol = semanticModel.GetSymbolInfo(invocationExpressionSyntax).Symbol;
110116

@@ -119,14 +125,17 @@ private async Task<bool> IsTaskFromResult(ExpressionSyntax expressionSyntax, Cod
119125
&& methodSymbol.ContainingNamespace.ToDisplayString() == "System.Threading.Tasks";
120126
}
121127

122-
private async Task<bool> IsAsTaskExtension(ExpressionSyntax expressionSyntax, CodeFixContext context)
128+
private static bool IsAsTaskExtension(ExpressionSyntax expressionSyntax, SemanticModel? semanticModel)
123129
{
124130
if (expressionSyntax is not InvocationExpressionSyntax invocationExpressionSyntax)
125131
{
126132
return false;
127133
}
128134

129-
var semanticModel = await context.Document.GetSemanticModelAsync();
135+
if (semanticModel is null)
136+
{
137+
return false;
138+
}
130139

131140
var symbol = semanticModel.GetSymbolInfo(invocationExpressionSyntax).Symbol;
132141

tools/ModularPipelines.OptionsGenerator/src/ModularPipelines.OptionsGenerator/Generators/GeneratorUtils.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public static partial class GeneratorUtils
1414
/// <summary>
1515
/// Escapes text for use in XML documentation comments.
1616
/// </summary>
17-
public static string EscapeXmlComment(string text)
17+
public static string EscapeXmlComment(string? text)
1818
{
1919
if (string.IsNullOrEmpty(text))
2020
{
@@ -156,11 +156,11 @@ public static string ToPascalCase(string input)
156156
/// <summary>
157157
/// Escapes a C# identifier if it's a reserved keyword by prefixing with @.
158158
/// </summary>
159-
public static string EscapeIdentifier(string identifier)
159+
public static string EscapeIdentifier(string? identifier)
160160
{
161161
if (string.IsNullOrEmpty(identifier))
162162
{
163-
return identifier;
163+
return string.Empty;
164164
}
165165

166166
return CSharpKeywords.Contains(identifier) ? $"@{identifier}" : identifier;

0 commit comments

Comments
 (0)