Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/api-doc-tools.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'mdoc/Mono.Documentation/Updater/Formatters/AttributeFormatters/AttributeValueFormatter.cs')
-rw-r--r--mdoc/Mono.Documentation/Updater/Formatters/AttributeFormatters/AttributeValueFormatter.cs389
1 files changed, 341 insertions, 48 deletions
diff --git a/mdoc/Mono.Documentation/Updater/Formatters/AttributeFormatters/AttributeValueFormatter.cs b/mdoc/Mono.Documentation/Updater/Formatters/AttributeFormatters/AttributeValueFormatter.cs
index a9e092bb..41566740 100644
--- a/mdoc/Mono.Documentation/Updater/Formatters/AttributeFormatters/AttributeValueFormatter.cs
+++ b/mdoc/Mono.Documentation/Updater/Formatters/AttributeFormatters/AttributeValueFormatter.cs
@@ -1,92 +1,385 @@
-using System;
+using Mono.Cecil;
+using System;
using System.Collections.Generic;
using System.Linq;
-using Mono.Cecil;
-
-using Mono.Documentation.Util;
namespace Mono.Documentation.Updater
{
- /// <summary>Formats attribute values. Should return true if it is able to format the value.</summary>
- class AttributeValueFormatter
- {
- public virtual bool TryFormatValue (object v, ResolvedTypeInfo type, out string returnvalue)
+ public class AttributeValueFormatter
+ {
+ public string Format(TypeReference argumentType, object argumentValue)
{
- TypeReference valueType = type.Reference;
- if (v == null)
+ // When a property type of an attribute is an object type you can assign any type to it,
+ // so we need to convert the object type to a concrete object type.
+ if (argumentValue is CustomAttributeArgument attributeArgument)
{
- returnvalue = "null";
- return true;
+ return Format(attributeArgument.Type, attributeArgument.Value);
}
- if (valueType.FullName == "System.Type")
+
+ return FormatValue(argumentType, argumentValue);
+ }
+
+ // The types of positional and named parameters for an attribute class are limited to the attribute parameter types, which are:
+ // https://github.com/dotnet/csharplang/blob/main/spec/attributes.md#attribute-parameter-types
+ private string FormatValue(TypeReference argumentType, object argumentValue)
+ {
+ if (IsNull(argumentValue))
{
- var vTypeRef = v as TypeReference;
- if (vTypeRef != null)
- returnvalue = "typeof(" + NativeTypeManager.GetTranslatedName (vTypeRef) + ")"; // TODO: drop NS handling
- else
- returnvalue = "typeof(" + v.ToString () + ")";
+ return "null";
+ }
- return true;
+ if (IsArrayType(argumentType, argumentValue))
+ {
+ return ConvertToArrayType(argumentType, argumentValue);
}
- if (valueType.FullName == "System.String")
+
+ if (IsTypeType(argumentType, argumentValue))
{
- returnvalue = "\"" + FilterSpecialChars (v.ToString ()) + "\"";
- return true;
+ return ConvertToType(argumentType, argumentValue);
}
- if (valueType.FullName == "System.Char")
+
+ if (IsStringType(argumentType, argumentValue))
{
- returnvalue = "'" + FilterSpecialChars (v.ToString ()) + "'";
- return true;
+ return ConvertToString(argumentType, argumentValue);
}
- if (v is Boolean)
+
+ if (IsCharType(argumentType, argumentValue))
{
- returnvalue = (bool)v ? "true" : "false";
- return true;
+ return ConvertToChar(argumentType, argumentValue);
}
- TypeDefinition valueDef = type.Definition;
- if (valueDef == null || !valueDef.IsEnum)
+ if (IsBoolType(argumentType, argumentValue))
{
- returnvalue = v.ToString ();
- return true;
+ return ConvertToBool(argumentType, argumentValue);
}
- string typename = MDocUpdater.GetDocTypeFullName (valueType);
- var values = GetEnumerationValues (valueDef);
- long c = ToInt64 (v);
- if (values.ContainsKey (c))
+ if (IsEnumType(argumentType, argumentValue))
{
- returnvalue = typename + "." + values[c];
- return true;
+ return ConvertToEnum(argumentType, argumentValue);
+ }
+
+ return ConvertUnhandledTypeToString(argumentValue);
+ }
+
+ private bool IsNull(object argumentValue)
+ {
+ return argumentValue == null;
+ }
+
+ private bool IsEnumType(TypeReference argumentType, object argumentValue)
+ {
+ return argumentType.Resolve().IsEnum;
+ }
+
+ private bool IsTypeType(TypeReference argumentType, object argumentValue)
+ {
+ return IsType("System.Type", argumentType, argumentValue);
+ }
+
+ private bool IsStringType(TypeReference argumentType, object argumentValue)
+ {
+ return IsType("System.String", argumentType, argumentValue);
+ }
+
+ private bool IsCharType(TypeReference argumentType, object argumentValue)
+ {
+ return IsType("System.Char", argumentType, argumentValue);
+ }
+
+ private bool IsBoolType(TypeReference argumentType, object argumentValue)
+ {
+ return IsType("System.Boolean", argumentType, argumentValue);
+ }
+
+ private bool IsType(string typeFullName, TypeReference argumentType, object argumentValue)
+ {
+ return argumentType.FullName.Equals(typeFullName);
+ }
+
+ private bool IsArrayType(TypeReference argumentType, object argumentValue)
+ {
+ return argumentType is ArrayType && argumentValue is CustomAttributeArgument[];
+ }
+
+ private string ConvertToArrayType(TypeReference argumentType, object argumentValue)
+ {
+ var arrayType = argumentType as ArrayType;
+ var arrayArguments = argumentValue as CustomAttributeArgument[];
+ var arrayTypeFullName = $"new {arrayType.FullName}{(arrayType.FullName.EndsWith("[]") ? "" : "[]")}";
+ var arrayArgumentValues = arrayArguments.Select(i => FormatValue(i.Type, i.Value));
+
+ return $"{arrayTypeFullName} {{ {string.Join(", ", arrayArgumentValues)} }}";
+ }
+
+ private bool IsFlagsEnum(TypeReference argumentType, object argumentValue)
+ {
+ var argumentTypeDefinition = argumentType.Resolve();
+ var isApplyFlagsAttributeEnumType = argumentTypeDefinition.CustomAttributes.Any(a => a.AttributeType.FullName == "System.FlagsAttribute");
+ var isNotApplyAttributeFlagsEnumType = IsNotApplyAttributeFlagsEnumType(argumentTypeDefinition, argumentValue);
+
+ return isApplyFlagsAttributeEnumType || isNotApplyAttributeFlagsEnumType;
+ }
+
+ /// <summary>
+ /// We have a few legacy flags enum type not apply FlagsAttribute to it.
+ /// For example, Microsoft.JScript.JSFunctionAttributeEnum in .NET Framework 1.1 but the issue has been fixed in the newer version.
+ /// </summary>
+ private bool IsNotApplyAttributeFlagsEnumType(TypeDefinition argumentType, object argumentValue)
+ {
+ (var typeFullName, var enumConstants, var enumValue) = ExtractEnumTypeData(argumentType, argumentValue);
+ if (enumConstants.ContainsKey(enumValue))
+ {
+ // Not is a combinations of values.
+ return false;
+ }
+
+ var flagsEnumValues = enumConstants.Keys.ToList();
+ flagsEnumValues.Remove(0); // The zero value is not a valid flags enum value.
+
+ // The following example is an invalid and valid flags enum type.
+ // None = 0, Read = 1, Write = 2, ReadWrite = 3 maybe is a flags enum type but sometimes it is not.
+ // Read = 1, Write = 2, Open = 4, Close = 8 actually is a flags enum type.
+ var minFlagsEnumValueCount = 4;
+ if (flagsEnumValues.Count() >= minFlagsEnumValueCount)
+ {
+ long allEnumLogicalOrValue = 0;
+ foreach (var item in flagsEnumValues)
+ {
+ allEnumLogicalOrValue = allEnumLogicalOrValue | item;
+ }
+
+ var isFlagsEnumType = !flagsEnumValues.Any(i => (i & allEnumLogicalOrValue) != i);
+ var isCombinationValue = flagsEnumValues.Count(i => (i & enumValue) != i) > 1;
+
+ return isFlagsEnumType && isCombinationValue;
}
- returnvalue = null;
return false;
}
- internal static IDictionary<long, string> GetEnumerationValues(TypeDefinition type)
+ private bool IsApplePlatformEnum(TypeReference argumentType, object argumentValue)
+ {
+ return MDocUpdater.GetDocTypeFullName(argumentType).Contains("ObjCRuntime.Platform");
+ }
+
+ private string ConvertToType(TypeReference argumentType, object argumentValue)
{
+ var valueResult = GetArgumentValue("System.Type", argumentType, argumentValue);
+ var typeFullName = MDocUpdater.GetDocTypeFullName((TypeReference)valueResult, isTypeofOperator: true);
+
+ return $"typeof({typeFullName})";
+ }
+
+ private string ConvertToString(TypeReference argumentType, object argumentValue)
+ {
+ var valueResult = GetArgumentValue("System.String", argumentType, argumentValue);
+ if (valueResult == null)
+ {
+ return "null";
+ }
+
+ return string.Format("\"{0}\"", FilterSpecialChars(valueResult.ToString()));
+ }
+
+ private string ConvertToBool(TypeReference argumentType, object argumentValue)
+ {
+ return GetArgumentValue("System.Boolean", argumentType, argumentValue).ToString().ToLower();
+ }
+
+ private string ConvertToChar(TypeReference argumentType, object argumentValue)
+ {
+ var valueResult = GetArgumentValue("System.Char", argumentType, argumentValue).ToString();
+
+ return string.Format("'{0}'", FilterSpecialChars(valueResult));
+ }
+
+ private string ConvertUnhandledTypeToString(object argumentValue)
+ {
+ return argumentValue.ToString();
+ }
+
+ private string ConvertToEnum(TypeReference argumentType, object argumentValue)
+ {
+ if (IsFlagsEnum(argumentType, argumentValue))
+ {
+ if (IsApplePlatformEnum(argumentType, argumentValue))
+ {
+ return ConvertToApplePlatformEnum(argumentType, argumentValue);
+ }
+
+ return ConvertToFlagsEnum(argumentType, argumentValue);
+ }
+
+ return ConvertToNormalEnum(argumentType, argumentValue);
+ }
+
+ private string ConvertToNormalEnum(TypeReference argumentType, object argumentValue)
+ {
+ (var typeFullName, var enumConstants, var enumValue) = ExtractEnumTypeData(argumentType, argumentValue);
+ if (enumConstants.ContainsKey(enumValue))
+ {
+ return typeFullName + "." + enumConstants[enumValue];
+ }
+
+ return ConvertToUnknownEnum(argumentType, argumentValue);
+ }
+
+ private string ConvertToUnknownEnum(TypeReference argumentType, object argumentValue)
+ {
+ (var typeFullName, var enumConstants, var enumValue) = ExtractEnumTypeData(argumentType, argumentValue);
+
+ return $"({typeFullName}) {enumValue}";
+ }
+
+ private string ConvertToFlagsEnum(TypeReference argumentType, object argumentValue)
+ {
+ (var typeFullName, var enumConstants, var enumValue) = ExtractEnumTypeData(argumentType, argumentValue);
+ if (enumConstants.ContainsKey(enumValue))
+ {
+ // Not is a combinations of values.
+ return $"{typeFullName}.{enumConstants[enumValue]}";
+ }
+
+ var flagsEnumValues = enumConstants.Keys.Where(i => (enumValue & i) == i && i != 0).ToList();
+ var duplicateEnumValues = flagsEnumValues.Where(i => flagsEnumValues.Any(a => (a & i) == i && a > i));
+
+ flagsEnumValues.RemoveAll(i => duplicateEnumValues.Contains(i));
+ var flagsEnumNames = flagsEnumValues
+ .Select(i => $"{typeFullName}.{enumConstants[i]}")
+ .OrderBy(val => val) // to maintain a consistent list across frameworks/versions
+ .ToArray();
+
+ if (flagsEnumNames.Length > 0)
+ {
+ return string.Join(" | ", flagsEnumNames);
+ }
+
+ return ConvertToUnknownEnum(argumentType, argumentValue);
+ }
+
+ private string ConvertToApplePlatformEnum(TypeReference argumentType, object argumentValue)
+ {
+ (var typeFullName, var enumConstants, var enumValue) = ExtractEnumTypeData(argumentType, argumentValue);
+ if (enumConstants.ContainsKey(enumValue))
+ {
+ return typeFullName + "." + enumConstants[enumValue];
+ }
+
+ return FormatApplePlatformEnum(enumValue);
+ }
+
+ private (string typeFullName, IDictionary<long, string> enumConstants, long enumValue) ExtractEnumTypeData(TypeReference argumentType, object argumentValue)
+ {
+ var argumentTypeDefinition = argumentType.Resolve();
+ var typeFullName = MDocUpdater.GetDocTypeFullName(argumentTypeDefinition);
+ var enumConstants = GetEnumerationValues(argumentTypeDefinition);
+ var enumValue = ToInt64(argumentValue);
+
+ return (typeFullName, enumConstants, enumValue);
+ }
+
+ private string FormatApplePlatformEnum(long enumValue)
+ {
+ int iosarch, iosmajor, iosminor, iossubminor;
+ int macarch, macmajor, macminor, macsubminor;
+ GetEncodingiOS(enumValue, out iosarch, out iosmajor, out iosminor, out iossubminor);
+ GetEncodingMac((ulong)enumValue, out macarch, out macmajor, out macminor, out macsubminor);
+
+ if (iosmajor == 0 & iosminor == 0 && iossubminor == 0)
+ {
+ return FormatApplePlatformEnumValue("Mac", macarch, macmajor, macminor, macsubminor);
+ }
+
+ if (macmajor == 0 & macminor == 0 && macsubminor == 0)
+ {
+ return FormatApplePlatformEnumValue("iOS", iosarch, iosmajor, iosminor, iossubminor);
+ }
+
+ return string.Format("(Platform){0}", enumValue);
+ }
+
+ private string FormatApplePlatformEnumValue(string plat, int arch, int major, int minor, int subminor)
+ {
+ var archstring = string.Empty;
+ switch (arch)
+ {
+ case 1:
+ archstring = "32";
+ break;
+ case 2:
+ archstring = "64";
+ break;
+ }
+
+ return string.Format("Platform.{4}_{0}_{1}{2} | Platform.{4}_Arch{3}",
+ major,
+ minor,
+ subminor == 0 ? "" : "_" + subminor.ToString(),
+ archstring,
+ plat
+ );
+ }
+
+ private void GetEncodingiOS(long entireLong, out int archindex, out int major, out int minor, out int subminor)
+ {
+ long lowerBits = entireLong & 0xffffffff;
+ int lowerBitsAsInt = (int)lowerBits;
+ GetEncodingApplePlatform(lowerBitsAsInt, out archindex, out major, out minor, out subminor);
+ }
+
+ private void GetEncodingMac(ulong entireLong, out int archindex, out int major, out int minor, out int subminor)
+ {
+ ulong higherBits = entireLong & 0xffffffff00000000;
+ int higherBitsAsInt = (int)((higherBits) >> 32);
+ GetEncodingApplePlatform(higherBitsAsInt, out archindex, out major, out minor, out subminor);
+ }
+
+ private void GetEncodingApplePlatform(Int32 encodedBits, out int archindex, out int major, out int minor, out int subminor)
+ {
+ // format is AAJJNNSS
+ archindex = (int)((encodedBits & 0xFF000000) >> 24);
+ major = (int)((encodedBits & 0x00FF0000) >> 16);
+ minor = (int)((encodedBits & 0x0000FF00) >> 8);
+ subminor = (int)((encodedBits & 0x000000FF) >> 0);
+ }
+
+ private object GetArgumentValue(string argumentTypeFullName, TypeReference argumentType, object argumentValue)
+ {
+ if (argumentType.FullName.Equals(argumentTypeFullName))
+ {
+ return argumentValue;
+ }
+
+ throw new ArgumentException($"The argument type does not match {argumentTypeFullName}.");
+ }
+
+ private IDictionary<long, string> GetEnumerationValues(TypeDefinition argumentType)
+ {
+ var enumValues = from f in argumentType.Fields
+ where !(f.IsRuntimeSpecialName || f.IsSpecialName)
+ select f;
+
var values = new Dictionary<long, string>();
- foreach (var f in
- (from f in type.Fields
- where !(f.IsRuntimeSpecialName || f.IsSpecialName)
- select f))
+ foreach (var item in enumValues)
{
- values[ToInt64(f.Constant)] = f.Name;
+ values[ToInt64(item.Constant)] = item.Name;
}
+
return values;
}
- internal static long ToInt64(object value)
+ private long ToInt64(object value)
{
if (value is ulong)
return (long)(ulong)value;
+
return Convert.ToInt64(value);
}
- public static string FilterSpecialChars(string value)
+ private string FilterSpecialChars(string value)
{
- return value.Replace("\0", "\\0")
+ return value
+ .Replace("\0", "\\0")
.Replace("\t", "\\t")
.Replace("\n", "\\n")
.Replace("\r", "\\r")