/NRefactory/ICSharpCode.NRefactory/Documentation/IdStringProvider.cs

http://github.com/icsharpcode/ILSpy · C# · 391 lines · 307 code · 11 blank · 73 comment · 103 complexity · d85f764819e12d9fdc20635007a97a34 MD5 · raw file

  1. // Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy of this
  4. // software and associated documentation files (the "Software"), to deal in the Software
  5. // without restriction, including without limitation the rights to use, copy, modify, merge,
  6. // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  7. // to whom the Software is furnished to do so, subject to the following conditions:
  8. //
  9. // The above copyright notice and this permission notice shall be included in all copies or
  10. // substantial portions of the Software.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  13. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  14. // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  15. // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  16. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  17. // DEALINGS IN THE SOFTWARE.
  18. using System;
  19. using System.Linq;
  20. using System.Collections.Generic;
  21. using System.Text;
  22. using ICSharpCode.NRefactory.TypeSystem;
  23. using ICSharpCode.NRefactory.TypeSystem.Implementation;
  24. namespace ICSharpCode.NRefactory.Documentation
  25. {
  26. /// <summary>
  27. /// Provides ID strings for entities. (C# 4.0 spec, §A.3.1)
  28. /// ID strings are used to identify members in XML documentation files.
  29. /// </summary>
  30. public static class IdStringProvider
  31. {
  32. #region GetIdString
  33. /// <summary>
  34. /// Gets the ID string (C# 4.0 spec, §A.3.1) for the specified entity.
  35. /// </summary>
  36. public static string GetIdString(this IEntity entity)
  37. {
  38. StringBuilder b = new StringBuilder();
  39. switch (entity.SymbolKind) {
  40. case SymbolKind.TypeDefinition:
  41. b.Append("T:");
  42. AppendTypeName(b, (ITypeDefinition)entity, false);
  43. return b.ToString();
  44. case SymbolKind.Field:
  45. b.Append("F:");
  46. break;
  47. case SymbolKind.Property:
  48. case SymbolKind.Indexer:
  49. b.Append("P:");
  50. break;
  51. case SymbolKind.Event:
  52. b.Append("E:");
  53. break;
  54. default:
  55. b.Append("M:");
  56. break;
  57. }
  58. IMember member = (IMember)entity;
  59. AppendTypeName(b, member.DeclaringType, false);
  60. b.Append('.');
  61. if (member.IsExplicitInterfaceImplementation && member.Name.IndexOf('.') < 0 && member.ImplementedInterfaceMembers.Count == 1) {
  62. AppendTypeName(b, member.ImplementedInterfaceMembers[0].DeclaringType, true);
  63. b.Append('#');
  64. }
  65. b.Append(member.Name.Replace('.', '#'));
  66. IMethod method = member as IMethod;
  67. if (method != null && method.TypeParameters.Count > 0) {
  68. b.Append("``");
  69. b.Append(method.TypeParameters.Count);
  70. }
  71. IParameterizedMember parameterizedMember = member as IParameterizedMember;
  72. if (parameterizedMember != null && parameterizedMember.Parameters.Count > 0) {
  73. b.Append('(');
  74. var parameters = parameterizedMember.Parameters;
  75. for (int i = 0; i < parameters.Count; i++) {
  76. if (i > 0) b.Append(',');
  77. AppendTypeName(b, parameters[i].Type, false);
  78. }
  79. b.Append(')');
  80. }
  81. if (member.SymbolKind == SymbolKind.Operator && (member.Name == "op_Implicit" || member.Name == "op_Explicit")) {
  82. b.Append('~');
  83. AppendTypeName(b, member.ReturnType, false);
  84. }
  85. return b.ToString();
  86. }
  87. #endregion
  88. #region GetTypeName
  89. public static string GetTypeName(IType type)
  90. {
  91. if (type == null)
  92. throw new ArgumentNullException("type");
  93. StringBuilder b = new StringBuilder();
  94. AppendTypeName(b, type, false);
  95. return b.ToString();
  96. }
  97. static void AppendTypeName(StringBuilder b, IType type, bool explicitInterfaceImpl)
  98. {
  99. switch (type.Kind) {
  100. case TypeKind.Dynamic:
  101. b.Append(explicitInterfaceImpl ? "System#Object" : "System.Object");
  102. break;
  103. case TypeKind.TypeParameter:
  104. ITypeParameter tp = (ITypeParameter)type;
  105. if (explicitInterfaceImpl) {
  106. b.Append(tp.Name);
  107. } else {
  108. b.Append('`');
  109. if (tp.OwnerType == SymbolKind.Method)
  110. b.Append('`');
  111. b.Append(tp.Index);
  112. }
  113. break;
  114. case TypeKind.Array:
  115. ArrayType array = (ArrayType)type;
  116. AppendTypeName(b, array.ElementType, explicitInterfaceImpl);
  117. b.Append('[');
  118. if (array.Dimensions > 1) {
  119. for (int i = 0; i < array.Dimensions; i++) {
  120. if (i > 0)
  121. b.Append(explicitInterfaceImpl ? '@' : ',');
  122. if (!explicitInterfaceImpl)
  123. b.Append("0:");
  124. }
  125. }
  126. b.Append(']');
  127. break;
  128. case TypeKind.Pointer:
  129. AppendTypeName(b, ((PointerType)type).ElementType, explicitInterfaceImpl);
  130. b.Append('*');
  131. break;
  132. case TypeKind.ByReference:
  133. AppendTypeName(b, ((ByReferenceType)type).ElementType, explicitInterfaceImpl);
  134. b.Append('@');
  135. break;
  136. default:
  137. IType declType = type.DeclaringType;
  138. if (declType != null) {
  139. AppendTypeName(b, declType, explicitInterfaceImpl);
  140. b.Append(explicitInterfaceImpl ? '#' : '.');
  141. b.Append(type.Name);
  142. AppendTypeParameters(b, type, declType.TypeParameterCount, explicitInterfaceImpl);
  143. } else {
  144. if (explicitInterfaceImpl)
  145. b.Append(type.FullName.Replace('.', '#'));
  146. else
  147. b.Append(type.FullName);
  148. AppendTypeParameters(b, type, 0, explicitInterfaceImpl);
  149. }
  150. break;
  151. }
  152. }
  153. static void AppendTypeParameters(StringBuilder b, IType type, int outerTypeParameterCount, bool explicitInterfaceImpl)
  154. {
  155. int tpc = type.TypeParameterCount - outerTypeParameterCount;
  156. if (tpc > 0) {
  157. ParameterizedType pt = type as ParameterizedType;
  158. if (pt != null) {
  159. b.Append('{');
  160. var ta = pt.TypeArguments;
  161. for (int i = outerTypeParameterCount; i < ta.Count; i++) {
  162. if (i > outerTypeParameterCount)
  163. b.Append(explicitInterfaceImpl ? '@' : ',');
  164. AppendTypeName(b, ta[i], explicitInterfaceImpl);
  165. }
  166. b.Append('}');
  167. } else {
  168. b.Append('`');
  169. b.Append(tpc);
  170. }
  171. }
  172. }
  173. #endregion
  174. #region ParseMemberName
  175. /// <summary>
  176. /// Parse the ID string into a member reference.
  177. /// </summary>
  178. /// <param name="memberIdString">The ID string representing the member (with "M:", "F:", "P:" or "E:" prefix).</param>
  179. /// <returns>A member reference that represents the ID string.</returns>
  180. /// <exception cref="ReflectionNameParseException">The syntax of the ID string is invalid</exception>
  181. /// <remarks>
  182. /// The member reference will look in <see cref="ITypeResolveContext.CurrentAssembly"/> first,
  183. /// and if the member is not found there,
  184. /// it will look in all other assemblies of the compilation.
  185. /// </remarks>
  186. public static IMemberReference ParseMemberIdString(string memberIdString)
  187. {
  188. if (memberIdString == null)
  189. throw new ArgumentNullException("memberIdString");
  190. if (memberIdString.Length < 2 || memberIdString[1] != ':')
  191. throw new ReflectionNameParseException(0, "Missing type tag");
  192. char typeChar = memberIdString[0];
  193. int parenPos = memberIdString.IndexOf('(');
  194. if (parenPos < 0)
  195. parenPos = memberIdString.LastIndexOf('~');
  196. if (parenPos < 0)
  197. parenPos = memberIdString.Length;
  198. int dotPos = memberIdString.LastIndexOf('.', parenPos - 1);
  199. if (dotPos < 0)
  200. throw new ReflectionNameParseException(0, "Could not find '.' separating type name from member name");
  201. string typeName = memberIdString.Substring(0, dotPos);
  202. int pos = 2;
  203. ITypeReference typeReference = ParseTypeName(typeName, ref pos);
  204. if (pos != typeName.Length)
  205. throw new ReflectionNameParseException(pos, "Expected end of type name");
  206. // string memberName = memberIDString.Substring(dotPos + 1, parenPos - (dotPos + 1));
  207. // pos = memberName.LastIndexOf("``");
  208. // if (pos > 0)
  209. // memberName = memberName.Substring(0, pos);
  210. // memberName = memberName.Replace('#', '.');
  211. return new IdStringMemberReference(typeReference, typeChar, memberIdString);
  212. }
  213. #endregion
  214. #region ParseTypeName
  215. /// <summary>
  216. /// Parse the ID string type name into a type reference.
  217. /// </summary>
  218. /// <param name="typeName">The ID string representing the type (the "T:" prefix is optional).</param>
  219. /// <returns>A type reference that represents the ID string.</returns>
  220. /// <exception cref="ReflectionNameParseException">The syntax of the ID string is invalid</exception>
  221. /// <remarks>
  222. /// <para>
  223. /// The type reference will look in <see cref="ITypeResolveContext.CurrentAssembly"/> first,
  224. /// and if the type is not found there,
  225. /// it will look in all other assemblies of the compilation.
  226. /// </para>
  227. /// <para>
  228. /// If the type is open (contains type parameters '`0' or '``0'),
  229. /// an <see cref="ITypeResolveContext"/> with the appropriate CurrentTypeDefinition/CurrentMember is required
  230. /// to resolve the reference to the ITypeParameter.
  231. /// </para>
  232. /// </remarks>
  233. public static ITypeReference ParseTypeName(string typeName)
  234. {
  235. if (typeName == null)
  236. throw new ArgumentNullException("typeName");
  237. int pos = 0;
  238. if (typeName.StartsWith("T:", StringComparison.Ordinal))
  239. pos = 2;
  240. ITypeReference r = ParseTypeName(typeName, ref pos);
  241. if (pos < typeName.Length)
  242. throw new ReflectionNameParseException(pos, "Expected end of type name");
  243. return r;
  244. }
  245. static bool IsIDStringSpecialCharacter(char c)
  246. {
  247. switch (c) {
  248. case ':':
  249. case '{':
  250. case '}':
  251. case '[':
  252. case ']':
  253. case '(':
  254. case ')':
  255. case '`':
  256. case '*':
  257. case '@':
  258. case ',':
  259. return true;
  260. default:
  261. return false;
  262. }
  263. }
  264. static ITypeReference ParseTypeName(string typeName, ref int pos)
  265. {
  266. string reflectionTypeName = typeName;
  267. if (pos == typeName.Length)
  268. throw new ReflectionNameParseException(pos, "Unexpected end");
  269. ITypeReference result;
  270. if (reflectionTypeName[pos] == '`') {
  271. // type parameter reference
  272. pos++;
  273. if (pos == reflectionTypeName.Length)
  274. throw new ReflectionNameParseException(pos, "Unexpected end");
  275. if (reflectionTypeName[pos] == '`') {
  276. // method type parameter reference
  277. pos++;
  278. int index = ReflectionHelper.ReadTypeParameterCount(reflectionTypeName, ref pos);
  279. result = TypeParameterReference.Create(SymbolKind.Method, index);
  280. } else {
  281. // class type parameter reference
  282. int index = ReflectionHelper.ReadTypeParameterCount(reflectionTypeName, ref pos);
  283. result = TypeParameterReference.Create(SymbolKind.TypeDefinition, index);
  284. }
  285. } else {
  286. // not a type parameter reference: read the actual type name
  287. List<ITypeReference> typeArguments = new List<ITypeReference>();
  288. int typeParameterCount;
  289. string typeNameWithoutSuffix = ReadTypeName(typeName, ref pos, true, out typeParameterCount, typeArguments);
  290. result = new GetPotentiallyNestedClassTypeReference(typeNameWithoutSuffix, typeParameterCount);
  291. while (pos < typeName.Length && typeName[pos] == '.') {
  292. pos++;
  293. string nestedTypeName = ReadTypeName(typeName, ref pos, false, out typeParameterCount, typeArguments);
  294. result = new NestedTypeReference(result, nestedTypeName, typeParameterCount);
  295. }
  296. if (typeArguments.Count > 0) {
  297. result = new ParameterizedTypeReference(result, typeArguments);
  298. }
  299. }
  300. while (pos < typeName.Length) {
  301. switch (typeName[pos]) {
  302. case '[':
  303. int dimensions = 1;
  304. do {
  305. pos++;
  306. if (pos == typeName.Length)
  307. throw new ReflectionNameParseException(pos, "Unexpected end");
  308. if (typeName[pos] == ',')
  309. dimensions++;
  310. } while (typeName[pos] != ']');
  311. result = new ArrayTypeReference(result, dimensions);
  312. break;
  313. case '*':
  314. result = new PointerTypeReference(result);
  315. break;
  316. case '@':
  317. result = new ByReferenceTypeReference(result);
  318. break;
  319. default:
  320. return result;
  321. }
  322. pos++;
  323. }
  324. return result;
  325. }
  326. static string ReadTypeName(string typeName, ref int pos, bool allowDottedName, out int typeParameterCount, List<ITypeReference> typeArguments)
  327. {
  328. int startPos = pos;
  329. // skip the simple name portion:
  330. while (pos < typeName.Length && !IsIDStringSpecialCharacter(typeName[pos]) && (allowDottedName || typeName[pos] != '.'))
  331. pos++;
  332. if (pos == startPos)
  333. throw new ReflectionNameParseException(pos, "Expected type name");
  334. string shortTypeName = typeName.Substring(startPos, pos - startPos);
  335. // read type arguments:
  336. typeParameterCount = 0;
  337. if (pos < typeName.Length && typeName[pos] == '`') {
  338. // unbound generic type
  339. pos++;
  340. typeParameterCount = ReflectionHelper.ReadTypeParameterCount(typeName, ref pos);
  341. } else if (pos < typeName.Length && typeName[pos] == '{') {
  342. // bound generic type
  343. typeArguments = new List<ITypeReference>();
  344. do {
  345. pos++;
  346. typeArguments.Add(ParseTypeName(typeName, ref pos));
  347. typeParameterCount++;
  348. if (pos == typeName.Length)
  349. throw new ReflectionNameParseException(pos, "Unexpected end");
  350. } while (typeName[pos] == ',');
  351. if (typeName[pos] != '}')
  352. throw new ReflectionNameParseException(pos, "Expected '}'");
  353. pos++;
  354. }
  355. return shortTypeName;
  356. }
  357. #endregion
  358. #region FindEntity
  359. /// <summary>
  360. /// Finds the entity in the given type resolve context.
  361. /// </summary>
  362. /// <param name="idString">ID string of the entity.</param>
  363. /// <param name="context">Type resolve context</param>
  364. /// <returns>Returns the entity, or null if it is not found.</returns>
  365. /// <exception cref="ReflectionNameParseException">The syntax of the ID string is invalid</exception>
  366. public static IEntity FindEntity(string idString, ITypeResolveContext context)
  367. {
  368. if (idString == null)
  369. throw new ArgumentNullException("idString");
  370. if (context == null)
  371. throw new ArgumentNullException("context");
  372. if (idString.StartsWith("T:", StringComparison.Ordinal)) {
  373. return ParseTypeName(idString.Substring(2)).Resolve(context).GetDefinition();
  374. } else {
  375. return ParseMemberIdString(idString).Resolve(context);
  376. }
  377. }
  378. #endregion
  379. }
  380. }