Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/Mocha/src/Mocha.Abstractions/DeclarationLocation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Mocha;

/// <summary>
/// Source declaration location captured by generated configuration metadata.
/// Line and column values are 1-based, matching editor coordinates, and the span covers
/// the entire declaration from its start to its end.
/// </summary>
/// <param name="File">
/// The source file path, relative to the project directory using forward slashes,
/// or the file name alone when a project-relative path is not available.
/// </param>
/// <param name="StartLine">The 1-based line number where the declaration starts.</param>
/// <param name="StartColumn">The 1-based column number where the declaration starts.</param>
/// <param name="EndLine">The 1-based line number where the declaration ends.</param>
/// <param name="EndColumn">The 1-based column number where the declaration ends.</param>
public sealed record DeclarationLocation(
string File,
int StartLine,
int StartColumn,
int EndLine,
int EndColumn);
33 changes: 33 additions & 0 deletions src/Mocha/src/Mocha.Abstractions/SourceMetadata.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace Mocha;

/// <summary>
/// Metadata captured from a source declaration.
/// </summary>
public sealed record SourceMetadata
{
/// <summary>
/// The simple name of the assembly that declares the type.
/// </summary>
public string? Assembly { get; init; }

/// <summary>
/// The source repository URL, or null when the build does not provide it.
/// </summary>
public string? RepositoryUrl { get; init; }

/// <summary>
/// The commit identifier the source was built from, or null when the build does not provide it.
/// </summary>
public string? Commit { get; init; }

/// <summary>
/// XML documentation captured from the source declaration.
/// </summary>
public string? XmlDocumentation { get; init; }

/// <summary>
/// Location of the source declaration, using 1-based line and column coordinates.
/// See <see cref="Mocha.DeclarationLocation"/> for the full contract.
/// </summary>
public DeclarationLocation? DeclarationLocation { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@ public sealed class DependencyInjectionFileBuilder : FileBuilderBase
{
private readonly string _extensionsClassName;
private readonly string _methodName;
private readonly SourceMetadataOptionsInfo _sourceMetadataOptions;

public DependencyInjectionFileBuilder(string moduleName, string assemblyName) : base(moduleName)
public DependencyInjectionFileBuilder(
string moduleName,
string assemblyName,
SourceMetadataOptionsInfo sourceMetadataOptions) : base(moduleName, assemblyName)
{
_extensionsClassName = moduleName + "MediatorBuilderExtensions";
_methodName = "Add" + moduleName;
_sourceMetadataOptions = sourceMetadataOptions;
HintName = _extensionsClassName + "." + HashHelper.ComputeSalt(assemblyName);
}

Expand All @@ -27,7 +32,7 @@ public DependencyInjectionFileBuilder(string moduleName, string assemblyName) :
public override void WriteBeginClass()
{
Writer.WriteGeneratedAttribute();
Writer.WriteIndentedLine("public static class {0}", _extensionsClassName);
Writer.WriteIndentedLine("public static partial class {0}", _extensionsClassName);
Writer.WriteIndentedLine("{");
Writer.IncreaseIndent();
}
Expand Down Expand Up @@ -97,9 +102,32 @@ public void WriteBeginRegistrationMethod(
}

/// <summary>
/// Writes an AddHandlerConfiguration call for a command/query handler.
/// Writes a mediator handler registration that points at a generated initializer method.
/// </summary>
public void WriteHandlerConfiguration(HandlerInfo handler)
public void WriteHandlerRegistration(string handlerTypeName, string initializerMethodName)
{
Writer.WriteIndentedLine("global::Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAdd(");
Writer.IncreaseIndent();
Writer.WriteIndentedLine("builder.Services,");
Writer.WriteIndentedLine("new global::Microsoft.Extensions.DependencyInjection.ServiceDescriptor(");
Writer.IncreaseIndent();
Writer.WriteIndentedLine("typeof({0}),", handlerTypeName);
Writer.WriteIndentedLine("typeof({0}),", handlerTypeName);
Writer.WriteIndentedLine("builder.Options.ServiceLifetime));");
Writer.DecreaseIndent();
Writer.DecreaseIndent();

Writer.WriteIndentedLine("global::Mocha.Mediator.MediatorHostBuilderExtensions.ConfigureMediator(");
Writer.IncreaseIndent();
Writer.WriteIndentedLine("builder,");
Writer.WriteIndentedLine(
"static b => b.AddHandler<{0}>({1}));",
handlerTypeName,
initializerMethodName);
Writer.DecreaseIndent();
}

public void WriteHandlerInitializer(string methodName, HandlerInfo handler)
{
var (kindEnum, terminalCall) = handler.Kind switch
{
Expand All @@ -115,66 +143,75 @@ public void WriteHandlerConfiguration(HandlerInfo handler)
_ => throw new ArgumentOutOfRangeException()
};

WriteAddHandlerConfiguration(
handler.HandlerTypeName,
WriteHandlerInitializer(
methodName,
handler.MessageTypeName,
handler.ResponseTypeName,
kindEnum,
$"global::Mocha.Mediator.PipelineBuilder.{terminalCall}");
$"global::Mocha.Mediator.PipelineBuilder.{terminalCall}",
handler.XmlDocumentation,
handler.Location);
}

/// <summary>
/// Writes an AddHandlerConfiguration call for a notification handler.
/// </summary>
public void WriteNotificationHandlerConfiguration(string notificationType, NotificationHandlerInfo handler)
{
WriteAddHandlerConfiguration(
handler.HandlerTypeName,
notificationType,
public void WriteNotificationHandlerInitializer(string methodName, NotificationHandlerInfo handler)
=> WriteHandlerInitializer(
methodName,
handler.NotificationTypeName,
responseTypeName: null,
"Notification",
$"global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline<{handler.HandlerTypeName}, {notificationType}>()");
$"global::Mocha.Mediator.PipelineBuilder.BuildNotificationPipeline<{handler.HandlerTypeName}, {handler.NotificationTypeName}>()",
handler.XmlDocumentation,
handler.Location);

public void WriteSectionComment(string comment)
{
Writer.WriteLine();
Writer.WriteIndentedLine("// {0}", comment);
}

private void WriteAddHandlerConfiguration(
string handlerTypeName,
public void WriteEndRegistrationMethod()
{
Writer.WriteLine();
Writer.WriteIndentedLine("return builder;");
Writer.DecreaseIndent();
Writer.WriteIndentedLine("}");
}

private void WriteHandlerInitializer(
string methodName,
string messageTypeName,
string? responseTypeName,
string kindEnum,
string terminalCall)
string terminalCall,
string? xmlDocumentation,
LocationInfo? location)
{
Writer.WriteLine();
Writer.WriteIndentedLine(
"global::Mocha.Mediator.MediatorHostBuilderHandlerExtensions.AddHandlerConfiguration<{0}>(builder,",
handlerTypeName);
Writer.IncreaseIndent();
Writer.WriteIndentedLine("new global::Mocha.Mediator.MediatorHandlerConfiguration");
"private static void {0}(global::Mocha.Mediator.IMediatorHandlerDescriptor descriptor)",
methodName);
Writer.WriteIndentedLine("{");
Writer.IncreaseIndent();
Writer.WriteIndentedLine("HandlerType = typeof({0}),", handlerTypeName);
Writer.WriteIndentedLine("MessageType = typeof({0}),", messageTypeName);
Writer.WriteIndentedLine("var configuration = descriptor.Extend().Configuration;");
Writer.WriteIndentedLine("configuration.MessageType = typeof({0});", messageTypeName);

if (responseTypeName is not null)
{
Writer.WriteIndentedLine("ResponseType = typeof({0}),", responseTypeName);
Writer.WriteIndentedLine("configuration.ResponseType = typeof({0});", responseTypeName);
}

Writer.WriteIndentedLine("Kind = global::Mocha.Mediator.MediatorHandlerKind.{0},", kindEnum);
Writer.WriteIndentedLine("Delegate = {0}", terminalCall);
Writer.DecreaseIndent();
Writer.WriteIndentedLine("});");
Writer.DecreaseIndent();
}
Writer.WriteIndentedLine("configuration.Kind = global::Mocha.Mediator.MediatorHandlerKind.{0};", kindEnum);
Writer.WriteIndentedLine("configuration.Delegate = {0};", terminalCall);

public void WriteSectionComment(string comment)
{
Writer.WriteLine();
Writer.WriteIndentedLine("// {0}", comment);
}
Writer.WriteSourceMetadataAssignment(
"configuration.Source",
"global::Mocha.SourceMetadata",
"global::Mocha.DeclarationLocation",
AssemblyName,
xmlDocumentation,
location,
_sourceMetadataOptions);

public void WriteEndRegistrationMethod()
{
Writer.WriteLine();
Writer.WriteIndentedLine("return builder;");
Writer.DecreaseIndent();
Writer.WriteIndentedLine("}");
}
Expand Down
41 changes: 40 additions & 1 deletion src/Mocha/src/Mocha.Analyzers/FileBuilders/FileBuilderBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ namespace Mocha.Analyzers.FileBuilders;
/// </summary>
public abstract class FileBuilderBase : IFileBuilder
{
private readonly string _assemblyName;
private StringBuilder _sb;
private CodeWriter _writer;
private bool _disposed;

protected FileBuilderBase(string moduleName)
protected FileBuilderBase(string moduleName, string assemblyName)
{
ModuleName = moduleName;
_assemblyName = assemblyName;
_sb = PooledObjects.GetStringBuilder();
_writer = new CodeWriter(_sb);
}
Expand All @@ -31,6 +33,11 @@ protected FileBuilderBase(string moduleName)
/// </summary>
protected string ModuleName { get; }

/// <summary>
/// Gets the simple name of the assembly being compiled.
/// </summary>
protected string AssemblyName => _assemblyName;

/// <summary>
/// Gets the namespace to use in the generated source file.
/// </summary>
Expand Down Expand Up @@ -74,6 +81,14 @@ public string ToSourceText()
return _sb.ToString();
}

public string CreateGeneratedMethodName(string runtimeTypeName, string suffix)
{
var runtimeName = GetRuntimeName(runtimeTypeName);
var salt = HashHelper.ComputeIdentifierSalt($"{_assemblyName}::{runtimeTypeName}::{suffix}");

return $"__Initialize_{runtimeName}_{suffix}_{salt}";
}

/// <inheritdoc />
public void Dispose()
{
Expand All @@ -88,4 +103,28 @@ public void Dispose()
_writer = null!;
_disposed = true;
}

private static string GetRuntimeName(string runtimeTypeName)
{
var text = RemoveGlobalPrefix(runtimeTypeName);
var separator = text.LastIndexOf('.');
var name = separator < 0 ? text : text.Substring(separator + 1);
var builder = new StringBuilder(name.Length);

foreach (var character in name)
{
builder.Append(char.IsLetterOrDigit(character) ? character : '_');
}

return builder.ToString();
}

protected static string RemoveGlobalPrefix(string typeName)
{
const string Prefix = "global::";

return typeName.StartsWith(Prefix, StringComparison.Ordinal)
? typeName.Substring(Prefix.Length)
: typeName;
}
}
Loading
Loading