public static void ParseComments(string scriptAssetFullPath, [NotNull] Dictionary <string, MemberCommentInfo> addToCollection)
        {
                        #if DEV_MODE && PI_ASSERTATIONS
            UnityEngine.Debug.Assert(scriptAssetFullPath.IndexOf(':') != -1);
                        #endif

            var commentXml       = new StringBuilder();
            var memberDefinition = new StringBuilder();

            using (var reader = new StreamReader(scriptAssetFullPath))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    line = line.TrimStart();
                    if (line.StartsWith("///", StringComparison.Ordinal))
                    {
                        commentXml.AppendLine(line.Substring(3));
                    }
                    else if (commentXml.Length > 0)
                    {
                        memberDefinition.AppendLine(line);

                        if (line.IndexOfAny(MemberDefinitionEndCharacters) != -1)
                        {
                            var info = new MemberCommentInfo(commentXml.ToString(), memberDefinition.ToString());
                            commentXml.Length       = 0;
                            memberDefinition.Length = 0;

                            if (info.name.Length > 0 && info.comment.Length > 0)
                            {
                                if (addToCollection.ContainsKey(info.name))
                                {
                                                                        #if DEV_MODE
                                    UnityEngine.Debug.LogWarning("ParseComments(" + scriptAssetFullPath + ") : \"" + info.name + "\" key already in dictionary");
                                                                        #endif
                                    continue;
                                }

                                addToCollection.Add(info.name, info);
                            }
                            else
                            {
                                info.Dispose();
                            }
                        }
                    }
                }
            }
        }
Example #2
0
        /// <summary>
        /// Attempts to find XML documentation file for dll that defines the given class type, parse XML documentation comments
        /// for all members of type, and populate the tooltips Dictionary with the results.
        /// </summary>
        /// <param name="classType"> Type of the class whose members' tooltips we want. This cannot be null. </param>
        /// <param name="tooltips"> [out] The member tooltips. If no XML documentation is found, this will be null. </param>
        /// <returns> True if XML documentation file was found and loaded successfully, false if not. </returns>
        public static bool TryGetMemberTooltips([NotNull] Type classType, [CanBeNull] out Dictionary <string, string> tooltips)
        {
            var         assembly = classType.Assembly;
            XmlDocument xmlDocumentation;

            if (!CachedXMLDocuments.TryGetValue(assembly, out xmlDocumentation))
            {
                xmlDocumentation             = GetXMLDocument(assembly);
                CachedXMLDocuments[assembly] = xmlDocumentation;
            }

            if (xmlDocumentation == null)
            {
                tooltips = null;
                return(false);
            }

            var doc = xmlDocumentation["doc"];

            if (doc == null)
            {
                                #if DEV_MODE
                UnityEngine.Debug.LogWarning("XML Documentation for assembly " + classType.Assembly.GetName().Name + " had no \"doc\" section");
                                #endif
                tooltips = null;
                return(false);
            }

            var members = doc["members"];
            if (members == null)
            {
                                #if DEV_MODE
                UnityEngine.Debug.LogWarning("XML Documentation for assembly " + classType.Assembly.GetName().Name + " had no \"members\" section under \"doc\"");
                                #endif
                tooltips = null;
                return(false);
            }

            tooltips = new Dictionary <string, string>();

            // For example:
            // T:UnityEngine.BoxCollider (class type)
            // T:Sisus.OdinSerializer.QueueFormatter`2 (class with two generic types)
            // F:UnityEngine.Camera.onPreCull (field)
            // P:UnityEngine.BoxCollider.size (property)
            // M:UnityEngine.AI.NavMeshAgent.CompleteOffMeshLink (method with no parameters)
            // M:UnityEngine.Collider.ClosestPoint(UnityEngine.Vector3) (method with parameters)
            // M:Namespace.ClassName`1.MethodName (method inside generic class)
            // M:Namespace.ClassName`1.MethodName(`0) (method inside generic class with parameter of class generic type)
            // M:Namespace.ClassName.#ctor (constructor with no parameters)
            // M:Namespace.ClassName.MethodName``1(``0[]) (method with generic parameter)
            // M:Namespace.ClassName.MethodName``2(System.Collections.Generic.Dictionary{``0,``1}) (method with two generic parameters)
            string match = ":" + classType.FullName + ".";

            foreach (XmlElement member in members)
            {
                // skip members without attributes to avoid exceptions
                if (!member.HasAttributes)
                {
                    continue;
                }

                var attributes            = member.Attributes;
                var typePrefixAndFullName = attributes["name"].InnerText;
                if (typePrefixAndFullName.IndexOf(match, StringComparison.Ordinal) != -1)
                {
                    MemberCommentInfo.ParseXmlComment(member, StringBuilder);
                    var comment = StringBuilder.ToString();
                    StringBuilder.Length = 0;

                    bool isMethod = typePrefixAndFullName[0] == 'M';
                    if (isMethod)
                    {
                        int methodParamsStart = typePrefixAndFullName.IndexOf('(');

                        // handle special case of methods with parameters
                        if (methodParamsStart != -1)
                        {
                            int    methodNameStart         = typePrefixAndFullName.LastIndexOf('.', methodParamsStart - 1) + 1;
                            string methodNameAndParameters = typePrefixAndFullName.Substring(methodNameStart);

                                                        #if CACHE_METHODS_USING_FULL_SIGNATURE
                            tooltips[methodNameAndParameters] = comment;
                                                        #else
                            int    parametersSectionLength = typePrefixAndFullName.Length - methodParamsStart;
                            int    methodNameLength        = methodNameAndParameters.Length - parametersSectionLength;
                            string methodNameOnly          = methodNameAndParameters.Substring(0, methodNameLength);

                            int genericTypeStart = methodNameOnly.IndexOf('`');
                            if (genericTypeStart != -1)
                            {
                                methodNameOnly = methodNameOnly.Substring(genericTypeStart);
                            }

                            tooltips[methodNameOnly] = comment;
                                                        #endif
                            continue;
                        }
                                                #if !CACHE_METHODS_USING_FULL_SIGNATURE
                        // handle special case of generic methods
                        else
                        {
                            int    methodNameStart  = typePrefixAndFullName.LastIndexOf('.') + 1;
                            string methodName       = typePrefixAndFullName.Substring(methodNameStart);
                            int    genericTypeStart = methodName.IndexOf('`');
                            if (genericTypeStart != -1)
                            {
                                methodName = methodName.Substring(genericTypeStart);
                            }
                            tooltips[methodName] = comment;
                        }
                                                #endif
                    }

                    int    memberNameStart = typePrefixAndFullName.LastIndexOf('.') + 1;
                    string memberName      = typePrefixAndFullName.Substring(memberNameStart);
                    tooltips[memberName] = comment;
                }
            }
            return(true);
        }
        /// <summary>
        /// Tries to parse XML Documentation Comments from script file at path.
        /// </summary>
        /// <param name="scriptAssetPath"></param>
        /// <param name="addToCollection"></param>
        /// <param name="classTypeMustMatch"></param>
        /// <returns> False if failed, either because of write permissions, or because class definition was not found inside script asset. </returns>
        public static bool ParseComments(string scriptAssetPath, [NotNull] Dictionary <string, string> addToCollection, [CanBeNull] Type classTypeMustMatch)
        {
                        #if DEV_MODE && PI_ASSERTATIONS
            UnityEngine.Debug.Assert(scriptAssetPath.EndsWith(".cs", StringComparison.OrdinalIgnoreCase), scriptAssetPath);
                        #endif

            if (classTypeMustMatch != null)
            {
                var  mustBeFound = new List <string>(classTypeMustMatch.FullName.Split('.'));
                bool allFound    = false;

                try
                {
                    using (var reader = new StreamReader(scriptAssetPath))
                    {
                        string line;
                        int    findCount = mustBeFound.Count;

                        while ((line = reader.ReadLine()) != null)
                        {
                            for (int n = findCount - 1; n >= 0; n--)
                            {
                                if (line.IndexOf(mustBeFound[n], StringComparison.Ordinal) != -1)
                                {
                                    mustBeFound.RemoveAt(n);
                                    findCount--;
                                    if (findCount == 0)
                                    {
                                        allFound = true;
                                        break;
                                    }
                                }
                            }

                            if (allFound)
                            {
                                break;
                            }
                        }
                    }
                }
                                #if DEV_MODE
                catch (PathTooLongException e)
                {
                    UnityEngine.Debug.LogError("ParseComments PathTooLongException. Path length was was " + scriptAssetPath.Length + ".\n" + e);
                    return(false);
                }
                catch (DirectoryNotFoundException e)
                {
                    UnityEngine.Debug.LogError("ParseComments DirectoryNotFoundException. Path length was " + scriptAssetPath.Length + ".\n" + e);
                    return(false);
                }
                catch (Exception e)
                {
                    UnityEngine.Debug.LogError("ParseComments " + e.GetType().Name + ". Path length was was " + scriptAssetPath.Length + ".\n" + e);
                    return(false);
                }
                                #else
                catch
                {
                    return(false);
                }
                                #endif

                if (!allFound)
                {
                                        #if DEV_MODE && DEBUG_FAIL_PARSE
                    UnityEngine.Debug.LogWarning("Failed to find the following parts of full class name inside script asset: " + string.Join(", ", mustBeFound.ToArray()) + "\nasset path: " + scriptAssetFullPath);
                                        #endif
                    return(false);
                }
            }

            var commentXml       = StringBuilderPool.Create();
            var memberDefinition = StringBuilderPool.Create();

            try
            {
                using (var reader = new StreamReader(scriptAssetPath))
                {
                    string line;
                    while ((line = reader.ReadLine()) != null)
                    {
                        line = line.TrimStart();
                        if (line.StartsWith("///", StringComparison.Ordinal))
                        {
                            commentXml.AppendLine(line.Substring(3));
                        }
                        else if (commentXml.Length > 0)
                        {
                            memberDefinition.AppendLine(line);

                            if (line.IndexOfAny(MemberDefinitionEndCharacters) != -1)
                            {
                                var info = new MemberCommentInfo(commentXml.ToString(), memberDefinition.ToString());
                                commentXml.Length       = 0;
                                memberDefinition.Length = 0;

                                if (info.name.Length > 0 && info.comment.Length > 0)
                                {
                                    if (addToCollection.ContainsKey(info.name))
                                    {
                                                                                #if DEV_MODE
                                        UnityEngine.Debug.LogWarning("ParseComments(" + scriptAssetPath + ") : \"" + info.name + "\" key already in dictionary");
                                                                                #endif
                                        continue;
                                    }

                                    addToCollection.Add(info.name, info.comment);
                                }
                                else
                                {
                                    info.Dispose();
                                }
                            }
                        }
                    }
                }
            }
                        #if DEV_MODE
            catch (PathTooLongException e)
            {
                UnityEngine.Debug.LogError("ParseComments PathTooLongException. Path \"" + scriptAssetPath + "\" length was " + scriptAssetPath.Length + ".\n" + e);
                StringBuilderPool.Dispose(ref commentXml);
                StringBuilderPool.Dispose(ref memberDefinition);
                return(false);
            }
            catch (DirectoryNotFoundException e)
            {
                UnityEngine.Debug.LogError("ParseComments DirectoryNotFoundException. Path was " + scriptAssetPath + ".\n" + e);
                StringBuilderPool.Dispose(ref commentXml);
                StringBuilderPool.Dispose(ref memberDefinition);
                return(false);
            }
            catch (Exception e)
            {
                UnityEngine.Debug.LogError("ParseComments " + e.GetType().Name + ". Path was " + scriptAssetPath + ".\n" + e);
                StringBuilderPool.Dispose(ref commentXml);
                StringBuilderPool.Dispose(ref memberDefinition);
                return(false);
            }
                        #else
            catch
            {
                StringBuilderPool.Dispose(ref commentXml);
                StringBuilderPool.Dispose(ref memberDefinition);
                return(false);
            }
                        #endif

            StringBuilderPool.Dispose(ref commentXml);
            StringBuilderPool.Dispose(ref memberDefinition);
            return(true);
        }