static void VerifyField(DiagnosticsReportContext context, IFieldSymbol field, HashSet <ITypeSymbol> alreadyAnalyzed, HashSet <int> definedIndexes) { if (field.IsStatic) { return; } if (field.DeclaredAccessibility != Accessibility.Public) { return; } var attributes = field.GetAttributes(); if (attributes.FindAttributeShortName(IgnoreShortName) != null) { return; } if (!field.ContainingType.IsValueType) { context.Add(Diagnostic.Create(ClassNotSupportPublicField, field.Locations[0], field.ContainingType?.Name, field.Name)); return; } var indexAttr = attributes.FindAttributeShortName(IndexAttributeShortName); if (indexAttr == null || indexAttr.ConstructorArguments.Length == 0) { context.Add(Diagnostic.Create(PublicPropertyNeedsIndex, field.Locations[0], field.ContainingType?.Name, field.Name)); return; } var index = indexAttr.ConstructorArguments[0]; if (index.IsNull) { return; // null is normal compiler error. } if (!definedIndexes.Add((int)index.Value)) { context.Add(Diagnostic.Create(IndexAttributeDuplicate, field.Locations[0], field.ContainingType?.Name, field.Name, index.Value)); return; } if ((int)index.Value >= 100) { context.Add(Diagnostic.Create(IndexIsTooLarge, field.Locations[0], index.Value, field.Name)); } var namedType = field.Type as INamedTypeSymbol; if (namedType != null) // if <T> is unnamed type, it can't analyze. { VerifyType(context, field.Locations[0], field.Type, alreadyAnalyzed, field); } }
static void VerifyUnion(DiagnosticsReportContext context, Location callerLocation, ITypeSymbol type) { var unionKeys = type.GetMembers().OfType <IPropertySymbol>().Where(x => x.GetAttributes().FindAttributeShortName(UnionKeyAttributeShortName) != null).ToArray(); if (unionKeys.Length == 0) { context.Add(Diagnostic.Create(UnionTypeRequiresUnionKey, callerLocation, type.Locations, type.Name)); return; } else if (unionKeys.Length != 1) { context.Add(Diagnostic.Create(UnionKeyDoesNotAllowMultipleKey, callerLocation, type.Locations, type.Name)); return; } var unionKeyProperty = unionKeys[0]; if (!unionKeyProperty.GetMethod.IsAbstract) { context.Add(Diagnostic.Create(UnionTypeRequiresUnionKey, callerLocation, type.Locations, type.Name)); return; } var constructorArguments = type.GetAttributes().FindAttributeShortName(UnionAttributeShortName)?.ConstructorArguments.FirstOrDefault(); if (constructorArguments == null) { return; } foreach (var item in constructorArguments.Value.Values.Select(x => x.Value).OfType <ITypeSymbol>()) { var found = item.FindBaseTargetType(type.ToDisplayString()); if (found == null) { context.Add(Diagnostic.Create(AllUnionSubTypesMustBeInheritedType, callerLocation, type.Locations, type.Name, item.Name)); return; } } }
static void Analyze(SyntaxNodeAnalysisContext context) { var model = context.SemanticModel; var typeDeclaration = context.Node as TypeDeclarationSyntax; if (typeDeclaration == null) { return; } var declaredSymbol = model.GetDeclaredSymbol(typeDeclaration); if (declaredSymbol == null) { return; } var isUnion = declaredSymbol.GetAttributes().FindAttributeShortName(UnionAttributeShortName) != null; var zeroFormattableAttribute = declaredSymbol.GetAttributes().FindAttributeShortName(ZeroFormattableAttributeShortName); if (!isUnion && zeroFormattableAttribute == null) { return; } var reportContext = new DiagnosticsReportContext(context); if (isUnion) { VerifyUnion(reportContext, typeDeclaration.GetLocation(), declaredSymbol); } if (zeroFormattableAttribute != null) { var explicitFormatter = zeroFormattableAttribute.NamedArguments.Where( x => x.Key == ZeroFormattableFormatterProperty); if (explicitFormatter.Any()) { if (explicitFormatter.First().Value.Type.BaseType.Name != FormatterTypeName) { reportContext.Add(Diagnostic.Create(ExplicitFormatterMustInheritFormatter, typeDeclaration.GetLocation(), typeDeclaration.GetLocation(), explicitFormatter.First().Value.Type)); } return; } VerifyType(reportContext, typeDeclaration.GetLocation(), declaredSymbol, new HashSet <ITypeSymbol>(), null); } reportContext.ReportAll(); }
static void VerifyProperty(DiagnosticsReportContext context, IPropertySymbol property, HashSet <ITypeSymbol> alreadyAnalyzed, HashSet <int> definedIndexes) { if (property.DeclaredAccessibility != Accessibility.Public) { return; } var attributes = property.GetAttributes(); if (attributes.FindAttributeShortName(IgnoreShortName) != null) { return; } if (property.FindAttributeIncludeBasePropertyShortName(UnionKeyAttributeShortName) != null) { return; } if (!property.IsVirtual && !property.ContainingType.IsValueType) { if (property.IsOverride && !property.IsSealed) { // ok, base type's override property. } else { context.Add(Diagnostic.Create(PublicPropertyMustBeVirtual, property.Locations[0], property.ContainingType?.Name, property.Name)); return; } } var indexAttr = attributes.FindAttributeShortName(IndexAttributeShortName); if (indexAttr == null || indexAttr.ConstructorArguments.Length == 0) { context.Add(Diagnostic.Create(PublicPropertyNeedsIndex, property.Locations[0], property.ContainingType?.Name, property.Name)); return; } var index = indexAttr.ConstructorArguments[0]; if (index.IsNull) { return; // null is normal compiler error. } if (!definedIndexes.Add((int)index.Value)) { context.Add(Diagnostic.Create(IndexAttributeDuplicate, property.Locations[0], property.ContainingType?.Name, property.Name, index.Value)); return; } if ((int)index.Value >= 100) { context.Add(Diagnostic.Create(IndexIsTooLarge, property.Locations[0], index.Value, property.Name)); } if (!property.ContainingType.IsValueType) { if (property.GetMethod == null || property.SetMethod == null || property.GetMethod.DeclaredAccessibility == Accessibility.Private || property.SetMethod.DeclaredAccessibility == Accessibility.Private) { context.Add(Diagnostic.Create(PublicPropertyNeedsGetAndSetAccessor, property.Locations[0], property.ContainingType?.Name, property.Name)); return; } } var namedType = property.Type as INamedTypeSymbol; if (namedType != null) // if <T> is unnamed type, it can't analyze. { VerifyType(context, property.Locations[0], property.Type, alreadyAnalyzed, property); } }
static void VerifyType(DiagnosticsReportContext context, Location callerLocation, ITypeSymbol type, HashSet <ITypeSymbol> alreadyAnalyzed, ISymbol callFromProperty) { if (!alreadyAnalyzed.Add(type)) { return; } var displayString = type.ToDisplayString(); if (AllowTypes.Contains(displayString)) { return; // it is primitive... } else if (context.AdditionalAllowTypes.Contains(displayString)) { return; } if (type.TypeKind == TypeKind.Enum) { return; } if (type.TypeKind == TypeKind.Array) { var array = type as IArrayTypeSymbol; var t = array.ElementType; VerifyType(context, callerLocation, t, alreadyAnalyzed, callFromProperty); return; } var namedType = type as INamedTypeSymbol; if (namedType != null && namedType.IsGenericType && callFromProperty != null) { var genericType = namedType.ConstructUnboundGenericType(); var genericTypeString = genericType.ToDisplayString(); if (genericTypeString == "T?") { VerifyType(context, callerLocation, namedType.TypeArguments[0], alreadyAnalyzed, callFromProperty); return; } else if (genericTypeString == "System.Collections.Generic.IList<>" || genericTypeString == "System.Collections.Generic.IDictionary<,>" || genericTypeString == "System.Collections.Generic.Dictionary<,>" || genericTypeString == "ZeroFormatter.ILazyDictionary<,>" || genericTypeString == "System.Collections.Generic.IReadOnlyList<>" || genericTypeString == "System.Collections.Generic.IReadOnlyCollection<>" || genericTypeString == "System.Collections.Generic.IReadOnlyDictionary<,>" || genericTypeString == "System.Collections.Generic.ICollection<>" || genericTypeString == "System.Collections.Generic.IEnumerable<>" || genericTypeString == "System.Collections.Generic.ISet<>" || genericTypeString == "System.Collections.ObjectModel.ReadOnlyCollection<>" || genericTypeString == "System.Collections.ObjectModel.ReadOnlyDictionary<,>" || genericTypeString == "ZeroFormatter.ILazyReadOnlyDictionary<,>" || genericTypeString == "System.Linq.ILookup<,>" || genericTypeString == "ZeroFormatter.ILazyLookup<,>" || genericTypeString.StartsWith("System.Collections.Generic.KeyValuePair") || genericTypeString.StartsWith("System.Tuple") || genericTypeString.StartsWith("ZeroFormatter.KeyTuple") || context.AdditionalAllowTypes.Contains(genericTypeString) ) { foreach (var t in namedType.TypeArguments) { VerifyType(context, callerLocation, t, alreadyAnalyzed, callFromProperty); } return; } else { if (namedType.AllInterfaces.Any(x => (x.IsGenericType ? x.ConstructUnboundGenericType().ToDisplayString() : "") == "System.Collections.Generic.ICollection<>")) { foreach (var t in namedType.TypeArguments) { VerifyType(context, callerLocation, t, alreadyAnalyzed, callFromProperty); } return; } } } if (type.GetAttributes().FindAttributeShortName(ZeroFormattableAttributeShortName) == null) { context.Add(Diagnostic.Create(TypeMustBeZeroFormattable, callerLocation, type.Locations, type.Name)); return; } if (namedType != null && !namedType.IsValueType) { if (!namedType.Constructors.Any(x => x.Parameters.Length == 0)) { context.Add(Diagnostic.Create(TypeMustNeedsParameterlessConstructor, callerLocation, type.Locations, type.Name)); return; } } // If in another project, we can not report so stop analyze. if (callFromProperty == null) { var definedIndexes = new HashSet <int>(); foreach (var member in type.GetAllMembers().OfType <IPropertySymbol>()) { VerifyProperty(context, member, alreadyAnalyzed, definedIndexes); } foreach (var member in type.GetAllMembers().OfType <IFieldSymbol>()) { VerifyField(context, member, alreadyAnalyzed, definedIndexes); } if (type.IsValueType && context.Diagnostics.Count == 0) { var indexes = new List <Tuple <int, ITypeSymbol> >(); foreach (var item in type.GetMembers()) { var propSymbol = item as IPropertySymbol; var fieldSymbol = item as IFieldSymbol; if ((propSymbol == null && fieldSymbol == null)) { continue; } var indexAttr = item.GetAttributes().FindAttributeShortName(IndexAttributeShortName); if (indexAttr != null) { var index = (int)indexAttr.ConstructorArguments[0].Value; indexes.Add(Tuple.Create(index, (propSymbol != null) ? propSymbol.Type : fieldSymbol.Type)); } } indexes = indexes.OrderBy(x => x.Item1).ToList(); var expected = 0; foreach (var item in indexes) { if (item.Item1 != expected) { context.Add(Diagnostic.Create(StructIndexMustBeStartedWithZeroAndSequential, callerLocation, type.Locations, type.Name, item.Item1)); return; } expected++; } var foundConstructor = false; var ctors = (type as INamedTypeSymbol)?.Constructors; foreach (var ctor in ctors) { var isMatch = indexes.Select(x => x.Item2.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)) .SequenceEqual(ctor.Parameters.Select(x => x.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))); if (isMatch) { foundConstructor = true; } } if (!foundConstructor && indexes.Count != 0) { context.Add(Diagnostic.Create(StructMustNeedsSameConstructorWithIndexes, callerLocation, type.Locations, type.Name)); return; } return; } } }
static void VerifyProperty(DiagnosticsReportContext context, IPropertySymbol property, HashSet <ITypeSymbol> alreadyAnalyzed, HashSet <int> definedIndexes) { if (property.DeclaredAccessibility != Accessibility.Public) { return; } var attributes = property.GetAttributes(); if (attributes.FindAttributeShortName(IgnoreShortName) != null) { return; } if (!property.IsVirtual && !property.ContainingType.IsValueType) { context.Add(Diagnostic.Create(PublicPropertyMustBeVirtual, property.Locations[0], property.ContainingType?.Name, property.Name)); return; } var indexAttr = attributes.FindAttributeShortName(IndexAttributeShortName); if (indexAttr == null || indexAttr.ConstructorArguments.Length == 0) { context.Add(Diagnostic.Create(PublicPropertyNeedsIndex, property.Locations[0], property.ContainingType?.Name, property.Name)); return; } var index = indexAttr.ConstructorArguments[0]; if (index.IsNull) { return; // null is normal compiler error. } if (!definedIndexes.Add((int)index.Value)) { context.Add(Diagnostic.Create(IndexAttributeDuplicate, property.Locations[0], property.ContainingType?.Name, property.Name, index.Value)); return; } if ((int)index.Value >= 100) { context.Add(Diagnostic.Create(IndexIsTooLarge, property.Locations[0], index.Value, property.Name)); } if (!property.ContainingType.IsValueType) { if (property.GetMethod == null || property.SetMethod == null || property.GetMethod.DeclaredAccessibility == Accessibility.Private || property.SetMethod.DeclaredAccessibility == Accessibility.Private) { context.Add(Diagnostic.Create(PublicPropertyNeedsGetAndSetAccessor, property.Locations[0], property.ContainingType?.Name, property.Name)); return; } } if (property.Type.TypeKind == TypeKind.Array) { var array = property.Type as IArrayTypeSymbol; var t = array.ElementType; if (t.SpecialType == SpecialType.System_Byte) // allows byte[] { return; } context.Add(Diagnostic.Create(ArrayNotSupport, property.Locations[0], property.ContainingType?.Name, property.Name)); return; } var namedType = property.Type as INamedTypeSymbol; if (namedType != null) // if <T> is unnamed type, it can't analyze. { VerifyType(context, property.Locations[0], property.Type, alreadyAnalyzed, property); } }
static void VerifyUnion(DiagnosticsReportContext context, Location callerLocation, ITypeSymbol type) { var unionKeys = type.GetMembers().OfType <IPropertySymbol>().Where(x => x.GetAttributes().FindAttributeShortName(UnionKeyAttributeShortName) != null).ToArray(); if (unionKeys.Length == 0) { context.Add(Diagnostic.Create(UnionTypeRequiresUnionKey, callerLocation, type.Locations, type.Name)); return; } else if (unionKeys.Length != 1) { context.Add(Diagnostic.Create(UnionKeyDoesNotAllowMultipleKey, callerLocation, type.Locations, type.Name)); return; } var unionKeyProperty = unionKeys[0]; if (type.TypeKind != TypeKind.Interface && !unionKeyProperty.GetMethod.IsAbstract) { context.Add(Diagnostic.Create(UnionTypeRequiresUnionKey, callerLocation, type.Locations, type.Name)); return; } var ctorArguments = type.GetAttributes().FindAttributeShortName(UnionAttributeShortName)?.ConstructorArguments; var firstArguments = ctorArguments?.FirstOrDefault(); if (firstArguments == null) { return; } TypedConstant fallbackType = default(TypedConstant); if (ctorArguments.Value.Length == 2) { fallbackType = ctorArguments.Value[1]; } if (type.TypeKind != TypeKind.Interface) { foreach (var item in firstArguments.Value.Values.Concat(new[] { fallbackType }).Where(x => !x.IsNull).Select(x => x.Value).OfType <ITypeSymbol>()) { var found = item.FindBaseTargetType(type.ToDisplayString()); if (found == null) { context.Add(Diagnostic.Create(AllUnionSubTypesMustBeInheritedType, callerLocation, type.Locations, type.Name, item.Name)); return; } } } else { foreach (var item in firstArguments.Value.Values.Concat(new[] { fallbackType }).Where(x => !x.IsNull).Select(x => x.Value).OfType <ITypeSymbol>()) { var typeString = type.ToDisplayString(); if (!(item as INamedTypeSymbol).AllInterfaces.Any(x => x.OriginalDefinition?.ToDisplayString() == typeString)) { context.Add(Diagnostic.Create(AllUnionSubTypesMustBeInheritedType, callerLocation, type.Locations, type.Name, item.Name)); return; } } } }