diff options
Diffstat (limited to 'mdoc/Mono.Documentation/Updater/DocumentationEnumerator.cs')
-rw-r--r-- | mdoc/Mono.Documentation/Updater/DocumentationEnumerator.cs | 378 |
1 files changed, 378 insertions, 0 deletions
diff --git a/mdoc/Mono.Documentation/Updater/DocumentationEnumerator.cs b/mdoc/Mono.Documentation/Updater/DocumentationEnumerator.cs new file mode 100644 index 00000000..53d5e8b0 --- /dev/null +++ b/mdoc/Mono.Documentation/Updater/DocumentationEnumerator.cs @@ -0,0 +1,378 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml; + +using Mono.Cecil; +using StringList = System.Collections.Generic.List<string>; + +using Mono.Documentation.Util; + +namespace Mono.Documentation.Updater +{
+ class DocumentationEnumerator + { + + public virtual IEnumerable<TypeDefinition> GetDocumentationTypes (AssemblyDefinition assembly, List<string> forTypes) + { + return GetDocumentationTypes (assembly, forTypes, null); + } + + protected IEnumerable<TypeDefinition> GetDocumentationTypes (AssemblyDefinition assembly, List<string> forTypes, HashSet<string> seen) + { + foreach (TypeDefinition type in assembly.GetTypes ()) + { + if (forTypes != null && forTypes.BinarySearch (type.FullName) < 0) + continue; + if (seen != null && seen.Contains (type.FullName)) + continue; + yield return type; + foreach (TypeDefinition nested in type.NestedTypes) + yield return nested; + } + } + + public virtual IEnumerable<DocsNodeInfo> GetDocumentationMembers (XmlDocument basefile, TypeDefinition type) + { + foreach (XmlElement oldmember in basefile.SelectNodes ("Type/Members/Member")) + { + if (oldmember.GetAttribute ("__monodocer-seen__") == "true") + { + oldmember.RemoveAttribute ("__monodocer-seen__"); + continue; + } + MemberReference m = GetMember (type, new DocumentationMember (oldmember)); + if (m == null) + { + yield return new DocsNodeInfo (oldmember); + } + else + { + yield return new DocsNodeInfo (oldmember, m); + } + } + } + + protected static MemberReference GetMember (TypeDefinition type, DocumentationMember member) + { + string membertype = member.MemberType; + + string returntype = member.ReturnType; + + string docName = member.MemberName; + + string[] docTypeParams = GetTypeParameters (docName, member.TypeParameters); + + // If we're using 'magic types', then we might get false positives ... in those cases, we keep searching + MemberReference likelyCandidate = null; + + // Loop through all members in this type with the same name + var reflectedMembers = GetReflectionMembers (type, docName).ToArray (); + foreach (MemberReference mi in reflectedMembers) + { + bool matchedMagicType = false; + if (mi is TypeDefinition) continue; + if (MDocUpdater.GetMemberType (mi) != membertype) continue; + + if (MDocUpdater.IsPrivate (mi)) + continue; + + IList<ParameterDefinition> pis = null; + string[] typeParams = null; + if (mi is MethodDefinition) + { + MethodDefinition mb = (MethodDefinition)mi; + pis = mb.Parameters; + if (mb.IsGenericMethod ()) + { + IList<GenericParameter> args = mb.GenericParameters; + typeParams = args.Select (p => p.Name).ToArray (); + } + } + else if (mi is PropertyDefinition) + pis = ((PropertyDefinition)mi).Parameters; + + // check type parameters + int methodTcount = member.TypeParameters == null ? 0 : member.TypeParameters.Count; + int reflectionTcount = typeParams == null ? 0 : typeParams.Length; + if (methodTcount != reflectionTcount) + continue; + + // check member parameters + int mcount = member.Parameters == null ? 0 : member.Parameters.Count; + int pcount = pis == null ? 0 : pis.Count; + if (mcount != pcount) + continue; + + MethodDefinition mDef = mi as MethodDefinition; + if (mDef != null && !mDef.IsConstructor) + { + // Casting operators can overload based on return type. + string rtype = GetReplacedString ( + MDocUpdater.GetDocTypeFullName (((MethodDefinition)mi).ReturnType), + typeParams, docTypeParams); + string originalRType = rtype; + if (MDocUpdater.SwitchingToMagicTypes) + { + rtype = NativeTypeManager.ConvertFromNativeType (rtype); + + } + if ((returntype != rtype && originalRType == rtype) || + (MDocUpdater.SwitchingToMagicTypes && returntype != originalRType && returntype != rtype && originalRType != rtype)) + { + continue; + } + + if (originalRType != rtype) + matchedMagicType = true; + } + + if (pcount == 0) + return mi; + bool good = true; + for (int i = 0; i < pis.Count; i++) + { + string paramType = GetReplacedString ( + MDocUpdater.GetDocParameterType (pis[i].ParameterType), + typeParams, docTypeParams); + + // if magictypes, replace paramType to "classic value" ... so the comparison works + string originalParamType = paramType; + if (MDocUpdater.SwitchingToMagicTypes) + { + paramType = NativeTypeManager.ConvertFromNativeType (paramType); + } + + string xmlMemberType = member.Parameters[i]; + if ((!paramType.Equals (xmlMemberType) && paramType.Equals (originalParamType)) || + (MDocUpdater.SwitchingToMagicTypes && !originalParamType.Equals (xmlMemberType) && !paramType.Equals (xmlMemberType) && !paramType.Equals (originalParamType))) + { + + // did not match ... if we're dropping the namespace, and the paramType has the dropped + // namespace, we should see if it matches when added + bool stillDoesntMatch = true; + if (MDocUpdater.HasDroppedNamespace (type) && paramType.StartsWith (MDocUpdater.droppedNamespace)) + { + string withDroppedNs = string.Format ("{0}.{1}", MDocUpdater.droppedNamespace, xmlMemberType); + + stillDoesntMatch = withDroppedNs != paramType; + } + + if (stillDoesntMatch) + { + good = false; + break; + } + } + + if (originalParamType != paramType) + matchedMagicType = true; + } + if (!good) continue; + + if (MDocUpdater.SwitchingToMagicTypes && likelyCandidate == null && matchedMagicType) + { + // we matched this on a magic type conversion ... let's keep going to see if there's another one we should look at that matches more closely + likelyCandidate = mi; + continue; + } + + return mi; + } + + return likelyCandidate; + } + + static string[] GetTypeParameters (string docName, IEnumerable<string> knownParameters) + { + if (docName[docName.Length - 1] != '>') + return null; + StringList types = new StringList (); + int endToken = docName.Length - 2; + int i = docName.Length - 2; + do + { + if (docName[i] == ',' || docName[i] == '<') + { + types.Add (docName.Substring (i + 1, endToken - i)); + endToken = i - 1; + } + if (docName[i] == '<') + break; + } while (--i >= 0); + + types.Reverse (); + var arrayTypes = types.ToArray (); + + if (knownParameters != null && knownParameters.Any () && arrayTypes.Length != knownParameters.Count ()) + return knownParameters.ToArray (); + else + return arrayTypes; + } + + protected static IEnumerable<MemberReference> GetReflectionMembers (TypeDefinition type, string docName) + { + // In case of dropping the namespace, we have to remove the dropped NS + // so that docName will match what's in the assembly/type + if (MDocUpdater.HasDroppedNamespace (type) && docName.StartsWith (MDocUpdater.droppedNamespace + ".")) + { + int droppedNsLength = MDocUpdater.droppedNamespace.Length; + docName = docName.Substring (droppedNsLength + 1, docName.Length - droppedNsLength - 1); + } + + // need to worry about 4 forms of //@MemberName values: + // 1. "Normal" (non-generic) member names: GetEnumerator + // - Lookup as-is. + // 2. Explicitly-implemented interface member names: System.Collections.IEnumerable.Current + // - try as-is, and try type.member (due to "kludge" for property + // support. + // 3. "Normal" Generic member names: Sort<T> (CSC) + // - need to remove generic parameters --> "Sort" + // 4. Explicitly-implemented interface members for generic interfaces: + // -- System.Collections.Generic.IEnumerable<T>.Current + // - Try as-is, and try type.member, *keeping* the generic parameters. + // --> System.Collections.Generic.IEnumerable<T>.Current, IEnumerable<T>.Current + // 5. As of 2008-01-02, gmcs will do e.g. 'IFoo`1[A].Method' instead of + // 'IFoo<A>.Method' for explicitly implemented methods; don't interpret + // this as (1) or (2). + if (docName.IndexOf ('<') == -1 && docName.IndexOf ('[') == -1) + { + // Cases 1 & 2 + foreach (MemberReference mi in type.GetMembers (docName)) + yield return mi; + if (CountChars (docName, '.') > 0) + { + + Func<MemberReference, bool> verifyInterface = (member) => + { + var meth = member as MethodDefinition; + + if (meth == null && member is PropertyReference) + { + var propertyDefinition = ((PropertyReference)member).Resolve (); + meth = propertyDefinition.GetMethod ?? propertyDefinition.SetMethod; + } + return meth != null && (member.Name.Equals (".ctor") || DocUtils.IsExplicitlyImplemented (meth)); + }; + + int memberCount = 0; + + // might be a property; try only type.member instead of + // namespace.type.member. + var typeMember = DocUtils.GetTypeDotMember (docName); + var memberName = DocUtils.GetMember (docName); + foreach (MemberReference mi in + type.GetMembers (typeMember).Where (verifyInterface)) + { + memberCount++; + yield return mi; + } + + // some VB libraries use just the member name + foreach (MemberReference mi in + type.GetMembers (memberName).Where (verifyInterface)) + { + memberCount++; + yield return mi; + } + + // some VB libraries use a `typemember` naming convention + foreach (MemberReference mi in + type.GetMembers (typeMember.Replace (".", "")).Where (verifyInterface)) + { + memberCount++; + yield return mi; + } + + // if we still haven't found the member, there are some VB libraries + // that use a different interface name for implementation. + if (memberCount == 0) + { + foreach (MemberReference mi in + type + .GetMembers () + .Where (m => m.Name.StartsWith ("I", StringComparison.InvariantCultureIgnoreCase) && m.Name.EndsWith (memberName, StringComparison.InvariantCultureIgnoreCase)) + .Where (verifyInterface)) + yield return mi; + } + } + yield break; + } + // cases 3 & 4 + int numLt = 0; + int numDot = 0; + int startLt, startType, startMethod; + startLt = startType = startMethod = -1; + for (int i = 0; i < docName.Length; ++i) + { + switch (docName[i]) + { + case '<': + if (numLt == 0) + { + startLt = i; + } + ++numLt; + break; + case '>': + --numLt; + if (numLt == 0 && (i + 1) < docName.Length) + // there's another character in docName, so this <...> sequence is + // probably part of a generic type -- case 4. + startLt = -1; + break; + case '.': + startType = startMethod; + startMethod = i; + ++numDot; + break; + } + } + string refName = startLt == -1 ? docName : docName.Substring (0, startLt); + // case 3 + foreach (MemberReference mi in type.GetMembers (refName)) + yield return mi; + + // case 4 + foreach (MemberReference mi in type.GetMembers (refName.Substring (startType + 1))) + yield return mi; + + // If we _still_ haven't found it, we've hit another generic naming issue: + // post Mono 1.1.18, gmcs generates [[FQTN]] instead of <TypeName> for + // explicitly-implemented METHOD names (not properties), e.g. + // "System.Collections.Generic.IEnumerable`1[[Foo, test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]].GetEnumerator" + // instead of "System.Collections.Generic.IEnumerable<Foo>.GetEnumerator", + // which the XML docs will contain. + // + // Alas, we can't derive the Mono name from docName, so we need to iterate + // over all member names, convert them into CSC format, and compare... :-( + if (numDot == 0) + yield break; + foreach (MemberReference mi in type.GetMembers ()) + { + if (MDocUpdater.GetMemberName (mi) == docName) + yield return mi; + } + } + + static string GetReplacedString (string typeName, string[] from, string[] to) + { + if (from == null) + return typeName; + for (int i = 0; i < from.Length; ++i) + typeName = typeName.Replace (from[i], to[i]); + return typeName; + } + + private static int CountChars (string s, char c) + { + int count = 0; + for (int i = 0; i < s.Length; ++i) + { + if (s[i] == c) + ++count; + } + return count; + } + } +}
\ No newline at end of file |