10 changed files with 235 additions and 162 deletions
@ -1,6 +1,8 @@ |
|||||
|
using System; |
||||
|
|
||||
namespace Avalonia.Generators.Common.Domain; |
namespace Avalonia.Generators.Common.Domain; |
||||
|
|
||||
internal interface IGlobPattern |
internal interface IGlobPattern : IEquatable<IGlobPattern> |
||||
{ |
{ |
||||
bool Matches(string str); |
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 Microsoft.CodeAnalysis.Text; |
||||
using System.Threading; |
|
||||
using Microsoft.CodeAnalysis; |
|
||||
|
|
||||
namespace Avalonia.Generators.NameGenerator; |
namespace Avalonia.Generators.NameGenerator; |
||||
|
|
||||
internal interface INameGenerator |
internal interface INameGenerator |
||||
{ |
{ |
||||
IEnumerable<GeneratedPartialClass> GenerateNameReferences(IEnumerable<AdditionalText> additionalFiles, CancellationToken cancellationToken); |
public GeneratedPartialClass? GenerateNameReferences(SourceText sourceText); |
||||
} |
} |
||||
|
|
||||
internal record GeneratedPartialClass(string FileName, string Content); |
internal record GeneratedPartialClass(string FileName, string Content); |
||||
|
|||||
Loading…
Reference in new issue