@@ -77,6 +77,24 @@ private static void AnalyzeMethod(SymbolAnalysisContext context)
7777 return ;
7878 }
7979
80+ // Only analyze methods declared in source (skip metadata / external assemblies).
81+ var hasSourceLocation = false ;
82+ foreach ( var location in method . Locations )
83+ {
84+ if ( ! location . IsInSource )
85+ {
86+ continue ;
87+ }
88+
89+ hasSourceLocation = true ;
90+ break ;
91+ }
92+
93+ if ( ! hasSourceLocation )
94+ {
95+ return ;
96+ }
97+
8098 if ( method . ReturnType is not INamedTypeSymbol returnType )
8199 {
82100 return ;
@@ -123,27 +141,42 @@ private static void AnalyzeAsyncMember(IMethodSymbol method,
123141 string displayName ,
124142 Action < Diagnostic > report )
125143 {
126- // PT0001 – name must end with Async (for named methods).
144+ var isContract = method . IsContractImplementation ( ) ;
145+
146+ // PT0001 – name must end with Async (for named methods),
147+ // but we DO NOT enforce it on contract implementations (MediatR Handle, overrides, etc.).
127148 if ( ! displayName . Equals ( "anonymous function" , StringComparison . Ordinal ) &&
128- ! displayName . EndsWith ( "Async" , StringComparison . Ordinal ) )
149+ ! displayName . EndsWith ( "Async" , StringComparison . Ordinal ) &&
150+ ! isContract )
129151 {
130152 report ( Diagnostic . Create ( AsyncSuffixRule , location , displayName ) ) ;
131153 }
132154
133155 var ctInfo = GetCancellationTokenInfo ( method ) ;
134156
157+ // PT0002 – missing CancellationToken
158+ // Only enforced on non-contract methods (e.g. interface itself, normal class methods).
135159 if ( ! ctInfo . HasCt )
136160 {
137- report ( Diagnostic . Create ( CancellationTokenMissingRule , location , displayName ) ) ;
161+ if ( ! isContract )
162+ {
163+ report ( Diagnostic . Create ( CancellationTokenMissingRule , location , displayName ) ) ;
164+ }
165+
138166 return ;
139167 }
140168
169+ // PT0003 – name must be ct
170+ // Always enforced when CT exists (both interface + implementation),
171+ // so implementations still get "rename to ct".
141172 if ( ! ctInfo . IsNamedCt )
142173 {
143174 report ( Diagnostic . Create ( CancellationTokenNameRule , location , displayName , ctInfo . Name ) ) ;
144175 }
145176
146- if ( ! ctInfo . IsLast )
177+ // PT0004 – CT must be last
178+ // Only enforced on non-contract methods (interface / own class methods).
179+ if ( ! ctInfo . IsLast && ! isContract )
147180 {
148181 report ( Diagnostic . Create ( CancellationTokenPositionRule , location , displayName , ctInfo . Name ) ) ;
149182 }
0 commit comments