@@ -40,7 +40,7 @@ public static void ClearCache()
4040 public static class XmlDocsExtensions
4141 {
4242 private static readonly ConcurrentDictionary < string , CachingXDocument ? > Cache =
43- new ConcurrentDictionary < string , CachingXDocument ? > ( StringComparer . OrdinalIgnoreCase ) ;
43+ new ( StringComparer . OrdinalIgnoreCase ) ;
4444
4545 internal static void ClearCache ( )
4646 {
@@ -218,7 +218,9 @@ public static string GetXmlDocsRemarks(this MemberInfo member, bool resolveExter
218218 {
219219 try
220220 {
221- if ( DynamicApis . SupportsXPathApis == false || DynamicApis . SupportsFileApis == false || DynamicApis . SupportsPathApis == false )
221+ if ( DynamicApis . SupportsXPathApis == false
222+ || DynamicApis . SupportsFileApis == false
223+ || DynamicApis . SupportsPathApis == false )
222224 {
223225 return null ;
224226 }
@@ -503,7 +505,10 @@ private static bool IsAssemblyIgnored(AssemblyName assemblyName, bool resolveExt
503505 return null ;
504506 }
505507
506- private static void ReplaceInheritdocElements ( this MemberInfo member , XElement ? element , bool resolveExternalXmlDocs = true )
508+ private static void ReplaceInheritdocElements (
509+ this MemberInfo member ,
510+ XElement ? element ,
511+ bool resolveExternalXmlDocs = true )
507512 {
508513#if ! NET40
509514 if ( element == null )
@@ -516,6 +521,14 @@ private static void ReplaceInheritdocElements(this MemberInfo member, XElement?
516521 {
517522 if ( child . Name . LocalName . ToLowerInvariant ( ) == "inheritdoc" )
518523 {
524+ #if ! NETSTANDARD1_0
525+ // if this a class/type
526+ if ( child . HasAttributes && ( member . MemberType is MemberTypes . TypeInfo or MemberTypes . Property ) )
527+ {
528+ ProcessInheritDocTypeElements ( member , element , child ) ;
529+ continue ;
530+ }
531+ #endif
519532 var baseType = member . DeclaringType . GetTypeInfo ( ) . BaseType ;
520533 var baseMember = baseType ? . GetTypeInfo ( ) . DeclaredMembers . SingleOrDefault ( m => m . Name == member . Name ) ;
521534 if ( baseMember != null )
@@ -541,7 +554,7 @@ private static void ReplaceInheritdocElements(this MemberInfo member, XElement?
541554
542555 private static void ProcessInheritdocInterfaceElements ( this MemberInfo member , XElement child , bool resolveExternalXmlDocs = true )
543556 {
544- foreach ( var baseInterface in member . DeclaringType . GetTypeInfo ( ) . ImplementedInterfaces )
557+ foreach ( var baseInterface in member . DeclaringType . GetTypeInfo ( ) . ImplementedInterfaces ?? new Type [ ] { } )
545558 {
546559 var baseMember = baseInterface ? . GetTypeInfo ( ) . DeclaredMembers . SingleOrDefault ( m => m . Name == member . Name ) ;
547560 if ( baseMember != null )
@@ -582,6 +595,11 @@ internal static string GetMemberElementName(dynamic member)
582595 string memberName ;
583596 string memberTypeName ;
584597
598+ if ( member is null )
599+ {
600+ throw new ArgumentNullException ( nameof ( member ) ) ;
601+ }
602+
585603 if ( member is MemberInfo memberInfo &&
586604 memberInfo . DeclaringType != null &&
587605 memberInfo . DeclaringType . GetTypeInfo ( ) . IsGenericType )
@@ -692,8 +710,14 @@ internal static string GetMemberElementName(dynamic member)
692710 return string . Format ( "{0}:{1}" , prefixCode , memberName . Replace ( "+" , "." ) ) ;
693711 }
694712
695- private static string ? GetXmlDocsPath ( dynamic ? assembly , bool resolveExternalXmlDocs = true )
713+ #if NETSTANDARD1_0
714+ // ReSharper disable once MemberCanBePrivate.Global
715+ public static string ? GetXmlDocsPath ( dynamic ? assembly , bool resolveExternalXmlDocs = true )
696716 {
717+ #else
718+ public static string ? GetXmlDocsPath ( Assembly ? assembly , bool resolveExternalXmlDocs = true )
719+ {
720+ #endif
697721 try
698722 {
699723 if ( assembly == null )
@@ -807,6 +831,127 @@ internal static string GetMemberElementName(dynamic member)
807831 return null ;
808832 }
809833 }
834+
835+ #if ! NETSTANDARD1_0
836+
837+ /// <summary>
838+ /// Get Type from a referencing string such as <c>!:MyType</c> or <c>!:MyType.MyProperty</c>
839+ /// </summary>
840+ /// <param name="referencingType">
841+ /// The type whose documentation contains the reference to <paramref name="referencedTypeXmlId"/>
842+ /// </param>
843+ /// <param name="referencedTypeXmlId">
844+ /// String within <c>inheritdoc cref=</c> referencing an unknown type (prefaced with <c>!:</c>
845+ /// </param>
846+ /// <returns></returns>
847+ private static void ProcessInheritDocTypeElements ( this MemberInfo member , XElement element , XElement child )
848+ {
849+ var referencedTypeXmlId = child . Attribute ( "cref" ) ? . Value ;
850+
851+ if ( referencedTypeXmlId is not null )
852+ {
853+ Match ? matches ;
854+ string ? referencedTypeName ;
855+ MemberInfo ? referencedType = null ;
856+ Assembly ? docAssembly = null ;
857+ switch ( referencedTypeXmlId [ 0 ] )
858+ {
859+ case 'P' :
860+ matches = Regex . Match (
861+ referencedTypeXmlId ,
862+ @"(?<FullName>(?<FullTypeName>(?<AssemblyName>[a-zA-Z.]*)\.(?<TypeName>[a-zA-Z]*))\.(?<MemberName>[a-zA-Z]*))" ) ;
863+ referencedTypeName = matches . Groups [ "FullTypeName" ] . Value ;
864+ break ;
865+ default :
866+ matches = Regex . Match (
867+ referencedTypeXmlId ,
868+ @"[A-Z]:(?<FullName>(?<Namespace>[a-zA-Z.]*)\.(?<TypeName>[a-zA-Z]*))" ) ;
869+ referencedTypeName = matches . Groups [ "FullName" ] . Value ;
870+ break ;
871+ }
872+
873+ if ( docAssembly is null && referencedTypeName is not null )
874+ {
875+ docAssembly = member . Module . Assembly ;
876+ referencedType = docAssembly . GetType ( referencedTypeName ) ;
877+ // check member's assembly first
878+ if ( referencedType is null )
879+ {
880+ foreach ( var assembly in AppDomain . CurrentDomain . GetAssemblies ( ) )
881+ {
882+ // limit the Assemblies that are searched by doing a basic name check.
883+ if ( referencedTypeXmlId . Contains ( assembly . GetName ( ) . Name ) )
884+ {
885+ referencedType = GetTypeByXmlDocTypeName ( referencedTypeName , assembly ) ;
886+ if ( referencedType != null )
887+ {
888+ docAssembly = assembly ;
889+ break ;
890+ }
891+ }
892+ }
893+ }
894+ }
895+
896+ if ( referencedType is null ||
897+ docAssembly is null )
898+ {
899+ return ;
900+ }
901+
902+ var referencedDocs = TryGetXmlDocsDocument (
903+ docAssembly . GetName ( ) ,
904+ GetXmlDocsPath ( docAssembly ) ,
905+ true
906+ ) ? . GetXmlDocsElement ( referencedTypeXmlId ) ;
907+
908+ /* for Record types ( as opposed to Class types ) the above lookup will fail for parameters defined in
909+ * shorthand form on the Constructor. Constructor-defined Properties will show up on the constructor
910+ * as <param name="PropertyName">...</param> rather than have the xml doc member element as a typical
911+ * property would.
912+ */
913+ if ( referencedDocs is null && referencedType . MemberType == MemberTypes . Property )
914+ {
915+ var documentationPath = GetXmlDocsPath ( member . Module . Assembly ) ;
916+ if ( documentationPath is null )
917+ return ;
918+ var parentElement = GetXmlDocsElement (
919+ referencedType . DeclaringType . GetTypeInfo ( ) ,
920+ documentationPath
921+ ) ;
922+ referencedDocs = parentElement ?
923+ . Elements ( "param" ) ?
924+ . FirstOrDefault ( x => x . Attribute ( "name" ) ?
925+ . Value == referencedType . Name ) ;
926+ // for records, replace node with the entirety of the found docs. So the whole <param> tag.
927+ child . ReplaceWith ( referencedDocs ) ;
928+ return ;
929+ }
930+
931+ if ( referencedDocs != null )
932+ {
933+ var nodes = referencedDocs . Nodes ( ) . OfType < object > ( ) . ToArray ( ) ;
934+ child . ReplaceWith ( nodes ) ;
935+ }
936+ }
937+ }
938+
939+ private static Type ? GetTypeByXmlDocTypeName ( string xmlDocTypeName , Assembly assembly )
940+ {
941+ var assemblyTypeNames = assembly . GetTypes ( )
942+ . Select ( type => new KeyValuePair < string , Type > ( NormalizeTypeName ( type . FullName ! ) , type ) )
943+ . ToDictionary ( x => x . Key , x => x . Value ) ;
944+ assemblyTypeNames . TryGetValue ( NormalizeTypeName ( xmlDocTypeName ) , out var resultType ) ;
945+ return resultType ;
946+ }
947+
948+ private static string NormalizeTypeName ( string typeName )
949+ {
950+ return typeName
951+ . Replace ( "." , string . Empty )
952+ . Replace ( "+" , string . Empty ) ;
953+ }
954+ #endif
810955
811956 private static string ? GetPathByOs ( dynamic ? assembly , AssemblyName assemblyName )
812957 {
0 commit comments