Skip to content

Commit 34ce2ba

Browse files
* Add source generator for DataDefinition validation * Add source generator for reading * Add source generator for writing * Include prototypes and other meansdatadefinition types * Target ISerializationGenerated in data definitions * Murder * Use array, struct, enum methods * Source generate get field definitions * serv5 * Fix and pray * Fix release compile * Tayrtahn review * a * Generator bugfix --------- Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
1 parent ba96b63 commit 34ce2ba

41 files changed

Lines changed: 3343 additions & 1806 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Robust.Analyzers.Tests/DataDefinitionAnalyzerTest.cs

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -94,29 +94,6 @@ await Verifier(code,
9494
);
9595
}
9696

97-
[Test]
98-
public async Task ReadOnlyFieldTest()
99-
{
100-
const string code = """
101-
using Robust.Shared.Serialization.Manager.Attributes;
102-
103-
[DataDefinition]
104-
public sealed partial class Foo
105-
{
106-
[DataField]
107-
public readonly int Bad;
108-
109-
[DataField]
110-
public int Good;
111-
}
112-
""";
113-
114-
await Verifier(code,
115-
// /0/Test0.cs(7,12): error RA0019: Data field Bad in data definition Foo is readonly
116-
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldWritableRule).WithSpan(7, 12, 7, 20).WithArguments("Bad", "Foo")
117-
);
118-
}
119-
12097
[Test]
12198
public async Task PartialDataDefinitionTest()
12299
{

Robust.Analyzers/DataDefinitionAnalyzer.cs

Lines changed: 57 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
#nullable enable
22
using System.Collections.Generic;
33
using System.Collections.Immutable;
4+
using System.Diagnostics;
45
using Microsoft.CodeAnalysis;
56
using Microsoft.CodeAnalysis.CSharp;
67
using Microsoft.CodeAnalysis.CSharp.Syntax;
78
using Microsoft.CodeAnalysis.Diagnostics;
89
using Robust.Roslyn.Shared;
10+
using Robust.Roslyn.Shared.Helpers;
911
using Robust.Shared.Serialization.Manager.Definition;
1012
using Robust.Shared.ViewVariables;
1113

@@ -14,9 +16,6 @@ namespace Robust.Analyzers;
1416
[DiagnosticAnalyzer(LanguageNames.CSharp)]
1517
public 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
}

Robust.Analyzers/DataDefinitionFixer.cs

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public sealed class DefinitionFixer : CodeFixProvider
1818
private const string ViewVariablesAttributeName = "ViewVariables";
1919

2020
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(
21-
IdDataDefinitionPartial, IdNestedDataDefinitionPartial, IdDataFieldWritable, IdDataFieldPropertyWritable,
21+
IdDataDefinitionPartial, IdNestedDataDefinitionPartial, IdDataFieldPropertyWritable,
2222
IdDataFieldRedundantTag, IdDataFieldNoVVReadWrite
2323
);
2424

@@ -32,8 +32,6 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
3232
return RegisterPartialTypeFix(context, diagnostic);
3333
case IdNestedDataDefinitionPartial:
3434
return RegisterPartialTypeFix(context, diagnostic);
35-
case IdDataFieldWritable:
36-
return RegisterDataFieldFix(context, diagnostic);
3735
case IdDataFieldPropertyWritable:
3836
return RegisterDataFieldPropertyFix(context, diagnostic);
3937
case IdDataFieldRedundantTag:
@@ -182,22 +180,6 @@ private static async Task<Document> RemoveVVAttribute(Document document, MemberD
182180
return document.WithSyntaxRoot(root);
183181
}
184182

185-
private static async Task RegisterDataFieldFix(CodeFixContext context, Diagnostic diagnostic)
186-
{
187-
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
188-
var span = diagnostic.Location.SourceSpan;
189-
var field = root?.FindToken(span.Start).Parent?.AncestorsAndSelf().OfType<FieldDeclarationSyntax>().FirstOrDefault();
190-
191-
if (field == null)
192-
return;
193-
194-
context.RegisterCodeFix(CodeAction.Create(
195-
"Make data field writable",
196-
c => MakeFieldWritable(context.Document, field, c),
197-
"Make data field writable"
198-
), diagnostic);
199-
}
200-
201183
private static async Task RegisterDataFieldPropertyFix(CodeFixContext context, Diagnostic diagnostic)
202184
{
203185
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
@@ -214,17 +196,6 @@ private static async Task RegisterDataFieldPropertyFix(CodeFixContext context, D
214196
), diagnostic);
215197
}
216198

217-
private static async Task<Document> MakeFieldWritable(Document document, FieldDeclarationSyntax declaration, CancellationToken cancellation)
218-
{
219-
var root = (CompilationUnitSyntax?) await document.GetSyntaxRootAsync(cancellation);
220-
var token = declaration.Modifiers.First(t => t.IsKind(ReadOnlyKeyword));
221-
var newDeclaration = declaration.WithModifiers(declaration.Modifiers.Remove(token));
222-
223-
root = root!.ReplaceNode(declaration, newDeclaration);
224-
225-
return document.WithSyntaxRoot(root);
226-
}
227-
228199
private static async Task<Document> MakePropertyWritable(Document document, PropertyDeclarationSyntax declaration, CancellationToken cancellation)
229200
{
230201
var root = (CompilationUnitSyntax?) await document.GetSyntaxRootAsync(cancellation);

0 commit comments

Comments
 (0)