|
|
|
@ -1,12 +1,14 @@ |
|
|
|
using System; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.Collections.Immutable; |
|
|
|
using System.Linq; |
|
|
|
using System.Xml; |
|
|
|
using System.Xml.Linq; |
|
|
|
using Avalonia.Generators.Common; |
|
|
|
using Avalonia.Generators.Common.Domain; |
|
|
|
using Avalonia.Generators.Compiler; |
|
|
|
using Microsoft.CodeAnalysis; |
|
|
|
using XamlX.Transform; |
|
|
|
using Microsoft.CodeAnalysis.Text; |
|
|
|
using XamlX; |
|
|
|
|
|
|
|
namespace Avalonia.Generators.NameGenerator; |
|
|
|
|
|
|
|
@ -62,39 +64,52 @@ public class AvaloniaNameIncrementalGenerator : IIncrementalGenerator |
|
|
|
.Select(static (file, cancellationToken) => |
|
|
|
{ |
|
|
|
cancellationToken.ThrowIfCancellationRequested(); |
|
|
|
var text = file.GetText(cancellationToken); |
|
|
|
var diagnostics = new List<DiagnosticDescriptor>(); |
|
|
|
if (text is not null) |
|
|
|
var xaml = file.GetText(cancellationToken)?.ToString(); |
|
|
|
if (xaml is 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 null; |
|
|
|
} |
|
|
|
|
|
|
|
return new XmlClassInfo( |
|
|
|
new ResolvedXmlView(view, xmlNames), |
|
|
|
new EquatableList<DiagnosticDescriptor>(diagnostics)); |
|
|
|
} |
|
|
|
catch (OperationCanceledException) |
|
|
|
{ |
|
|
|
throw; |
|
|
|
} |
|
|
|
catch (Exception ex) |
|
|
|
ResolvedXmlView? resolvedXmlView; |
|
|
|
DiagnosticFactory? diagnosticFactory = null; |
|
|
|
var location = new FileLinePositionSpan(file.Path, default); |
|
|
|
try |
|
|
|
{ |
|
|
|
var viewResolver = new XamlXViewResolver(s_noopCompiler); |
|
|
|
var view = viewResolver.ResolveView(xaml, cancellationToken); |
|
|
|
if (view is null) |
|
|
|
{ |
|
|
|
diagnostics.Add(GeneratorExtensions.NameGeneratorUnhandledError(ex)); |
|
|
|
return new XmlClassInfo(null, new EquatableList<DiagnosticDescriptor>(diagnostics)); |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
var xmlNames = EquatableList<ResolvedXmlName>.Empty; |
|
|
|
var nameResolver = new XamlXNameResolver(); |
|
|
|
xmlNames = nameResolver.ResolveXmlNames(view.Xaml, cancellationToken); |
|
|
|
|
|
|
|
resolvedXmlView = new ResolvedXmlView(view, xmlNames); |
|
|
|
} |
|
|
|
catch (OperationCanceledException) |
|
|
|
{ |
|
|
|
throw; |
|
|
|
} |
|
|
|
catch (XmlException ex) |
|
|
|
{ |
|
|
|
diagnosticFactory = new(NameGeneratorDiagnostics.ParseFailed, new(file.Path, GetLinePositionSpan(ex)), new([ex.Message])); |
|
|
|
|
|
|
|
resolvedXmlView = ex is XamlParseException ? TryExtractTypeFromXml(xaml) : null; |
|
|
|
} |
|
|
|
catch (XamlTypeSystemException ex) |
|
|
|
{ |
|
|
|
diagnosticFactory = new(NameGeneratorDiagnostics.ParseFailed, location, new([ex.Message])); |
|
|
|
resolvedXmlView = TryExtractTypeFromXml(xaml); |
|
|
|
} |
|
|
|
catch (Exception ex) |
|
|
|
{ |
|
|
|
diagnosticFactory = GetInternalErrorDiagnostic(location, ex); |
|
|
|
resolvedXmlView = null; |
|
|
|
} |
|
|
|
|
|
|
|
return null; |
|
|
|
return new XmlClassInfo(file.Path, resolvedXmlView, diagnosticFactory); |
|
|
|
}) |
|
|
|
.Where(request => request is not null) |
|
|
|
.WithTrackingName(TrackingNames.ParsedXamlClasses); |
|
|
|
@ -119,15 +134,20 @@ public class AvaloniaNameIncrementalGenerator : IIncrementalGenerator |
|
|
|
var hasDevToolsReference = compiler.TypeSystem.FindAssembly("Avalonia.Diagnostics") is not null; |
|
|
|
var nameResolver = new XamlXNameResolver(); |
|
|
|
|
|
|
|
var diagnostics = new List<DiagnosticDescriptor>(classInfo!.Diagnostics); |
|
|
|
var diagnostics = new List<DiagnosticFactory>(2); |
|
|
|
if (classInfo?.Diagnostic != null) |
|
|
|
{ |
|
|
|
diagnostics.Add(classInfo.Diagnostic); |
|
|
|
} |
|
|
|
|
|
|
|
ResolvedView? view = null; |
|
|
|
if (classInfo.XmlView is { } xmlView) |
|
|
|
if (classInfo?.XmlView is { } xmlView) |
|
|
|
{ |
|
|
|
var type = compiler.TypeSystem.FindType(xmlView.FullName); |
|
|
|
|
|
|
|
if (type is null) |
|
|
|
{ |
|
|
|
diagnostics.Add(GeneratorExtensions.NameGeneratorInvalidType(xmlView.FullName)); |
|
|
|
diagnostics.Add(new(NameGeneratorDiagnostics.InvalidType, new(classInfo.FilePath, default), new([xmlView.FullName]))); |
|
|
|
} |
|
|
|
else if (type.IsAvaloniaStyledElement()) |
|
|
|
{ |
|
|
|
@ -147,17 +167,22 @@ public class AvaloniaNameIncrementalGenerator : IIncrementalGenerator |
|
|
|
resolvedNames.Add(nameResolver |
|
|
|
.ResolveName(clrType, xmlName.Name, xmlName.FieldModifier)); |
|
|
|
} |
|
|
|
catch (XmlException ex) |
|
|
|
{ |
|
|
|
diagnostics.Add(new(NameGeneratorDiagnostics.NamedElementFailed, |
|
|
|
new(classInfo.FilePath, GetLinePositionSpan(ex)), new([xmlName.Name, ex.Message]))); |
|
|
|
} |
|
|
|
catch (Exception ex) |
|
|
|
{ |
|
|
|
diagnostics.Add(GeneratorExtensions.NameGeneratorUnhandledError(ex)); |
|
|
|
diagnostics.Add(GetInternalErrorDiagnostic(new(classInfo.FilePath, default), ex)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
view = new ResolvedView(xmlView, type.IsAvaloniaWindow(), new EquatableList<ResolvedName>(resolvedNames)); |
|
|
|
view = new ResolvedView(xmlView, type.IsAvaloniaWindow(), new(resolvedNames)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return new ResolvedClassInfo(view, hasDevToolsReference, new EquatableList<DiagnosticDescriptor>(diagnostics)); |
|
|
|
return new ResolvedClassInfo(view, hasDevToolsReference, new(diagnostics)); |
|
|
|
}) |
|
|
|
.WithTrackingName(TrackingNames.ResolvedNamesProvider); |
|
|
|
|
|
|
|
@ -165,9 +190,9 @@ public class AvaloniaNameIncrementalGenerator : IIncrementalGenerator |
|
|
|
{ |
|
|
|
var (info, options) = pair; |
|
|
|
|
|
|
|
foreach (var diagnostic in info!.Diagnostics) |
|
|
|
foreach (var diagnostic in info.Diagnostics) |
|
|
|
{ |
|
|
|
context.Report(diagnostic); |
|
|
|
context.ReportDiagnostic(diagnostic.Create()); |
|
|
|
} |
|
|
|
|
|
|
|
if (info.View is { } view && options.AvaloniaNameGeneratorFilterByNamespace.Matches(view.Namespace)) |
|
|
|
@ -200,12 +225,53 @@ public class AvaloniaNameIncrementalGenerator : IIncrementalGenerator |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
private static DiagnosticFactory GetInternalErrorDiagnostic(FileLinePositionSpan location, Exception ex) => |
|
|
|
new(NameGeneratorDiagnostics.InternalError, location, new([ex.ToString().Replace('\n', '*').Replace('\r', '*')])); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Fallback in case XAML parsing fails. Extracts just the class name and namespace of the root element.
|
|
|
|
/// </summary>
|
|
|
|
private static ResolvedXmlView? TryExtractTypeFromXml(string xaml) |
|
|
|
{ |
|
|
|
try |
|
|
|
{ |
|
|
|
var document = XDocument.Parse(xaml); |
|
|
|
var classValue = document.Root.Attribute(XName.Get("Class", XamlNamespaces.Xaml2006))?.Value; |
|
|
|
if (classValue?.LastIndexOf('.') is { } lastDotIndex && lastDotIndex != -1) |
|
|
|
{ |
|
|
|
return new(classValue.Substring(lastDotIndex + 1), classValue.Substring(0, lastDotIndex), EquatableList<ResolvedXmlName>.Empty); |
|
|
|
} |
|
|
|
} |
|
|
|
catch |
|
|
|
{ |
|
|
|
// ignore
|
|
|
|
} |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
private static LinePositionSpan GetLinePositionSpan(XmlException ex) |
|
|
|
{ |
|
|
|
var position = new LinePosition(Math.Max(0, ex.LineNumber - 1), Math.Max(0, ex.LinePosition - 1)); |
|
|
|
return new(position, position); |
|
|
|
} |
|
|
|
|
|
|
|
internal record XmlClassInfo( |
|
|
|
string FilePath, |
|
|
|
ResolvedXmlView? XmlView, |
|
|
|
EquatableList<DiagnosticDescriptor> Diagnostics); |
|
|
|
DiagnosticFactory? Diagnostic); |
|
|
|
|
|
|
|
internal record ResolvedClassInfo( |
|
|
|
ResolvedView? View, |
|
|
|
bool CanAttachDevTools, |
|
|
|
EquatableList<DiagnosticDescriptor> Diagnostics); |
|
|
|
EquatableList<DiagnosticFactory> Diagnostics); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Avoid holding references to <see cref="Diagnostic"/> because it can hold references to <see cref="ISymbol"/>, <see cref="SyntaxTree"/>, etc.
|
|
|
|
/// </summary>
|
|
|
|
internal record DiagnosticFactory(DiagnosticDescriptor Descriptor, FileLinePositionSpan LinePosition, EquatableList<string> FormatArguments) |
|
|
|
{ |
|
|
|
public Diagnostic Create() => Diagnostic.Create(Descriptor, |
|
|
|
Location.Create(LinePosition.Path, default, new(LinePosition.StartLinePosition, LinePosition.EndLinePosition)), |
|
|
|
messageArgs: [.. FormatArguments]); |
|
|
|
} |
|
|
|
} |
|
|
|
|