mpir/mpir.net/mpir.net-tests/IntegrationTests/XmlCommentsTests.cs

219 lines
8.7 KiB
C#

/*
Copyright 2014 Alex Dyachenko
This file is part of the MPIR Library.
The MPIR Library is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 3 of the License, or (at
your option) any later version.
The MPIR Library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the MPIR Library. If not, see http://www.gnu.org/licenses/.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace MPIR.Tests.HugeIntTests
{
[TestClass]
public class XmlCommentsTests
{
private static readonly string[] ValidMemberTypePrefixes = { "M:", "P:", "F:", "C:" };
[TestMethod]
public void TestComments()
{
var assembly = typeof(HugeInt).Assembly;
var xmlPath = Path.GetFileNameWithoutExtension(assembly.Location) + ".xml";
var xml = XmlCommentsDoc.Deserialize(xmlPath);
var publicTypes = assembly.GetTypes().Where(x => x.IsPublic).ToArray();
var definedClassComments = xml.Members.Where(x => x.Name.StartsWith("T:") && ("" + x.Summary).Trim().Length > 0);
var missingPublicTypes = publicTypes.Select(x => x.FullName).Except(definedClassComments.Select(x => x.MemberName)).ToArray();
Assert.AreEqual(0, missingPublicTypes.Length, "XML Comments are not defined on class(es): " + string.Join(Environment.NewLine, missingPublicTypes));
var publicMembers = publicTypes.SelectMany(x => x.GetMembers())
.Where(XmlCommentsMember.IsIncluded)
.ToArray();
var definedMemberComments = xml.Members.Where(x => ValidMemberTypePrefixes.Contains(x.Name.Substring(0, 2)) && ("" + x.Summary).Trim().Length > 0).ToArray();
var invalidComments = publicMembers.Join(definedMemberComments, XmlCommentsMember.Signature, x => x.MemberName, (Member, Comment) => new { Member, Comment })
.SelectMany(x => x.Comment.Validate(x.Member)).ToArray();
Assert.AreEqual(0, invalidComments.Length, string.Join(Environment.NewLine, invalidComments));
var missingComments = publicMembers.Select(XmlCommentsMember.Signature).Except(definedMemberComments.Select(x => x.MemberName)).ToArray();
Assert.AreEqual(0, missingComments.Length, "XML Comments are not defined on public member(s): " + string.Join(Environment.NewLine, missingComments));
}
}
[Serializable]
[XmlRoot("doc")]
public class XmlCommentsDoc
{
[XmlElement("assembly")]
public string Assembly { get; set; }
[XmlArray("members"), XmlArrayItem("member")]
public List<XmlCommentsMember> Members { get; set; }
public static XmlCommentsDoc Deserialize(string path)
{
var contents = File.ReadAllText(path);
contents = Regex.Replace(contents, "<paramref[^>]*>", "paramref");
contents = Regex.Replace(contents, "</?para>", "");
contents = contents.Replace("!System.Runtime.CompilerServices.IsLong", "");
var serializer = new XmlSerializer(typeof(XmlCommentsDoc));
using (var reader = new StringReader(contents))
return (XmlCommentsDoc)serializer.Deserialize(reader);
}
}
[Serializable]
public class XmlCommentsMember
{
[XmlAttribute("name")]
public string Name { get; set; }
[XmlElement("summary")]
public string Summary { get; set; }
[XmlElement("typeparam")]
public List<XmlCommentsParam> TypeParams { get; set; }
[XmlElement("param")]
public List<XmlCommentsParam> Params { get; set; }
[XmlElement("returns")]
public string Returns { get; set; }
[XmlIgnore]
public string MemberName { get { return Name.Substring(2); } }
public IEnumerable<string> Validate(MemberInfo member)
{
if (("" + Summary).Trim().Length == 0)
yield return "Missing Summary on " + MemberName;
var method = member as MethodBase;
if (method != null)
{
var missingParams = method.GetParameters().Select(x => x.Name).Except(Params.Where(x => ("" + x.Text).Trim().Length > 0).Select(x => x.Name)).ToArray();
if (missingParams.Any())
yield return string.Format("Missing param {0} on {1}", string.Join(", ", missingParams), MemberName);
var invalidParams = Params.Select(x => x.Name).Except(method.GetParameters().Select(x => x.Name)).ToArray();
if (invalidParams.Any())
yield return string.Format("Invalid param {0} on {1}", string.Join(", ", invalidParams), MemberName);
if (method.ContainsGenericParameters)
{
var missingGenericParms = method.GetGenericArguments().Select(x => x.Name).Except(TypeParams.Where(x => ("" + x.Text).Trim().Length > 0).Select(x => x.Name)).ToArray();
if (missingGenericParms.Any())
yield return string.Format("Missing generic param {0} on {1}", string.Join(", ", missingGenericParms), MemberName);
var invalidGenericParams = TypeParams.Select(x => x.Name).Except(method.GetGenericArguments().Select(x => x.Name)).ToArray();
if (invalidGenericParams.Any())
yield return string.Format("Invalid generic param {0} on {1}", string.Join(", ", invalidGenericParams), MemberName);
}
}
var method2 = member as MethodInfo;
if (method2 != null)
{
if (method2.ReturnType != typeof(void) && ("" + Returns).Trim().Length == 0)
yield return "Missing Returns on " + MemberName;
}
}
public static bool IsIncluded(MemberInfo member)
{
var method = member as MethodInfo;
if (method != null)
{
if (method.Name.StartsWith("get_") || method.Name.StartsWith("set_"))
return false;
if (method.ReflectedType != method.DeclaringType)
return false;
}
if (member.Name == "value__" && member.DeclaringType.IsEnum)
return false;
return true;
}
public static string Signature(MemberInfo member)
{
var sig = new StringBuilder();
sig.Append(member.DeclaringType.FullName).Append('.');
var method = member as MethodInfo;
var ctor = member as ConstructorInfo;
var methodBase = member as MethodBase;
if (ctor != null)
sig.Append("#ctor");
else
sig.Append(member.Name);
if(methodBase != null)
{
if (methodBase.IsGenericMethodDefinition)
{
sig.Append("``").Append(methodBase.GetGenericArguments().Length);
}
var parameters = methodBase.GetParameters();
if(parameters.Length > 0)
{
sig.Append('(')
.Append(string.Join(",", parameters.Select(FormatParameterType)))
.Append(')');
}
}
return sig.ToString();
}
public static string FormatParameterType(ParameterInfo p)
{
if (p.ParameterType.IsGenericType)
return p.ParameterType.Namespace + "." + p.ParameterType.Name + "{" + string.Join(",", p.ParameterType.GetGenericArguments().Select(x => x.FullName)) + "}";
if (p.ParameterType.FullName == null)
return "``" + p.ParameterType.Name.Replace((p.Member as MethodInfo).GetGenericArguments()[p.Position].Name, p.Position.ToString());
if (p.IsOut)
return p.ParameterType.FullName.Replace('&', '@');
return p.ParameterType.FullName;
}
public override string ToString()
{
return MemberName;
}
}
[Serializable]
public class XmlCommentsParam
{
[XmlAttribute("name")]
public string Name { get; set; }
[XmlText]
public string Text { get; set; }
}
}