11#nullable enable
22using System . Collections . Generic ;
33using System . Collections . Immutable ;
4+ using System . Diagnostics ;
45using Microsoft . CodeAnalysis ;
56using Microsoft . CodeAnalysis . CSharp ;
67using Microsoft . CodeAnalysis . CSharp . Syntax ;
78using Microsoft . CodeAnalysis . Diagnostics ;
89using Robust . Roslyn . Shared ;
10+ using Robust . Roslyn . Shared . Helpers ;
911using Robust . Shared . Serialization . Manager . Definition ;
1012using Robust . Shared . ViewVariables ;
1113
@@ -14,9 +16,6 @@ namespace Robust.Analyzers;
1416[ DiagnosticAnalyzer ( LanguageNames . CSharp ) ]
1517public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
1618{
17- private const string DataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.DataDefinitionAttribute" ;
18- private const string ImplicitDataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.ImplicitDataDefinitionForInheritorsAttribute" ;
19- private const string MeansDataDefinitionNamespace = "Robust.Shared.Serialization.Manager.Attributes.MeansDataDefinitionAttribute" ;
2019 private const string DataFieldBaseNamespace = "Robust.Shared.Serialization.Manager.Attributes.DataFieldBaseAttribute" ;
2120 private const string ViewVariablesNamespace = "Robust.Shared.ViewVariables.ViewVariablesAttribute" ;
2221 private const string NotYamlSerializableName = "Robust.Shared.Serialization.Manager.Attributes.NotYamlSerializableAttribute" ;
@@ -43,16 +42,6 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
4342 "Make sure to mark any type containing a nested data definition as partial."
4443 ) ;
4544
46- public static readonly DiagnosticDescriptor DataFieldWritableRule = new (
47- Diagnostics . IdDataFieldWritable ,
48- "Data field must not be readonly" ,
49- "Data field {0} in data definition {1} is readonly" ,
50- "Usage" ,
51- DiagnosticSeverity . Error ,
52- true ,
53- "Make sure to remove the readonly modifier."
54- ) ;
55-
5645 public static readonly DiagnosticDescriptor DataFieldPropertyWritableRule = new (
5746 Diagnostics . IdDataFieldPropertyWritable ,
5847 "Data field property must have a setter" ,
@@ -93,9 +82,24 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
9382 "Make sure to use a type that is YAML serializable."
9483 ) ;
9584
85+ public static readonly DiagnosticDescriptor DataFieldOutsideDefinition = new (
86+ Diagnostics . IdDataFieldOutsideDefinition ,
87+ "Data field defined in a type that is not marked as a data definition or data record" ,
88+ "Data field {0} is defined in type {1} which is not a data definition or data record" ,
89+ "Usage" ,
90+ DiagnosticSeverity . Error ,
91+ true ,
92+ "Make sure to add a data definition or data record attribute, or inherit a type or add an attribute that implicitly makes its inheritors data definitions or data records."
93+ ) ;
94+
9695 public override ImmutableArray < DiagnosticDescriptor > SupportedDiagnostics => ImmutableArray . Create (
97- DataDefinitionPartialRule , NestedDataDefinitionPartialRule , DataFieldWritableRule , DataFieldPropertyWritableRule ,
98- DataFieldRedundantTagRule , DataFieldNoVVReadWriteRule , DataFieldYamlSerializableRule
96+ DataDefinitionPartialRule ,
97+ NestedDataDefinitionPartialRule ,
98+ DataFieldPropertyWritableRule ,
99+ DataFieldRedundantTagRule ,
100+ DataFieldNoVVReadWriteRule ,
101+ DataFieldYamlSerializableRule ,
102+ DataFieldOutsideDefinition
99103 ) ;
100104
101105 public override void Initialize ( AnalysisContext context )
@@ -108,6 +112,9 @@ public override void Initialize(AnalysisContext context)
108112 if ( symbolContext . Symbol is not INamedTypeSymbol typeSymbol )
109113 return ;
110114
115+ symbolContext . RegisterSyntaxNodeAction ( AnalyzeDataField , SyntaxKind . FieldDeclaration ) ;
116+ symbolContext . RegisterSyntaxNodeAction ( AnalyzeDataFieldProperty , SyntaxKind . PropertyDeclaration ) ;
117+
111118 if ( ! IsDataDefinition ( typeSymbol ) )
112119 return ;
113120
@@ -116,9 +123,6 @@ public override void Initialize(AnalysisContext context)
116123 symbolContext . RegisterSyntaxNodeAction ( AnalyzeDataDefinition , SyntaxKind . RecordDeclaration ) ;
117124 symbolContext . RegisterSyntaxNodeAction ( AnalyzeDataDefinition , SyntaxKind . RecordStructDeclaration ) ;
118125 symbolContext . RegisterSyntaxNodeAction ( AnalyzeDataDefinition , SyntaxKind . InterfaceDeclaration ) ;
119-
120- symbolContext . RegisterSyntaxNodeAction ( AnalyzeDataField , SyntaxKind . FieldDeclaration ) ;
121- symbolContext . RegisterSyntaxNodeAction ( AnalyzeDataFieldProperty , SyntaxKind . PropertyDeclaration ) ;
122126 } , SymbolKind . NamedType ) ;
123127 }
124128
@@ -156,20 +160,21 @@ private static void AnalyzeDataField(SyntaxNodeAnalysisContext context)
156160 if ( context . ContainingSymbol ? . ContainingType is not INamedTypeSymbol type )
157161 return ;
158162
163+ var isDataDefinition = DataDefinitionHelper . IsDataDefinition ( type , out var isDataRecord ) ;
159164 foreach ( var variable in field . Declaration . Variables )
160165 {
161166 var fieldSymbol = context . SemanticModel . GetDeclaredSymbol ( variable ) ;
162167
163168 if ( fieldSymbol == null )
164169 continue ;
165170
166- if ( ! IsDataField ( fieldSymbol , out _ , out var datafieldAttribute ) )
171+ if ( ! DataDefinitionHelper . IsDataField ( fieldSymbol , isDataRecord , out _ , out var datafieldAttribute ) )
167172 continue ;
168173
169- if ( IsReadOnlyDataField ( type , fieldSymbol ) )
174+ if ( ! isDataDefinition )
170175 {
171- TryGetModifierLocation ( field , SyntaxKind . ReadOnlyKeyword , out var location ) ;
172- context . ReportDiagnostic ( Diagnostic . Create ( DataFieldWritableRule , location , fieldSymbol . Name , type . Name ) ) ;
176+ var location = type . DeclaringSyntaxReferences [ 0 ] . GetSyntax ( ) . GetLocation ( ) ;
177+ context . ReportDiagnostic ( Diagnostic . Create ( DataFieldOutsideDefinition , location , fieldSymbol . Name , type . Name ) ) ;
173178 }
174179
175180 if ( HasRedundantTag ( fieldSymbol , datafieldAttribute ) )
@@ -212,19 +217,30 @@ private static void AnalyzeDataFieldProperty(SyntaxNodeAnalysisContext context)
212217 if ( propertySymbol . ContainingType is not INamedTypeSymbol type )
213218 return ;
214219
215- if ( type . IsRecord || type . IsValueType )
220+ if ( propertySymbol == null )
216221 return ;
217222
218- if ( propertySymbol == null )
223+ var isDataDefinition = DataDefinitionHelper . IsDataDefinition ( type , out var isDataRecord ) ;
224+ if ( isDataDefinition &&
225+ ! isDataRecord &&
226+ propertySymbol . SetMethod == null &&
227+ HasDataFieldAttribute ( propertySymbol ) )
228+ {
229+ context . ReportDiagnostic ( Diagnostic . Create (
230+ DataFieldPropertyWritableRule ,
231+ property . AccessorList ? . GetLocation ( ) ?? property . GetLocation ( ) ,
232+ propertySymbol . Name ,
233+ type . Name ) ) ;
219234 return ;
235+ }
220236
221- if ( ! IsDataField ( propertySymbol , out _ , out var datafieldAttribute ) )
237+ if ( ! DataDefinitionHelper . IsDataField ( propertySymbol , isDataRecord , out _ , out var datafieldAttribute ) )
222238 return ;
223239
224- if ( IsReadOnlyDataField ( type , propertySymbol ) )
240+ if ( ! isDataDefinition )
225241 {
226- var location = property . AccessorList != null ? property . AccessorList . GetLocation ( ) : property . GetLocation ( ) ;
227- context . ReportDiagnostic ( Diagnostic . Create ( DataFieldPropertyWritableRule , location , propertySymbol . Name , type . Name ) ) ;
242+ var location = type . DeclaringSyntaxReferences [ 0 ] . GetSyntax ( ) . GetLocation ( ) ;
243+ context . ReportDiagnostic ( Diagnostic . Create ( DataFieldOutsideDefinition , location , propertySymbol . Name , type . Name ) ) ;
228244 }
229245
230246 if ( HasRedundantTag ( propertySymbol , datafieldAttribute ) )
@@ -255,9 +271,16 @@ private static void AnalyzeDataFieldProperty(SyntaxNodeAnalysisContext context)
255271 }
256272 }
257273
258- private static bool IsReadOnlyDataField ( ITypeSymbol type , ISymbol field )
274+ private static bool HasDataFieldAttribute ( ISymbol symbol )
259275 {
260- return IsReadOnlyMember ( type , field ) ;
276+ foreach ( var attribute in symbol . GetAttributes ( ) )
277+ {
278+ if ( attribute . AttributeClass is { } attributeClass &&
279+ TypeSymbolHelper . Inherits ( attributeClass , DataFieldBaseNamespace ) )
280+ return true ;
281+ }
282+
283+ return false ;
261284 }
262285
263286 private static bool IsPartial ( TypeDeclarationSyntax type )
@@ -270,53 +293,7 @@ private static bool IsDataDefinition(ITypeSymbol? type)
270293 if ( type == null )
271294 return false ;
272295
273- return HasAttribute ( type , DataDefinitionNamespace ) ||
274- MeansDataDefinition ( type ) ||
275- IsImplicitDataDefinition ( type ) ;
276- }
277-
278- private static bool IsDataField ( ISymbol member , out ITypeSymbol type , out AttributeData attribute )
279- {
280- // TODO data records and other attributes
281- if ( member is IFieldSymbol field )
282- {
283- foreach ( var attr in field . GetAttributes ( ) )
284- {
285- if ( attr . AttributeClass != null && Inherits ( attr . AttributeClass , DataFieldBaseNamespace ) )
286- {
287- type = field . Type ;
288- attribute = attr ;
289- return true ;
290- }
291- }
292- }
293- else if ( member is IPropertySymbol property )
294- {
295- foreach ( var attr in property . GetAttributes ( ) )
296- {
297- if ( attr . AttributeClass != null && Inherits ( attr . AttributeClass , DataFieldBaseNamespace ) )
298- {
299- type = property . Type ;
300- attribute = attr ;
301- return true ;
302- }
303- }
304- }
305-
306- type = null ! ;
307- attribute = null ! ;
308- return false ;
309- }
310-
311- private static bool Inherits ( ITypeSymbol type , string parent )
312- {
313- foreach ( var baseType in GetBaseTypes ( type ) )
314- {
315- if ( baseType . ToDisplayString ( ) == parent )
316- return true ;
317- }
318-
319- return false ;
296+ return DataDefinitionHelper . IsDataDefinition ( type , out _ ) ;
320297 }
321298
322299 private static bool TryGetAttributeLocation ( MemberDeclarationSyntax syntax , string attributeName , out Location location )
@@ -382,14 +359,14 @@ private static bool HasAttribute(ITypeSymbol type, string attributeName)
382359 return false ;
383360 }
384361
385- private static bool HasRedundantTag ( ISymbol symbol , AttributeData datafieldAttribute )
362+ private static bool HasRedundantTag ( ISymbol symbol , DataFieldAttribute datafieldAttribute )
386363 {
387364 // No args, no problem
388- if ( datafieldAttribute . ConstructorArguments . Length == 0 )
365+ if ( datafieldAttribute . Data is not { ConstructorArguments . Length : > 0 } )
389366 return false ;
390367
391368 // If a tag is explicitly specified, it will be the first argument...
392- var tagArgument = datafieldAttribute . ConstructorArguments [ 0 ] ;
369+ var tagArgument = datafieldAttribute . Data . ConstructorArguments [ 0 ] ;
393370 // ...but the first arg could also something else, since tag is optional
394371 // so we make sure that it's a string
395372 if ( tagArgument . Value is not string explicitName )
@@ -427,65 +404,8 @@ private static bool HasVVReadWrite(ISymbol symbol)
427404 return ( VVAccess ) accessByte == VVAccess . ReadWrite ;
428405 }
429406
430- private static bool MeansDataDefinition ( ITypeSymbol type )
431- {
432- foreach ( var attribute in type . GetAttributes ( ) )
433- {
434- if ( attribute . AttributeClass is null )
435- continue ;
436-
437- if ( HasAttribute ( attribute . AttributeClass , MeansDataDefinitionNamespace ) )
438- return true ;
439- }
440- return false ;
441- }
442-
443407 private static bool IsNotYamlSerializable ( ISymbol field , ITypeSymbol type )
444408 {
445409 return HasAttribute ( type , NotYamlSerializableName ) ;
446410 }
447-
448- private static bool IsImplicitDataDefinition ( ITypeSymbol type )
449- {
450- if ( HasAttribute ( type , ImplicitDataDefinitionNamespace ) )
451- return true ;
452-
453- foreach ( var baseType in GetBaseTypes ( type ) )
454- {
455- if ( HasAttribute ( baseType , ImplicitDataDefinitionNamespace ) )
456- return true ;
457- }
458-
459- foreach ( var @interface in type . AllInterfaces )
460- {
461- if ( IsImplicitDataDefinitionInterface ( @interface ) )
462- return true ;
463- }
464-
465- return false ;
466- }
467-
468- private static bool IsImplicitDataDefinitionInterface ( ITypeSymbol @interface )
469- {
470- if ( HasAttribute ( @interface , ImplicitDataDefinitionNamespace ) )
471- return true ;
472-
473- foreach ( var subInterface in @interface . AllInterfaces )
474- {
475- if ( HasAttribute ( subInterface , ImplicitDataDefinitionNamespace ) )
476- return true ;
477- }
478-
479- return false ;
480- }
481-
482- private static IEnumerable < ITypeSymbol > GetBaseTypes ( ITypeSymbol type )
483- {
484- var baseType = type . BaseType ;
485- while ( baseType != null )
486- {
487- yield return baseType ;
488- baseType = baseType . BaseType ;
489- }
490- }
491411}
0 commit comments