From b6e072bcfe0502118f8d363e0849982111e5f0a2 Mon Sep 17 00:00:00 2001 From: Jb Evain Date: Mon, 8 Feb 2016 22:11:27 -0800 Subject: Add the ability to get an xml documentation comment id from a Cecil member --- rocks/Mono.Cecil.Rocks.csproj | 1 + rocks/Mono.Cecil.Rocks/DocCommentId.cs | 253 +++++++++++++++++++++++ rocks/Test/Mono.Cecil.Rocks.Tests.csproj | 1 + rocks/Test/Mono.Cecil.Tests/DocCommentIdTests.cs | 248 ++++++++++++++++++++++ 4 files changed, 503 insertions(+) create mode 100644 rocks/Mono.Cecil.Rocks/DocCommentId.cs create mode 100644 rocks/Test/Mono.Cecil.Tests/DocCommentIdTests.cs diff --git a/rocks/Mono.Cecil.Rocks.csproj b/rocks/Mono.Cecil.Rocks.csproj index 52576c6..eac4ccc 100644 --- a/rocks/Mono.Cecil.Rocks.csproj +++ b/rocks/Mono.Cecil.Rocks.csproj @@ -9,6 +9,7 @@ + diff --git a/rocks/Mono.Cecil.Rocks/DocCommentId.cs b/rocks/Mono.Cecil.Rocks/DocCommentId.cs new file mode 100644 index 0000000..465f0ec --- /dev/null +++ b/rocks/Mono.Cecil.Rocks/DocCommentId.cs @@ -0,0 +1,253 @@ +// +// Author: +// Jb Evain (jbevain@gmail.com) +// +// Copyright (c) 2008 - 2015 Jb Evain +// Copyright (c) 2008 - 2011 Novell, Inc. +// +// Licensed under the MIT/X11 license. +// + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Mono.Cecil.Rocks { + + public class DocCommentId + { + StringBuilder id; + + DocCommentId () + { + id = new StringBuilder (); + } + + void WriteField (FieldDefinition field) + { + WriteDefinition ('F', field); + } + + void WriteEvent (EventDefinition @event) + { + WriteDefinition ('E', @event); + } + + void WriteType (TypeDefinition type) + { + id.Append ('T').Append (':'); + WriteTypeFullName (type); + } + + void WriteMethod (MethodDefinition method) + { + WriteDefinition ('M', method); + + if (method.HasGenericParameters) { + id.Append ('`').Append ('`'); + id.Append (method.GenericParameters.Count); + } + + if (method.HasParameters) + WriteParameters (method.Parameters); + + if (IsConversionOperator (method)) + WriteReturnType (method); + } + + static bool IsConversionOperator (MethodDefinition self) + { + if (self == null) + throw new ArgumentNullException ("self"); + + return self.IsSpecialName + && (self.Name == "op_Explicit" || self.Name == "op_Implicit"); + } + + void WriteReturnType (MethodDefinition method) + { + id.Append ('~'); + WriteTypeSignature (method.ReturnType); + } + + void WriteProperty (PropertyDefinition property) + { + WriteDefinition ('P', property); + + if (property.HasParameters) + WriteParameters (property.Parameters); + } + + void WriteParameters (IList parameters) + { + id.Append ('('); + WriteList (parameters, p => WriteTypeSignature (p.ParameterType)); + id.Append (')'); + } + + void WriteTypeSignature (TypeReference type) + { + switch (type.MetadataType) + { + case MetadataType.Array: + WriteArrayTypeSignature ((ArrayType) type); + break; + case MetadataType.ByReference: + WriteTypeSignature (((ByReferenceType) type).ElementType); + id.Append ('@'); + break; + case MetadataType.FunctionPointer: + WriteFunctionPointerTypeSignature ((FunctionPointerType) type); + break; + case MetadataType.GenericInstance: + WriteGenericInstanceTypeSignature ((GenericInstanceType) type); + break; + case MetadataType.Var: + id.Append ('`'); + id.Append (((GenericParameter) type).Position); + break; + case MetadataType.MVar: + id.Append ('`').Append ('`'); + id.Append (((GenericParameter) type).Position); + break; + case MetadataType.OptionalModifier: + WriteModiferTypeSignature ((RequiredModifierType) type, '!'); + break; + case MetadataType.RequiredModifier: + WriteModiferTypeSignature ((RequiredModifierType) type, '|'); + break; + case MetadataType.Pointer: + WriteTypeSignature (((PointerType) type).ElementType); + id.Append ('*'); + break; + default: + WriteTypeFullName (type); + break; + } + } + + void WriteGenericInstanceTypeSignature (GenericInstanceType type) + { + WriteTypeSignature (type.ElementType); + id.Append ('{'); + WriteList (type.GenericArguments, WriteTypeSignature); + id.Append ('}'); + } + + void WriteList (IList list, Action action) + { + for (int i = 0; i < list.Count; i++) { + if (i > 0) + id.Append (','); + + action (list [i]); + } + } + + void WriteModiferTypeSignature (IModifierType type, char id) + { + WriteTypeSignature (type.ElementType); + this.id.Append (id); + WriteTypeSignature (type.ModifierType); + } + + void WriteFunctionPointerTypeSignature (FunctionPointerType type) + { + id.Append ("=FUNC:"); + WriteTypeFullName (type.ReturnType); + + if (type.HasParameters) + WriteParameters (type.Parameters); + } + + void WriteArrayTypeSignature (ArrayType type) + { + WriteTypeSignature (type.ElementType); + + if (type.IsVector) { + id.Append ("[]"); + return; + } + + id.Append ("["); + + WriteList (type.Dimensions, dimension => { + if (dimension.LowerBound.HasValue) + id.Append (dimension.LowerBound.Value); + + id.Append (':'); + + if (dimension.UpperBound.HasValue) + id.Append (dimension.UpperBound.Value - (dimension.LowerBound.GetValueOrDefault () + 1)); + }); + + id.Append ("]"); + } + + void WriteDefinition (char id, IMemberDefinition member) + { + this.id.Append (id) + .Append (':'); + + WriteTypeFullName (member.DeclaringType); + this.id.Append ('.'); + WriteItemName (member.Name); + } + + void WriteTypeFullName (TypeReference type) + { + if (type.DeclaringType != null) { + WriteTypeFullName (type.DeclaringType); + id.Append ('.'); + } + + if (!string.IsNullOrEmpty (type.Namespace)) { + id.Append (type.Namespace); + id.Append ('.'); + } + + id.Append (type.Name); + } + + void WriteItemName (string name) + { + id.Append (name.Replace ('.', '#')); + } + + public override string ToString () + { + return id.ToString (); + } + + public static string GetDocCommentId (IMemberDefinition member) + { + if (member == null) + throw new ArgumentNullException ("member"); + + var documentId = new DocCommentId (); + + switch (member.MetadataToken.TokenType) + { + case TokenType.Field: + documentId.WriteField ((FieldDefinition) member); + break; + case TokenType.Method: + documentId.WriteMethod ((MethodDefinition) member); + break; + case TokenType.TypeDef: + documentId.WriteType ((TypeDefinition) member); + break; + case TokenType.Event: + documentId.WriteEvent ((EventDefinition) member); + break; + case TokenType.Property: + documentId.WriteProperty ((PropertyDefinition) member); + break; + default: + throw new NotSupportedException (member.FullName); + } + + return documentId.ToString (); + } + } +} diff --git a/rocks/Test/Mono.Cecil.Rocks.Tests.csproj b/rocks/Test/Mono.Cecil.Rocks.Tests.csproj index be0f3eb..808c2a1 100644 --- a/rocks/Test/Mono.Cecil.Rocks.Tests.csproj +++ b/rocks/Test/Mono.Cecil.Rocks.Tests.csproj @@ -20,6 +20,7 @@ + diff --git a/rocks/Test/Mono.Cecil.Tests/DocCommentIdTests.cs b/rocks/Test/Mono.Cecil.Tests/DocCommentIdTests.cs new file mode 100644 index 0000000..de45a2f --- /dev/null +++ b/rocks/Test/Mono.Cecil.Tests/DocCommentIdTests.cs @@ -0,0 +1,248 @@ +using System; +using System.Linq; + +using NUnit.Framework; + +using Mono.Cecil.Rocks; + +namespace N +{ + /// + /// ID string generated is "T:N.X". + /// + public class X + { + /// + /// ID string generated is "M:N.X.#ctor". + /// + public X() { } + + + /// + /// ID string generated is "M:N.X.#ctor(System.Int32)". + /// + /// Describe parameter. + public X(int i) { } + + + /// + /// ID string generated is "F:N.X.q". + /// + public string q; + + + /// + /// ID string generated is "F:N.X.PI". + /// + public const double PI = 3.14; + + + /// + /// ID string generated is "M:N.X.f". + /// + public int f() { return 1; } + + + /// + /// ID string generated is "M:N.X.bb(System.String,System.Int32@)". + /// + public int bb(string s, ref int y) { return 1; } + + + /// + /// ID string generated is "M:N.X.gg(System.Int16[],System.Int32[0:,0:])". + /// + public int gg(short[] array1, int[,] array) { return 0; } + + + /// + /// ID string generated is "M:N.X.op_Addition(N.X,N.X)". + /// + public static X operator +(X x, X xx) { return x; } + + + /// + /// ID string generated is "P:N.X.prop". + /// + public int prop { get { return 1; } set { } } + + + /// + /// ID string generated is "E:N.X.d". + /// + public event D d; + + + /// + /// ID string generated is "P:N.X.Item(System.String)". + /// + public int this[string s] { get { return 1; } } + + + /// + /// ID string generated is "T:N.X.Nested". + /// + public class Nested { } + + + /// + /// ID string generated is "T:N.X.D". + /// + public delegate void D(int i); + + + /// + /// ID string generated is "M:N.X.op_Explicit(N.X)~System.Int32". + /// + public static explicit operator int(X x) { return 1; } + } +} + +namespace Mono.Cecil.Tests { + + [TestFixture] + public class DocCommentIdTests { + + [Test] + public void TypeDef () + { + AssertDocumentID ("T:N.X", GetTestType ()); + } + + [Test] + public void ParameterlessCtor () + { + var type = GetTestType (); + var ctor = type.GetConstructors ().Single (m => m.Parameters.Count == 0); + + AssertDocumentID ("M:N.X.#ctor", ctor); + } + + [Test] + public void CtorWithParameters () + { + var type = GetTestType (); + var ctor = type.GetConstructors ().Single (m => m.Parameters.Count == 1); + + AssertDocumentID ("M:N.X.#ctor(System.Int32)", ctor); + } + + [Test] + public void Field () + { + var type = GetTestType (); + var field = type.Fields.Single (m => m.Name == "q"); + + AssertDocumentID ("F:N.X.q", field); + } + + [Test] + public void ConstField () + { + var type = GetTestType (); + var field = type.Fields.Single (m => m.Name == "PI"); + + AssertDocumentID ("F:N.X.PI", field); + } + + [Test] + public void ParameterlessMethod () + { + var type = GetTestType (); + var method = type.Methods.Single (m => m.Name == "f"); + + AssertDocumentID ("M:N.X.f", method); + } + + [Test] + public void MethodWithByRefParameters () + { + var type = GetTestType (); + var method = type.Methods.Single (m => m.Name == "bb"); + + AssertDocumentID ("M:N.X.bb(System.String,System.Int32@)", method); + } + + [Test] + public void MethodWithArrayParameters () + { + var type = GetTestType (); + var method = type.Methods.Single (m => m.Name == "gg"); + + AssertDocumentID ("M:N.X.gg(System.Int16[],System.Int32[0:,0:])", method); + } + + [Test] + public void OpAddition () + { + var type = GetTestType (); + var op = type.Methods.Single (m => m.Name == "op_Addition"); + + AssertDocumentID ("M:N.X.op_Addition(N.X,N.X)", op); + } + + [Test] + public void OpExplicit () + { + var type = GetTestType (); + var op = type.Methods.Single (m => m.Name == "op_Explicit"); + + AssertDocumentID ("M:N.X.op_Explicit(N.X)~System.Int32", op); + } + + [Test] + public void Property () + { + var type = GetTestType (); + var property = type.Properties.Single (p => p.Name == "prop"); + + AssertDocumentID ("P:N.X.prop", property); + } + + [Test] + public void Indexer () + { + var type = GetTestType (); + var indexer = type.Properties.Single (p => p.Name == "Item"); + + AssertDocumentID ("P:N.X.Item(System.String)", indexer); + } + + [Test] + public void Event () + { + var type = GetTestType (); + var @event = type.Events.Single (e => e.Name == "d"); + + AssertDocumentID ("E:N.X.d", @event); + } + + [Test] + public void Delegate () + { + var type = GetTestType (); + var @delegate = type.NestedTypes.Single (t => t.Name == "D"); + + AssertDocumentID ("T:N.X.D", @delegate); + } + + [Test] + public void NestedType () + { + var type = GetTestType (); + var nestedType = type.NestedTypes.Single (t => t.Name == "Nested"); + + AssertDocumentID ("T:N.X.Nested", nestedType); + } + + TypeDefinition GetTestType () + { + return typeof (N.X).ToDefinition (); + } + + static void AssertDocumentID (string docId, IMemberDefinition member) + { + Assert.AreEqual (docId, DocCommentId.GetDocCommentId (member)); + } + } +} -- cgit v1.2.3