|
|
@@ -28,6 +28,13 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
|
|
|
}
|
|
|
|
|
|
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
|
|
|
@@ -36,91 +43,109 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
|
|
|
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.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;
|
|
|
}
|
|
|
|
|
|
- if (originalAssetPath.StartsWith("/"))
|
|
|
+ var uriPath = new Uri(originalAssetPath, (UriKind)uriKind);
|
|
|
+ if (!uriPath.IsAbsoluteUri)
|
|
|
{
|
|
|
var baseUrl = context.CurrentDocument.Uri ?? throw new InvalidOperationException("CurrentDocument URI is null.");
|
|
|
- originalAssetPath = baseUrl.Substring(0, baseUrl.LastIndexOf('/')) + originalAssetPath;
|
|
|
+ uriPath = new Uri(new Uri(baseUrl, UriKind.Absolute), uriPath);
|
|
|
}
|
|
|
- else if (!originalAssetPath.StartsWith("avares://"))
|
|
|
+ else if (!uriPath.Scheme.Equals("avares", StringComparison.CurrentCultureIgnoreCase))
|
|
|
{
|
|
|
return context.ParseError(
|
|
|
- $"Avalonia supports only \"avares://\" sources or relative sources starting with \"/\" on the \"{nodeTypeName}\" node.",
|
|
|
- node);
|
|
|
+ $"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri.",
|
|
|
+ sourceUriNode, node);
|
|
|
}
|
|
|
|
|
|
- originalAssetPath = Uri.UnescapeDataString(new Uri(originalAssetPath).AbsoluteUri);
|
|
|
- var assetPath = originalAssetPath.Replace("avares://", "");
|
|
|
+ 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('/', '.'));
|
|
|
|
|
|
- if (context.Documents.FirstOrDefault(d => string.Equals(d.Uri, originalAssetPath, StringComparison.InvariantCultureIgnoreCase)) is {} targetDocument)
|
|
|
+ // 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.ClassType is not null)
|
|
|
+ if (targetDocument.BuildMethod is not null)
|
|
|
{
|
|
|
- return FromType(context, targetDocument.ClassType, node);
|
|
|
+ return FromMethod(context, targetDocument.BuildMethod, sourceUriNode, expectedLoadedType, node, assetPathUri, assembly);
|
|
|
}
|
|
|
|
|
|
- if (targetDocument.BuildMethod is null)
|
|
|
+ if (targetDocument.ClassType is not null)
|
|
|
{
|
|
|
- return context.ParseError($"\"{originalAssetPath}\" cannot be instantiated.", node);
|
|
|
+ return FromType(context, targetDocument.ClassType, sourceUriNode, expectedLoadedType, node, assetPathUri, assembly);
|
|
|
}
|
|
|
|
|
|
- return FromMethod(context, targetDocument.BuildMethod, node);
|
|
|
+ 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 \"{originalAssetPath}\" source.", node);
|
|
|
+ return context.ParseError($"Assembly \"{assembly}\" was not found from the \"{assetPathUri}\" source.", sourceUriNode, node);
|
|
|
}
|
|
|
|
|
|
- if (assetAssembly.FindType(fullTypeName) is { } type
|
|
|
- && type.FindMethod(m => m.Name == "!XamlIlPopulate") is not null)
|
|
|
+ var avaResType = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources");
|
|
|
+ if (avaResType is null)
|
|
|
{
|
|
|
- return FromType(context, type, node);
|
|
|
+ return context.ParseError(
|
|
|
+ $"Unable to resolve \"!AvaloniaResources\" type on \"{assembly}\" assembly.", sourceUriNode, node);
|
|
|
}
|
|
|
- else
|
|
|
- {
|
|
|
- var avaResType = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources");
|
|
|
- if (avaResType is null)
|
|
|
- {
|
|
|
- return context.ParseError(
|
|
|
- $"Unable to resolve \"!AvaloniaResources\" type on \"{assembly}\" assembly.", node);
|
|
|
- }
|
|
|
-
|
|
|
- var relativeName = "Build:" + assetPath.Substring(assemblyNameSeparator);
|
|
|
- var buildMethod = avaResType.FindMethod(m => m.Name == relativeName);
|
|
|
- if (buildMethod is null)
|
|
|
- {
|
|
|
- return context.ParseError(
|
|
|
- $"Unable to resolve build method \"{relativeName}\" resource on the \"{assembly}\" assembly.",
|
|
|
- node);
|
|
|
- }
|
|
|
|
|
|
- return FromMethod(context, buildMethod, 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);
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- private static IXamlAstNode FromType(AstTransformationContext context, IXamlType type, IXamlLineInfo li)
|
|
|
+ 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, IXamlLineInfo li)
|
|
|
+ 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) });
|