Извлекать xml комментарии только для публичных пользователей - PullRequest
17 голосов
/ 09 марта 2009

Я использую xml-комментарии для документирования как публичных, так и внутренних и закрытых членов моих компонентов. Я хотел бы упаковать сгенерированные xml-файлы документации со сборками компонентов, чтобы включить «rich» (например, с методом, исключениями и описаниями параметров) Visual Studio Intellisense с конечным продуктом. Проблема в том, что компилятор C # создает записи документации для всего (включая внутренние классы, методы, закрытые поля внутренних перечислений и т. Д.), И, похоже, нет перехода в режим «только открытые члены».

Теперь я не хочу просматривать более 50 файлов с методами XX в каждом и удалять все комментарии для частных и внутренних участников. Даже если бы я это сделал, у меня, вероятно, не было бы большого успеха с файлами ресурсов auto-gen, потому что эти строго типизированные классы ресурсов автоматически комментируются и не являются публичными.

Мой вопрос: есть ли какая-то опция / флаг, который я пропускаю? Если нет, существуют ли какие-либо инструменты, которые могут помочь отделить открытых участников от остальных (прежде чем я начну кодировать один)?

Ответы [ 6 ]

5 голосов
/ 01 июня 2011

Конструктор файлов справки SandCastle имеет возможность воссоздать XML-файлы, содержащие только настроенные режимы доступа для методов, свойств и т. Д. *

Единственным недостатком является то, что вам придется создавать документацию.

EDIT

Поскольку это было давно, я забыл, что добавил в SHFB "компонент" для генерации XML.

Хорошая новость заключается в том, что этот компонент включен в SHFB.

Вы должны добавить «Компонент Intellisense» в проект SHFB. Затем он сгенерирует XML в соответствии с настроенным проектом SHFB.

Для получения дополнительной информации: Компонент Intellisense в SHFB

4 голосов
/ 01 июня 2011

Внутри eazfuscator есть инструмент, который может удалять непубличную документацию. Вы можете увидеть пример здесь

3 голосов
/ 03 июня 2011

Я подумал над этим и решил изменить способ решения этой конкретной проблемы. Вместо того, чтобы находить тип / член в сборке, пытаясь разобрать нотацию XML-документации. Я решил просто создать набор строк (нотации XML-документации) для общедоступного API, который затем можно будет использовать для проверки того, что член не является публичным.

Это действительно просто. Отправьте сборку в XmlDocumentationStringSet, и она создаст набор строк открытого API и удалит элементы, которые не являются публичными.

static void Main(string[] args)
{
    var el = XElement.Load("ConsoleApplication18.XML");

    // obviously, improve this if necessary (might not work like this if DLL isn't already loaded)
    // you can use file paths
    var assemblyName = el.Descendants("assembly").FirstOrDefault();
    var assembly = Assembly.ReflectionOnlyLoad(assemblyName.Value);

    var stringSet = new XmlDocumentationStringSet(assembly);

    foreach (var member in el.Descendants("member").ToList()) // .ToList enables removing while traversing
    {
        var attr = member.Attribute("name");
        if (attr == null)
        {
            continue;
        }
        if (!stringSet.Contains(attr.Value))
        {
            member.Remove();
        }
    }

    el.Save("ConsoleApplication18-public.XML");
}

А вот класс, который создает имена документации XML (он немного большой, но я все равно решил разместить здесь весь источник):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace ConsoleApplication18
{
    public class XmlDocumentationStringSet : IEnumerable<string>
    {
        private HashSet<string> stringSet = new HashSet<string>(StringComparer.Ordinal);

        public XmlDocumentationStringSet(Assembly assembly)
        {
            AddRange(assembly.GetExportedTypes());
        }

        public bool Contains(string name)
        {
            return stringSet.Contains(name);
        }

        /// <summary>
        /// Heelloasdasdasd
        /// </summary>
        /// <param name="types"></param>
        public void AddRange(IEnumerable<Type> types)
        {
            foreach (var type in types)
            {
                Add(type);
            }
        }

        public void Add(Type type)
        {
            // Public API only
            if (!type.IsVisible)
            {
                return;
            }
            var members = type.GetMembers(BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            foreach (var member in members)
            {
                Add(type, member);
            }
        }

        StringBuilder sb = new StringBuilder();

        private void Add(Type type, MemberInfo member)
        {
            Type nestedType = null;

            sb.Length = 0;

            switch (member.MemberType)
            {
                case MemberTypes.Constructor:
                    sb.Append("M:");
                    AppendConstructor(sb, (ConstructorInfo)member);
                    break;
                case MemberTypes.Event:
                    sb.Append("E:");
                    AppendEvent(sb, (EventInfo)member);
                    break;
                case MemberTypes.Field:
                    sb.Append("F:");
                    AppendField(sb, (FieldInfo)member);
                    break;
                case MemberTypes.Method:
                    sb.Append("M:");
                    AppendMethod(sb, (MethodInfo)member);
                    break;
                case MemberTypes.NestedType:
                    nestedType = (Type)member;
                    if (IsVisible(nestedType))
                    {
                        sb.Append("T:");
                        AppendNestedType(sb, (Type)member);
                    }
                    break;
                case MemberTypes.Property:
                    sb.Append("P:");
                    AppendProperty(sb, (PropertyInfo)member);
                    break;
            }

            if (sb.Length > 0)
            {
                stringSet.Add(sb.ToString());
            }

            if (nestedType != null)
            {
                Add(nestedType);
            }
        }

        private bool IsVisible(Type nestedType)
        {
            return nestedType.IsVisible;
        }

        private void AppendProperty(StringBuilder sb, PropertyInfo propertyInfo)
        {
            if (!IsVisible(propertyInfo))
            {
                sb.Length = 0;
                return;
            }
            AppendType(sb, propertyInfo.DeclaringType);
            sb.Append('.').Append(propertyInfo.Name);
        }

        private bool IsVisible(PropertyInfo propertyInfo)
        {
            var getter = propertyInfo.GetGetMethod();
            var setter = propertyInfo.GetSetMethod();
            return (getter != null && IsVisible(getter)) || (setter != null && IsVisible(setter));
        }

        private void AppendNestedType(StringBuilder sb, Type type)
        {
            AppendType(sb, type.DeclaringType);
        }

        private void AppendMethod(StringBuilder sb, MethodInfo methodInfo)
        {
            if (!IsVisible(methodInfo) || (methodInfo.IsHideBySig && methodInfo.IsSpecialName))
            {
                sb.Length = 0;
                return;
            }
            AppendType(sb, methodInfo.DeclaringType);
            sb.Append('.').Append(methodInfo.Name);
            AppendParameters(sb, methodInfo.GetParameters());
        }

        private bool IsVisible(MethodInfo methodInfo)
        {
            return methodInfo.IsFamily || methodInfo.IsPublic;
        }

        private void AppendParameters(StringBuilder sb, ParameterInfo[] parameterInfo)
        {
            if (parameterInfo.Length == 0)
            {
                return;
            }
            sb.Append('(');
            for (int i = 0; i < parameterInfo.Length; i++)
            {
                if (i > 0)
                {
                    sb.Append(',');
                }
                var p = parameterInfo[i];
                AppendType(sb, p.ParameterType);
            }
            sb.Append(')');
        }

        private void AppendField(StringBuilder sb, FieldInfo fieldInfo)
        {
            if (!IsVisible(fieldInfo))
            {
                sb.Length = 0;
                return;
            }
            AppendType(sb, fieldInfo.DeclaringType);
            sb.Append('.').Append(fieldInfo.Name);
        }

        private bool IsVisible(FieldInfo fieldInfo)
        {
            return fieldInfo.IsFamily || fieldInfo.IsPublic;
        }

        private void AppendEvent(StringBuilder sb, EventInfo eventInfo)
        {
            if (!IsVisible(eventInfo))
            {
                sb.Length = 0;
                return;
            }
            AppendType(sb, eventInfo.DeclaringType);
            sb.Append('.').Append(eventInfo.Name);
        }

        private bool IsVisible(EventInfo eventInfo)
        {
            return true; // hu?
        }

        private void AppendConstructor(StringBuilder sb, ConstructorInfo constructorInfo)
        {
            if (!IsVisible(constructorInfo))
            {
                sb.Length = 0;
                return;
            }
            AppendType(sb, constructorInfo.DeclaringType);
            sb.Append('.').Append("#ctor");
            AppendParameters(sb, constructorInfo.GetParameters());
        }

        private bool IsVisible(ConstructorInfo constructorInfo)
        {
            return constructorInfo.IsFamily || constructorInfo.IsPublic;
        }

        private void AppendType(StringBuilder sb, Type type)
        {
            if (type.DeclaringType != null)
            {
                AppendType(sb, type.DeclaringType);
                sb.Append('.');
            }
            else if (!string.IsNullOrEmpty(type.Namespace))
            {
                sb.Append(type.Namespace);
                sb.Append('.');
            }
            sb.Append(type.Name);
            if (type.IsGenericType && !type.IsGenericTypeDefinition)
            {
                // Remove "`1" suffix from type name
                while (char.IsDigit(sb[sb.Length - 1]))
                    sb.Length--;
                sb.Length--;
                {
                    var args = type.GetGenericArguments();
                    sb.Append('{');
                    for (int i = 0; i < args.Length; i++)
                    {
                        if (i > 0)
                        {
                            sb.Append(',');
                        }
                        AppendType(sb, args[i]);
                    }
                    sb.Append('}');
                }
            }
        }

        public IEnumerator<string> GetEnumerator()
        {
            return stringSet.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}

О, и я еще не выяснил, как обрабатывать события, они всегда видны в этом примере.

2 голосов
/ 10 октября 2012

@ Джон Лейдгрен

У меня такое же требование, и я нашел ответ на недостающий кусочек вашего кода. Событие имеет 2 метода, Add и Remove, и считается публичным, если любой из них является публичным. Так было бы что-то вроде:

private bool IsVisible(EventInfo eventInfo)
{
    return eventInfo.GetAddMethod(false) != null
        || eventInfo.GetRemoveMethod(false) != null;
}

хотя я не могу придумать причину, по которой один будет публичным, а не другой.

0 голосов
/ 04 июля 2011

Я столкнулся с той же проблемой. SHFB очень медленный, и, поскольку у нас есть другая кодовая база документации, она нам не нужна, чтобы сгенерировать документацию для нас.

В итоге я использовал XMLStarlet плюс отдельное пространство имен для внутренних классов. Например, все мои внутренние классы будут находиться в MyCompany.MyProduct.Internal. Тогда я могу использовать одну простую команду

xml ed -L -d "//member[contains(@name, 'MyCompany.MyProduct.Internal')]" MyProduct.xml

для очистки XML. Это, конечно, не пуленепробиваемый - он не охватывает внутренних членов в открытых классах, и он требует некоторой дисциплины, чтобы помнить, чтобы поместить внутренние классы во внутренние. Но это самый чистый и наименее навязчивый метод, который работает для меня. Это также отдельный EXE-файл, легко проверяемый на сервере сборки, без проблем.

0 голосов
/ 09 марта 2009

Какой инструмент вы используете для создания документации? Я использую Sandcastle, и это дает вам возможность выбирать участников для доступа по доступности.

В общем, ожидается, что XML будет содержать всю информацию, которая может понадобиться , , и это до инструмента обработки, чтобы выбрать из него то, что нужно.

...