Add AddVisitor method and Configure hook (#3897)

Fixes https://github.com/microsoft/typespec/issues/3827

Also adds a Configure hook so that AddVisitor can be called directly
from the Plugin. Considered just using the ctor but that would mean we
would have virtual property calls in a ctor.
This commit is contained in:
JoshLove-msft 2024-07-19 10:43:49 -07:00 коммит произвёл GitHub
Родитель 2c16b4b089
Коммит 998440879a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
7 изменённых файлов: 141 добавлений и 33 удалений

Просмотреть файл

@ -41,6 +41,14 @@ namespace Microsoft.Generator.CSharp
_inputLibrary = new(() => new InputLibrary(Instance.Configuration.OutputDirectory));
}
// for mocking
protected CodeModelPlugin()
{
// should be mocked
Configuration = null!;
_inputLibrary = new(() => null!);
}
private Lazy<InputLibrary> _inputLibrary;
// Extensibility points to be implemented by a plugin
@ -50,5 +58,9 @@ namespace Microsoft.Generator.CSharp
public InputLibrary InputLibrary => _inputLibrary.Value;
public virtual TypeProviderWriter GetWriter(TypeProvider provider) => new(provider);
public virtual IReadOnlyList<MetadataReference> AdditionalMetadataReferences => Array.Empty<MetadataReference>();
public virtual void Configure()
{
}
}
}

Просмотреть файл

@ -8,6 +8,7 @@ namespace Microsoft.Generator.CSharp
{
public class OutputLibrary
{
private List<OutputLibraryVisitor> _visitors = new();
public OutputLibrary()
{
}
@ -57,6 +58,11 @@ namespace Microsoft.Generator.CSharp
}
// TODO - make this more additive instead of replace https://github.com/microsoft/typespec/issues/3827
protected internal virtual IEnumerable<OutputLibraryVisitor> GetOutputLibraryVisitors() => [];
protected internal virtual IEnumerable<OutputLibraryVisitor> GetOutputLibraryVisitors() => _visitors;
public void AddVisitor(OutputLibraryVisitor visitor)
{
_visitors.Add(visitor);
}
}
}

Просмотреть файл

@ -13,26 +13,32 @@ namespace Microsoft.Generator.CSharp
public void LoadPlugin(CommandLineOptions options)
{
using DirectoryCatalog directoryCatalog = new(AppContext.BaseDirectory);
using (CompositionContainer container = new(directoryCatalog))
{
container.ComposeExportedValue(new GeneratorContext(Configuration.Load(options.OutputDirectory)));
container.ComposeParts(this);
bool loaded = false;
foreach (var plugin in Plugins!)
{
if (plugin.Metadata.PluginName == options.PluginName)
{
CodeModelPlugin.Instance = plugin.Value;
loaded = true;
break;
}
}
using CompositionContainer container = new(directoryCatalog);
if (!loaded)
container.ComposeExportedValue(new GeneratorContext(Configuration.Load(options.OutputDirectory)));
container.ComposeParts(this);
SelectPlugin(options.PluginName!);
}
internal void SelectPlugin(string pluginName)
{
bool loaded = false;
foreach (var plugin in Plugins!)
{
if (plugin.Metadata.PluginName == pluginName)
{
throw new InvalidOperationException($"Plugin {options.PluginName} not found.");
CodeModelPlugin.Instance = plugin.Value;
loaded = true;
CodeModelPlugin.Instance.Configure();
break;
}
}
if (!loaded)
{
throw new InvalidOperationException($"Plugin {pluginName} not found.");
}
}
[ImportMany]

Просмотреть файл

@ -2,6 +2,8 @@
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Generator.CSharp.Providers;
using NUnit.Framework;
@ -16,7 +18,7 @@ namespace Microsoft.Generator.CSharp.Tests
[SetUp]
public void Setup()
{
_outputLibrary = new MockOutputLibrary();
_outputLibrary = new TestOutputLibrary();
}
// Tests that the BuildTypeProviders method is successfully overridden.
@ -26,9 +28,54 @@ namespace Microsoft.Generator.CSharp.Tests
Assert.Throws<NotImplementedException>(() => { object shouldFail = _outputLibrary.TypeProviders; });
}
internal class MockOutputLibrary : OutputLibrary
[Test]
public void CanAddVisitors()
{
public MockOutputLibrary() : base() { }
_outputLibrary.AddVisitor(new TestOutputLibraryVisitor());
Assert.AreEqual(1, _outputLibrary.GetOutputLibraryVisitors().Count());
}
[Test]
public void CanOverrideGetOutputLibraryVisitors()
{
var outputLibrary = new TestOutputLibraryOverridingVisitors(new [] { new TestOutputLibraryVisitor() });
Assert.AreEqual(1, outputLibrary.GetOutputLibraryVisitors().Count());
outputLibrary.AddVisitor(new TestOutputLibraryVisitor());
Assert.AreEqual(2, outputLibrary.GetOutputLibraryVisitors().Count());
}
private class TestOutputLibraryVisitor : OutputLibraryVisitor
{
}
private class TestOutputLibrary : OutputLibrary
{
protected override TypeProvider[] BuildTypeProviders()
{
throw new NotImplementedException();
}
}
private class TestOutputLibraryOverridingVisitors : OutputLibrary
{
private readonly IEnumerable<OutputLibraryVisitor>? _visitors;
public TestOutputLibraryOverridingVisitors(IEnumerable<OutputLibraryVisitor>? visitors = null)
{
_visitors = visitors;
}
protected internal override IEnumerable<OutputLibraryVisitor> GetOutputLibraryVisitors()
{
foreach (var visitor in base.GetOutputLibraryVisitors())
{
yield return visitor;
}
foreach (var visitor in _visitors ?? [])
{
yield return visitor;
}
}
protected override TypeProvider[] BuildTypeProviders()
{

Просмотреть файл

@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
namespace Microsoft.Generator.CSharp.Tests.StartUp
{
public class PluginHandlerTests
{
[Test]
public void SelectPluginFindsMatchingPlugin()
{
var pluginHandler = new PluginHandler();
var metadataMock = new Mock<IMetadata>();
metadataMock.SetupGet(m => m.PluginName).Returns("MockPlugin");
var mockPlugin = new Mock<CodeModelPlugin>();
pluginHandler.Plugins = new List<System.Lazy<CodeModelPlugin, IMetadata>>
{
new(() => mockPlugin.Object, metadataMock.Object),
};
Assert.DoesNotThrow(() => pluginHandler.SelectPlugin("MockPlugin"));
mockPlugin.Verify(p => p.Configure(), Times.Once);
}
[Test]
public void SelectPluginThrowsWhenNoMatch()
{
var pluginHandler = new PluginHandler();
var metadataMock = new Mock<IMetadata>();
metadataMock.SetupGet(m => m.PluginName).Returns("MockPlugin");
var mockPlugin = new Mock<CodeModelPlugin>();
pluginHandler.Plugins = new List<System.Lazy<CodeModelPlugin, IMetadata>>
{
new(() => mockPlugin.Object, metadataMock.Object),
};
Assert.Throws<System.InvalidOperationException>(() => pluginHandler.SelectPlugin("NonExistentPlugin"));
mockPlugin.Verify(p => p.Configure(), Times.Never);
}
}
}

Просмотреть файл

@ -1,12 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Collections.Generic;
using Microsoft.Generator.CSharp.ClientModel;
namespace SamplePlugin
{
public class SamplePluginOutputLibrary : ScmOutputLibrary
{
protected override IEnumerable<SamplePluginOutputLibraryVisitor> GetOutputLibraryVisitors() => [new SamplePluginOutputLibraryVisitor()];
}
}

Просмотреть файл

@ -14,6 +14,9 @@ namespace SamplePlugin
{
public override SamplePluginTypeFactory TypeFactory { get; } = new SamplePluginTypeFactory();
public override OutputLibrary OutputLibrary { get; } = new SamplePluginOutputLibrary();
public override void Configure()
{
OutputLibrary.AddVisitor(new SamplePluginOutputLibraryVisitor());
}
}
}