Skip to content

Commit b1401b0

Browse files
authored
fix: merge members in same class name different assemblies (#9209)
* fix: merge members in same class name different assemblies * test(snapshot): update snapshots for b04a519 --------- Co-authored-by: Yufei Huang <yufeih@users.noreply.github.com>
1 parent 00a3ec4 commit b1401b0

15 files changed

+1263
-100
lines changed

samples/seed/dotnet/solution/CatLibrary.Core/BaseClass.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,12 @@ public class ExplicitLayoutClass
9696
{
9797

9898
}
99+
}
100+
101+
namespace CatLibrary.Core
102+
{
103+
public static class Issue231
104+
{
105+
public static void Foo(this ContainersRefType c) {}
106+
}
99107
}

samples/seed/dotnet/solution/CatLibrary/Class1.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,3 +413,11 @@ public enum ColorType
413413
Yellow
414414
}
415415
}
416+
417+
namespace CatLibrary.Core
418+
{
419+
public static class Issue231
420+
{
421+
public static void Bar(this ContainersRefType c) {}
422+
}
423+
}

src/Docfx.Dotnet/ExtractMetadata/ExtractMetadataWorker.cs

Lines changed: 49 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
using Microsoft.CodeAnalysis;
1212
using Microsoft.CodeAnalysis.MSBuild;
1313

14+
#nullable enable
15+
1416
namespace Docfx.Dotnet;
1517

1618
internal class ExtractMetadataWorker : IDisposable
@@ -29,7 +31,7 @@ public ExtractMetadataWorker(ExtractMetadataConfig config, DotnetApiOptions opti
2931
_options = options;
3032
_files = config.Files?.Select(s => new FileInformation(s))
3133
.GroupBy(f => f.Type)
32-
.ToDictionary(s => s.Key, s => s.Distinct().ToList());
34+
.ToDictionary(s => s.Key, s => s.Distinct().ToList()) ?? new();
3335

3436
var msbuildProperties = config.MSBuildProperties ?? new Dictionary<string, string>();
3537
if (!msbuildProperties.ContainsKey("Configuration"))
@@ -55,7 +57,7 @@ public void Dispose()
5557

5658
public async Task ExtractMetadataAsync()
5759
{
58-
if (_files.TryGetValue(FileType.NotSupported, out List<FileInformation> unsupportedFiles))
60+
if (_files.TryGetValue(FileType.NotSupported, out var unsupportedFiles))
5961
{
6062
foreach (var file in unsupportedFiles)
6163
{
@@ -152,33 +154,25 @@ await LoadCompilationFromProject(project.AbsolutePath) is { } compilation)
152154
}
153155

154156
Logger.LogInfo($"Creating output...");
155-
Dictionary<string, MetadataItem> allMembers;
156-
Dictionary<string, ReferenceItem> allReferences;
157-
using (new PerformanceScope("MergeMetadata"))
158-
{
159-
allMembers = MergeYamlProjectMetadata(projectMetadataList);
160-
}
157+
var allMembers = new Dictionary<string, MetadataItem>();
158+
var allReferences = new Dictionary<string, ReferenceItem>();
159+
MergeMembers(allMembers, projectMetadataList);
160+
MergeReferences(allReferences, projectMetadataList);
161161

162-
using (new PerformanceScope("MergeReference"))
163-
{
164-
allReferences = MergeYamlProjectReferences(projectMetadataList);
165-
}
166-
167-
if (allMembers == null || allMembers.Count == 0)
162+
if (allMembers.Count == 0)
168163
{
169164
var value = StringExtension.ToDelimitedString(projectMetadataList.Select(s => s.Name));
170165
Logger.Log(LogLevel.Warning, $"No .NET API detected for {value}.");
166+
return;
171167
}
172-
else
168+
169+
using (new PerformanceScope("ResolveAndExport"))
173170
{
174-
using (new PerformanceScope("ResolveAndExport"))
175-
{
176-
ResolveAndExportYamlMetadata(allMembers, allReferences);
177-
}
171+
ResolveAndExportYamlMetadata(allMembers, allReferences);
178172
}
179173
}
180174

181-
private async Task<Compilation> LoadCompilationFromProject(string path)
175+
private async Task<Compilation?> LoadCompilationFromProject(string path)
182176
{
183177
var project = _workspace.CurrentSolution.Projects.FirstOrDefault(
184178
p => FilePathComparer.OSPlatformSensitiveRelativePathComparer.Equals(p.FilePath, path));
@@ -272,9 +266,9 @@ private static void AddMemberToIndexer(MetadataItem memberModel, string outputPa
272266
}
273267
else
274268
{
275-
TreeIterator.Preorder(memberModel, null, s => s.Items, (member, parent) =>
269+
TreeIterator.Preorder(memberModel, null, s => s!.Items, (member, parent) =>
276270
{
277-
if (indexer.TryGetValue(member.Name, out string path))
271+
if (indexer.TryGetValue(member!.Name, out var path))
278272
{
279273
Logger.LogWarning($"{member.Name} already exists in {path}, the duplicate one {outputPath} will be ignored.");
280274
}
@@ -287,94 +281,55 @@ private static void AddMemberToIndexer(MetadataItem memberModel, string outputPa
287281
}
288282
}
289283

290-
private static Dictionary<string, MetadataItem> MergeYamlProjectMetadata(List<MetadataItem> projectMetadataList)
284+
private static void MergeMembers(Dictionary<string, MetadataItem> result, List<MetadataItem> items)
291285
{
292-
if (projectMetadataList == null || projectMetadataList.Count == 0)
286+
foreach (var item in items)
293287
{
294-
return null;
288+
MergeNode(item);
295289
}
296290

297-
Dictionary<string, MetadataItem> namespaceMapping = new();
298-
Dictionary<string, MetadataItem> allMembers = new();
299-
300-
foreach (var project in projectMetadataList)
291+
bool MergeNode(MetadataItem node)
301292
{
302-
if (project.Items != null)
293+
if (node.Type is MemberType.Assembly)
303294
{
304-
foreach (var ns in project.Items)
295+
foreach (var item in node.Items ?? new())
305296
{
306-
if (ns.Type == MemberType.Namespace)
307-
{
308-
if (namespaceMapping.TryGetValue(ns.Name, out MetadataItem nsOther))
309-
{
310-
if (ns.Items != null)
311-
{
312-
nsOther.Items ??= new List<MetadataItem>();
313-
314-
foreach (var i in ns.Items)
315-
{
316-
if (!nsOther.Items.Any(s => s.Name == i.Name))
317-
{
318-
nsOther.Items.Add(i);
319-
}
320-
else
321-
{
322-
Logger.Log(LogLevel.Info, $"{i.Name} already exists in {nsOther.Name}, ignore current one");
323-
}
324-
}
325-
}
326-
}
327-
else
328-
{
329-
namespaceMapping.Add(ns.Name, ns);
330-
}
331-
}
297+
MergeNode(item);
298+
}
299+
return false;
300+
}
332301

333-
if (!allMembers.ContainsKey(ns.Name))
334-
{
335-
allMembers.Add(ns.Name, ns);
336-
}
302+
if (!result.TryGetValue(node.Name, out var existingNode))
303+
{
304+
result.Add(node.Name, node);
305+
foreach (var item in node.Items ?? new())
306+
{
307+
MergeNode(item);
308+
}
309+
return true;
310+
}
337311

338-
ns.Items?.ForEach(s =>
312+
if (node.Type is MemberType.Namespace or MemberType.Class)
313+
{
314+
foreach (var item in node.Items ?? new())
315+
{
316+
if (MergeNode(item))
339317
{
340-
if (allMembers.TryGetValue(s.Name, out MetadataItem existingMetadata))
341-
{
342-
Logger.Log(LogLevel.Warning, $"Duplicate member {s.Name} is found from {existingMetadata.Source?.Path} and {s.Source?.Path}, use the one in {existingMetadata.Source?.Path} and ignore the one from {s.Source?.Path}");
343-
}
344-
else
345-
{
346-
allMembers.Add(s.Name, s);
347-
}
348-
349-
s.Items?.ForEach(s1 =>
350-
{
351-
if (allMembers.TryGetValue(s1.Name, out MetadataItem existingMetadata1))
352-
{
353-
Logger.Log(LogLevel.Warning, $"Duplicate member {s1.Name} is found from {existingMetadata1.Source?.Path} and {s1.Source?.Path}, use the one in {existingMetadata1.Source?.Path} and ignore the one from {s1.Source?.Path}");
354-
}
355-
else
356-
{
357-
allMembers.Add(s1.Name, s1);
358-
}
359-
});
360-
});
318+
existingNode.Items ??= new();
319+
existingNode.Items.Add(item);
320+
}
361321
}
322+
return false;
362323
}
363-
}
364324

365-
return allMembers;
325+
Logger.Log(LogLevel.Warning, $"Ignore duplicated member {node.Type}:{node.Name} from {node.Source?.Path} as it already exist in {existingNode.Source?.Path}.");
326+
return false;
327+
}
366328
}
367329

368-
private static Dictionary<string, ReferenceItem> MergeYamlProjectReferences(List<MetadataItem> projectMetadataList)
330+
private static void MergeReferences(Dictionary<string, ReferenceItem> result, List<MetadataItem> items)
369331
{
370-
if (projectMetadataList == null || projectMetadataList.Count == 0)
371-
{
372-
return null;
373-
}
374-
375-
var result = new Dictionary<string, ReferenceItem>();
376-
377-
foreach (var project in projectMetadataList)
332+
foreach (var project in items)
378333
{
379334
if (project.References != null)
380335
{
@@ -391,7 +346,5 @@ private static Dictionary<string, ReferenceItem> MergeYamlProjectReferences(List
391346
}
392347
}
393348
}
394-
395-
return result;
396349
}
397350
}

test/docfx.Snapshot.Tests/SamplesTest.Seed/api/CatLibrary.Core.ContainersRefType.html.view.verified.json

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,6 +1163,106 @@
11631163
"level": 0.0
11641164
}
11651165
],
1166+
"extensionMethods": [
1167+
{
1168+
"uid": "CatLibrary.Core.ContainersRefType.CatLibrary.Core.Issue231.Bar",
1169+
"isEii": false,
1170+
"isExtensionMethod": false,
1171+
"parent": "CatLibrary.Core.Issue231",
1172+
"definition": "CatLibrary.Core.Issue231.Bar(CatLibrary.Core.ContainersRefType)",
1173+
"href": "CatLibrary.Core.Issue231.html#CatLibrary.Core.Issue231.Bar(CatLibrary.Core.ContainersRefType)",
1174+
"name": [
1175+
{
1176+
"lang": "csharp",
1177+
"value": "Bar(ContainersRefType)"
1178+
},
1179+
{
1180+
"lang": "vb",
1181+
"value": "Bar(ContainersRefType)"
1182+
}
1183+
],
1184+
"nameWithType": [
1185+
{
1186+
"lang": "csharp",
1187+
"value": "Issue231.Bar(ContainersRefType)"
1188+
},
1189+
{
1190+
"lang": "vb",
1191+
"value": "Issue231.Bar(ContainersRefType)"
1192+
}
1193+
],
1194+
"fullName": [
1195+
{
1196+
"lang": "csharp",
1197+
"value": "CatLibrary.Core.Issue231.Bar(CatLibrary.Core.ContainersRefType)"
1198+
},
1199+
{
1200+
"lang": "vb",
1201+
"value": "CatLibrary.Core.Issue231.Bar(CatLibrary.Core.ContainersRefType)"
1202+
}
1203+
],
1204+
"specName": [
1205+
{
1206+
"lang": "csharp",
1207+
"value": "<a class=\"xref\" href=\"CatLibrary.Core.Issue231.html#CatLibrary.Core.Issue231.Bar(CatLibrary.Core.ContainersRefType)\">Bar</a>(<a class=\"xref\" href=\"CatLibrary.Core.ContainersRefType.html\">ContainersRefType</a>)"
1208+
},
1209+
{
1210+
"lang": "vb",
1211+
"value": "<a class=\"xref\" href=\"CatLibrary.Core.Issue231.html#CatLibrary.Core.Issue231.Bar(CatLibrary.Core.ContainersRefType)\">Bar</a>(<a class=\"xref\" href=\"CatLibrary.Core.ContainersRefType.html\">ContainersRefType</a>)"
1212+
}
1213+
],
1214+
"level": 0.0
1215+
},
1216+
{
1217+
"uid": "CatLibrary.Core.ContainersRefType.CatLibrary.Core.Issue231.Foo",
1218+
"isEii": false,
1219+
"isExtensionMethod": false,
1220+
"parent": "CatLibrary.Core.Issue231",
1221+
"definition": "CatLibrary.Core.Issue231.Foo(CatLibrary.Core.ContainersRefType)",
1222+
"href": "CatLibrary.Core.Issue231.html#CatLibrary.Core.Issue231.Foo(CatLibrary.Core.ContainersRefType)",
1223+
"name": [
1224+
{
1225+
"lang": "csharp",
1226+
"value": "Foo(ContainersRefType)"
1227+
},
1228+
{
1229+
"lang": "vb",
1230+
"value": "Foo(ContainersRefType)"
1231+
}
1232+
],
1233+
"nameWithType": [
1234+
{
1235+
"lang": "csharp",
1236+
"value": "Issue231.Foo(ContainersRefType)"
1237+
},
1238+
{
1239+
"lang": "vb",
1240+
"value": "Issue231.Foo(ContainersRefType)"
1241+
}
1242+
],
1243+
"fullName": [
1244+
{
1245+
"lang": "csharp",
1246+
"value": "CatLibrary.Core.Issue231.Foo(CatLibrary.Core.ContainersRefType)"
1247+
},
1248+
{
1249+
"lang": "vb",
1250+
"value": "CatLibrary.Core.Issue231.Foo(CatLibrary.Core.ContainersRefType)"
1251+
}
1252+
],
1253+
"specName": [
1254+
{
1255+
"lang": "csharp",
1256+
"value": "<a class=\"xref\" href=\"CatLibrary.Core.Issue231.html#CatLibrary.Core.Issue231.Foo(CatLibrary.Core.ContainersRefType)\">Foo</a>(<a class=\"xref\" href=\"CatLibrary.Core.ContainersRefType.html\">ContainersRefType</a>)"
1257+
},
1258+
{
1259+
"lang": "vb",
1260+
"value": "<a class=\"xref\" href=\"CatLibrary.Core.Issue231.html#CatLibrary.Core.Issue231.Foo(CatLibrary.Core.ContainersRefType)\">Foo</a>(<a class=\"xref\" href=\"CatLibrary.Core.ContainersRefType.html\">ContainersRefType</a>)"
1261+
}
1262+
],
1263+
"level": 0.0
1264+
}
1265+
],
11661266
"_appName": "Seed",
11671267
"_appTitle": "docfx seed website",
11681268
"_enableSearch": true,

0 commit comments

Comments
 (0)