Browse Source
* Add AnalyzerProject.targets targets
* PrivateAssets on Workspaces.Common
* Migrate AvaloniaNameSourceGenerator to IIncrementalGenerator
* Remove outdated lins in the generator readme
* Add GlobPattern.ToString
* Fix tests
* Formatting
* Redo pipeline, make steps more independent from each other, compilation should be reused between dependency changes
* Split XAML parsing and type resolution into separated steps
* Restore CompilationReferencesComparer usage
* Revert "Restore CompilationReferencesComparer usage"
This reverts commit c51341990b.
* Split ResolvedNamesProvider pipeline step, process files in parallel
* Add comment
* Switch back to EquatableList
* Add cancellation to the incremenetal source gen
* Rethrow cancellation exception
pull/19469/head
committed by
GitHub
38 changed files with 708 additions and 484 deletions
@ -0,0 +1,14 @@ |
|||
<Project> |
|||
|
|||
<PropertyGroup> |
|||
<EnforceExtendedAnalyzerRules Condition="'$(EnforceExtendedAnalyzerRules)' == ''">true</EnforceExtendedAnalyzerRules> |
|||
<IsRoslynComponent Condition="'$(IsRoslynComponent)' == ''">true</IsRoslynComponent> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all"/> |
|||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" PrivateAssets="all" /> |
|||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.5.0" PrivateAssets="all" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -1,9 +1,8 @@ |
|||
using System.Collections.Generic; |
|||
using XamlX.TypeSystem; |
|||
|
|||
namespace Avalonia.Generators.Common.Domain; |
|||
|
|||
internal interface ICodeGenerator |
|||
{ |
|||
string GenerateCode(string className, string nameSpace, IXamlType xamlType, IEnumerable<ResolvedName> names); |
|||
string GenerateCode(string className, string nameSpace, IEnumerable<ResolvedName> names); |
|||
} |
|||
|
|||
@ -1,6 +1,8 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Generators.Common.Domain; |
|||
|
|||
internal interface IGlobPattern |
|||
internal interface IGlobPattern : IEquatable<IGlobPattern> |
|||
{ |
|||
bool Matches(string str); |
|||
} |
|||
|
|||
@ -1,11 +1,46 @@ |
|||
using System.Collections.Immutable; |
|||
using System.Threading; |
|||
using XamlX.Ast; |
|||
using XamlX.TypeSystem; |
|||
|
|||
namespace Avalonia.Generators.Common.Domain; |
|||
|
|||
internal interface IViewResolver |
|||
{ |
|||
ResolvedView? ResolveView(string xaml); |
|||
ResolvedViewDocument? ResolveView(string xaml, CancellationToken cancellationToken); |
|||
} |
|||
|
|||
internal record ResolvedView(string ClassName, IXamlType XamlType, string Namespace, XamlDocument Xaml); |
|||
internal record ResolvedViewInfo(string ClassName, string Namespace) |
|||
{ |
|||
public string FullName => $"{Namespace}.{ClassName}"; |
|||
public override string ToString() => FullName; |
|||
} |
|||
|
|||
internal record ResolvedViewDocument(string ClassName, string Namespace, XamlDocument Xaml) |
|||
: ResolvedViewInfo(ClassName, Namespace); |
|||
|
|||
internal record ResolvedXmlView( |
|||
string ClassName, |
|||
string Namespace, |
|||
EquatableList<ResolvedXmlName> XmlNames) |
|||
: ResolvedViewInfo(ClassName, Namespace) |
|||
{ |
|||
public ResolvedXmlView(ResolvedViewInfo info, EquatableList<ResolvedXmlName> xmlNames) |
|||
: this(info.ClassName, info.Namespace, xmlNames) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
|
|||
internal record ResolvedView( |
|||
string ClassName, |
|||
string Namespace, |
|||
bool IsWindow, |
|||
EquatableList<ResolvedName> Names) |
|||
: ResolvedViewInfo(ClassName, Namespace) |
|||
{ |
|||
public ResolvedView(ResolvedViewInfo info, bool isWindow, EquatableList<ResolvedName> names) |
|||
: this(info.ClassName, info.Namespace, isWindow, names) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,58 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Collections.ObjectModel; |
|||
|
|||
namespace Avalonia.Generators.Common; |
|||
|
|||
// https://github.com/dotnet/roslyn/blob/main/docs/features/incremental-generators.cookbook.md#pipeline-model-design
|
|||
// With minor modification to use ReadOnlyCollection instead of List
|
|||
internal class EquatableList<T>(IList<T> collection) |
|||
: ReadOnlyCollection<T>(collection), IEquatable<EquatableList<T>> |
|||
{ |
|||
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() |
|||
{ |
|||
var hash = 0; |
|||
for (var i = 0; i < Count; i++) |
|||
{ |
|||
hash ^= this[i]?.GetHashCode() ?? 0; |
|||
} |
|||
return hash; |
|||
} |
|||
|
|||
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,22 @@ |
|||
using System.Collections.Generic; |
|||
using XamlX.TypeSystem; |
|||
|
|||
namespace Avalonia.Generators.Compiler; |
|||
|
|||
internal class NoopTypeSystem : IXamlTypeSystem |
|||
{ |
|||
public IEnumerable<IXamlAssembly> Assemblies => [NoopAssembly.Instance]; |
|||
public IXamlAssembly? FindAssembly(string substring) => null; |
|||
public IXamlType? FindType(string name) => XamlPseudoType.Unresolved(name); |
|||
public IXamlType? FindType(string name, string assembly) => XamlPseudoType.Unresolved(name); |
|||
|
|||
internal class NoopAssembly : IXamlAssembly |
|||
{ |
|||
public static NoopAssembly Instance { get; } = new(); |
|||
public bool Equals(IXamlAssembly other) => ReferenceEquals(this, other); |
|||
public string Name { get; } = "Noop"; |
|||
public IReadOnlyList<IXamlCustomAttribute> CustomAttributes { get; } = []; |
|||
public IXamlType? FindType(string fullName) => XamlPseudoType.Unresolved(fullName); |
|||
} |
|||
} |
|||
|
|||
@ -1,44 +0,0 @@ |
|||
using System; |
|||
using Microsoft.CodeAnalysis; |
|||
|
|||
namespace Avalonia.Generators; |
|||
|
|||
internal static class GeneratorContextExtensions |
|||
{ |
|||
private const string UnhandledErrorDescriptorId = "AXN0002"; |
|||
private const string InvalidTypeDescriptorId = "AXN0001"; |
|||
|
|||
public static string GetMsBuildProperty( |
|||
this GeneratorExecutionContext context, |
|||
string name, |
|||
string defaultValue = "") |
|||
{ |
|||
context.AnalyzerConfigOptions.GlobalOptions.TryGetValue($"build_property.{name}", out var value); |
|||
return value ?? defaultValue; |
|||
} |
|||
|
|||
public static void ReportNameGeneratorUnhandledError(this GeneratorExecutionContext context, Exception error) => |
|||
context.Report(UnhandledErrorDescriptorId, |
|||
"Unhandled exception occurred while generating typed Name references. " + |
|||
"Please file an issue: https://github.com/avaloniaui/Avalonia", |
|||
error.Message, |
|||
error.ToString()); |
|||
|
|||
public static void ReportNameGeneratorInvalidType(this GeneratorExecutionContext context, string typeName) => |
|||
context.Report(InvalidTypeDescriptorId, |
|||
$"Avalonia x:Name generator was unable to generate names for type '{typeName}'. " + |
|||
$"The type '{typeName}' does not exist in the assembly."); |
|||
|
|||
private static void Report(this GeneratorExecutionContext context, string id, string title, string? message = null, string? description = null) => |
|||
context.ReportDiagnostic( |
|||
Diagnostic.Create( |
|||
new DiagnosticDescriptor( |
|||
id: id, |
|||
title: title, |
|||
messageFormat: message ?? title, |
|||
category: "Usage", |
|||
defaultSeverity: DiagnosticSeverity.Error, |
|||
isEnabledByDefault: true, |
|||
description), |
|||
Location.None)); |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
using System; |
|||
using Microsoft.CodeAnalysis; |
|||
using Microsoft.CodeAnalysis.Diagnostics; |
|||
|
|||
namespace Avalonia.Generators; |
|||
|
|||
internal static class GeneratorExtensions |
|||
{ |
|||
private const string UnhandledErrorDescriptorId = "AXN0002"; |
|||
private const string InvalidTypeDescriptorId = "AXN0001"; |
|||
|
|||
public static string GetMsBuildProperty( |
|||
this AnalyzerConfigOptions options, |
|||
string name, |
|||
string defaultValue = "") |
|||
{ |
|||
options.TryGetValue($"build_property.{name}", out var value); |
|||
return value ?? defaultValue; |
|||
} |
|||
|
|||
public static DiagnosticDescriptor NameGeneratorUnhandledError(Exception error) => new( |
|||
UnhandledErrorDescriptorId, |
|||
title: "Unhandled exception occurred while generating typed Name references. " + |
|||
"Please file an issue: https://github.com/avaloniaui/Avalonia", |
|||
messageFormat: error.Message, |
|||
description: error.ToString(), |
|||
category: "Usage", |
|||
defaultSeverity: DiagnosticSeverity.Error, |
|||
isEnabledByDefault: true); |
|||
|
|||
public static DiagnosticDescriptor NameGeneratorInvalidType(string typeName) => new( |
|||
InvalidTypeDescriptorId, |
|||
title: $"Avalonia x:Name generator was unable to generate names for type '{typeName}'. " + |
|||
$"The type '{typeName}' does not exist in the assembly.", |
|||
messageFormat: $"Avalonia x:Name generator was unable to generate names for type '{typeName}'. " + |
|||
$"The type '{typeName}' does not exist in the assembly.", |
|||
category: "Usage", |
|||
defaultSeverity: DiagnosticSeverity.Error, |
|||
isEnabledByDefault: true); |
|||
|
|||
public static void Report(this SourceProductionContext context, DiagnosticDescriptor diagnostics) => |
|||
context.ReportDiagnostic(Diagnostic.Create(diagnostics, Location.None)); |
|||
} |
|||
@ -1,66 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using Avalonia.Generators.Common.Domain; |
|||
using Microsoft.CodeAnalysis; |
|||
|
|||
namespace Avalonia.Generators.NameGenerator; |
|||
|
|||
internal class AvaloniaNameGenerator : INameGenerator |
|||
{ |
|||
private readonly ViewFileNamingStrategy _naming; |
|||
private readonly IGlobPattern _pathPattern; |
|||
private readonly IGlobPattern _namespacePattern; |
|||
private readonly IViewResolver _classes; |
|||
private readonly INameResolver _names; |
|||
private readonly ICodeGenerator _code; |
|||
|
|||
public AvaloniaNameGenerator( |
|||
ViewFileNamingStrategy naming, |
|||
IGlobPattern pathPattern, |
|||
IGlobPattern namespacePattern, |
|||
IViewResolver classes, |
|||
INameResolver names, |
|||
ICodeGenerator code) |
|||
{ |
|||
_naming = naming; |
|||
_pathPattern = pathPattern; |
|||
_namespacePattern = namespacePattern; |
|||
_classes = classes; |
|||
_names = names; |
|||
_code = code; |
|||
} |
|||
|
|||
public IEnumerable<GeneratedPartialClass> GenerateNameReferences(IEnumerable<AdditionalText> additionalFiles, CancellationToken cancellationToken) |
|||
{ |
|||
var resolveViews = |
|||
from file in additionalFiles |
|||
let filePath = file.Path |
|||
where (filePath.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase) || |
|||
filePath.EndsWith(".paml", StringComparison.OrdinalIgnoreCase) || |
|||
filePath.EndsWith(".axaml", StringComparison.OrdinalIgnoreCase)) && |
|||
_pathPattern.Matches(filePath) |
|||
let xaml = file.GetText(cancellationToken)?.ToString() |
|||
where xaml != null |
|||
let view = _classes.ResolveView(xaml) |
|||
where view != null && _namespacePattern.Matches(view.Namespace) |
|||
select view; |
|||
|
|||
var query = |
|||
from view in resolveViews |
|||
let names = _names.ResolveNames(view.Xaml) |
|||
let code = _code.GenerateCode(view.ClassName, view.Namespace, view.XamlType, names) |
|||
let fileName = ResolveViewFileName(view, _naming) |
|||
select new GeneratedPartialClass(fileName, code); |
|||
|
|||
return query; |
|||
} |
|||
|
|||
private static string ResolveViewFileName(ResolvedView view, ViewFileNamingStrategy strategy) => strategy switch |
|||
{ |
|||
ViewFileNamingStrategy.ClassName => $"{view.ClassName}.g.cs", |
|||
ViewFileNamingStrategy.NamespaceAndClassName => $"{view.Namespace}.{view.ClassName}.g.cs", |
|||
_ => throw new ArgumentOutOfRangeException(nameof(strategy), strategy, "Unknown naming strategy!") |
|||
}; |
|||
} |
|||
@ -0,0 +1,211 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Linq; |
|||
using Avalonia.Generators.Common; |
|||
using Avalonia.Generators.Common.Domain; |
|||
using Avalonia.Generators.Compiler; |
|||
using Microsoft.CodeAnalysis; |
|||
using XamlX.Transform; |
|||
|
|||
namespace Avalonia.Generators.NameGenerator; |
|||
|
|||
[Generator(LanguageNames.CSharp)] |
|||
public class AvaloniaNameIncrementalGenerator : IIncrementalGenerator |
|||
{ |
|||
private const string SourceItemGroupMetadata = "build_metadata.AdditionalFiles.SourceItemGroup"; |
|||
private static readonly MiniCompiler s_noopCompiler = MiniCompiler.CreateNoop(); |
|||
|
|||
public void Initialize(IncrementalGeneratorInitializationContext context) |
|||
{ |
|||
// Map MSBuild properties onto readonly GeneratorOptions.
|
|||
var options = context.AnalyzerConfigOptionsProvider |
|||
.Select(static (options, _) => new GeneratorOptions(options.GlobalOptions)) |
|||
.WithTrackingName(TrackingNames.XamlGeneratorOptionsProvider); |
|||
|
|||
// Filter additional texts, we only need Avalonia XAML files.
|
|||
var xamlFiles = context.AdditionalTextsProvider |
|||
.Combine(options.Combine(context.AnalyzerConfigOptionsProvider)) |
|||
.Where(static pair => |
|||
{ |
|||
var text = pair.Left; |
|||
var (options, optionsProvider) = pair.Right; |
|||
var filePath = text.Path; |
|||
|
|||
if (!(filePath.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase) || |
|||
filePath.EndsWith(".paml", StringComparison.OrdinalIgnoreCase) || |
|||
filePath.EndsWith(".axaml", StringComparison.OrdinalIgnoreCase))) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (!options.AvaloniaNameGeneratorFilterByPath.Matches(filePath)) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (!optionsProvider.GetOptions(pair.Left).TryGetValue(SourceItemGroupMetadata, out var itemGroup) |
|||
|| itemGroup != "AvaloniaXaml") |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
}) |
|||
.Select(static (pair, _) => pair.Left) |
|||
.WithTrackingName(TrackingNames.InputXamlFilesProvider); |
|||
|
|||
// Actual parsing step. We input XAML files one by one, but don't resolve any types.
|
|||
// That's why we use NoOp type system here, allowing parsing to run detached from C# compilation.
|
|||
// Otherwise we would need to re-parse XAML on any C# file changed.
|
|||
var parsedXamlClasses = xamlFiles |
|||
.Select(static (file, cancellationToken) => |
|||
{ |
|||
cancellationToken.ThrowIfCancellationRequested(); |
|||
var text = file.GetText(cancellationToken); |
|||
var diagnostics = new List<DiagnosticDescriptor>(); |
|||
if (text is not null) |
|||
{ |
|||
try |
|||
{ |
|||
var xaml = text.ToString(); |
|||
var viewResolver = new XamlXViewResolver(s_noopCompiler); |
|||
var view = viewResolver.ResolveView(xaml, cancellationToken); |
|||
if (view is null) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
var nameResolver = new XamlXNameResolver(); |
|||
var xmlNames = nameResolver.ResolveXmlNames(view.Xaml, cancellationToken); |
|||
|
|||
return new XmlClassInfo( |
|||
new ResolvedXmlView(view, xmlNames), |
|||
new EquatableList<DiagnosticDescriptor>(diagnostics)); |
|||
} |
|||
catch (OperationCanceledException) |
|||
{ |
|||
throw; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
diagnostics.Add(GeneratorExtensions.NameGeneratorUnhandledError(ex)); |
|||
return new XmlClassInfo(null, new EquatableList<DiagnosticDescriptor>(diagnostics)); |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
}) |
|||
.Where(request => request is not null) |
|||
.WithTrackingName(TrackingNames.ParsedXamlClasses); |
|||
|
|||
// IMPORTANT: we shouldn't cache CompilationProvider as a whole,
|
|||
// But we also should keep in mind that CompilationProvider can frequently re-trigger generator.
|
|||
var compiler = context.CompilationProvider |
|||
.Select(static (compilation, _) => |
|||
{ |
|||
var roslynTypeSystem = new RoslynTypeSystem(compilation); |
|||
return MiniCompiler.CreateRoslyn(roslynTypeSystem, MiniCompiler.AvaloniaXmlnsDefinitionAttribute); |
|||
}) |
|||
.WithTrackingName(TrackingNames.XamlTypeSystem); |
|||
|
|||
// Note: this step will be re-executed on any C# file changes.
|
|||
// As much as possible heavy tasks should be moved outside of this step, like XAML parsing.
|
|||
var resolvedNames = parsedXamlClasses |
|||
.Combine(compiler) |
|||
.Select(static (pair, ct) => |
|||
{ |
|||
var (classInfo, compiler) = pair; |
|||
var hasDevToolsReference = compiler.TypeSystem.FindAssembly("Avalonia.Diagnostics") is not null; |
|||
var nameResolver = new XamlXNameResolver(); |
|||
|
|||
var diagnostics = new List<DiagnosticDescriptor>(classInfo!.Diagnostics); |
|||
ResolvedView? view = null; |
|||
if (classInfo.XmlView is { } xmlView) |
|||
{ |
|||
var type = compiler.TypeSystem.FindType(xmlView.FullName); |
|||
|
|||
if (type is null) |
|||
{ |
|||
diagnostics.Add(GeneratorExtensions.NameGeneratorInvalidType(xmlView.FullName)); |
|||
} |
|||
else if (type.IsAvaloniaStyledElement()) |
|||
{ |
|||
var resolvedNames = new List<ResolvedName>(); |
|||
foreach (var xmlName in xmlView.XmlNames) |
|||
{ |
|||
ct.ThrowIfCancellationRequested(); |
|||
|
|||
try |
|||
{ |
|||
var clrType = compiler.ResolveXamlType(xmlName.XmlType); |
|||
if (!clrType.IsAvaloniaStyledElement()) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
resolvedNames.Add(nameResolver |
|||
.ResolveName(clrType, xmlName.Name, xmlName.FieldModifier)); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
diagnostics.Add(GeneratorExtensions.NameGeneratorUnhandledError(ex)); |
|||
} |
|||
} |
|||
|
|||
view = new ResolvedView(xmlView, type.IsAvaloniaWindow(), new EquatableList<ResolvedName>(resolvedNames)); |
|||
} |
|||
} |
|||
|
|||
return new ResolvedClassInfo(view, hasDevToolsReference, new EquatableList<DiagnosticDescriptor>(diagnostics)); |
|||
}) |
|||
.WithTrackingName(TrackingNames.ResolvedNamesProvider); |
|||
|
|||
context.RegisterSourceOutput(resolvedNames.Combine(options), static (context, pair) => |
|||
{ |
|||
var (info, options) = pair; |
|||
|
|||
foreach (var diagnostic in info!.Diagnostics) |
|||
{ |
|||
context.Report(diagnostic); |
|||
} |
|||
|
|||
if (info.View is { } view && options.AvaloniaNameGeneratorFilterByNamespace.Matches(view.Namespace)) |
|||
{ |
|||
ICodeGenerator codeGenerator = options.AvaloniaNameGeneratorBehavior switch |
|||
{ |
|||
Behavior.OnlyProperties => new OnlyPropertiesCodeGenerator( |
|||
options.AvaloniaNameGeneratorClassFieldModifier), |
|||
Behavior.InitializeComponent => new InitializeComponentCodeGenerator( |
|||
options.AvaloniaNameGeneratorAttachDevTools && info.CanAttachDevTools && view.IsWindow, |
|||
options.AvaloniaNameGeneratorClassFieldModifier), |
|||
_ => throw new ArgumentOutOfRangeException() |
|||
}; |
|||
var fileName = options.AvaloniaNameGeneratorViewFileNamingStrategy switch |
|||
{ |
|||
ViewFileNamingStrategy.ClassName => $"{view.ClassName}.g.cs", |
|||
ViewFileNamingStrategy.NamespaceAndClassName => $"{view.Namespace}.{view.ClassName}.g.cs", |
|||
_ => throw new ArgumentOutOfRangeException( |
|||
nameof(ViewFileNamingStrategy), options.AvaloniaNameGeneratorViewFileNamingStrategy, |
|||
"Unknown naming strategy!") |
|||
}; |
|||
|
|||
var generatedPartialClass = codeGenerator.GenerateCode( |
|||
info.View.ClassName, |
|||
info.View.Namespace, |
|||
info.View.Names); |
|||
|
|||
context.AddSource(fileName, generatedPartialClass); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
internal record XmlClassInfo( |
|||
ResolvedXmlView? XmlView, |
|||
EquatableList<DiagnosticDescriptor> Diagnostics); |
|||
|
|||
internal record ResolvedClassInfo( |
|||
ResolvedView? View, |
|||
bool CanAttachDevTools, |
|||
EquatableList<DiagnosticDescriptor> Diagnostics); |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
namespace Avalonia.Generators.NameGenerator; |
|||
|
|||
internal static class TrackingNames |
|||
{ |
|||
public const string ResolvedNamesProvider = nameof(ResolvedNamesProvider); |
|||
public const string XamlGeneratorOptionsProvider = nameof(XamlGeneratorOptionsProvider); |
|||
public const string InputXamlFilesProvider = nameof(InputXamlFilesProvider); |
|||
public const string ParsedXamlClasses = nameof(ParsedXamlClasses); |
|||
public const string XamlTypeSystem = nameof(XamlTypeSystem); |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Avalonia.Generators.Common; |
|||
using Avalonia.Generators.Common.Domain; |
|||
using Avalonia.Generators.Compiler; |
|||
using Microsoft.CodeAnalysis; |
|||
|
|||
namespace Avalonia.Generators.Tests; |
|||
|
|||
internal static class CompilationUtils |
|||
{ |
|||
internal static IEnumerable<ResolvedName> ResolveNames(this IEnumerable<ResolvedXmlName> names, Compilation compilation, XamlXNameResolver nameResolver) |
|||
{ |
|||
var compiler = MiniCompiler.CreateRoslyn(new RoslynTypeSystem(compilation), MiniCompiler.AvaloniaXmlnsDefinitionAttribute); |
|||
return names |
|||
.Select(xmlName => |
|||
{ |
|||
var clrType = compiler.ResolveXamlType(xmlName.XmlType); |
|||
return (clrType, nameResolver.ResolveName(clrType, xmlName.Name, xmlName.FieldModifier)); |
|||
}) |
|||
.Where(t => t.clrType.IsAvaloniaStyledElement()) |
|||
.Select(t => t.Item2); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue