48 changed files with 1375 additions and 622 deletions
@ -0,0 +1,78 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Xml; |
|||
using XamlX; |
|||
using XamlX.Ast; |
|||
using XamlX.Transform; |
|||
|
|||
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers; |
|||
|
|||
internal class AstGroupTransformationContext : AstTransformationContext |
|||
{ |
|||
public AstGroupTransformationContext(IReadOnlyCollection<IXamlDocumentResource> documents, TransformerConfiguration configuration, bool strictMode = true) |
|||
: base(configuration, null, strictMode) |
|||
{ |
|||
Documents = documents; |
|||
} |
|||
|
|||
public IXamlDocumentResource CurrentDocument { get; set; } |
|||
|
|||
public IReadOnlyCollection<IXamlDocumentResource> Documents { get; } |
|||
|
|||
public new IXamlAstNode ParseError(string message, IXamlAstNode node) => |
|||
Error(node, new XamlDocumentParseException(CurrentDocument?.FileSource?.FilePath, message, node)); |
|||
|
|||
public new IXamlAstNode ParseError(string message, IXamlAstNode offender, IXamlAstNode ret) => |
|||
Error(ret, new XamlDocumentParseException(CurrentDocument?.FileSource?.FilePath, message, offender)); |
|||
|
|||
class Visitor : IXamlAstVisitor |
|||
{ |
|||
private readonly AstGroupTransformationContext _context; |
|||
private readonly IXamlAstGroupTransformer _transformer; |
|||
|
|||
public Visitor(AstGroupTransformationContext context, IXamlAstGroupTransformer transformer) |
|||
{ |
|||
_context = context; |
|||
_transformer = transformer; |
|||
} |
|||
|
|||
public IXamlAstNode Visit(IXamlAstNode node) |
|||
{ |
|||
#if Xaml_DEBUG
|
|||
return _transformer.Transform(_context, node); |
|||
#else
|
|||
try |
|||
{ |
|||
return _transformer.Transform(_context, node); |
|||
} |
|||
catch (Exception e) when (!(e is XmlException)) |
|||
{ |
|||
throw new XamlDocumentParseException( |
|||
_context.CurrentDocument?.FileSource?.FilePath, |
|||
"Internal compiler error while transforming node " + node + ":\n" + e, |
|||
node); |
|||
} |
|||
#endif
|
|||
} |
|||
|
|||
public void Push(IXamlAstNode node) => _context.PushParent(node); |
|||
|
|||
public void Pop() => _context.PopParent(); |
|||
} |
|||
|
|||
public IXamlAstNode Visit(IXamlAstNode root, IXamlAstGroupTransformer transformer) |
|||
{ |
|||
root = root.Visit(new Visitor(this, transformer)); |
|||
return root; |
|||
} |
|||
|
|||
public void VisitChildren(IXamlAstNode root, IXamlAstGroupTransformer transformer) |
|||
{ |
|||
root.VisitChildren(new Visitor(this, transformer)); |
|||
} |
|||
} |
|||
|
|||
internal interface IXamlAstGroupTransformer |
|||
{ |
|||
IXamlAstNode Transform(AstGroupTransformationContext context, IXamlAstNode node); |
|||
} |
|||
@ -0,0 +1,173 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; |
|||
using Avalonia.Platform; |
|||
using XamlX; |
|||
using XamlX.Ast; |
|||
using XamlX.Emit; |
|||
using XamlX.IL; |
|||
using XamlX.Transform; |
|||
using XamlX.Transform.Transformers; |
|||
using XamlX.TypeSystem; |
|||
|
|||
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers; |
|||
|
|||
internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer |
|||
{ |
|||
public IXamlAstNode Transform(AstGroupTransformationContext context, IXamlAstNode node) |
|||
{ |
|||
if (node is not XamlValueWithManipulationNode valueNode |
|||
|| valueNode.Value is not XamlAstNewClrObjectNode objectNode |
|||
|| (objectNode.Type.GetClrType() != context.GetAvaloniaTypes().StyleInclude |
|||
&& objectNode.Type.GetClrType() != context.GetAvaloniaTypes().ResourceInclude)) |
|||
{ |
|||
return node; |
|||
} |
|||
|
|||
var nodeTypeName = objectNode.Type.GetClrType().Name; |
|||
var expectedLoadedType = objectNode.Type.GetClrType().GetAllProperties() |
|||
.FirstOrDefault(p => p.Name == "Loaded")?.PropertyType; |
|||
if (expectedLoadedType is null) |
|||
{ |
|||
throw new InvalidOperationException($"\"{nodeTypeName}\".Loaded property is expected to be defined"); |
|||
} |
|||
|
|||
if (valueNode.Manipulation is not XamlObjectInitializationNode |
|||
{ |
|||
Manipulation: XamlPropertyAssignmentNode { Property: { Name: "Source" } } sourceProperty |
|||
}) |
|||
{ |
|||
return context.ParseError($"Source property must be set on the \"{nodeTypeName}\" node.", node); |
|||
} |
|||
|
|||
// We expect that AvaloniaXamlIlLanguageParseIntrinsics has already parsed the Uri and created node like: `new Uri(assetPath, uriKind)`.
|
|||
if (sourceProperty.Values.OfType<XamlAstNewClrObjectNode>().FirstOrDefault() is not { } sourceUriNode |
|||
|| sourceUriNode.Type.GetClrType() != context.GetAvaloniaTypes().Uri |
|||
|| sourceUriNode.Arguments.FirstOrDefault() is not XamlConstantNode { Constant: string originalAssetPath } |
|||
|| sourceUriNode.Arguments.Skip(1).FirstOrDefault() is not XamlConstantNode { Constant: int uriKind }) |
|||
{ |
|||
// TODO: make it a compiler warning
|
|||
// Source value can be set with markup extension instead of the Uri object node, we don't support it here yet.
|
|||
return node; |
|||
} |
|||
|
|||
var uriPath = new Uri(originalAssetPath, (UriKind)uriKind); |
|||
if (!uriPath.IsAbsoluteUri) |
|||
{ |
|||
var baseUrl = context.CurrentDocument.Uri ?? throw new InvalidOperationException("CurrentDocument URI is null."); |
|||
uriPath = new Uri(new Uri(baseUrl, UriKind.Absolute), uriPath); |
|||
} |
|||
else if (!uriPath.Scheme.Equals("avares", StringComparison.CurrentCultureIgnoreCase)) |
|||
{ |
|||
return context.ParseError( |
|||
$"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri.", |
|||
sourceUriNode, node); |
|||
} |
|||
|
|||
var assetPathUri = Uri.UnescapeDataString(uriPath.AbsoluteUri); |
|||
var assetPath = assetPathUri.Replace("avares://", ""); |
|||
var assemblyNameSeparator = assetPath.IndexOf('/'); |
|||
var assembly = assetPath.Substring(0, assemblyNameSeparator); |
|||
var fullTypeName = Path.GetFileNameWithoutExtension(assetPath.Replace('/', '.')); |
|||
|
|||
// Search file in the current assembly among other XAML resources.
|
|||
if (context.Documents.FirstOrDefault(d => string.Equals(d.Uri, assetPathUri, StringComparison.InvariantCultureIgnoreCase)) is {} targetDocument) |
|||
{ |
|||
if (targetDocument.BuildMethod is not null) |
|||
{ |
|||
return FromMethod(context, targetDocument.BuildMethod, sourceUriNode, expectedLoadedType, node, assetPathUri, assembly); |
|||
} |
|||
|
|||
if (targetDocument.ClassType is not null) |
|||
{ |
|||
return FromType(context, targetDocument.ClassType, sourceUriNode, expectedLoadedType, node, assetPathUri, assembly); |
|||
} |
|||
|
|||
return context.ParseError( |
|||
$"Unable to resolve XAML resource \"{assetPathUri}\" in the current assembly.", |
|||
sourceUriNode, node); |
|||
} |
|||
|
|||
// If resource wasn't found in the current assembly, search in the others.
|
|||
if (context.Configuration.TypeSystem.FindAssembly(assembly) is not { } assetAssembly) |
|||
{ |
|||
return context.ParseError($"Assembly \"{assembly}\" was not found from the \"{assetPathUri}\" source.", sourceUriNode, node); |
|||
} |
|||
|
|||
var avaResType = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources"); |
|||
if (avaResType is null) |
|||
{ |
|||
return context.ParseError( |
|||
$"Unable to resolve \"!AvaloniaResources\" type on \"{assembly}\" assembly.", sourceUriNode, node); |
|||
} |
|||
|
|||
var relativeName = "Build:" + assetPath.Substring(assemblyNameSeparator); |
|||
var buildMethod = avaResType.FindMethod(m => m.Name == relativeName); |
|||
if (buildMethod is not null) |
|||
{ |
|||
return FromMethod(context, buildMethod, sourceUriNode, expectedLoadedType, node, assetPathUri, assembly); |
|||
} |
|||
else if (assetAssembly.FindType(fullTypeName) is { } type) |
|||
{ |
|||
return FromType(context, type, sourceUriNode, expectedLoadedType, node, assetPathUri, assembly); |
|||
} |
|||
|
|||
return context.ParseError( |
|||
$"Unable to resolve XAML resource \"{assetPathUri}\" in the \"{assembly}\" assembly.", |
|||
sourceUriNode, node); |
|||
} |
|||
|
|||
private static IXamlAstNode FromType(AstTransformationContext context, IXamlType type, IXamlAstNode li, |
|||
IXamlType expectedLoadedType, IXamlAstNode fallbackNode, string assetPathUri, string assembly) |
|||
{ |
|||
if (!expectedLoadedType.IsAssignableFrom(type)) |
|||
{ |
|||
return context.ParseError( |
|||
$"Resource \"{assetPathUri}\" is defined as \"{type}\" type in the \"{assembly}\" assembly, but expected \"{expectedLoadedType}\".", |
|||
li, fallbackNode); |
|||
} |
|||
|
|||
IXamlAstNode newObjNode = new XamlAstObjectNode(li, new XamlAstClrTypeReference(li, type, false)); |
|||
newObjNode = new AvaloniaXamlIlConstructorServiceProviderTransformer().Transform(context, newObjNode); |
|||
newObjNode = new ConstructableObjectTransformer().Transform(context, newObjNode); |
|||
return new NewObjectTransformer().Transform(context, newObjNode); |
|||
} |
|||
|
|||
private static IXamlAstNode FromMethod(AstTransformationContext context, IXamlMethod method, IXamlAstNode li, |
|||
IXamlType expectedLoadedType, IXamlAstNode fallbackNode, string assetPathUri, string assembly) |
|||
{ |
|||
if (!expectedLoadedType.IsAssignableFrom(method.ReturnType)) |
|||
{ |
|||
return context.ParseError( |
|||
$"Resource \"{assetPathUri}\" is defined as \"{method.ReturnType}\" type in the \"{assembly}\" assembly, but expected \"{expectedLoadedType}\".", |
|||
li, fallbackNode); |
|||
} |
|||
|
|||
var sp = context.Configuration.TypeMappings.ServiceProvider; |
|||
return new XamlStaticOrTargetedReturnMethodCallNode(li, method, |
|||
new[] { new NewServiceProviderNode(sp, li) }); |
|||
} |
|||
|
|||
internal class NewServiceProviderNode : XamlAstNode, IXamlAstValueNode,IXamlAstNodeNeedsParentStack, |
|||
IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult> |
|||
{ |
|||
public NewServiceProviderNode(IXamlType type, IXamlLineInfo lineInfo) : base(lineInfo) |
|||
{ |
|||
Type = new XamlAstClrTypeReference(lineInfo, type, false); |
|||
} |
|||
|
|||
public IXamlAstTypeReference Type { get; } |
|||
public bool NeedsParentStack => true; |
|||
public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen) |
|||
{ |
|||
var method = context.GetAvaloniaTypes().RuntimeHelpers |
|||
.FindMethod(m => m.Name == "CreateRootServiceProviderV2"); |
|||
codeGen.EmitCall(method); |
|||
|
|||
return XamlILNodeEmitResult.Type(0, Type.GetClrType()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System; |
|||
using XamlX.Ast; |
|||
using XamlX.TypeSystem; |
|||
|
|||
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions; |
|||
|
|||
#nullable enable |
|||
|
|||
internal interface IXamlDocumentResource |
|||
{ |
|||
IXamlMethod? BuildMethod { get; } |
|||
IXamlType? ClassType { get; } |
|||
string? Uri { get; } |
|||
IXamlMethod PopulateMethod { get; } |
|||
IFileSource? FileSource { get; } |
|||
XamlDocument XamlDocument { get; } |
|||
} |
|||
@ -1,93 +0,0 @@ |
|||
using System.Linq; |
|||
using XamlX; |
|||
using XamlX.Ast; |
|||
using XamlX.Emit; |
|||
using XamlX.IL; |
|||
using XamlX.Transform; |
|||
using XamlX.TypeSystem; |
|||
|
|||
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; |
|||
|
|||
internal class AvaloniaXamlIlAssetIncludeTransformer : IXamlAstTransformer |
|||
{ |
|||
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) |
|||
{ |
|||
if (node is not XamlAstObjectNode objectNode |
|||
|| (objectNode.Type.GetClrType() != context.GetAvaloniaTypes().StyleInclude |
|||
&& objectNode.Type.GetClrType() != context.GetAvaloniaTypes().ResourceInclude)) |
|||
{ |
|||
return node; |
|||
} |
|||
|
|||
var nodeTypeName = objectNode.Type.GetClrType().Name; |
|||
|
|||
var sourceProperty = objectNode.Children.OfType<XamlAstXamlPropertyValueNode>().FirstOrDefault(n => n.Property.GetClrProperty().Name == "Source"); |
|||
var directives = objectNode.Children.OfType<XamlAstXmlDirective>().ToList(); |
|||
if (sourceProperty is null |
|||
|| objectNode.Children.Count != (directives.Count + 1)) |
|||
{ |
|||
throw new XamlParseException($"Unexpected property on the {nodeTypeName} node", node); |
|||
} |
|||
|
|||
if (sourceProperty.Values.OfType<XamlAstTextNode>().FirstOrDefault() is not { } sourceTextNode) |
|||
{ |
|||
// TODO: make it a compiler warning
|
|||
// Source value can be set with markup extension instead of a text node, we don't support it here yet.
|
|||
return node; |
|||
} |
|||
|
|||
var originalAssetPath = sourceTextNode.Text; |
|||
if (!(originalAssetPath.StartsWith("avares://") || originalAssetPath.StartsWith("/"))) |
|||
{ |
|||
return node; |
|||
} |
|||
|
|||
var runtimeHelpers = context.GetAvaloniaTypes().RuntimeHelpers; |
|||
var markerMethodName = "Resolve" + nodeTypeName; |
|||
var markerMethod = runtimeHelpers.FindMethod(m => m.Name == markerMethodName && m.Parameters.Count == 3); |
|||
if (markerMethod is null) |
|||
{ |
|||
throw new XamlParseException($"Marker method \"{markerMethodName}\" was not found for the \"{nodeTypeName}\" node", node); |
|||
} |
|||
|
|||
return new XamlValueWithManipulationNode( |
|||
node, |
|||
new AssetIncludeMethodNode(node, markerMethod, originalAssetPath), |
|||
new XamlManipulationGroupNode(node, directives)); |
|||
} |
|||
|
|||
private class AssetIncludeMethodNode : XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode |
|||
{ |
|||
private readonly IXamlMethod _method; |
|||
private readonly string _originalAssetPath; |
|||
|
|||
public AssetIncludeMethodNode( |
|||
IXamlAstNode original, IXamlMethod method, string originalAssetPath) |
|||
: base(original) |
|||
{ |
|||
_method = method; |
|||
_originalAssetPath = originalAssetPath; |
|||
} |
|||
|
|||
public IXamlAstTypeReference Type => new XamlAstClrTypeReference(this, _method.ReturnType, false); |
|||
|
|||
public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen) |
|||
{ |
|||
var absoluteSource = _originalAssetPath; |
|||
if (absoluteSource.StartsWith("/")) |
|||
{ |
|||
// Avoid Uri class here to avoid potential problems with escaping.
|
|||
// Keeping string as close to the original as possible.
|
|||
var absoluteBaseUrl = context.RuntimeContext.BaseUrl; |
|||
absoluteSource = absoluteBaseUrl.Substring(0, absoluteBaseUrl.LastIndexOf('/')) + absoluteSource; |
|||
} |
|||
|
|||
codeGen.Ldstr(absoluteSource); |
|||
codeGen.Ldc_I4(Line); |
|||
codeGen.Ldc_I4(Position); |
|||
codeGen.EmitCall(_method); |
|||
|
|||
return XamlILNodeEmitResult.Type(0, _method.ReturnType); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
using XamlX; |
|||
using XamlX.Ast; |
|||
|
|||
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions; |
|||
|
|||
internal class XamlDocumentParseException : XamlParseException |
|||
{ |
|||
public string FilePath { get; } |
|||
|
|||
public XamlDocumentParseException(string path, XamlParseException parseException) |
|||
: base(parseException.Message, parseException.LineNumber, parseException.LinePosition) |
|||
{ |
|||
FilePath = path; |
|||
} |
|||
|
|||
public XamlDocumentParseException(string path, string message, IXamlLineInfo lineInfo) |
|||
: base(message, lineInfo.Line, lineInfo.Position) |
|||
{ |
|||
FilePath = path; |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
using System; |
|||
using XamlX.Ast; |
|||
using XamlX.IL; |
|||
using XamlX.TypeSystem; |
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions; |
|||
|
|||
internal class XamlDocumentResource : IXamlDocumentResource |
|||
{ |
|||
public XamlDocumentResource( |
|||
XamlDocument xamlDocument, |
|||
string? uri, |
|||
IFileSource? fileSource, |
|||
IXamlType? classType, |
|||
IXamlTypeBuilder<IXamlILEmitter> typeBuilder, |
|||
IXamlMethodBuilder<IXamlILEmitter> populateMethod, |
|||
IXamlMethodBuilder<IXamlILEmitter>? buildMethod) |
|||
{ |
|||
XamlDocument = xamlDocument; |
|||
Uri = uri; |
|||
FileSource = fileSource; |
|||
ClassType = classType; |
|||
TypeBuilder = typeBuilder; |
|||
PopulateMethod = populateMethod; |
|||
BuildMethod = buildMethod; |
|||
} |
|||
|
|||
public XamlDocument XamlDocument { get; } |
|||
public string? Uri { get; } |
|||
public IFileSource? FileSource { get; } |
|||
|
|||
public IXamlType? ClassType { get; } |
|||
public IXamlTypeBuilder<IXamlILEmitter> TypeBuilder { get; } |
|||
public IXamlMethodBuilder<IXamlILEmitter> PopulateMethod { get; } |
|||
public IXamlMethodBuilder<IXamlILEmitter>? BuildMethod { get; } |
|||
|
|||
IXamlMethod? IXamlDocumentResource.BuildMethod => BuildMethod; |
|||
IXamlMethod IXamlDocumentResource.PopulateMethod => PopulateMethod; |
|||
} |
|||
@ -1 +1 @@ |
|||
Subproject commit cd3682c61577a3518de765f7938295d98ff9808c |
|||
Subproject commit 491de981dd4433ee58bc9540e2cd4a5d168f8168 |
|||
@ -0,0 +1,70 @@ |
|||
#nullable enable |
|||
using System; |
|||
using System.IO; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Markup.Xaml; |
|||
|
|||
public class RuntimeXamlLoaderDocument |
|||
{ |
|||
public RuntimeXamlLoaderDocument(string xaml) |
|||
{ |
|||
XamlStream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)); |
|||
} |
|||
|
|||
public RuntimeXamlLoaderDocument(Uri? baseUri, string xaml) |
|||
: this(xaml) |
|||
{ |
|||
BaseUri = baseUri; |
|||
} |
|||
|
|||
public RuntimeXamlLoaderDocument(object? rootInstance, string xaml) |
|||
: this(xaml) |
|||
{ |
|||
RootInstance = rootInstance; |
|||
} |
|||
|
|||
public RuntimeXamlLoaderDocument(Uri? baseUri, object? rootInstance, string xaml) |
|||
: this(baseUri, xaml) |
|||
{ |
|||
RootInstance = rootInstance; |
|||
} |
|||
|
|||
public RuntimeXamlLoaderDocument(Stream stream) |
|||
{ |
|||
XamlStream = stream; |
|||
} |
|||
|
|||
public RuntimeXamlLoaderDocument(Uri? baseUri, Stream stream) |
|||
: this(stream) |
|||
{ |
|||
BaseUri = baseUri; |
|||
} |
|||
|
|||
public RuntimeXamlLoaderDocument(object? rootInstance, Stream stream) |
|||
: this(stream) |
|||
{ |
|||
RootInstance = rootInstance; |
|||
} |
|||
|
|||
public RuntimeXamlLoaderDocument(Uri? baseUri, object? rootInstance, Stream stream) |
|||
: this(baseUri, stream) |
|||
{ |
|||
RootInstance = rootInstance; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The URI of the XAML being loaded.
|
|||
/// </summary>
|
|||
public Uri? BaseUri { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The optional instance into which the XAML should be loaded.
|
|||
/// </summary>
|
|||
public object? RootInstance { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The stream containing the XAML.
|
|||
/// </summary>
|
|||
public Stream XamlStream { get; set; } |
|||
} |
|||
@ -1,51 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.UnitTests; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Markup.Xaml.UnitTests |
|||
{ |
|||
public class StyleIncludeTests : XamlTestBase |
|||
{ |
|||
[Fact] |
|||
public void Missing_ResourceKey_In_StyleInclude_Does_Not_Cause_StackOverflow() |
|||
{ |
|||
var styleXaml = @"
|
|||
<Style xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Style.Resources> |
|||
<StaticResource x:Key='brush' ResourceKey='missing' /> |
|||
</Style.Resources> |
|||
</Style>";
|
|||
|
|||
using (StartWithResources(("test:style.xaml", styleXaml))) |
|||
{ |
|||
var xaml = @"
|
|||
<Application xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Application.Styles> |
|||
<StyleInclude Source='test:style.xaml'/> |
|||
</Application.Styles> |
|||
</Application>";
|
|||
|
|||
var app = Application.Current; |
|||
|
|||
try |
|||
{ |
|||
AvaloniaRuntimeXamlLoader.Load(xaml, null, app); |
|||
} |
|||
catch (KeyNotFoundException) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
|
|||
private IDisposable StartWithResources(params (string, string)[] assets) |
|||
{ |
|||
var assetLoader = new MockAssetLoader(assets); |
|||
var services = new TestServices(assetLoader: assetLoader); |
|||
return UnitTestApplication.Start(services); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,268 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Styling; |
|||
using Avalonia.Themes.Simple; |
|||
using Avalonia.UnitTests; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Markup.Xaml.UnitTests.Xaml; |
|||
|
|||
public class StyleIncludeTests |
|||
{ |
|||
[Fact] |
|||
public void StyleInclude_Is_Built() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.StyledWindow |
|||
.With(theme: () => new Styles()))) |
|||
{ |
|||
var xaml = @"
|
|||
<ContentControl xmlns='https://github.com/avaloniaui'>
|
|||
<ContentControl.Styles> |
|||
<StyleInclude Source='avares://Avalonia.Markup.Xaml.UnitTests/Xaml/Style1.xaml'/>
|
|||
</ContentControl.Styles> |
|||
</ContentControl>";
|
|||
|
|||
var window = AvaloniaRuntimeXamlLoader.Parse<ContentControl>(xaml); |
|||
|
|||
Assert.IsType<Style>(window.Styles[0]); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void StyleInclude_Is_Built_Resources() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.StyledWindow |
|||
.With(theme: () => new Styles()))) |
|||
{ |
|||
var xaml = @"
|
|||
<ContentControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<ContentControl.Resources> |
|||
<StyleInclude x:Key='Include' Source='avares://Avalonia.Markup.Xaml.UnitTests/Xaml/Style1.xaml'/>
|
|||
</ContentControl.Resources> |
|||
</ContentControl>";
|
|||
|
|||
var contentControl = AvaloniaRuntimeXamlLoader.Parse<ContentControl>(xaml); |
|||
|
|||
Assert.IsType<Style>(contentControl.Resources["Include"]); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void StyleInclude_Is_Resolved_With_Two_Files() |
|||
{ |
|||
var documents = new[] |
|||
{ |
|||
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Style.xaml"), @"
|
|||
<Style xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Style.Resources> |
|||
<Color x:Key='Red'>Red</Color> |
|||
</Style.Resources> |
|||
</Style>"),
|
|||
new RuntimeXamlLoaderDocument(@"
|
|||
<ContentControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<ContentControl.Resources> |
|||
<StyleInclude x:Key='Include' Source='avares://Tests/Style.xaml'/>
|
|||
</ContentControl.Resources> |
|||
</ContentControl>")
|
|||
}; |
|||
|
|||
var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents); |
|||
var style = Assert.IsType<Style>(objects[0]); |
|||
var contentControl = Assert.IsType<ContentControl>(objects[1]); |
|||
|
|||
Assert.IsType<Style>(contentControl.Resources["Include"]); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Relative_Back_StyleInclude_Is_Resolved_With_Two_Files() |
|||
{ |
|||
var documents = new[] |
|||
{ |
|||
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Subfolder/Style.xaml"), @"
|
|||
<Style xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Style.Resources> |
|||
<Color x:Key='Red'>Red</Color> |
|||
</Style.Resources> |
|||
</Style>"),
|
|||
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Subfolder/Folder/Root.xaml"), @"
|
|||
<ContentControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<ContentControl.Resources> |
|||
<StyleInclude x:Key='Include' Source='../Style.xaml'/> |
|||
</ContentControl.Resources> |
|||
</ContentControl>")
|
|||
}; |
|||
|
|||
var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents); |
|||
var style = Assert.IsType<Style>(objects[0]); |
|||
var contentControl = Assert.IsType<ContentControl>(objects[1]); |
|||
|
|||
Assert.IsType<Style>(contentControl.Resources["Include"]); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Relative_Root_StyleInclude_Is_Resolved_With_Two_Files() |
|||
{ |
|||
var documents = new[] |
|||
{ |
|||
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Style.xaml"), @"
|
|||
<Style xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Style.Resources> |
|||
<Color x:Key='Red'>Red</Color> |
|||
</Style.Resources> |
|||
</Style>"),
|
|||
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Folder/Root.xaml"), @"
|
|||
<ContentControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<ContentControl.Resources> |
|||
<StyleInclude x:Key='Include' Source='/Style.xaml'/> |
|||
</ContentControl.Resources> |
|||
</ContentControl>")
|
|||
}; |
|||
|
|||
var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents); |
|||
var style = Assert.IsType<Style>(objects[0]); |
|||
var contentControl = Assert.IsType<ContentControl>(objects[1]); |
|||
|
|||
Assert.IsType<Style>(contentControl.Resources["Include"]); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Relative_StyleInclude_Is_Resolved_With_Two_Files() |
|||
{ |
|||
var documents = new[] |
|||
{ |
|||
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Folder/Style.xaml"), @"
|
|||
<Style xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Style.Resources> |
|||
<Color x:Key='Red'>Red</Color> |
|||
</Style.Resources> |
|||
</Style>"),
|
|||
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Folder/Root.xaml"), @"
|
|||
<ContentControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<ContentControl.Resources> |
|||
<StyleInclude x:Key='Include' Source='Style.xaml'/> |
|||
</ContentControl.Resources> |
|||
</ContentControl>")
|
|||
}; |
|||
|
|||
var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents); |
|||
var style = Assert.IsType<Style>(objects[0]); |
|||
var contentControl = Assert.IsType<ContentControl>(objects[1]); |
|||
|
|||
Assert.IsType<Style>(contentControl.Resources["Include"]); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Relative_Dot_Syntax__StyleInclude_Is_Resolved_With_Two_Files() |
|||
{ |
|||
var documents = new[] |
|||
{ |
|||
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Folder/Style.xaml"), @"
|
|||
<Style xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Style.Resources> |
|||
<Color x:Key='Red'>Red</Color> |
|||
</Style.Resources> |
|||
</Style>"),
|
|||
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Folder/Root.xaml"), @"
|
|||
<ContentControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<ContentControl.Resources> |
|||
<StyleInclude x:Key='Include' Source='./Style.xaml'/> |
|||
</ContentControl.Resources> |
|||
</ContentControl>")
|
|||
}; |
|||
|
|||
var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents); |
|||
var style = Assert.IsType<Style>(objects[0]); |
|||
var contentControl = Assert.IsType<ContentControl>(objects[1]); |
|||
|
|||
Assert.IsType<Style>(contentControl.Resources["Include"]); |
|||
} |
|||
|
|||
[Fact] |
|||
public void NonLatin_StyleInclude_Is_Resolved_With_Two_Files() |
|||
{ |
|||
var documents = new[] |
|||
{ |
|||
new RuntimeXamlLoaderDocument(new Uri("avares://アセンブリ/スタイル.xaml"), @"
|
|||
<Style xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Style.Resources> |
|||
<Color x:Key='Red'>Red</Color> |
|||
</Style.Resources> |
|||
</Style>"),
|
|||
new RuntimeXamlLoaderDocument(@"
|
|||
<ContentControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<ContentControl.Resources> |
|||
<StyleInclude x:Key='Include' Source='avares://アセンブリ/スタイル.xaml'/>
|
|||
</ContentControl.Resources> |
|||
</ContentControl>")
|
|||
}; |
|||
|
|||
var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents); |
|||
var style = Assert.IsType<Style>(objects[0]); |
|||
var contentControl = Assert.IsType<ContentControl>(objects[1]); |
|||
|
|||
Assert.IsType<Style>(contentControl.Resources["Include"]); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Missing_ResourceKey_In_StyleInclude_Does_Not_Cause_StackOverflow() |
|||
{ |
|||
var documents = new[] |
|||
{ |
|||
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Style.xaml"), @"
|
|||
<Style xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Style.Resources> |
|||
<StaticResource x:Key='brush' ResourceKey='missing' /> |
|||
</Style.Resources> |
|||
</Style>"),
|
|||
new RuntimeXamlLoaderDocument(@"
|
|||
<ContentControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<ContentControl.Styles> |
|||
<StyleInclude Source='avares://Tests/Style.xaml'/>
|
|||
</ContentControl.Styles> |
|||
</ContentControl>")
|
|||
}; |
|||
|
|||
|
|||
try |
|||
{ |
|||
_ = AvaloniaRuntimeXamlLoader.LoadGroup(documents); |
|||
} |
|||
catch (KeyNotFoundException) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void StyleInclude_Should_Be_Replaced_With_Direct_Call() |
|||
{ |
|||
var control = (ContentControl)AvaloniaRuntimeXamlLoader.Load(@"
|
|||
<ContentControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
|
|||
xmlns:themes='clr-namespace:Avalonia.Themes.Simple;assembly=Avalonia.Themes.Simple'> |
|||
<ContentControl.Styles> |
|||
<themes:SimpleTheme /> |
|||
<StyleInclude Source='avares://Avalonia.Themes.Simple/SimpleTheme.xaml'/>
|
|||
</ContentControl.Styles> |
|||
</ContentControl>");
|
|||
Assert.IsType<SimpleTheme>(control.Styles[0]); |
|||
Assert.IsType<SimpleTheme>(control.Styles[1]); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue