Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exposed new interfaces for the Context types and exposed access to the wrapped types #38

Merged
merged 1 commit into from
Dec 26, 2024
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
25 changes: 4 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,28 +246,11 @@ When your source generator is being used in the wild it can happen there are exc



## Project Layout

This library is made up of quite a few different components leveraging various techniques to help
make the experience of integrating the library simple. Below I break down the purpose of each project and what they provide to you as the end users.

### Source.Generator.Foundations

A very small library that only appears to contain a C# analyzer for adding errors and warnings when trying to use this library. However once compiled this dll will have embedded as a resource every other project that is needed during compilation.


### Source.Generator.Foundations.Contracts

Contains common classes and utility methods that can be leveraged by source generators.

### Source.Generator.Foundations.MSBuild

Contains a custom MSBuild C# target implementation used to figure out which assemblies should be embedded as resources and which should be ignored. For example this will not embed any resources that are part of `.netstandard`.

### Source.Generator.Foundations.Shared

A shared project that every project that references this will have the files copied to it.
## Projects Using SGF
* [ByronMayne.AutoFactories](https://github.com/ByronMayne/AutoFactories)
* [AssetRipper.AssetRipper](https://github.com/AssetRipper/AssetRipper)

*Want to add your project here? Just make a PR*

## Attribution
Icon by <a href="https://www.flaticon.com/free-icons/drywall" title="drywall icons">Drywall icons created by HAJICON - Flaticon</a>
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using System;

namespace SGF
{
/// <summary>
/// An abstraction on top of <see cref="SgfInitializationContext"/> that allow for some extra api
/// and features not exposed in the public base.
/// </summary>
public interface ISgfInitializationContext
{
/// <summary>
/// Gets access to the provider of <see cref="AdditionalText"/>
/// </summary>
IncrementalValuesProvider<AdditionalText> AdditionalTextsProvider { get; }

/// <summary>
/// Gets access to the provider for <see cref="AnalyzerConfigOptionsProvider"/>
/// </summary>
IncrementalValueProvider<AnalyzerConfigOptionsProvider> AnalyzerConfigOptionsProvider { get; }

/// <summary>
/// Gets access to the provider for <see cref="Compilation"/>
/// </summary>
IncrementalValueProvider<Compilation> CompilationProvider { get; }

/// <summary>
/// Gets access to the provider for <see cref="MetadataReference"/>
/// </summary>
IncrementalValuesProvider<MetadataReference> MetadataReferencesProvider { get; }

/// <summary>
/// Gets access to the provider for <see cref="ParseOptions"/>
/// </summary>
IncrementalValueProvider<ParseOptions> ParseOptionsProvider { get; }

/// <summary>
/// Gets access to the provider for <see cref="SyntaxProvider"/>
/// </summary>
SyntaxValueProvider SyntaxProvider { get; }

/// <summary>
/// Gets the original <see cref="IncrementalGeneratorInitializationContext"/> that was produced by
/// roslyn. This should only be used in cases where you are interacting with third party api that needs it. The
/// issue is that SGF provides type loading an exception handling that MUST run before your code otherwise the generator
/// will crash.
/// </summary>
IncrementalGeneratorInitializationContext OriginalContext { get; }

/// <summary>
/// Register a callback that roduce source that is semantically 'invisible'; that is the added code has a runtime effect,
/// but adds no user visible types in completion or intellisense etc.
/// </summary>
/// <typeparam name="TSource">The source type</typeparam>
/// <param name="source">The source provider</param>
/// <param name="action">The action that will be invoked</param>
void RegisterImplementationSourceOutput<TSource>(IncrementalValueProvider<TSource> source, Action<SgfSourceProductionContext, TSource> action);

/// <summary>
/// Register a callback that roduce source that is semantically 'invisible'; that is the added code has a runtime effect,
/// but adds no user visible types in completion or intellisense etc.
/// </summary>
/// <typeparam name="TSource">The source type</typeparam>
/// <param name="sources">The source provider</param>
/// <param name="action">The action that will be invoked</param>
void RegisterImplementationSourceOutput<TSource>(IncrementalValuesProvider<TSource> sources, Action<SgfSourceProductionContext, TSource> action);

/// <summary>
/// Register a callback that produces source that will be added regardless of changes to any of the providers. This source does not have access to analyzer
/// information so it must be constant. Unlike other callbacks types added here can be referenced as it's added to the compiliation info. This is often used
/// for creating <see cref="Attribute"/> that are used by the source generator.
/// </summary>
/// <param name="callback"></param>
void RegisterPostInitializationOutput(Action<IncrementalGeneratorPostInitializationContext> callback);

/// <summary>
/// Registers a callback that produces source that effects comepliation and intelliesense. This callback will effect IDE evaluation time so
/// preformance is critical.
/// </summary>
/// <typeparam name="TSource">The source type</typeparam>
/// <param name="source">The source to add</param>
/// <param name="action">The action to be preformed</param>
void RegisterSourceOutput<TSource>(IncrementalValueProvider<TSource> source, Action<SgfSourceProductionContext, TSource> action);

/// <summary>
/// Registers a callback that produces source that effects comepliation and intelliesense. This callback will effect IDE evaluation time so
/// preformance is critical.
/// </summary>
/// <typeparam name="TSource">The source type</typeparam>
/// <param name="source">The source to add</param>
/// <param name="action">The action to be preformed</param>
void RegisterSourceOutput<TSource>(IncrementalValuesProvider<TSource> sourcess, Action<SgfSourceProductionContext, TSource> action);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Threading;

namespace SGF
{
/// <summary>
/// Provides an abstraction of <see cref="SgfSourceProductionContext"/>
/// </summary>
public interface ISgfSourceProductionContext
{
/// <summary>
/// <inheritdoc cref="CancellationToken"/>
/// </summary>
CancellationToken CancellationToken { get; }

/// <summary>
/// Gets the number of source files that were added
/// </summary>
int SourceCount { get; }

/// <summary>
/// Gets the original <see cref="ISgfSourceProductionContext"/> that was created. This should only be used
/// when required by third party apis. Using this side steps the exception handling and assembly
/// resolution that comes with SGF.
/// </summary>
SourceProductionContext OriginalContext { get; }

/// <summary>
/// Adds source code in the form of a <see cref="string"/> to the compilation.
/// </summary>
/// <param name="hintName">An identifier that can be used to reference this source text, must be unique within this generator</param>
/// <param name="source">The source code to add to the compilation</param>
void AddSource(string hintName, SourceText sourceText);

/// <summary>
/// Adds a <see cref="SourceText"/> to the compilation
/// </summary>
/// <param name="hintName">An identifier that can be used to reference this source text, must be unique within this generator</param>
/// <param name="sourceText">The <see cref="SourceText"/> to add to the compilation</param>
void AddSource(string hintName, string source);

/// <summary>
/// Adds a <see cref="Diagnostic"/> to the users compilation
/// </summary>
/// <param name="diagnostic">The diagnostic that should be added to the compilation</param>
/// <remarks>
/// The severity of the diagnostic may cause the compilation to fail, depending on the <see cref="Compilation"/> settings.
/// </remarks>
void ReportDiagnostic(Diagnostic diagnostic);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,37 @@

namespace SGF
{

/// <summary>
/// Middleware wrapper around a <see cref="IncrementalGeneratorInitializationContext"/> to allow for
/// wrapping with exception handling and provide a better user experience
/// </summary>
public readonly struct SgfInitializationContext
public readonly struct SgfInitializationContext : ISgfInitializationContext
{
private readonly ILogger m_logger;
private readonly IncrementalGeneratorInitializationContext m_context;

/// <inheritdoc/>
public SyntaxValueProvider SyntaxProvider => m_context.SyntaxProvider;

/// <inheritdoc/>
public IncrementalValueProvider<Compilation> CompilationProvider => m_context.CompilationProvider;

/// <inheritdoc/>
public IncrementalValueProvider<ParseOptions> ParseOptionsProvider => m_context.ParseOptionsProvider;

/// <inheritdoc />
public IncrementalValuesProvider<AdditionalText> AdditionalTextsProvider => m_context.AdditionalTextsProvider;

/// <inheritdoc/>
public IncrementalValueProvider<AnalyzerConfigOptionsProvider> AnalyzerConfigOptionsProvider => m_context.AnalyzerConfigOptionsProvider;

/// <inheritdoc/>
public IncrementalValuesProvider<MetadataReference> MetadataReferencesProvider => m_context.MetadataReferencesProvider;

/// <inheritdoc/>
IncrementalGeneratorInitializationContext ISgfInitializationContext.OriginalContext => m_context;

public SgfInitializationContext(
IncrementalGeneratorInitializationContext context,
ILogger logger)
Expand All @@ -30,6 +45,7 @@ public SgfInitializationContext(
m_context = context;
}

/// <inheritdoc/>
public void RegisterSourceOutput<TSource>(IncrementalValueProvider<TSource> source, Action<SgfSourceProductionContext, TSource> action)
{
ILogger logger = m_logger;
Expand All @@ -48,6 +64,7 @@ void wrappedAction(SourceProductionContext context, TSource source)
m_context.RegisterSourceOutput(source, wrappedAction);
}

/// <inheritdoc/>
public void RegisterSourceOutput<TSource>(IncrementalValuesProvider<TSource> source, Action<SgfSourceProductionContext, TSource> action)
{
ILogger logger = m_logger;
Expand All @@ -65,6 +82,7 @@ void wrappedAction(SourceProductionContext context, TSource source)
m_context.RegisterSourceOutput(source, wrappedAction);
}

/// <inheritdoc/>
public void RegisterImplementationSourceOutput<TSource>(IncrementalValueProvider<TSource> source, Action<SgfSourceProductionContext, TSource> action)
{
ILogger logger = m_logger;
Expand All @@ -84,8 +102,7 @@ void wrappedAction(SourceProductionContext context, TSource source)
m_context.RegisterImplementationSourceOutput(source, wrappedAction);
}



/// <inheritdoc/>
public void RegisterImplementationSourceOutput<TSource>(IncrementalValuesProvider<TSource> source, Action<SgfSourceProductionContext, TSource> action)
{
ILogger logger = m_logger;
Expand All @@ -104,9 +121,10 @@ void wrappedAction(SourceProductionContext context, TSource source)
m_context.RegisterImplementationSourceOutput(source, wrappedAction);
}

/// <inheritdoc/>
public void RegisterPostInitializationOutput(Action<IncrementalGeneratorPostInitializationContext> callback)
{
ILogger logger = m_logger;
ILogger logger = m_logger;
void wrappedCallback(IncrementalGeneratorPostInitializationContext context)
{
try
Expand All @@ -121,6 +139,9 @@ void wrappedCallback(IncrementalGeneratorPostInitializationContext context)
m_context.RegisterPostInitializationOutput(wrappedCallback);
}

/// <summary>
/// Logs an exception to the lagger to be presented in the IDE.
/// </summary>
private static void LogException(ILogger logger, Exception exception, MethodInfo actionInfo)
{
string methodName = actionInfo.Name;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using SGF.Diagnostics;
using System.Text;
using System.Threading;
using System.Reflection;
using System;

namespace SGF
{
/// <summary>
/// Wrapper around a <see cref="SourceProductionContext"/> used to help capture errors and report logs
/// </summary>
public struct SgfSourceProductionContext
public struct SgfSourceProductionContext : ISgfSourceProductionContext
{
private readonly ILogger m_logger;
private readonly SourceProductionContext m_context;

/// <summary>
/// Gets the number of source files that were added
/// </summary>
/// <inheritdoc/>
public int SourceCount { get; private set; }

/// <inheritdoc/>
SourceProductionContext ISgfSourceProductionContext.OriginalContext => m_context;

/// <summary>
/// A token that will be canceled when generation should stop
Expand All @@ -32,35 +30,23 @@ internal SgfSourceProductionContext(SourceProductionContext context, ILogger log
SourceCount = 0;
m_logger = logger;
m_context = context;

}

/// <summary>
/// Adds source code in the form of a <see cref="string"/> to the compilation.
/// </summary>
/// <param name="hintName">An identifier that can be used to reference this source text, must be unique within this generator</param>
/// <param name="source">The source code to add to the compilation</param>
public void AddSource(string hintName, string source) => AddSource(hintName, SourceText.From(source, Encoding.UTF8));
/// <inheritdoc/>
public void AddSource(string hintName, string source)
=> AddSource(hintName, SourceText.From(source, Encoding.UTF8));

/// <summary>
/// Adds a <see cref="SourceText"/> to the compilation
/// </summary>
/// <param name="hintName">An identifier that can be used to reference this source text, must be unique within this generator</param>
/// <param name="sourceText">The <see cref="SourceText"/> to add to the compilation</param>
/// <inheritdoc/>
public void AddSource(string hintName, SourceText sourceText)
{
SourceCount++;
m_logger.Information($" SourceAdded: {SourceCount}. {hintName}");
m_context.AddSource(hintName, sourceText);
}

/// <summary>
/// Adds a <see cref="Diagnostic"/> to the users compilation
/// </summary>
/// <param name="diagnostic">The diagnostic that should be added to the compilation</param>
/// <remarks>
/// The severity of the diagnostic may cause the compilation to fail, depending on the <see cref="Compilation"/> settings.
/// </remarks>
public void ReportDiagnostic(Diagnostic diagnostic) => m_context.ReportDiagnostic(diagnostic);
/// <inheritdoc/>
public void ReportDiagnostic(Diagnostic diagnostic)
=> m_context.ReportDiagnostic(diagnostic);
}
}