@@ -22,31 +22,42 @@ public override void Initialize(AnalysisContext context)
2222 if ( ! Debugger . IsAttached )
2323 context . EnableConcurrentExecution ( ) ;
2424
25- context . RegisterSyntaxNodeAction ( Analyze , SyntaxKind . ClassDeclaration ) ;
26- context . RegisterSyntaxNodeAction ( Analyze , SyntaxKind . StructDeclaration ) ;
27- context . RegisterSyntaxNodeAction ( Analyze , SyntaxKind . RecordDeclaration ) ;
28- context . RegisterSyntaxNodeAction ( Analyze , SyntaxKind . RecordStructDeclaration ) ;
25+ context . RegisterCompilationStartAction ( start =>
26+ {
27+ var known = new KnownTypes ( start . Compilation ) ;
28+ if ( known . IStructId is null || known . IStructIdT is null )
29+ return ;
30+
31+ start . RegisterSymbolAction ( AnalyzeSymbol , SymbolKind . NamedType ) ;
32+ } ) ;
2933 }
3034
31- static void Analyze ( SyntaxNodeAnalysisContext context )
35+ static void AnalyzeSymbol ( SymbolAnalysisContext context )
3236 {
37+ if ( context . Symbol is not INamedTypeSymbol symbol )
38+ return ;
39+
3340 var known = new KnownTypes ( context . Compilation ) ;
3441
35- if ( context . Node is not TypeDeclarationSyntax typeDeclaration ||
36- known . IStructIdT is not { } structIdTypeOfT ||
37- known . IStructId is not { } structIdType )
42+ // We only care about IStructId and IStructId<T>
43+ if ( ! symbol . Is ( known . IStructId ) && ! symbol . Is ( known . IStructIdT ) )
3844 return ;
3945
40- var symbol = context . SemanticModel . GetDeclaredSymbol ( typeDeclaration ) ;
41- if ( symbol is null )
46+ // We can only analyze if there's a declaration in source.
47+ if ( symbol . DeclaringSyntaxReferences . Length == 0 ||
48+ symbol . DeclaringSyntaxReferences
49+ . Select ( x => x . GetSyntax ( ) )
50+ . OfType < TypeDeclarationSyntax > ( )
51+ . FirstOrDefault ( ) is not { } typeDeclaration )
4252 return ;
4353
44- if ( ! symbol . Is ( structIdType ) && ! symbol . Is ( structIdTypeOfT ) )
45- return ;
54+ // TODO: report or ignore if more than one declaration?
4655
4756 // If there's only one declaration and it's not partial
48- var report = symbol . DeclaringSyntaxReferences . Length == 1 && ! typeDeclaration . Modifiers . Any ( SyntaxKind . PartialKeyword ) ;
49- report |= ! typeDeclaration . IsKind ( SyntaxKind . RecordStructDeclaration ) || ! symbol . IsReadOnly ;
57+ var report = symbol . DeclaringSyntaxReferences . Length == 1 &&
58+ ! typeDeclaration . Modifiers . Any ( SyntaxKind . PartialKeyword ) ;
59+
60+ report |= ! symbol . IsRecord || symbol . TypeKind != TypeKind . Struct || ! symbol . IsReadOnly ;
5061
5162 if ( report )
5263 {
@@ -55,7 +66,7 @@ known.IStructIdT is not { } structIdTypeOfT ||
5566 else if ( typeDeclaration . BaseList ? . Types . FirstOrDefault ( t => t . Type is IdentifierNameSyntax { Identifier . Text : "IStructId" } ) is { } implementation )
5667 context . ReportDiagnostic ( Diagnostic . Create ( MustBeRecordStruct , implementation . GetLocation ( ) , symbol . Name ) ) ;
5768 else
58- context . ReportDiagnostic ( Diagnostic . Create ( MustBeRecordStruct , typeDeclaration . Identifier . GetLocation ( ) , symbol . Name ) ) ;
69+ context . ReportDiagnostic ( Diagnostic . Create ( MustBeRecordStruct , symbol . Locations . FirstOrDefault ( ) , symbol . Name ) ) ;
5970 }
6071
6172 if ( typeDeclaration . ParameterList is null )
@@ -72,6 +83,5 @@ known.IStructIdT is not { } structIdTypeOfT ||
7283 var parameter = typeDeclaration . ParameterList . Parameters [ 0 ] ;
7384 if ( parameter . Identifier . Text != "Value" )
7485 context . ReportDiagnostic ( Diagnostic . Create ( MustHaveValueConstructor , parameter . Identifier . GetLocation ( ) , symbol . Name ) ) ;
75-
7686 }
7787}
0 commit comments