private static JensonPropertyInfo CreatePropertyInfo(JensonContext context, IPropertySymbol prop) { var name = prop.Name; string?jsonName = null; int? order = null; string?shouldSerializeFunction = null; var canBeNull = !prop.Type.IsValueType; var isReadOnly = prop.IsReadOnly; var shouldSerialize = !prop.IsWriteOnly && prop.GetMethod !.DeclaredAccessibility != Accessibility.Private && prop.GetMethod.DeclaredAccessibility != Accessibility.Protected; var shouldDeserialize = !prop.IsReadOnly && prop.SetMethod !.DeclaredAccessibility != Accessibility.Private && prop.SetMethod.DeclaredAccessibility != Accessibility.Protected; var typeFormat = new SymbolDisplayFormat(typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces); var propType = prop.Type.ToDisplayString(typeFormat); foreach (var attr in prop.GetAttributes()) { if (SymbolEqualityComparer.Default.Equals(attr.AttributeClass, context.JsonIgnoreAttribute)) { shouldSerialize = false; shouldDeserialize = false; } else if (SymbolEqualityComparer.Default.Equals(attr.AttributeClass, context.JsonPropertyNameAttribute)) { jsonName = (string)attr.ConstructorArguments[0].Value !; } else if (SymbolEqualityComparer.Default.Equals(attr.AttributeClass, context.JensonPropertyAttribute)) { attr.TryGetProperty("Order", out order); attr.TryGetProperty("ShouldSerializeFunction", out shouldSerializeFunction); } } return(new JensonPropertyInfo( name, jsonName, propType, isReadOnly, shouldSerialize, shouldDeserialize, canBeNull, order, shouldSerializeFunction)); }
private static List <JensonPropertyInfo> ExtractPropertyInfo(JensonContext context, INamedTypeSymbol t, string typeName) { // TODO need better filtering to avoid duplicate properties (e.g. virtual) // Actually we need to fold property overrides into parent props so // users can override attributes in inherited classes. // E.g. JsonIgnore on A.Foo, but JsonInclude on B.Foo where B : A var properties = t.GetThisAndBaseTypes() .Reverse() // Reverse so we handle base types first (for merging) .SelectMany(t => t.GetMembers() .Where(m => m.Kind == SymbolKind.Property && !m.IsImplicitlyDeclared) .Select(p => (IPropertySymbol)p)) .OrderBy(p => p.Name); var propertiesInfo = GetPropertyInfoFromSymbols(context, properties); // Use OrderBy for stable sort return(propertiesInfo.OrderBy(p => p.Order ?? 0).ToList()); }
private static IEnumerable <JensonPropertyInfo> GetPropertyInfoFromSymbols(JensonContext context, IEnumerable <IPropertySymbol> properties) { using var en = properties.GetEnumerator(); var propsLeft = en.MoveNext(); while (propsLeft) { var propName = en.Current.Name; var propInfo = CreatePropertyInfo(context, en.Current); propsLeft = en.MoveNext(); while (propsLeft && propName.Equals(en.Current.Name) && en.Current.IsOverride) { var additionalInfo = CreatePropertyInfo(context, en.Current); propInfo = MergePropertyInfo(propInfo, additionalInfo); propsLeft = en.MoveNext(); } yield return(propInfo); } }
public void Execute(SourceGeneratorContext generatorContext) { var ct = generatorContext.CancellationToken; var compilation = generatorContext.Compilation; var jensonSerializeAttribute = compilation.GetTypeByMetadataName("Jenson.Attributes.JensonSerializeAttribute"); var jensonPropertyAttribute = compilation.GetTypeByMetadataName("Jenson.Attributes.JensonPropertyAttribute"); var jensonTypeDiscriminatorAttribute = compilation.GetTypeByMetadataName("Jenson.Attributes.JensonTypeDiscriminatorAttribute"); var jsonPropertyNameAttribute = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonPropertyNameAttribute"); var jsonIgnoreAttribute = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonIgnoreAttribute"); if (jensonSerializeAttribute is null) { throw new Exception("Missing JensonSerializeAttribute in compilation."); } if (jensonPropertyAttribute is null) { throw new Exception("Missing JensonPropertyAttribute in compilation."); } if (jensonTypeDiscriminatorAttribute is null) { throw new Exception("Missing JensonTypeDiscriminatorAttribute in compilation."); } if (jsonPropertyNameAttribute is null) { throw new Exception("Missing JsonPropertyName in compilation."); } if (jsonIgnoreAttribute is null) { throw new Exception("Missing JsonIgnore in compilation."); } var context = new JensonContext( generatorContext, jensonSerializeAttribute, jensonPropertyAttribute, jensonTypeDiscriminatorAttribute, jsonPropertyNameAttribute, jsonIgnoreAttribute); var rootTypes = compilation.GlobalNamespace.GetAllTypes(ct) .Where(t => t.GetAttributes().Any(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, jensonSerializeAttribute))); ct.ThrowIfCancellationRequested(); var sb = new StringBuilder(); foreach (var t in rootTypes) { var typeDeclarations = t.DeclaringSyntaxReferences.Select(sr => { return((TypeDeclarationSyntax)sr.GetSyntax()); }); var isPartial = typeDeclarations.Any(td => td.Modifiers.Any(SyntaxKind.PartialKeyword)); if (!isPartial) { foreach (var td in typeDeclarations) { var diagnostic = Diagnostic.Create(MissingPartialModifier, td.GetLocation(), new object[] { t.Name }); generatorContext.ReportDiagnostic(diagnostic); } } var w = new SourceWriter(); GenerateTypeSerializer(context, w, t); generatorContext.AddSource($"{t.Name}.jenson", SourceText.From(w.ToString(), Encoding.UTF8)); // for debugging sb.Append(w.ToString()); sb.AppendLine(); sb.AppendLine(); } // for debugging File.WriteAllText("C:\\Users\\jesse\\Desktop\\converter.cs", sb.ToString()); }
private void GenerateTypeSerializer(JensonContext context, SourceWriter w, INamedTypeSymbol t) { AppendImports(w); w.Line(); var ns = t.ContainingNamespace; var nsString = ns.Name; ns = ns.ContainingNamespace; while (!ns.IsGlobalNamespace) { nsString = ns.Name + "." + nsString; ns = ns.ContainingNamespace; } w.Line($"namespace {nsString}"); w.Line("{"); w.Indent(); var typeName = t.Name; var declKeyword = t.TypeKind == TypeKind.Class ? (t.IsRecord() ? "record" : "class") : "struct"; w.Line($"[JsonConverter(typeof({typeName}Converter))]"); w.Line($"public partial {declKeyword} {typeName} {{ }}"); var propertiesInfo = ExtractPropertyInfo(context, t, typeName); w.Line($"public class {typeName}Converter : JsonConverter<{typeName}>"); w.Line("{"); w.Indent(); var typeDiscriminatorAttr = t.GetAttributes() .FirstOrDefault(attr => SymbolEqualityComparer.Default.Equals( attr.AttributeClass, context.JensonTypeDiscriminatorAttribute)); if (typeDiscriminatorAttr is not null) { var discrPropName = (string)typeDiscriminatorAttr.ConstructorArguments[0].Value; var discrFun = (string)typeDiscriminatorAttr.ConstructorArguments[1].Value; var discrProp = propertiesInfo.First(p => p.Name.Equals(discrPropName)); WriteTypeDiscriminatorConverterBody(w, typeName, discrProp, discrFun); } else { foreach (var p in propertiesInfo) { // TODO When property naming policy is set this won't necessarily match // UTF-8 json variable names. We should take JsonSerializerOptions into account. // Relevant props: // - PropertyNameCaseInsensitive // - PropertyNamingPolicy // TODO use utf8 string literals when available w.Line($"private static readonly byte[] _{p.Name}Name = Encoding.UTF8.GetBytes(\"{p.JsonName ?? p.Name}\");"); } w.Line(); AppendRead(w, typeName, propertiesInfo); w.Line(); AppendWrite(w, typeName, propertiesInfo); } w.Dedent(); w.Line("}"); // class w.Dedent(); w.Line("}"); // namespace Debug.Assert(w.Indentation == 0); }