static CodeTypeDeclaration CreateDelegateDeclaration(TypeDefinition publicType, AttributeFilter attributeFilter) { var invokeMethod = publicType.Methods.Single(m => m.Name == "Invoke"); using (NullableContext.Push(invokeMethod)) // for delegates NullableContextAttribute is stored on Invoke method { var name = publicType.Name; var index = name.IndexOf('`'); if (index != -1) { name = name.Substring(0, index); } var declaration = new CodeTypeDelegate(name) { Attributes = MemberAttributes.Public, CustomAttributes = CreateCustomAttributes(publicType, attributeFilter), ReturnType = invokeMethod.ReturnType.CreateCodeTypeReference(invokeMethod.MethodReturnType), }; // CodeDOM. No support. Return type attributes. PopulateCustomAttributes(invokeMethod.MethodReturnType, declaration.CustomAttributes, type => type.MakeReturn(), attributeFilter); PopulateGenericParameters(publicType, declaration.TypeParameters, attributeFilter, _ => true); PopulateMethodParameters(invokeMethod, declaration.Parameters, attributeFilter); // Of course, CodeDOM doesn't support generic type parameters for delegates. Of course. if (declaration.TypeParameters.Count > 0) { var parameterNames = from parameterType in declaration.TypeParameters.Cast <CodeTypeParameter>() select parameterType.Name; declaration.Name = string.Format(CultureInfo.InvariantCulture, "{0}<{1}>", declaration.Name, string.Join(", ", parameterNames)); } return(declaration); } }
static void AddMemberToTypeDeclaration(CodeTypeDeclaration typeDeclaration, IMemberDefinition memberInfo, AttributeFilter attributeFilter) { using (NullableContext.Push(memberInfo)) { if (memberInfo is MethodDefinition methodDefinition) { if (methodDefinition.IsConstructor) { AddCtorToTypeDeclaration(typeDeclaration, methodDefinition, attributeFilter); } else { AddMethodToTypeDeclaration(typeDeclaration, methodDefinition, attributeFilter); } } else if (memberInfo is PropertyDefinition propertyDefinition) { AddPropertyToTypeDeclaration(typeDeclaration, propertyDefinition, attributeFilter); } else if (memberInfo is EventDefinition eventDefinition) { AddEventToTypeDeclaration(typeDeclaration, eventDefinition, attributeFilter); } else if (memberInfo is FieldDefinition fieldDefinition) { AddFieldToTypeDeclaration(typeDeclaration, fieldDefinition, attributeFilter); } } }
// TODO: Assembly references? // TODO: Better handle namespaces - using statements? - requires non-qualified type names static string CreatePublicApiForAssembly(AssemblyDefinition assembly, Func <TypeDefinition, bool> shouldIncludeType, bool shouldIncludeAssemblyAttributes, string[] whitelistedNamespacePrefixes, AttributeFilter attributeFilter) { using (var provider = new CSharpCodeProvider()) { var compileUnit = new CodeCompileUnit(); if (shouldIncludeAssemblyAttributes && assembly.HasCustomAttributes) { PopulateCustomAttributes(assembly, compileUnit.AssemblyCustomAttributes, attributeFilter); } var publicTypes = assembly.Modules.SelectMany(m => m.GetTypes()) .Where(t => !t.IsNested && ShouldIncludeType(t) && shouldIncludeType(t)) .OrderBy(t => t.FullName, StringComparer.Ordinal); foreach (var publicType in publicTypes) { var @namespace = compileUnit.Namespaces.Cast <CodeNamespace>() .FirstOrDefault(n => n.Name == publicType.Namespace); if (@namespace == null) { @namespace = new CodeNamespace(publicType.Namespace); compileUnit.Namespaces.Add(@namespace); } using (NullableContext.Push(publicType)) { var typeDeclaration = CreateTypeDeclaration(publicType, whitelistedNamespacePrefixes, attributeFilter); @namespace.Types.Add(typeDeclaration); } } using (var writer = new StringWriter()) { var cgo = new CodeGeneratorOptions { BracingStyle = "C", BlankLinesBetweenMembers = false, VerbatimOrder = false, IndentString = " " }; provider.GenerateCodeFromCompileUnit(compileUnit, writer, cgo); return(CodeNormalizer.NormalizeGeneratedCode(writer)); } } }
static void PopulateGenericParameters(IGenericParameterProvider publicType, CodeTypeParameterCollection parameters, AttributeFilter attributeFilter, Func <GenericParameter, bool> shouldUseParameter) { foreach (var parameter in publicType.GenericParameters.Where(shouldUseParameter)) { // A little hacky. Means we get "in" and "out" prefixed on any constraints, but it's either that // or add it as a custom attribute var name = parameter.Name; if (parameter.IsCovariant) { name = "out " + name; } if (parameter.IsContravariant) { name = "in " + name; } var attributeCollection = new CodeAttributeDeclarationCollection(); if (parameter.HasCustomAttributes) { PopulateCustomAttributes(parameter, attributeCollection, attributeFilter); } var typeParameter = new CodeTypeParameter(name) { HasConstructorConstraint = parameter.HasDefaultConstructorConstraint && !parameter.HasNotNullableValueTypeConstraint }; typeParameter.CustomAttributes.AddRange(attributeCollection.OfType <CodeAttributeDeclaration>().ToArray()); var nullableConstraint = parameter.GetNullabilityMap().First(); var unmanagedConstraint = parameter.CustomAttributes.Any(attr => attr.AttributeType.FullName == "System.Runtime.CompilerServices.IsUnmanagedAttribute"); if (parameter.HasNotNullableValueTypeConstraint) { typeParameter.Constraints.Add(unmanagedConstraint ? " unmanaged" : " struct"); } if (parameter.HasReferenceTypeConstraint) { typeParameter.Constraints.Add(nullableConstraint == true ? " class?" : " class"); } else if (nullableConstraint == false) { typeParameter.Constraints.Add(" notnull"); } using (NullableContext.Push(parameter)) { foreach (var constraint in parameter.Constraints.Where(constraint => !IsSpecialConstraint(constraint))) { // for generic constraints like IEnumerable<T> call to GetElementType() returns TypeReference with Name = !0 var typeReference = constraint.ConstraintType /*.GetElementType()*/.CreateCodeTypeReference(constraint); typeParameter.Constraints.Add(typeReference); } } parameters.Add(typeParameter); } bool IsSpecialConstraint(GenericParameterConstraint constraint) { // struct if (constraint.ConstraintType is TypeReference reference && reference.FullName == "System.ValueType") { return(true); } // unmanaged if (constraint.ConstraintType.IsUnmanaged()) { return(true); } return(false); } }
static CodeTypeDeclaration CreateTypeDeclaration(TypeDefinition publicType, string[] whitelistedNamespacePrefixes, AttributeFilter attributeFilter) { if (publicType.IsDelegate()) { return(CreateDelegateDeclaration(publicType, attributeFilter)); } var @static = false; TypeAttributes attributes = 0; if (publicType.IsPublic || publicType.IsNestedPublic) { attributes |= TypeAttributes.Public; } if (publicType.IsNestedFamily || publicType.IsNestedFamilyOrAssembly) { attributes |= TypeAttributes.NestedFamily; } if (publicType.IsSealed && !publicType.IsAbstract) { attributes |= TypeAttributes.Sealed; } else if (!publicType.IsSealed && publicType.IsAbstract && !publicType.IsInterface) { attributes |= TypeAttributes.Abstract; } else if (publicType.IsSealed && publicType.IsAbstract) { @static = true; } // Static support is a hack. CodeDOM does support it, and this isn't // correct C#, but it's good enough for our API outline var name = publicType.Name; var isStruct = publicType.IsValueType && !publicType.IsPrimitive && !publicType.IsEnum; var @readonly = isStruct && publicType.CustomAttributes.Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.IsReadOnlyAttribute"); var index = name.IndexOf('`'); if (index != -1) { name = name.Substring(0, index); } var declarationName = string.Empty; if (@readonly) { declarationName += CodeNormalizer.ReadonlyMarker; } if (@static) { declarationName += CodeNormalizer.StaticMarker; } declarationName += name; var declaration = new CodeTypeDeclaration(declarationName) { CustomAttributes = CreateCustomAttributes(publicType, attributeFilter), // TypeAttributes must be specified before the IsXXX as they manipulate TypeAttributes! TypeAttributes = attributes, IsClass = publicType.IsClass, IsEnum = publicType.IsEnum, IsInterface = publicType.IsInterface, IsStruct = isStruct, }; if (declaration.IsInterface && publicType.BaseType != null) { throw new NotImplementedException("Base types for interfaces needs testing"); } PopulateGenericParameters(publicType, declaration.TypeParameters, attributeFilter, parameter => { var declaringType = publicType.DeclaringType; while (declaringType != null) { if (declaringType.GenericParameters.Any(p => p.Name == parameter.Name)) { return(false); // https://github.com/ApiApprover/ApiApprover/issues/108 } declaringType = declaringType.DeclaringType; } return(true); }); if (publicType.BaseType != null && ShouldOutputBaseType(publicType)) { if (publicType.BaseType.FullName == "System.Enum") { var underlyingType = publicType.GetEnumUnderlyingType(); if (underlyingType.FullName != "System.Int32") { declaration.BaseTypes.Add(underlyingType.CreateCodeTypeReference()); } } else { declaration.BaseTypes.Add(publicType.BaseType.CreateCodeTypeReference(publicType)); } } foreach (var @interface in publicType.Interfaces.OrderBy(i => i.InterfaceType.FullName, StringComparer.Ordinal) .Select(t => new { Reference = t, Definition = t.InterfaceType.Resolve() }) .Where(t => ShouldIncludeType(t.Definition)) .Select(t => t.Reference)) { declaration.BaseTypes.Add(@interface.InterfaceType.CreateCodeTypeReference(@interface)); } foreach (var memberInfo in publicType.GetMembers().Where(memberDefinition => ShouldIncludeMember(memberDefinition, whitelistedNamespacePrefixes)).OrderBy(m => m.Name, StringComparer.Ordinal)) { AddMemberToTypeDeclaration(declaration, memberInfo, attributeFilter); } // Fields should be in defined order for an enum var fields = !publicType.IsEnum ? publicType.Fields.OrderBy(f => f.Name, StringComparer.Ordinal) : (IEnumerable <FieldDefinition>)publicType.Fields; foreach (var field in fields) { AddMemberToTypeDeclaration(declaration, field, attributeFilter); } foreach (var nestedType in publicType.NestedTypes.Where(ShouldIncludeType).OrderBy(t => t.FullName, StringComparer.Ordinal)) { using (NullableContext.Push(nestedType)) { var nestedTypeDeclaration = CreateTypeDeclaration(nestedType, whitelistedNamespacePrefixes, attributeFilter); declaration.Members.Add(nestedTypeDeclaration); } } return(declaration); }