1+ using System . Collections . Immutable ;
2+ using Microsoft . CodeAnalysis ;
3+ using Microsoft . CodeAnalysis . CSharp ;
4+ using Microsoft . CodeAnalysis . CSharp . Syntax ;
5+ using Microsoft . CodeAnalysis . Diagnostics ;
6+
7+ namespace DataverseAnalyzer ;
8+
9+ [ DiagnosticAnalyzer ( LanguageNames . CSharp ) ]
10+ public sealed class DuplicateConstructorParameterTypeAnalyzer : DiagnosticAnalyzer
11+ {
12+ private static readonly Lazy < DiagnosticDescriptor > LazyRule = new ( ( ) => new DiagnosticDescriptor (
13+ "CT0005" ,
14+ Resources . CT0005_Title ,
15+ Resources . CT0005_MessageFormat ,
16+ "Usage" ,
17+ DiagnosticSeverity . Warning ,
18+ isEnabledByDefault : true ,
19+ description : Resources . CT0005_Description ) ) ;
20+
21+ public static DiagnosticDescriptor Rule => LazyRule . Value ;
22+
23+ public override ImmutableArray < DiagnosticDescriptor > SupportedDiagnostics => ImmutableArray . Create ( Rule ) ;
24+
25+ public override void Initialize ( AnalysisContext context )
26+ {
27+ if ( context is null )
28+ {
29+ throw new ArgumentNullException ( nameof ( context ) ) ;
30+ }
31+
32+ context . ConfigureGeneratedCodeAnalysis ( GeneratedCodeAnalysisFlags . None ) ;
33+ context . EnableConcurrentExecution ( ) ;
34+
35+ context . RegisterSyntaxNodeAction ( AnalyzeConstructor , SyntaxKind . ConstructorDeclaration ) ;
36+ context . RegisterSyntaxNodeAction (
37+ AnalyzePrimaryConstructor ,
38+ SyntaxKind . ClassDeclaration ,
39+ SyntaxKind . StructDeclaration ,
40+ SyntaxKind . RecordDeclaration ,
41+ SyntaxKind . RecordStructDeclaration ) ;
42+ }
43+
44+ private static void AnalyzeConstructor ( SyntaxNodeAnalysisContext context )
45+ {
46+ var constructor = ( ConstructorDeclarationSyntax ) context . Node ;
47+ if ( constructor . ParameterList is null )
48+ return ;
49+
50+ AnalyzeParameterList ( context , constructor . ParameterList ) ;
51+ }
52+
53+ private static void AnalyzePrimaryConstructor ( SyntaxNodeAnalysisContext context )
54+ {
55+ var typeDeclaration = ( TypeDeclarationSyntax ) context . Node ;
56+ if ( typeDeclaration . ParameterList is null )
57+ return ;
58+
59+ AnalyzeParameterList ( context , typeDeclaration . ParameterList ) ;
60+ }
61+
62+ private static void AnalyzeParameterList ( SyntaxNodeAnalysisContext context , ParameterListSyntax parameterList )
63+ {
64+ var parameters = parameterList . Parameters ;
65+ if ( parameters . Count < 2 )
66+ return ;
67+
68+ var parametersByType = new Dictionary < ITypeSymbol , List < string > > ( SymbolEqualityComparer . Default ) ;
69+
70+ foreach ( var parameter in parameters )
71+ {
72+ if ( parameter . Type is null )
73+ continue ;
74+
75+ var typeInfo = context . SemanticModel . GetTypeInfo ( parameter . Type ) ;
76+ var typeSymbol = typeInfo . Type ;
77+
78+ if ( typeSymbol is null )
79+ continue ;
80+
81+ if ( ! IsDependencyInjectionType ( typeSymbol ) )
82+ continue ;
83+
84+ var paramName = parameter . Identifier . ValueText ;
85+
86+ if ( ! parametersByType . TryGetValue ( typeSymbol , out var paramNames ) )
87+ {
88+ paramNames = new List < string > ( ) ;
89+ parametersByType [ typeSymbol ] = paramNames ;
90+ }
91+
92+ paramNames . Add ( paramName ) ;
93+ }
94+
95+ foreach ( var kvp in parametersByType )
96+ {
97+ if ( kvp . Value . Count < 2 )
98+ continue ;
99+
100+ var typeSymbol = kvp . Key ;
101+ var paramNames = kvp . Value ;
102+
103+ var typeName = typeSymbol . ToDisplayString ( SymbolDisplayFormat . MinimallyQualifiedFormat ) ;
104+ var paramNamesJoined = string . Join ( ", " , paramNames ) ;
105+
106+ var diagnostic = Diagnostic . Create (
107+ Rule ,
108+ parameterList . GetLocation ( ) ,
109+ typeName ,
110+ paramNamesJoined ) ;
111+
112+ context . ReportDiagnostic ( diagnostic ) ;
113+ }
114+ }
115+
116+ private static bool IsDependencyInjectionType ( ITypeSymbol typeSymbol )
117+ {
118+ var typeName = typeSymbol . Name ;
119+ return typeName . EndsWith ( "Service" , StringComparison . Ordinal ) ||
120+ typeName . EndsWith ( "Repository" , StringComparison . Ordinal ) ||
121+ typeName . EndsWith ( "Handler" , StringComparison . Ordinal ) ||
122+ typeName . EndsWith ( "Provider" , StringComparison . Ordinal ) ||
123+ typeName . EndsWith ( "Factory" , StringComparison . Ordinal ) ||
124+ typeName . EndsWith ( "Manager" , StringComparison . Ordinal ) ||
125+ typeName . EndsWith ( "Client" , StringComparison . Ordinal ) ;
126+ }
127+ }
0 commit comments