@@ -5,6 +5,7 @@ namespace StyleCop.Analyzers.ReadabilityRules
55{
66 using System ;
77 using System . Collections . Immutable ;
8+ using System . Linq ;
89 using Microsoft . CodeAnalysis ;
910 using Microsoft . CodeAnalysis . CSharp ;
1011 using Microsoft . CodeAnalysis . CSharp . Syntax ;
@@ -21,6 +22,12 @@ internal class SA1130UseLambdaSyntax : DiagnosticAnalyzer
2122 /// The ID for diagnostics produced by the <see cref="SA1130UseLambdaSyntax"/> analyzer.
2223 /// </summary>
2324 public const string DiagnosticId = "SA1130" ;
25+
26+ /// <summary>
27+ /// Property identifier used to pass information to the codefix.
28+ /// </summary>
29+ internal const string DelegateArgumentNamesProperty = "DelegateArgumentNames" ;
30+
2431 private static readonly LocalizableString Title = new LocalizableResourceString ( nameof ( ReadabilityResources . SA1130Title ) , ReadabilityResources . ResourceManager , typeof ( ReadabilityResources ) ) ;
2532 private static readonly LocalizableString MessageFormat = new LocalizableResourceString ( nameof ( ReadabilityResources . SA1130MessageFormat ) , ReadabilityResources . ResourceManager , typeof ( ReadabilityResources ) ) ;
2633 private static readonly LocalizableString Description = new LocalizableResourceString ( nameof ( ReadabilityResources . SA1130Description ) , ReadabilityResources . ResourceManager , typeof ( ReadabilityResources ) ) ;
@@ -47,35 +54,130 @@ public override void Initialize(AnalysisContext context)
4754
4855 private static void HandleAnonymousMethodExpression ( SyntaxNodeAnalysisContext context )
4956 {
57+ var diagnosticProperties = ImmutableDictionary . CreateBuilder < string , string > ( ) ;
58+
59+ bool reportDiagnostic = true ;
5060 var anonymousMethod = ( AnonymousMethodExpressionSyntax ) context . Node ;
5161
52- if ( anonymousMethod . Parent . IsKind ( SyntaxKind . Argument ) )
62+ switch ( anonymousMethod . Parent . Kind ( ) )
5363 {
54- // invocation -> argument list -> argument -> anonymous method
55- if ( anonymousMethod ? . Parent ? . Parent ? . Parent is InvocationExpressionSyntax originalInvocationExpression )
64+ case SyntaxKind . Argument :
65+ reportDiagnostic = HandleMethodInvocation ( context . SemanticModel , anonymousMethod , ( ArgumentSyntax ) anonymousMethod . Parent , diagnosticProperties ) ;
66+ break ;
67+
68+ case SyntaxKind . EqualsValueClause :
69+ reportDiagnostic = HandleAssignment ( context . SemanticModel , ( EqualsValueClauseSyntax ) anonymousMethod . Parent , diagnosticProperties ) ;
70+ break ;
71+
72+ case SyntaxKind . AddAssignmentExpression :
73+ case SyntaxKind . SubtractAssignmentExpression :
74+ reportDiagnostic = HandleAssignmentExpression ( context . SemanticModel , anonymousMethod , ( AssignmentExpressionSyntax ) anonymousMethod . Parent , diagnosticProperties ) ;
75+ break ;
76+ }
77+
78+ if ( reportDiagnostic )
79+ {
80+ context . ReportDiagnostic ( Diagnostic . Create ( Descriptor , anonymousMethod . DelegateKeyword . GetLocation ( ) , diagnosticProperties . ToImmutable ( ) ) ) ;
81+ }
82+ }
83+
84+ private static bool HandleMethodInvocation ( SemanticModel semanticModel , AnonymousMethodExpressionSyntax anonymousMethod , ArgumentSyntax argumentSyntax , ImmutableDictionary < string , string > . Builder propertiesBuilder )
85+ {
86+ // invocation -> argument list -> argument -> anonymous method
87+ var argumentListSyntax = argumentSyntax ? . Parent as ArgumentListSyntax ;
88+
89+ var originalInvocationExpression = argumentListSyntax ? . Parent as InvocationExpressionSyntax ;
90+ if ( originalInvocationExpression != null )
91+ {
92+ SymbolInfo originalSymbolInfo = semanticModel . GetSymbolInfo ( originalInvocationExpression ) ;
93+ Location location = originalInvocationExpression . GetLocation ( ) ;
94+
95+ var argumentIndex = argumentListSyntax . Arguments . IndexOf ( argumentSyntax ) ;
96+ var parameterList = GetDelegateParameterList ( originalSymbolInfo , argumentIndex ) ;
97+
98+ // In some cases passing a delegate as an argument to a method is required to call the right overload
99+ // When there is an other overload that takes an expression.
100+ var lambdaExpression = SyntaxFactory . ParenthesizedLambdaExpression (
101+ anonymousMethod . AsyncKeyword ,
102+ parameterList ,
103+ SyntaxFactory . Token ( SyntaxKind . EqualsGreaterThanToken ) ,
104+ anonymousMethod . Body ) ;
105+
106+ var invocationExpression = originalInvocationExpression . ReplaceNode ( anonymousMethod , lambdaExpression ) ;
107+ SymbolInfo newSymbolInfo = semanticModel . GetSpeculativeSymbolInfo ( location . SourceSpan . Start , invocationExpression , SpeculativeBindingOption . BindAsExpression ) ;
108+
109+ if ( originalSymbolInfo . Symbol != newSymbolInfo . Symbol )
56110 {
57- // In some cases passing a delegate as an argument to a method is required to call the right overload
58- // When there is an other overload that takes an expression.
59- var lambdaExpression = SyntaxFactory . ParenthesizedLambdaExpression (
60- anonymousMethod . AsyncKeyword ,
61- anonymousMethod . ParameterList ?? EmptyParameterList ,
62- SyntaxFactory . Token ( SyntaxKind . EqualsGreaterThanToken ) ,
63- anonymousMethod . Body ) ;
64-
65- var invocationExpression = originalInvocationExpression . ReplaceNode ( anonymousMethod , lambdaExpression ) ;
66-
67- SymbolInfo originalSymbolInfo = context . SemanticModel . GetSymbolInfo ( originalInvocationExpression ) ;
68- Location location = originalInvocationExpression . GetLocation ( ) ;
69- SymbolInfo newSymbolInfo = context . SemanticModel . GetSpeculativeSymbolInfo ( location . SourceSpan . Start , invocationExpression , SpeculativeBindingOption . BindAsExpression ) ;
70-
71- if ( originalSymbolInfo . Symbol != newSymbolInfo . Symbol )
72- {
73- return ;
74- }
111+ return false ;
75112 }
113+
114+ var parameterNames = parameterList . Parameters . Select ( p => p . Identifier . ToString ( ) ) ;
115+ propertiesBuilder . Add ( DelegateArgumentNamesProperty , string . Join ( "," , parameterNames ) ) ;
116+ }
117+
118+ return true ;
119+ }
120+
121+ private static bool HandleAssignment ( SemanticModel semanticModel , EqualsValueClauseSyntax equalsValueClauseSyntax , ImmutableDictionary < string , string > . Builder propertiesBuilder )
122+ {
123+ var variableDeclaration = ( VariableDeclarationSyntax ) equalsValueClauseSyntax . Parent . Parent ;
124+ var symbol = semanticModel . GetSymbolInfo ( variableDeclaration . Type ) ;
125+
126+ var namedTypeSymbol = symbol . Symbol as INamedTypeSymbol ;
127+ if ( namedTypeSymbol ? . TypeKind == TypeKind . Delegate )
128+ {
129+ var delegateParameters = namedTypeSymbol . DelegateInvokeMethod . Parameters ;
130+ propertiesBuilder . Add ( DelegateArgumentNamesProperty , string . Join ( "," , delegateParameters . Select ( ps => ps . Name ) ) ) ;
131+ return true ;
76132 }
77133
78- context . ReportDiagnostic ( Diagnostic . Create ( Descriptor , anonymousMethod . DelegateKeyword . GetLocation ( ) ) ) ;
134+ return false ;
135+ }
136+
137+ private static bool HandleAssignmentExpression ( SemanticModel semanticModel , AnonymousMethodExpressionSyntax anonymousMethod , AssignmentExpressionSyntax assignmentExpressionSyntax , ImmutableDictionary < string , string > . Builder propertiesBuilder )
138+ {
139+ var symbol = semanticModel . GetSymbolInfo ( assignmentExpressionSyntax . Left ) ;
140+
141+ var eventSymbol = symbol . Symbol as IEventSymbol ;
142+ if ( eventSymbol ? . Type . TypeKind == TypeKind . Delegate )
143+ {
144+ var delegateParameters = ( ( INamedTypeSymbol ) eventSymbol . Type ) . DelegateInvokeMethod . Parameters ;
145+ propertiesBuilder . Add ( DelegateArgumentNamesProperty , string . Join ( "," , delegateParameters . Select ( ps => ps . Name ) ) ) ;
146+ return true ;
147+ }
148+
149+ return false ;
150+ }
151+
152+ private static ParameterListSyntax GetDelegateParameterList ( SymbolInfo originalSymbolInfo , int argumentIndex )
153+ {
154+ // Determine the parameter list from the method that is invoked, as delegates without parameters are allowed, but they cannot be replaced by a lambda without parameters.
155+ var methodSymbol = ( IMethodSymbol ) originalSymbolInfo . Symbol ;
156+ var delegateType = ( INamedTypeSymbol ) methodSymbol . Parameters [ argumentIndex ] . Type ;
157+ var delegateParameters = delegateType . DelegateInvokeMethod . Parameters ;
158+
159+ var syntaxParameters = GetSyntaxParametersFromSymbolParameters ( delegateParameters ) ;
160+
161+ return SyntaxFactory . ParameterList ( SyntaxFactory . SeparatedList ( syntaxParameters ) ) ;
162+ }
163+
164+ private static ImmutableArray < ParameterSyntax > GetSyntaxParametersFromSymbolParameters ( ImmutableArray < IParameterSymbol > symbolParameters )
165+ {
166+ var result = ImmutableArray . CreateBuilder < ParameterSyntax > ( symbolParameters . Length ) ;
167+
168+ foreach ( var symbolParameter in symbolParameters )
169+ {
170+ var syntaxParameter = GetParameterSyntaxFromParameterSymbol ( symbolParameter ) ;
171+ result . Add ( syntaxParameter ) ;
172+ }
173+
174+ return result . ToImmutable ( ) ;
175+ }
176+
177+ private static ParameterSyntax GetParameterSyntaxFromParameterSymbol ( IParameterSymbol symbolParameter )
178+ {
179+ return SyntaxFactory . Parameter ( SyntaxFactory . Identifier ( symbolParameter . Name ) )
180+ . WithType ( SyntaxFactory . ParseTypeName ( symbolParameter . Type . Name ) ) ;
79181 }
80182 }
81183}
0 commit comments