11using System . Collections . Immutable ;
22using Microsoft . CodeAnalysis ;
3+ using Microsoft . CodeAnalysis . CSharp ;
4+ using Microsoft . CodeAnalysis . CSharp . Syntax ;
35using Microsoft . CodeAnalysis . Diagnostics ;
46using Microsoft . CodeAnalysis . Operations ;
57
@@ -16,8 +18,16 @@ public sealed class BoundAnalyzer : DiagnosticAnalyzer
1618 defaultSeverity : DiagnosticSeverity . Warning ,
1719 isEnabledByDefault : true ) ;
1820
21+ public static readonly DiagnosticDescriptor GetBoundFieldNonStaticLambda = new (
22+ id : "WHAM002" ,
23+ title : "GetBoundField called with non-static lambda" ,
24+ messageFormat : "GetBoundField lambda should be static to avoid delegate allocation on every access. Add the 'static' modifier." ,
25+ category : "WarHub.ArmouryModel" ,
26+ defaultSeverity : DiagnosticSeverity . Warning ,
27+ isEnabledByDefault : true ) ;
28+
1929 public override ImmutableArray < DiagnosticDescriptor > SupportedDiagnostics { get ; } =
20- ImmutableArray . Create ( GetBoundFieldWithoutBoundAttribute ) ;
30+ ImmutableArray . Create ( GetBoundFieldWithoutBoundAttribute , GetBoundFieldNonStaticLambda ) ;
2131
2232 public override void Initialize ( AnalysisContext context )
2333 {
@@ -33,6 +43,12 @@ private static void AnalyzeInvocation(OperationAnalysisContext context)
3343 if ( invocation . TargetMethod . Name != "GetBoundField" )
3444 return ;
3545
46+ CheckBoundAttribute ( context , invocation ) ;
47+ CheckStaticLambda ( context , invocation ) ;
48+ }
49+
50+ private static void CheckBoundAttribute ( OperationAnalysisContext context , IInvocationOperation invocation )
51+ {
3652 // Walk up to find the containing property
3753 var containingSymbol = context . ContainingSymbol ;
3854 if ( containingSymbol is not IMethodSymbol { AssociatedSymbol : IPropertySymbol property } )
@@ -59,4 +75,37 @@ private static void AnalyzeInvocation(OperationAnalysisContext context)
5975 context . ReportDiagnostic ( diagnostic ) ;
6076 }
6177 }
78+
79+ private static void CheckStaticLambda ( OperationAnalysisContext context , IInvocationOperation invocation )
80+ {
81+ // The last argument to GetBoundField is the binding lambda
82+ var args = invocation . Arguments ;
83+ if ( args . Length < 3 )
84+ return ;
85+
86+ var lambdaArg = args [ args . Length - 1 ] ;
87+ if ( lambdaArg . Value is not IDelegateCreationOperation delegateCreation )
88+ return ;
89+
90+ if ( delegateCreation . Target is not IAnonymousFunctionOperation anonymousFunc )
91+ return ;
92+
93+ // Check the syntax node for the 'static' modifier
94+ var syntax = anonymousFunc . Syntax ;
95+ bool isStatic = false ;
96+ if ( syntax is LambdaExpressionSyntax lambda )
97+ {
98+ isStatic = lambda . Modifiers . Any ( SyntaxKind . StaticKeyword ) ;
99+ }
100+ else if ( syntax is AnonymousMethodExpressionSyntax anonymousMethod )
101+ {
102+ isStatic = anonymousMethod . Modifiers . Any ( SyntaxKind . StaticKeyword ) ;
103+ }
104+
105+ if ( ! isStatic )
106+ {
107+ context . ReportDiagnostic (
108+ Diagnostic . Create ( GetBoundFieldNonStaticLambda , syntax . GetLocation ( ) ) ) ;
109+ }
110+ }
62111}
0 commit comments