10 changed files with 235 additions and 162 deletions
@ -1,6 +1,8 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Generators.Common.Domain; |
|||
|
|||
internal interface IGlobPattern |
|||
internal interface IGlobPattern : IEquatable<IGlobPattern> |
|||
{ |
|||
bool Matches(string str); |
|||
} |
|||
|
|||
@ -0,0 +1,57 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace Avalonia.Generators.Common; |
|||
|
|||
// https://github.com/dotnet/roslyn/blob/main/docs/features/incremental-generators.cookbook.md#pipeline-model-design
|
|||
internal class EquatableList<T> : List<T>, IEquatable<EquatableList<T>> |
|||
{ |
|||
public EquatableList(IEnumerable<T> collection) : base(collection) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public EquatableList() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public bool Equals(EquatableList<T>? other) |
|||
{ |
|||
// If the other list is null or a different size, they're not equal
|
|||
if (other is null || Count != other.Count) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
// Compare each pair of elements for equality
|
|||
for (int i = 0; i < Count; i++) |
|||
{ |
|||
if (!EqualityComparer<T>.Default.Equals(this[i], other[i])) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
// If we got this far, the lists are equal
|
|||
return true; |
|||
} |
|||
public override bool Equals(object? obj) |
|||
{ |
|||
return Equals(obj as EquatableList<T>); |
|||
} |
|||
public override int GetHashCode() |
|||
{ |
|||
return this.Select(item => item?.GetHashCode() ?? 0).Aggregate(0, (x, y) => x ^ y); |
|||
} |
|||
public static bool operator ==(EquatableList<T>? list1, EquatableList<T>? list2) |
|||
{ |
|||
return ReferenceEquals(list1, list2) |
|||
|| list1 is not null && list2 is not null && list1.Equals(list2); |
|||
} |
|||
public static bool operator !=(EquatableList<T>? list1, EquatableList<T>? list2) |
|||
{ |
|||
return !(list1 == list2); |
|||
} |
|||
} |
|||
@ -0,0 +1,93 @@ |
|||
using System; |
|||
using Avalonia.Generators.Common; |
|||
using Avalonia.Generators.Common.Domain; |
|||
using Avalonia.Generators.Compiler; |
|||
using Microsoft.CodeAnalysis; |
|||
using Microsoft.CodeAnalysis.CSharp; |
|||
|
|||
namespace Avalonia.Generators.NameGenerator; |
|||
|
|||
[Generator(LanguageNames.CSharp)] |
|||
public class AvaloniaNameIncrementalGenerator : IIncrementalGenerator |
|||
{ |
|||
private const string SourceItemGroupMetadata = "build_metadata.AdditionalFiles.SourceItemGroup"; |
|||
|
|||
public void Initialize(IncrementalGeneratorInitializationContext context) |
|||
{ |
|||
var options = context.AnalyzerConfigOptionsProvider |
|||
.Select(static (options, _) => new GeneratorOptions(options.GlobalOptions)) |
|||
.Combine(context.AnalyzerConfigOptionsProvider); |
|||
|
|||
var xamlFiles = context.AdditionalTextsProvider.Combine(options) |
|||
.Select(static (pair, cancellationToken) => |
|||
{ |
|||
var text = pair.Left; |
|||
var options = pair.Right.Left; |
|||
var optionsProvider = pair.Right.Right; |
|||
var filePath = text.Path; |
|||
|
|||
if (!(filePath.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase) || |
|||
filePath.EndsWith(".paml", StringComparison.OrdinalIgnoreCase) || |
|||
filePath.EndsWith(".axaml", StringComparison.OrdinalIgnoreCase))) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
if (!options.AvaloniaNameGeneratorFilterByPath.Matches(filePath)) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
if (!optionsProvider.GetOptions(pair.Left).TryGetValue(SourceItemGroupMetadata, out var itemGroup) |
|||
|| itemGroup != "AvaloniaXaml") |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
if (text.GetText(cancellationToken) is not { } textContent) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
return (textContent, options); |
|||
}) |
|||
.Where(tuple => tuple.textContent is not null); |
|||
|
|||
var generatorInput = xamlFiles.Combine(context.CompilationProvider); |
|||
|
|||
context.RegisterSourceOutput(generatorInput, static (context, pair) => |
|||
{ |
|||
var options = pair.Left.options; |
|||
var textSource = pair.Left.textContent; |
|||
var compilation = pair.Right; |
|||
|
|||
if (!options.AvaloniaNameGeneratorIsEnabled) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var types = new RoslynTypeSystem((CSharpCompilation)compilation); |
|||
ICodeGenerator codeGenerator = options.AvaloniaNameGeneratorBehavior switch { |
|||
Behavior.OnlyProperties => new OnlyPropertiesCodeGenerator(), |
|||
Behavior.InitializeComponent => new InitializeComponentCodeGenerator(types, options.AvaloniaNameGeneratorAttachDevTools), |
|||
_ => throw new ArgumentOutOfRangeException() |
|||
}; |
|||
|
|||
var compiler = MiniCompiler.CreateDefault(types, MiniCompiler.AvaloniaXmlnsDefinitionAttribute); |
|||
var generator = new AvaloniaNameGenerator( |
|||
options.AvaloniaNameGeneratorViewFileNamingStrategy, |
|||
options.AvaloniaNameGeneratorFilterByNamespace, |
|||
new XamlXViewResolver(types, compiler, true, |
|||
type => context.ReportNameGeneratorInvalidType(type), |
|||
error => context.ReportNameGeneratorUnhandledError(error)), |
|||
new XamlXNameResolver(options.AvaloniaNameGeneratorClassFieldModifier), |
|||
codeGenerator); |
|||
|
|||
var partialClass = generator.GenerateNameReferences(textSource); |
|||
if (partialClass is not null) |
|||
{ |
|||
context.AddSource(partialClass.FileName, partialClass.Content); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
@ -1,86 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
using Avalonia.Generators.Common; |
|||
using Avalonia.Generators.Common.Domain; |
|||
using Avalonia.Generators.Compiler; |
|||
using Microsoft.CodeAnalysis; |
|||
using Microsoft.CodeAnalysis.CSharp; |
|||
|
|||
namespace Avalonia.Generators.NameGenerator; |
|||
|
|||
[Generator] |
|||
public class AvaloniaNameSourceGenerator : ISourceGenerator |
|||
{ |
|||
private const string SourceItemGroupMetadata = "build_metadata.AdditionalFiles.SourceItemGroup"; |
|||
|
|||
public void Initialize(GeneratorInitializationContext context) { } |
|||
|
|||
public void Execute(GeneratorExecutionContext context) |
|||
{ |
|||
try |
|||
{ |
|||
var generator = CreateNameGenerator(context); |
|||
if (generator is null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var partials = generator.GenerateNameReferences(ResolveAdditionalFiles(context), context.CancellationToken); |
|||
foreach (var (fileName, content) in partials) |
|||
{ |
|||
if(context.CancellationToken.IsCancellationRequested) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
context.AddSource(fileName, content); |
|||
} |
|||
} |
|||
catch (OperationCanceledException) |
|||
{ |
|||
} |
|||
catch (Exception exception) |
|||
{ |
|||
context.ReportNameGeneratorUnhandledError(exception); |
|||
} |
|||
} |
|||
|
|||
private static IEnumerable<AdditionalText> ResolveAdditionalFiles(GeneratorExecutionContext context) |
|||
{ |
|||
return context |
|||
.AdditionalFiles |
|||
.Where(f => context.AnalyzerConfigOptions |
|||
.GetOptions(f) |
|||
.TryGetValue(SourceItemGroupMetadata, out var sourceItemGroup) |
|||
&& sourceItemGroup == "AvaloniaXaml"); |
|||
} |
|||
|
|||
private static INameGenerator? CreateNameGenerator(GeneratorExecutionContext context) |
|||
{ |
|||
var options = new GeneratorOptions(context); |
|||
if (!options.AvaloniaNameGeneratorIsEnabled) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
var types = new RoslynTypeSystem((CSharpCompilation)context.Compilation); |
|||
ICodeGenerator generator = options.AvaloniaNameGeneratorBehavior switch { |
|||
Behavior.OnlyProperties => new OnlyPropertiesCodeGenerator(), |
|||
Behavior.InitializeComponent => new InitializeComponentCodeGenerator(types, options.AvaloniaNameGeneratorAttachDevTools), |
|||
_ => throw new ArgumentOutOfRangeException() |
|||
}; |
|||
|
|||
var compiler = MiniCompiler.CreateDefault(types, MiniCompiler.AvaloniaXmlnsDefinitionAttribute); |
|||
return new AvaloniaNameGenerator( |
|||
options.AvaloniaNameGeneratorViewFileNamingStrategy, |
|||
new GlobPatternGroup(options.AvaloniaNameGeneratorFilterByPath), |
|||
new GlobPatternGroup(options.AvaloniaNameGeneratorFilterByNamespace), |
|||
new XamlXViewResolver(types, compiler, true, |
|||
type => context.ReportNameGeneratorInvalidType(type), |
|||
error => context.ReportNameGeneratorUnhandledError(error)), |
|||
new XamlXNameResolver(options.AvaloniaNameGeneratorClassFieldModifier), |
|||
generator); |
|||
} |
|||
} |
|||
@ -1,12 +1,10 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading; |
|||
using Microsoft.CodeAnalysis; |
|||
using Microsoft.CodeAnalysis.Text; |
|||
|
|||
namespace Avalonia.Generators.NameGenerator; |
|||
|
|||
internal interface INameGenerator |
|||
{ |
|||
IEnumerable<GeneratedPartialClass> GenerateNameReferences(IEnumerable<AdditionalText> additionalFiles, CancellationToken cancellationToken); |
|||
public GeneratedPartialClass? GenerateNameReferences(SourceText sourceText); |
|||
} |
|||
|
|||
internal record GeneratedPartialClass(string FileName, string Content); |
|||
|
|||
Loading…
Reference in new issue