diff --git a/src/Docfx.Dotnet/CompilationHelper.cs b/src/Docfx.Dotnet/CompilationHelper.cs index 8de142094d1..405e416407c 100644 --- a/src/Docfx.Dotnet/CompilationHelper.cs +++ b/src/Docfx.Dotnet/CompilationHelper.cs @@ -54,7 +54,7 @@ public static bool CheckDiagnostics(this Compilation compilation, bool errorAsWa return errorCount > 0; } - public static Compilation CreateCompilationFromCSharpFiles(IEnumerable files, IDictionary msbuildProperties) + public static Compilation CreateCompilationFromCSharpFiles(IEnumerable files, IDictionary msbuildProperties, MetadataReference[] references) { var parserOption = GetCSharpParseOptions(msbuildProperties); var syntaxTrees = files.Select(path => CS.CSharpSyntaxTree.ParseText(File.ReadAllText(path), parserOption, path: path)); @@ -63,7 +63,7 @@ public static Compilation CreateCompilationFromCSharpFiles(IEnumerable f assemblyName: null, options: new CS.CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, xmlReferenceResolver: XmlFileResolver.Default), syntaxTrees: syntaxTrees, - references: GetDefaultMetadataReferences("C#")); + references: GetDefaultMetadataReferences("C#").Concat(references)); } public static Compilation CreateCompilationFromCSharpCode(string code, IDictionary msbuildProperties, string? name = null, params MetadataReference[] references) @@ -75,10 +75,10 @@ public static Compilation CreateCompilationFromCSharpCode(string code, IDictiona name, options: new CS.CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, xmlReferenceResolver: XmlFileResolver.Default), syntaxTrees: [syntaxTree], - references: GetDefaultMetadataReferences("C#").Concat(references)); + references: GetDefaultMetadataReferences("C#").Concat(references ?? [])); } - public static Compilation CreateCompilationFromVBFiles(IEnumerable files, IDictionary msbuildProperties) + public static Compilation CreateCompilationFromVBFiles(IEnumerable files, IDictionary msbuildProperties, MetadataReference[] references) { var parserOption = GetVisualBasicParseOptions(msbuildProperties); var syntaxTrees = files.Select(path => VB.VisualBasicSyntaxTree.ParseText(File.ReadAllText(path), parserOption, path: path)); @@ -87,7 +87,7 @@ public static Compilation CreateCompilationFromVBFiles(IEnumerable files assemblyName: null, options: new VB.VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary, globalImports: GetVBGlobalImports(), xmlReferenceResolver: XmlFileResolver.Default), syntaxTrees: syntaxTrees, - references: GetDefaultMetadataReferences("VB")); + references: GetDefaultMetadataReferences("VB").Concat(references)); } public static Compilation CreateCompilationFromVBCode(string code, IDictionary msbuildProperties, string? name = null, params MetadataReference[] references) @@ -99,10 +99,10 @@ public static Compilation CreateCompilationFromVBCode(string code, IDictionary? references = null) + public static (Compilation, IAssemblySymbol) CreateCompilationFromAssembly(string assemblyPath, params MetadataReference[] references) { var metadataReference = CreateMetadataReference(assemblyPath); var compilation = CS.CSharpCompilation.Create( @@ -110,8 +110,8 @@ public static (Compilation, IAssemblySymbol) CreateCompilationFromAssembly(strin options: new CS.CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary), syntaxTrees: s_assemblyBootstrap, references: GetReferenceAssemblies(assemblyPath) - .Concat(references ?? Enumerable.Empty()) .Select(CreateMetadataReference) + .Concat(references ?? []) .Append(metadataReference)); var assembly = (IAssemblySymbol)compilation.GetAssemblyOrModuleSymbol(metadataReference)!; diff --git a/src/Docfx.Dotnet/DotnetApiCatalog.Compile.cs b/src/Docfx.Dotnet/DotnetApiCatalog.Compile.cs index 3473f2b6182..ae7a801f30e 100644 --- a/src/Docfx.Dotnet/DotnetApiCatalog.Compile.cs +++ b/src/Docfx.Dotnet/DotnetApiCatalog.Compile.cs @@ -40,7 +40,7 @@ partial class DotnetApiCatalog _ => LoggerVerbosity.Quiet, }); - var workspace = MSBuildWorkspace.Create(msbuildProperties); + using var workspace = MSBuildWorkspace.Create(msbuildProperties); workspace.WorkspaceFailed += (sender, e) => Logger.LogWarning($"{e.Diagnostic}"); if (files.TryGetValue(FileType.NotSupported, out var unsupportedFiles)) @@ -88,26 +88,36 @@ await LoadCompilationFromProject(project.AbsolutePath) is { } compilation) assemblies.Add((compilation.Assembly, compilation)); } + var references = config.References ?? []; + var metadataReferences = references.Select(assemblyPath => + { + var documentation = XmlDocumentationProvider.CreateFromFile(Path.ChangeExtension(assemblyPath, ".xml")); + return MetadataReference.CreateFromFile(assemblyPath, documentation: documentation); + }).ToArray(); + + // LoadCompilationFrom C# source files if (files.TryGetValue(FileType.CSSourceCode, out var csFiles)) { - var compilation = CompilationHelper.CreateCompilationFromCSharpFiles(csFiles.Select(f => f.NormalizedPath), msbuildProperties); + var compilation = CompilationHelper.CreateCompilationFromCSharpFiles(csFiles.Select(f => f.NormalizedPath), msbuildProperties, metadataReferences); hasCompilationError |= compilation.CheckDiagnostics(config.AllowCompilationErrors); assemblies.Add((compilation.Assembly, compilation)); } + // LoadCompilationFrom VB source files if (files.TryGetValue(FileType.VBSourceCode, out var vbFiles)) { - var compilation = CompilationHelper.CreateCompilationFromVBFiles(vbFiles.Select(f => f.NormalizedPath), msbuildProperties); + var compilation = CompilationHelper.CreateCompilationFromVBFiles(vbFiles.Select(f => f.NormalizedPath), msbuildProperties, metadataReferences); hasCompilationError |= compilation.CheckDiagnostics(config.AllowCompilationErrors); assemblies.Add((compilation.Assembly, compilation)); } + // Load Compilation from assembly files if (files.TryGetValue(FileType.Assembly, out var assemblyFiles)) { foreach (var assemblyFile in assemblyFiles) { Logger.LogInfo($"Loading assembly {assemblyFile.NormalizedPath}"); - var (compilation, assembly) = CompilationHelper.CreateCompilationFromAssembly(assemblyFile.NormalizedPath, config.References); + var (compilation, assembly) = CompilationHelper.CreateCompilationFromAssembly(assemblyFile.NormalizedPath, metadataReferences); hasCompilationError |= compilation.CheckDiagnostics(config.AllowCompilationErrors); assemblies.Add((assembly, compilation)); } diff --git a/test/Docfx.Dotnet.Tests/GenerateMetadataFromCSUnitTest.cs b/test/Docfx.Dotnet.Tests/GenerateMetadataFromCSUnitTest.cs index 3a2e89e05dd..3f266d5453f 100644 --- a/test/Docfx.Dotnet.Tests/GenerateMetadataFromCSUnitTest.cs +++ b/test/Docfx.Dotnet.Tests/GenerateMetadataFromCSUnitTest.cs @@ -14,9 +14,9 @@ public class GenerateMetadataFromCSUnitTest { private static readonly Dictionary EmptyMSBuildProperties = new(); - private static MetadataItem Verify(string code, ExtractMetadataConfig config = null, IDictionary msbuildProperties = null) + private static MetadataItem Verify(string code, ExtractMetadataConfig config = null, IDictionary msbuildProperties = null, MetadataReference[] references = null) { - var compilation = CompilationHelper.CreateCompilationFromCSharpCode(code, msbuildProperties ?? EmptyMSBuildProperties, "test.dll"); + var compilation = CompilationHelper.CreateCompilationFromCSharpCode(code, msbuildProperties ?? EmptyMSBuildProperties, "test.dll", references); var extensionMethods = compilation.Assembly.FindExtensionMethods(new(new(), new())).ToArray(); return compilation.Assembly.GenerateMetadataItem(compilation, config, extensionMethods: extensionMethods); } @@ -3771,4 +3771,30 @@ public void F1() {} Assert.Empty(foo.Items); } } + + [Fact] + public void TestGenerateMetadataWithReference() + { + string code = @" +namespace Test +{ + public class Foo + { + public TupleLibrary.XmlTasks Tasks{ get;set; } + } +} +"; + var references = new string[] { "TestData/TupleLibrary.dll" }.Select(assemblyPath => + { + var documentation = XmlDocumentationProvider.CreateFromFile(Path.ChangeExtension(assemblyPath, ".xml")); + return MetadataReference.CreateFromFile(assemblyPath, documentation: documentation); + }).ToArray(); + + // Act + var output = Verify(code, references: references); + + // Assert + Assert.Contains("TupleLibrary", output.References.Keys); + Assert.Contains("TupleLibrary.XmlTasks", output.References.Keys); + } } diff --git a/test/Docfx.Dotnet.Tests/GenerateMetadataFromVBUnitTest.cs b/test/Docfx.Dotnet.Tests/GenerateMetadataFromVBUnitTest.cs index e3c5463bdca..ea798501ca4 100644 --- a/test/Docfx.Dotnet.Tests/GenerateMetadataFromVBUnitTest.cs +++ b/test/Docfx.Dotnet.Tests/GenerateMetadataFromVBUnitTest.cs @@ -13,10 +13,10 @@ public class GenerateMetadataFromVBUnitTest { private static readonly Dictionary EmptyMSBuildProperties = new(); - private static MetadataItem Verify(string code, ExtractMetadataConfig options = null, IDictionary msbuildProperties = null, params MetadataReference[] references) + private static MetadataItem Verify(string code, ExtractMetadataConfig config = null, IDictionary msbuildProperties = null, MetadataReference[] references = null) { var compilation = CompilationHelper.CreateCompilationFromVBCode(code, msbuildProperties ?? EmptyMSBuildProperties, "test.dll", references); - return compilation.Assembly.GenerateMetadataItem(compilation, options); + return compilation.Assembly.GenerateMetadataItem(compilation, config); } [Trait("Related", "Generic")] @@ -1643,4 +1643,36 @@ End Namespace Assert.Empty(foo.Items); } } + + [Fact] + public void TestGenerateMetadataWithReference() + { + string code = @" +Namespace Test + Public Class Foo + Property Tasks As TupleLibrary.XmlTasks + End Class +End Namespace +namespace Test +{ + public class Foo + { + public TupleLibrary.XmlTasks Tasks { get;set; } + } +} +"; + + var references = new string[] { "TestData/TupleLibrary.dll" }.Select(assemblyPath => + { + var documentation = XmlDocumentationProvider.CreateFromFile(Path.ChangeExtension(assemblyPath, ".xml")); + return MetadataReference.CreateFromFile(assemblyPath, documentation: documentation); + }).ToArray(); + + // Act + var output = Verify(code, references: references); + + // Assert + Assert.Contains("TupleLibrary", output.References.Keys); + Assert.Contains("TupleLibrary.XmlTasks", output.References.Keys); + } }