* #19962 Add AXAML Source Information to debug Builds * SimplifyXamlSourceInfo * Add XamlSourceInfo for as many elements as possible * Add tests to confirm XamlSourceInfo is set for all types * Remove property only added for debugging during development * update skipped test so it runs (even though it doesn't yet pass) * Wrap XamlAstNewClrObjectNode instead of XamlAstObjectNode, run transformer late * Remove unsupported value types from the More_Resources_Get_XamlSourceInfo_Set * Fix Document property not being set in runtime parser * Add a dedicated CreateSourceInfo parameter for RuntimeXamlLoaderConfiguration, instead of reusing DesignMode * Inherit real XamlValueWithManipulationNode, move actual manipulation to a separate class * Fix group transformers by unwrapping manipulation nodes first * minor Resource related test change * Update public API as agreed * Add new failing tests for the dictionaries * Fix randomly failing tests, that depend on the test order * Fix assert * Rename AvaloniaXamlResourceTransformer * Emit XamlSourceInfo from AvaloniaXamlResourceTransformer * Rename AvaloniaXamlIlResourceTransformer for consistency * Cleanup comments * Remove XamlSourceInfoValueWithManipulationNode, use standard XamlValueWithManipulationNode * Add new RuntimeXamlLoaderDocument.Document property * Use UriBuilder trick to support unix paths on windows * Add private AttachedProperty for avalonia objects, instead of always using weak table * Fix wrong UriBuilder usage and add more test assets * Fix "Invalid URI" exception --------- Co-authored-by: KimHenrik <kimhenrik@outlook.de> Co-authored-by: Max Katz <maxkatz6@outlook.com>
This commit is contained in:
@@ -134,6 +134,8 @@
|
|||||||
<AvaloniaXamlIlVerifyIl Condition="'$(AvaloniaXamlIlVerifyIl)' == ''">false</AvaloniaXamlIlVerifyIl>
|
<AvaloniaXamlIlVerifyIl Condition="'$(AvaloniaXamlIlVerifyIl)' == ''">false</AvaloniaXamlIlVerifyIl>
|
||||||
<AvaloniaXamlIlDebuggerLaunch Condition="'$(AvaloniaXamlIlDebuggerLaunch)' == ''">false</AvaloniaXamlIlDebuggerLaunch>
|
<AvaloniaXamlIlDebuggerLaunch Condition="'$(AvaloniaXamlIlDebuggerLaunch)' == ''">false</AvaloniaXamlIlDebuggerLaunch>
|
||||||
<AvaloniaXamlVerboseExceptions Condition="'$(AvaloniaXamlVerboseExceptions)' == ''">false</AvaloniaXamlVerboseExceptions>
|
<AvaloniaXamlVerboseExceptions Condition="'$(AvaloniaXamlVerboseExceptions)' == ''">false</AvaloniaXamlVerboseExceptions>
|
||||||
|
<AvaloniaXamlCreateSourceInfo Condition="'$(AvaloniaXamlCreateSourceInfo)' == '' AND '$(Configuration)' == 'Debug'">true</AvaloniaXamlCreateSourceInfo>
|
||||||
|
<AvaloniaXamlCreateSourceInfo Condition="'$(AvaloniaXamlCreateSourceInfo)' == '' AND '$(Configuration)' != 'Debug'">false</AvaloniaXamlCreateSourceInfo>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -162,6 +164,7 @@
|
|||||||
DelaySign="$(DelaySign)"
|
DelaySign="$(DelaySign)"
|
||||||
SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)"
|
SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)"
|
||||||
DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)"
|
DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)"
|
||||||
|
CreateSourceInfo="$(AvaloniaXamlCreateSourceInfo)"
|
||||||
DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)"
|
DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)"
|
||||||
VerboseExceptions="$(AvaloniaXamlVerboseExceptions)"
|
VerboseExceptions="$(AvaloniaXamlVerboseExceptions)"
|
||||||
AnalyzerConfigFiles="@(EditorConfigFiles)"/>
|
AnalyzerConfigFiles="@(EditorConfigFiles)"/>
|
||||||
|
|||||||
@@ -31,6 +31,11 @@
|
|||||||
Description="Allow debug XAML compilation"
|
Description="Allow debug XAML compilation"
|
||||||
Category="Debug" />
|
Category="Debug" />
|
||||||
|
|
||||||
|
<BoolProperty Name="AvaloniaXamlCreateSourceInfo"
|
||||||
|
DisplayName="Generate XAML Source Info"
|
||||||
|
Description="When enabled, the XAML compiler embeds SourceInfo metadata (file path, line, and column) into generated code. This allows tools and debuggers to locate the original .axaml source position of a selected element at runtime or in the designer."
|
||||||
|
Category="Debug" />
|
||||||
|
|
||||||
<BoolProperty Name="AvaloniaXamlVerboseExceptions"
|
<BoolProperty Name="AvaloniaXamlVerboseExceptions"
|
||||||
DisplayName="Report verbose internal exceptions with stack traces"
|
DisplayName="Report verbose internal exceptions with stack traces"
|
||||||
Description="Also includes inner exceptions"
|
Description="Also includes inner exceptions"
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ namespace Avalonia.Build.Tasks
|
|||||||
ProjectDirectory, VerifyIl, DefaultCompileBindings, outputImportance,
|
ProjectDirectory, VerifyIl, DefaultCompileBindings, outputImportance,
|
||||||
new XamlCompilerDiagnosticsFilter(AnalyzerConfigFiles),
|
new XamlCompilerDiagnosticsFilter(AnalyzerConfigFiles),
|
||||||
(SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null,
|
(SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null,
|
||||||
SkipXamlCompilation, DebuggerLaunch, VerboseExceptions);
|
SkipXamlCompilation, DebuggerLaunch, VerboseExceptions, CreateSourceInfo);
|
||||||
|
|
||||||
if (res.Success && !res.WrittenFile)
|
if (res.Success && !res.WrittenFile)
|
||||||
{
|
{
|
||||||
@@ -99,6 +99,8 @@ namespace Avalonia.Build.Tasks
|
|||||||
|
|
||||||
public bool DebuggerLaunch { get; set; }
|
public bool DebuggerLaunch { get; set; }
|
||||||
|
|
||||||
|
public bool CreateSourceInfo { get; set; }
|
||||||
|
|
||||||
public bool VerboseExceptions { get; set; }
|
public bool VerboseExceptions { get; set; }
|
||||||
|
|
||||||
public ITaskItem[] AnalyzerConfigFiles { get; set; }
|
public ITaskItem[] AnalyzerConfigFiles { get; set; }
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ namespace Avalonia.Build.Tasks
|
|||||||
string[] references, string projectDirectory,
|
string[] references, string projectDirectory,
|
||||||
bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance,
|
bool verifyIl, bool defaultCompileBindings, MessageImportance logImportance,
|
||||||
XamlCompilerDiagnosticsFilter diagnosticsFilter, string strongNameKey,
|
XamlCompilerDiagnosticsFilter diagnosticsFilter, string strongNameKey,
|
||||||
bool skipXamlCompilation, bool debuggerLaunch, bool verboseExceptions)
|
bool skipXamlCompilation, bool debuggerLaunch, bool verboseExceptions, bool createSourceInfo)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -67,7 +67,7 @@ namespace Avalonia.Build.Tasks
|
|||||||
var compileRes = CompileCore(
|
var compileRes = CompileCore(
|
||||||
engine, typeSystem, projectDirectory, verifyIl,
|
engine, typeSystem, projectDirectory, verifyIl,
|
||||||
defaultCompileBindings, logImportance, diagnosticsFilter,
|
defaultCompileBindings, logImportance, diagnosticsFilter,
|
||||||
debuggerLaunch, verboseExceptions);
|
debuggerLaunch, verboseExceptions, createSourceInfo);
|
||||||
if (compileRes == null)
|
if (compileRes == null)
|
||||||
return new CompileResult(true);
|
return new CompileResult(true);
|
||||||
if (compileRes == false)
|
if (compileRes == false)
|
||||||
@@ -107,7 +107,8 @@ namespace Avalonia.Build.Tasks
|
|||||||
MessageImportance logImportance,
|
MessageImportance logImportance,
|
||||||
XamlCompilerDiagnosticsFilter diagnosticsFilter,
|
XamlCompilerDiagnosticsFilter diagnosticsFilter,
|
||||||
bool debuggerLaunch,
|
bool debuggerLaunch,
|
||||||
bool verboseExceptions)
|
bool verboseExceptions,
|
||||||
|
bool createSourceInfo)
|
||||||
{
|
{
|
||||||
if (debuggerLaunch)
|
if (debuggerLaunch)
|
||||||
{
|
{
|
||||||
@@ -210,6 +211,7 @@ namespace Avalonia.Build.Tasks
|
|||||||
{
|
{
|
||||||
EnableIlVerification = verifyIl,
|
EnableIlVerification = verifyIl,
|
||||||
DefaultCompileBindings = defaultCompileBindings,
|
DefaultCompileBindings = defaultCompileBindings,
|
||||||
|
CreateSourceInfo = createSourceInfo,
|
||||||
DynamicSetterContainerProvider = new DefaultXamlDynamicSetterContainerProvider(dynamicSettersBuilder)
|
DynamicSetterContainerProvider = new DefaultXamlDynamicSetterContainerProvider(dynamicSettersBuilder)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
@@ -344,7 +344,8 @@ namespace Avalonia.Markup.Xaml.XamlIl
|
|||||||
{
|
{
|
||||||
EnableIlVerification = true,
|
EnableIlVerification = true,
|
||||||
DefaultCompileBindings = configuration.UseCompiledBindingsByDefault,
|
DefaultCompileBindings = configuration.UseCompiledBindingsByDefault,
|
||||||
IsDesignMode = configuration.DesignMode
|
IsDesignMode = configuration.DesignMode,
|
||||||
|
CreateSourceInfo = configuration.CreateSourceInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
var parsedDocuments = new List<XamlDocumentResource>();
|
var parsedDocuments = new List<XamlDocumentResource>();
|
||||||
@@ -363,7 +364,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
|
|||||||
}
|
}
|
||||||
|
|
||||||
var parsed = compiler.Parse(xaml, overrideType);
|
var parsed = compiler.Parse(xaml, overrideType);
|
||||||
parsed.Document = "runtimexaml:" + parsedDocuments.Count;
|
parsed.Document = document.Document ?? ("runtimexaml" + parsedDocuments.Count);
|
||||||
compiler.Transform(parsed);
|
compiler.Transform(parsed);
|
||||||
|
|
||||||
var xamlName = GetSafeUriIdentifier(document.BaseUri)
|
var xamlName = GetSafeUriIdentifier(document.BaseUri)
|
||||||
|
|||||||
+14
-1
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Avalonia.Markup.Xaml.Loader.CompilerExtensions.Transformers;
|
||||||
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers;
|
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers;
|
||||||
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
|
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
|
||||||
using XamlX;
|
using XamlX;
|
||||||
@@ -20,6 +21,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
|
|||||||
private readonly IXamlType _contextType = null!;
|
private readonly IXamlType _contextType = null!;
|
||||||
private readonly AvaloniaXamlIlDesignPropertiesTransformer _designTransformer;
|
private readonly AvaloniaXamlIlDesignPropertiesTransformer _designTransformer;
|
||||||
private readonly AvaloniaBindingExtensionTransformer _bindingTransformer;
|
private readonly AvaloniaBindingExtensionTransformer _bindingTransformer;
|
||||||
|
private readonly AvaloniaXamlIlAddSourceInfoTransformer _addSourceInfoTransformer;
|
||||||
|
private readonly AvaloniaXamlResourceTransformer _resourceTransformer;
|
||||||
|
|
||||||
private AvaloniaXamlIlCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings)
|
private AvaloniaXamlIlCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings)
|
||||||
: base(configuration, emitMappings, true)
|
: base(configuration, emitMappings, true)
|
||||||
@@ -47,6 +50,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
|
|||||||
new AvaloniaXamlIlResolveClassesPropertiesTransformer(),
|
new AvaloniaXamlIlResolveClassesPropertiesTransformer(),
|
||||||
new AvaloniaXamlIlTransformInstanceAttachedProperties(),
|
new AvaloniaXamlIlTransformInstanceAttachedProperties(),
|
||||||
new AvaloniaXamlIlTransformSyntheticCompiledBindingMembers());
|
new AvaloniaXamlIlTransformSyntheticCompiledBindingMembers());
|
||||||
|
|
||||||
|
|
||||||
InsertAfter<PropertyReferenceResolver>(
|
InsertAfter<PropertyReferenceResolver>(
|
||||||
new AvaloniaXamlIlAvaloniaPropertyResolver(),
|
new AvaloniaXamlIlAvaloniaPropertyResolver(),
|
||||||
new AvaloniaXamlIlReorderClassesPropertiesTransformer(),
|
new AvaloniaXamlIlReorderClassesPropertiesTransformer(),
|
||||||
@@ -85,7 +90,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
|
|||||||
);
|
);
|
||||||
|
|
||||||
InsertBeforeMany(new [] { typeof(DeferredContentTransformer), typeof(AvaloniaXamlIlCompiledBindingsMetadataRemover) },
|
InsertBeforeMany(new [] { typeof(DeferredContentTransformer), typeof(AvaloniaXamlIlCompiledBindingsMetadataRemover) },
|
||||||
new AvaloniaXamlIlDeferredResourceTransformer());
|
_resourceTransformer = new AvaloniaXamlResourceTransformer());
|
||||||
|
|
||||||
InsertBefore<AvaloniaXamlIlTransformInstanceAttachedProperties>(new AvaloniaXamlIlTransformRoutedEvent());
|
InsertBefore<AvaloniaXamlIlTransformInstanceAttachedProperties>(new AvaloniaXamlIlTransformRoutedEvent());
|
||||||
|
|
||||||
@@ -94,6 +99,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
|
|||||||
Transformers.Add(new AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer());
|
Transformers.Add(new AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer());
|
||||||
Transformers.Add(new AvaloniaXamlIlRootObjectScope());
|
Transformers.Add(new AvaloniaXamlIlRootObjectScope());
|
||||||
|
|
||||||
|
Transformers.Add(_addSourceInfoTransformer = new AvaloniaXamlIlAddSourceInfoTransformer());
|
||||||
|
|
||||||
Emitters.Add(new AvaloniaNameScopeRegistrationXamlIlNodeEmitter());
|
Emitters.Add(new AvaloniaNameScopeRegistrationXamlIlNodeEmitter());
|
||||||
Emitters.Add(new AvaloniaXamlIlRootObjectScope.Emitter());
|
Emitters.Add(new AvaloniaXamlIlRootObjectScope.Emitter());
|
||||||
|
|
||||||
@@ -122,6 +129,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
|
|||||||
public const string PopulateName = "__AvaloniaXamlIlPopulate";
|
public const string PopulateName = "__AvaloniaXamlIlPopulate";
|
||||||
public const string BuildName = "__AvaloniaXamlIlBuild";
|
public const string BuildName = "__AvaloniaXamlIlBuild";
|
||||||
|
|
||||||
|
public bool CreateSourceInfo
|
||||||
|
{
|
||||||
|
get => _addSourceInfoTransformer.CreateSourceInfo || _resourceTransformer.CreateSourceInfo;
|
||||||
|
set => _addSourceInfoTransformer.CreateSourceInfo = _resourceTransformer.CreateSourceInfo = value;
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsDesignMode
|
public bool IsDesignMode
|
||||||
{
|
{
|
||||||
get => _designTransformer.IsDesignMode;
|
get => _designTransformer.IsDesignMode;
|
||||||
|
|||||||
+24
-9
@@ -20,8 +20,12 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
|
|||||||
{
|
{
|
||||||
public IXamlAstNode Transform(AstGroupTransformationContext context, IXamlAstNode node)
|
public IXamlAstNode Transform(AstGroupTransformationContext context, IXamlAstNode node)
|
||||||
{
|
{
|
||||||
if (node is not XamlValueWithManipulationNode valueNode
|
// Filter object initialization nodes like:
|
||||||
|| valueNode.Value is not XamlAstNewClrObjectNode objectNode
|
// > XamlValueWithManipulationNode
|
||||||
|
// > > XamlAstNewClrObjectNode // StyleInclude or ResourceInclude, can be nested in another XamlValueWithManipulationNode
|
||||||
|
// > > XamlObjectInitializationNode
|
||||||
|
if (node is not XamlValueWithManipulationNode { Manipulation: XamlObjectInitializationNode initializationNode } valueNode
|
||||||
|
|| valueNode.UnwrapValue<XamlAstNewClrObjectNode>() is not { } objectNode
|
||||||
|| (objectNode.Type.GetClrType() != context.GetAvaloniaTypes().StyleInclude
|
|| (objectNode.Type.GetClrType() != context.GetAvaloniaTypes().StyleInclude
|
||||||
&& objectNode.Type.GetClrType() != context.GetAvaloniaTypes().ResourceInclude))
|
&& objectNode.Type.GetClrType() != context.GetAvaloniaTypes().ResourceInclude))
|
||||||
{
|
{
|
||||||
@@ -36,11 +40,6 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
|
|||||||
throw new InvalidOperationException($"\"{nodeTypeName}\".Loaded property is expected to be defined");
|
throw new InvalidOperationException($"\"{nodeTypeName}\".Loaded property is expected to be defined");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valueNode.Manipulation is not XamlObjectInitializationNode initializationNode)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Invalid \"{nodeTypeName}\" node initialization.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var additionalProperties = new List<IXamlAstManipulationNode>();
|
var additionalProperties = new List<IXamlAstManipulationNode>();
|
||||||
if (initializationNode.Manipulation is not XamlPropertyAssignmentNode { Property: { Name: "Source" } } sourceProperty)
|
if (initializationNode.Manipulation is not XamlPropertyAssignmentNode { Property: { Name: "Source" } } sourceProperty)
|
||||||
{
|
{
|
||||||
@@ -176,9 +175,25 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
|
|||||||
strictSourceValueType ? XamlDiagnosticSeverity.Error : XamlDiagnosticSeverity.Warning,
|
strictSourceValueType ? XamlDiagnosticSeverity.Error : XamlDiagnosticSeverity.Warning,
|
||||||
$"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri. This {nodeTypeName} will be resolved in runtime instead.",
|
$"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri. This {nodeTypeName} will be resolved in runtime instead.",
|
||||||
node);
|
node);
|
||||||
|
|
||||||
// We expect that AvaloniaXamlIlLanguageParseIntrinsics has already parsed the Uri and created node like: `new Uri(assetPath, uriKind)`.
|
// 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
|
if (sourceProperty.Values.Count != 1)
|
||||||
|
{
|
||||||
|
OnInvalidSource(sourceProperty);
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// `new Uri` can be wrapped in manipulation node if source info or another manipulation was applied.
|
||||||
|
var sourceUriNodeWrapped = sourceProperty.Values.Single();
|
||||||
|
var sourceUriNode = sourceUriNodeWrapped switch
|
||||||
|
{
|
||||||
|
XamlAstNewClrObjectNode newObj => newObj,
|
||||||
|
XamlValueWithManipulationNode manipulation => manipulation.UnwrapValue<XamlAstNewClrObjectNode>(),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validate Uri type and constant arguments.
|
||||||
|
if (sourceUriNode is null
|
||||||
|| sourceUriNode.Type.GetClrType() != context.GetAvaloniaTypes().Uri
|
|| 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 })
|
|| sourceUriNode.Arguments.Skip(1).FirstOrDefault() is not XamlConstantNode { Constant: int uriKind })
|
||||||
|
|||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
|
||||||
|
using XamlX.Ast;
|
||||||
|
using XamlX.Emit;
|
||||||
|
using XamlX.IL;
|
||||||
|
using XamlX.Transform;
|
||||||
|
|
||||||
|
namespace Avalonia.Markup.Xaml.Loader.CompilerExtensions.Transformers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An XAMLIL AST transformer that injects <see cref="Avalonia.Markup.Xaml.Diagnostics.XamlSourceInfo"/> metadata into the generated XAML code.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This transformer wraps object creation nodes with a manipulation node that adds source information.
|
||||||
|
/// This source information includes line number, position, and document name, which can be useful for debugging and diagnostics.
|
||||||
|
/// Note: ResourceDictionary source info is handled separately in <see cref="AvaloniaXamlResourceTransformer"/>.
|
||||||
|
/// </remarks>
|
||||||
|
internal class AvaloniaXamlIlAddSourceInfoTransformer : IXamlAstTransformer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether source information should be generated
|
||||||
|
/// and injected into the compiled XAML output.
|
||||||
|
/// </summary>
|
||||||
|
public bool CreateSourceInfo { get; set; }
|
||||||
|
|
||||||
|
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
|
||||||
|
{
|
||||||
|
if (CreateSourceInfo
|
||||||
|
&& node is XamlAstNewClrObjectNode objNode
|
||||||
|
&& context.ParentNodes().FirstOrDefault() is not XamlValueWithManipulationNode { Manipulation: XamlSourceInfoValueManipulation }
|
||||||
|
&& !objNode.Type.GetClrType().IsValueType)
|
||||||
|
{
|
||||||
|
var avaloniaTypes = context.GetAvaloniaTypes();
|
||||||
|
|
||||||
|
return new XamlValueWithManipulationNode(
|
||||||
|
objNode, objNode,
|
||||||
|
new XamlSourceInfoValueManipulation(avaloniaTypes, objNode, context.Document));
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class XamlSourceInfoValueManipulation(
|
||||||
|
AvaloniaXamlIlWellKnownTypes avaloniaTypes,
|
||||||
|
XamlAstNewClrObjectNode objNode, string? document)
|
||||||
|
: XamlAstNode(objNode), IXamlAstManipulationNode, IXamlAstILEmitableNode
|
||||||
|
{
|
||||||
|
public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
|
||||||
|
{
|
||||||
|
// Target object is already on stack.
|
||||||
|
|
||||||
|
// var info = new XamlSourceInfo(Line, Position, Document);
|
||||||
|
codeGen.Ldc_I4(Line);
|
||||||
|
codeGen.Ldc_I4(Position);
|
||||||
|
if (document is not null)
|
||||||
|
codeGen.Ldstr(document);
|
||||||
|
else
|
||||||
|
codeGen.Ldnull();
|
||||||
|
codeGen.Newobj(avaloniaTypes.XamlSourceInfoConstructor);
|
||||||
|
|
||||||
|
// Set the XamlSourceInfo property on the current object
|
||||||
|
// XamlSourceInfo.SetValue(@this, info);
|
||||||
|
codeGen.EmitCall(avaloniaTypes.XamlSourceInfoSetter);
|
||||||
|
|
||||||
|
return XamlILNodeEmitResult.Void(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-191
@@ -1,191 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Visitors;
|
|
||||||
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 AvaloniaXamlIlDeferredResourceTransformer : IXamlAstTransformer
|
|
||||||
{
|
|
||||||
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
|
|
||||||
{
|
|
||||||
if (!(node is XamlPropertyAssignmentNode pa) || pa.Values.Count != 2)
|
|
||||||
return node;
|
|
||||||
|
|
||||||
var types = context.GetAvaloniaTypes();
|
|
||||||
|
|
||||||
if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content"
|
|
||||||
&& ShouldBeDeferred(pa.Values[1]))
|
|
||||||
{
|
|
||||||
IXamlMethod addMethod = TryGetSharedValue(pa.Values[1], out var isShared) && !isShared
|
|
||||||
? types.ResourceDictionaryNotSharedDeferredAdd
|
|
||||||
: types.ResourceDictionaryDeferredAdd;
|
|
||||||
|
|
||||||
pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration);
|
|
||||||
pa.PossibleSetters = new List<IXamlPropertySetter>
|
|
||||||
{
|
|
||||||
new XamlDirectCallPropertySetter(addMethod),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else if (pa.Property.Name == "Resources" && pa.Property.Getter?.ReturnType.Equals(types.IResourceDictionary) == true
|
|
||||||
&& ShouldBeDeferred(pa.Values[1]))
|
|
||||||
{
|
|
||||||
IXamlMethod addMethod = TryGetSharedValue(pa.Values[1], out var isShared) && !isShared
|
|
||||||
? types.ResourceDictionaryNotSharedDeferredAdd
|
|
||||||
: types.ResourceDictionaryDeferredAdd;
|
|
||||||
|
|
||||||
pa.Values[1] = new XamlDeferredContentNode(pa.Values[1], types.XamlIlTypes.Object, context.Configuration);
|
|
||||||
pa.PossibleSetters = new List<IXamlPropertySetter>
|
|
||||||
{
|
|
||||||
new AdderSetter(pa.Property.Getter, addMethod),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
|
|
||||||
bool TryGetSharedValue(IXamlAstValueNode valueNode, out bool value)
|
|
||||||
{
|
|
||||||
value = default;
|
|
||||||
if (valueNode is XamlAstConstructableObjectNode co)
|
|
||||||
{
|
|
||||||
// Try find x:Share directive
|
|
||||||
if (co.Children.Find(d => d is XamlAstXmlDirective { Namespace: XamlNamespaces.Xaml2006, Name: "Shared" }) is XamlAstXmlDirective sharedDirective)
|
|
||||||
{
|
|
||||||
if (sharedDirective.Values.Count == 1 && sharedDirective.Values[0] is XamlAstTextNode text)
|
|
||||||
{
|
|
||||||
if (bool.TryParse(text.Text, out var parseValue))
|
|
||||||
{
|
|
||||||
// If the parser succeeds, remove the x:Share directive
|
|
||||||
co.Children.Remove(sharedDirective);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.ReportTransformError("Invalid argument type for x:Shared directive.", node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.ReportTransformError("Invalid number of arguments for x:Shared directive.", node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool ShouldBeDeferred(IXamlAstValueNode node)
|
|
||||||
{
|
|
||||||
var clrType = node.Type.GetClrType();
|
|
||||||
|
|
||||||
// XAML compiler is currently strict about value types, allowing them to be created only through converters.
|
|
||||||
// At the moment it should be safe to not defer structs.
|
|
||||||
if (clrType.IsValueType)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Never defer strings.
|
|
||||||
if (clrType.FullName == "System.String")
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not defer resources, if it has any x:Name registration, as it cannot be delayed.
|
|
||||||
// This visitor will count x:Name registrations, ignoring nested NestedScopeMetadataNode scopes.
|
|
||||||
// We set target scope level to 0, assuming that this resource node is a scope of itself.
|
|
||||||
var nameRegistrationsVisitor = new NameScopeRegistrationVisitor(
|
|
||||||
targetMetadataScopeLevel: 0);
|
|
||||||
node.Visit(nameRegistrationsVisitor);
|
|
||||||
if (nameRegistrationsVisitor.Count > 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
class AdderSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable<AdderSetter>
|
|
||||||
{
|
|
||||||
private readonly IXamlMethod _getter;
|
|
||||||
private readonly IXamlMethod _adder;
|
|
||||||
|
|
||||||
public AdderSetter(IXamlMethod getter, IXamlMethod adder)
|
|
||||||
{
|
|
||||||
_getter = getter;
|
|
||||||
_adder = adder;
|
|
||||||
TargetType = getter.DeclaringType;
|
|
||||||
Parameters = adder.ParametersWithThis().Skip(1).ToList();
|
|
||||||
|
|
||||||
bool allowNull = Parameters.Last().AcceptsNull();
|
|
||||||
BinderParameters = new PropertySetterBinderParameters
|
|
||||||
{
|
|
||||||
AllowMultiple = true,
|
|
||||||
AllowXNull = allowNull,
|
|
||||||
AllowRuntimeNull = allowNull,
|
|
||||||
AllowAttributeSyntax = false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public IXamlType TargetType { get; }
|
|
||||||
|
|
||||||
public PropertySetterBinderParameters BinderParameters { get; }
|
|
||||||
|
|
||||||
public IReadOnlyList<IXamlType> Parameters { get; }
|
|
||||||
public IReadOnlyList<IXamlCustomAttribute> CustomAttributes => _adder.CustomAttributes;
|
|
||||||
|
|
||||||
public void Emit(IXamlILEmitter emitter)
|
|
||||||
{
|
|
||||||
var locals = new Stack<XamlLocalsPool.PooledLocal>();
|
|
||||||
// Save all "setter" parameters
|
|
||||||
for (var c = Parameters.Count - 1; c >= 0; c--)
|
|
||||||
{
|
|
||||||
var loc = emitter.LocalsPool.GetLocal(Parameters[c]);
|
|
||||||
locals.Push(loc);
|
|
||||||
emitter.Stloc(loc.Local);
|
|
||||||
}
|
|
||||||
|
|
||||||
emitter.EmitCall(_getter);
|
|
||||||
while (locals.Count>0)
|
|
||||||
using (var loc = locals.Pop())
|
|
||||||
emitter.Ldloc(loc.Local);
|
|
||||||
emitter.EmitCall(_adder, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EmitWithArguments(
|
|
||||||
XamlEmitContextWithLocals<IXamlILEmitter, XamlILNodeEmitResult> context,
|
|
||||||
IXamlILEmitter emitter,
|
|
||||||
IReadOnlyList<IXamlAstValueNode> arguments)
|
|
||||||
{
|
|
||||||
emitter.EmitCall(_getter);
|
|
||||||
|
|
||||||
for (var i = 0; i < arguments.Count; ++i)
|
|
||||||
context.Emit(arguments[i], emitter, Parameters[i]);
|
|
||||||
|
|
||||||
emitter.EmitCall(_adder, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(AdderSetter? other)
|
|
||||||
{
|
|
||||||
if (ReferenceEquals(null, other))
|
|
||||||
return false;
|
|
||||||
if (ReferenceEquals(this, other))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return _getter.Equals(other._getter) && _adder.Equals(other._adder);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
=> Equals(obj as AdderSetter);
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
=> (_getter.GetHashCode() * 397) ^ _adder.GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+343
@@ -0,0 +1,343 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Visitors;
|
||||||
|
using XamlX;
|
||||||
|
using XamlX.Ast;
|
||||||
|
using XamlX.Emit;
|
||||||
|
using XamlX.IL;
|
||||||
|
using XamlX.Transform;
|
||||||
|
using XamlX.TypeSystem;
|
||||||
|
|
||||||
|
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Transforms ResourceDictionary and IResourceDictionary property assignments
|
||||||
|
/// to use Add method calls with deferred content where applicable.
|
||||||
|
/// Additionally, handles x:Shared on assignments and injects XamlSourceInfo.
|
||||||
|
/// </summary>
|
||||||
|
internal class AvaloniaXamlResourceTransformer : IXamlAstTransformer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether source information should be generated
|
||||||
|
/// and injected into the compiled XAML output.
|
||||||
|
/// </summary>
|
||||||
|
public bool CreateSourceInfo { get; set; } = true;
|
||||||
|
|
||||||
|
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
|
||||||
|
{
|
||||||
|
if (!(node is XamlPropertyAssignmentNode pa) || pa.Values.Count != 2)
|
||||||
|
return node;
|
||||||
|
|
||||||
|
var types = context.GetAvaloniaTypes();
|
||||||
|
var document = context.Document;
|
||||||
|
|
||||||
|
if (pa.Property.DeclaringType == types.ResourceDictionary && pa.Property.Name == "Content")
|
||||||
|
{
|
||||||
|
var value = pa.Values[1];
|
||||||
|
(var adder, value) = ResolveAdderAndValue(value);
|
||||||
|
|
||||||
|
pa.Values[1] = value;
|
||||||
|
pa.PossibleSetters = new List<IXamlPropertySetter>
|
||||||
|
{
|
||||||
|
new AdderSetter(adder, CreateSourceInfo, types, value.Line, value.Position, document),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (pa.Property.Name == "Resources" && pa.Property.Getter?.ReturnType.Equals(types.IResourceDictionary) == true)
|
||||||
|
{
|
||||||
|
var value = pa.Values[1];
|
||||||
|
(var adder, value) = ResolveAdderAndValue(value);
|
||||||
|
|
||||||
|
pa.Values[1] = value;
|
||||||
|
pa.PossibleSetters = new List<IXamlPropertySetter>
|
||||||
|
{
|
||||||
|
new AdderSetter(pa.Property.Getter, adder, CreateSourceInfo, types, value.Line, value.Position, document),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
|
||||||
|
(IXamlMethod adder, IXamlAstValueNode newValue) ResolveAdderAndValue(IXamlAstValueNode valueNode)
|
||||||
|
{
|
||||||
|
if (ShouldBeDeferred(valueNode))
|
||||||
|
{
|
||||||
|
var adder = TryGetSharedValue(valueNode, out var isShared) && !isShared
|
||||||
|
? types.ResourceDictionaryNotSharedDeferredAdd
|
||||||
|
: types.ResourceDictionaryDeferredAdd;
|
||||||
|
var deferredNode = new XamlDeferredContentNode(valueNode, types.XamlIlTypes.Object, context.Configuration);
|
||||||
|
return (adder, deferredNode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var adder = XamlTransformHelpers.FindPossibleAdders(context, types.IResourceDictionary)
|
||||||
|
.FirstOrDefault() ?? throw new XamlTransformException("No suitable Add method found for IResourceDictionary.", node);
|
||||||
|
return (adder, valueNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TryGetSharedValue(IXamlAstValueNode valueNode, out bool value)
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
if (valueNode is XamlAstConstructableObjectNode co)
|
||||||
|
{
|
||||||
|
// Try find x:Share directive
|
||||||
|
if (co.Children.Find(d => d is XamlAstXmlDirective { Namespace: XamlNamespaces.Xaml2006, Name: "Shared" }) is XamlAstXmlDirective sharedDirective)
|
||||||
|
{
|
||||||
|
if (sharedDirective.Values.Count == 1 && sharedDirective.Values[0] is XamlAstTextNode text)
|
||||||
|
{
|
||||||
|
if (bool.TryParse(text.Text, out var parseValue))
|
||||||
|
{
|
||||||
|
// If the parser succeeds, remove the x:Share directive
|
||||||
|
co.Children.Remove(sharedDirective);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.ReportTransformError("Invalid argument type for x:Shared directive.", node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.ReportTransformError("Invalid number of arguments for x:Shared directive.", node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ShouldBeDeferred(IXamlAstValueNode node)
|
||||||
|
{
|
||||||
|
var clrType = node.Type.GetClrType();
|
||||||
|
|
||||||
|
// XAML compiler is currently strict about value types, allowing them to be created only through converters.
|
||||||
|
// At the moment it should be safe to not defer structs.
|
||||||
|
if (clrType.IsValueType)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Never defer strings.
|
||||||
|
if (clrType.FullName == "System.String")
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not defer resources, if it has any x:Name registration, as it cannot be delayed.
|
||||||
|
// This visitor will count x:Name registrations, ignoring nested NestedScopeMetadataNode scopes.
|
||||||
|
// We set target scope level to 0, assuming that this resource node is a scope of itself.
|
||||||
|
var nameRegistrationsVisitor = new NameScopeRegistrationVisitor(
|
||||||
|
targetMetadataScopeLevel: 0);
|
||||||
|
node.Visit(nameRegistrationsVisitor);
|
||||||
|
if (nameRegistrationsVisitor.Count > 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AdderSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable<AdderSetter>
|
||||||
|
{
|
||||||
|
private readonly IXamlMethod? _getter;
|
||||||
|
private readonly IXamlMethod _adder;
|
||||||
|
private readonly bool _emitSourceInfo;
|
||||||
|
private readonly AvaloniaXamlIlWellKnownTypes _avaloniaTypes;
|
||||||
|
private readonly string? _document;
|
||||||
|
private readonly int _line, _position;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an adder-only setter. Target is assumed to be already on the stack before emit.
|
||||||
|
/// For example:
|
||||||
|
/// var resourceDictionary = ...
|
||||||
|
/// resourceDictionary.Add(key, value);
|
||||||
|
/// resourceDictionary.Add(key2, value2);
|
||||||
|
/// </summary>
|
||||||
|
public AdderSetter(
|
||||||
|
IXamlMethod adder,
|
||||||
|
bool emitSourceInfo,
|
||||||
|
AvaloniaXamlIlWellKnownTypes avaloniaTypes,
|
||||||
|
int line, int position, string? document)
|
||||||
|
{
|
||||||
|
_adder = adder;
|
||||||
|
_emitSourceInfo = emitSourceInfo;
|
||||||
|
_avaloniaTypes = avaloniaTypes;
|
||||||
|
_line = line;
|
||||||
|
_position = position;
|
||||||
|
_document = document;
|
||||||
|
|
||||||
|
TargetType = adder.ThisOrFirstParameter();
|
||||||
|
Parameters = adder.ParametersWithThis().Skip(1).ToList();
|
||||||
|
bool allowNull = Parameters.Last().AcceptsNull();
|
||||||
|
BinderParameters = new PropertySetterBinderParameters
|
||||||
|
{
|
||||||
|
AllowMultiple = true,
|
||||||
|
AllowXNull = allowNull,
|
||||||
|
AllowRuntimeNull = allowNull
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Explicit target getter - target will be obtained by calling the getter first.
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public AdderSetter(
|
||||||
|
IXamlMethod getter, IXamlMethod adder,
|
||||||
|
bool emitSourceInfo,
|
||||||
|
AvaloniaXamlIlWellKnownTypes avaloniaTypes,
|
||||||
|
int line, int position, string? document)
|
||||||
|
: this(adder, emitSourceInfo, avaloniaTypes, line, position, document)
|
||||||
|
{
|
||||||
|
_getter = getter;
|
||||||
|
TargetType = getter.DeclaringType;
|
||||||
|
BinderParameters.AllowMultiple = false;
|
||||||
|
BinderParameters.AllowAttributeSyntax = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IXamlType TargetType { get; }
|
||||||
|
|
||||||
|
public PropertySetterBinderParameters BinderParameters { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<IXamlType> Parameters { get; }
|
||||||
|
public IReadOnlyList<IXamlCustomAttribute> CustomAttributes => _adder.CustomAttributes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Emits the setter with arguments already on the stack.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If _getter is null - assume target is already on the stack.
|
||||||
|
/// In this case, we can just call Emit. Unless _emitSourceInfo is true.
|
||||||
|
///
|
||||||
|
/// If _emitSourceInfo is true - we need to make sure that target and key are on the stack for XamlSourceInfo setting,
|
||||||
|
/// so we need to store parameters to locals first regardless.
|
||||||
|
/// </remarks>
|
||||||
|
public void Emit(IXamlILEmitter emitter)
|
||||||
|
{
|
||||||
|
using var keyLocal = emitter.LocalsPool.GetLocal(Parameters[0]);
|
||||||
|
|
||||||
|
if (_getter is not null || _emitSourceInfo)
|
||||||
|
{
|
||||||
|
var locals = new Stack<XamlLocalsPool.PooledLocal>();
|
||||||
|
// Save all "setter" parameters
|
||||||
|
for (var c = Parameters.Count - 1; c >= 0; c--)
|
||||||
|
{
|
||||||
|
var loc = emitter.LocalsPool.GetLocal(Parameters[c]);
|
||||||
|
locals.Push(loc);
|
||||||
|
emitter.Stloc(loc.Local);
|
||||||
|
|
||||||
|
if (c == 0 && _emitSourceInfo)
|
||||||
|
{
|
||||||
|
// Store the key argument for XamlSourceInfo later
|
||||||
|
emitter.Ldloc(loc.Local);
|
||||||
|
emitter.Stloc(keyLocal.Local);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_getter is not null)
|
||||||
|
{
|
||||||
|
emitter.EmitCall(_getter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duplicate the target object on stack for setting XamlSourceInfo later
|
||||||
|
emitter.Dup();
|
||||||
|
|
||||||
|
while (locals.Count > 0)
|
||||||
|
using (var loc = locals.Pop())
|
||||||
|
emitter.Ldloc(loc.Local);
|
||||||
|
}
|
||||||
|
|
||||||
|
emitter.EmitCall(_adder, true);
|
||||||
|
|
||||||
|
if (_emitSourceInfo)
|
||||||
|
{
|
||||||
|
// Target is already on stack (dup)
|
||||||
|
// Load the key argument from local
|
||||||
|
emitter.Ldloc(keyLocal.Local);
|
||||||
|
EmitSetSourceInfo(emitter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Emits the setter with provided arguments that are not yet on the stack.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If _getter is null - assume target is already on the stack.
|
||||||
|
/// If _emitSourceInfo is true - we need to make sure that target and key are on the stack for XamlSourceInfo setting.
|
||||||
|
/// </remarks>
|
||||||
|
public void EmitWithArguments(
|
||||||
|
XamlEmitContextWithLocals<IXamlILEmitter, XamlILNodeEmitResult> context,
|
||||||
|
IXamlILEmitter emitter,
|
||||||
|
IReadOnlyList<IXamlAstValueNode> arguments)
|
||||||
|
{
|
||||||
|
using var keyLocal = _emitSourceInfo ? emitter.LocalsPool.GetLocal(Parameters[0]) : null;
|
||||||
|
|
||||||
|
if (_getter is not null)
|
||||||
|
{
|
||||||
|
emitter.EmitCall(_getter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_emitSourceInfo)
|
||||||
|
{
|
||||||
|
// Duplicate the target object on stack for setting XamlSourceInfo later
|
||||||
|
emitter.Dup();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < arguments.Count; ++i)
|
||||||
|
{
|
||||||
|
context.Emit(arguments[i], emitter, Parameters[i]);
|
||||||
|
|
||||||
|
// Store the key argument for XamlSourceInfo later
|
||||||
|
if (i == 0 && _emitSourceInfo)
|
||||||
|
{
|
||||||
|
emitter.Stloc(keyLocal!.Local);
|
||||||
|
emitter.Ldloc(keyLocal.Local);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emitter.EmitCall(_adder, true);
|
||||||
|
|
||||||
|
if (_emitSourceInfo)
|
||||||
|
{
|
||||||
|
// Target is already on stack (dub)
|
||||||
|
// Load the key argument from local
|
||||||
|
emitter.Ldloc(keyLocal!.Local);
|
||||||
|
|
||||||
|
EmitSetSourceInfo(emitter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EmitSetSourceInfo(IXamlILEmitter emitter)
|
||||||
|
{
|
||||||
|
// Assumes the target object and key are already on the stack
|
||||||
|
|
||||||
|
emitter.Ldc_I4(_line);
|
||||||
|
emitter.Ldc_I4(_position);
|
||||||
|
if (_document is not null)
|
||||||
|
emitter.Ldstr(_document);
|
||||||
|
else
|
||||||
|
emitter.Ldnull();
|
||||||
|
emitter.Newobj(_avaloniaTypes.XamlSourceInfoConstructor);
|
||||||
|
|
||||||
|
// Set the XamlSourceInfo property on the current object
|
||||||
|
// XamlSourceInfo.SetXamlSourceInfo(@this, key, info);
|
||||||
|
emitter.EmitCall(_avaloniaTypes.XamlSourceInfoDictionarySetter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(AdderSetter? other)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(null, other))
|
||||||
|
return false;
|
||||||
|
if (ReferenceEquals(this, other))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return _getter?.Equals(other._getter) == true && _adder.Equals(other._adder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
=> Equals(obj as AdderSetter);
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
=> (_getter, _adder).GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -134,6 +134,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
|
|||||||
public IXamlType ControlTemplate { get; }
|
public IXamlType ControlTemplate { get; }
|
||||||
public IXamlType EventHandlerT { get; }
|
public IXamlType EventHandlerT { get; }
|
||||||
public IXamlMethod GetClassProperty { get; }
|
public IXamlMethod GetClassProperty { get; }
|
||||||
|
public IXamlConstructor XamlSourceInfoConstructor { get; }
|
||||||
|
public IXamlMethod XamlSourceInfoSetter { get; }
|
||||||
|
public IXamlMethod XamlSourceInfoDictionarySetter { get; }
|
||||||
|
|
||||||
sealed internal class InteractivityWellKnownTypes
|
sealed internal class InteractivityWellKnownTypes
|
||||||
{
|
{
|
||||||
@@ -343,6 +346,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
|
|||||||
allowDowncast:false,
|
allowDowncast:false,
|
||||||
cfg.WellKnownTypes.String
|
cfg.WellKnownTypes.String
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var xamlSourceInfo = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Diagnostics.XamlSourceInfo");
|
||||||
|
XamlSourceInfoConstructor = xamlSourceInfo.GetConstructor([
|
||||||
|
XamlIlTypes.Int32, XamlIlTypes.Int32, XamlIlTypes.String
|
||||||
|
]);
|
||||||
|
XamlSourceInfoSetter =
|
||||||
|
xamlSourceInfo.GetMethod("SetXamlSourceInfo", XamlIlTypes.Void, false, XamlIlTypes.Object, xamlSourceInfo);
|
||||||
|
XamlSourceInfoDictionarySetter =
|
||||||
|
xamlSourceInfo.GetMethod("SetXamlSourceInfo", XamlIlTypes.Void, false, IResourceDictionary, XamlIlTypes.Object, xamlSourceInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
using XamlX.Ast;
|
||||||
|
|
||||||
|
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
|
||||||
|
|
||||||
|
internal static class XamlAstNewClrObjectHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to resolve the underlying value of a <see cref="XamlValueWithManipulationNode"/>,
|
||||||
|
/// unwrapping any nested <see cref="XamlValueWithManipulationNode"/> instances.
|
||||||
|
/// </summary>
|
||||||
|
public static TXamlAstValueNode? UnwrapValue<TXamlAstValueNode>(this XamlValueWithManipulationNode node)
|
||||||
|
where TXamlAstValueNode : class, IXamlAstValueNode
|
||||||
|
{
|
||||||
|
var current = node.Value;
|
||||||
|
while (current is XamlValueWithManipulationNode valueWithManipulation)
|
||||||
|
{
|
||||||
|
current = valueWithManipulation.Value;
|
||||||
|
if (current is TXamlAstValueNode typedValue)
|
||||||
|
{
|
||||||
|
return typedValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return current as TXamlAstValueNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@
|
|||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="RuntimeXamlLoaderConfiguration.cs" />
|
<Compile Include="RuntimeXamlLoaderConfiguration.cs" />
|
||||||
<Compile Include="RuntimeXamlLoaderDocument.cs" />
|
<Compile Include="RuntimeXamlLoaderDocument.cs" />
|
||||||
|
<Compile Include="Diagnostics\XamlSourceInfo.cs" />
|
||||||
<Compile Include="Styling\MergeResourceInclude.cs" />
|
<Compile Include="Styling\MergeResourceInclude.cs" />
|
||||||
<Compile Include="Styling\ResourceInclude.cs" />
|
<Compile Include="Styling\ResourceInclude.cs" />
|
||||||
<Compile Include="Styling\StyleInclude.cs" />
|
<Compile Include="Styling\StyleInclude.cs" />
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace Avalonia.Markup.Xaml.Diagnostics
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents source location information for an element within a XAML or code file.
|
||||||
|
/// </summary>
|
||||||
|
// ReSharper disable once ClassNeverInstantiated.Global //This class is instantiated through the XAML compiler.
|
||||||
|
public record XamlSourceInfo
|
||||||
|
{
|
||||||
|
private static readonly AttachedProperty<XamlSourceInfo?> s_xamlSourceInfo =
|
||||||
|
AvaloniaProperty.RegisterAttached<AvaloniaObject, XamlSourceInfo?>(
|
||||||
|
"XamlSourceInfo", typeof(XamlSourceInfo));
|
||||||
|
|
||||||
|
private static readonly ConditionalWeakTable<object, XamlSourceInfo?> s_sourceInfo = [];
|
||||||
|
private static readonly ConditionalWeakTable<IResourceDictionary, Dictionary<object, XamlSourceInfo?>> s_keyedSourceInfo = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the full path of the source file containing the element, or <c>null</c> if unavailable.
|
||||||
|
/// </summary>
|
||||||
|
public Uri? SourceUri { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the 1-based line number in the source file where the element is defined.
|
||||||
|
/// </summary>
|
||||||
|
public int LineNumber { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the 1-based column number in the source file where the element is defined.
|
||||||
|
/// </summary>
|
||||||
|
public int LinePosition { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="XamlSourceInfo"/> class
|
||||||
|
/// with a specified line, column, and file path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="line">The line number of the source element.</param>
|
||||||
|
/// <param name="column">The column number of the source element.</param>
|
||||||
|
/// <param name="filePath">The full path of the source file.</param>
|
||||||
|
public XamlSourceInfo(int line, int column, string? filePath)
|
||||||
|
{
|
||||||
|
LineNumber = line;
|
||||||
|
LinePosition = column;
|
||||||
|
SourceUri = filePath is not null ? new UriBuilder("file", "") { Path = filePath }.Uri : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Associates XAML source information with the specified object for debugging or diagnostic purposes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This method is typically used to enable enhanced debugging or diagnostics by tracking
|
||||||
|
/// the origin of XAML elements at runtime. If the same object is passed multiple times, the most recent source
|
||||||
|
/// information will overwrite any previous value.</remarks>
|
||||||
|
/// <param name="obj">The object to associate with the XAML source information. Cannot be null.</param>
|
||||||
|
/// <param name="info">The XAML source information to associate with the object, or null to remove any existing association.</param>
|
||||||
|
public static void SetXamlSourceInfo(object obj, XamlSourceInfo? info)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
|
||||||
|
if (obj is AvaloniaObject avaloniaObject)
|
||||||
|
{
|
||||||
|
avaloniaObject.SetValue(s_xamlSourceInfo, info);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s_sourceInfo.AddOrUpdate(obj, info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Associates XAML source information with the specified key in the given resource dictionary.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dictionary"> The resource dictionary to associate with the XAML source information.</param>
|
||||||
|
/// <param name="key">The key associated with the source info.</param>
|
||||||
|
/// <param name="info">The XAML source information to associate with the object, or null to remove any existing association.</param>
|
||||||
|
public static void SetXamlSourceInfo(IResourceDictionary dictionary, object key, XamlSourceInfo? info)
|
||||||
|
{
|
||||||
|
if (dictionary is null)
|
||||||
|
throw new ArgumentNullException(nameof(dictionary));
|
||||||
|
|
||||||
|
var dict = s_keyedSourceInfo.GetOrCreateValue(dictionary);
|
||||||
|
if (info == null)
|
||||||
|
{
|
||||||
|
_ = dict.Remove(key);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dict[key] = info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the XAML source information associated with the specified object, if available.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The object for which to obtain XAML source information. Cannot be null.</param>
|
||||||
|
/// <returns>A <see cref="XamlSourceInfo"/> instance containing the XAML source information for the specified object, or
|
||||||
|
/// <see langword="null"/> if no source information is available.</returns>
|
||||||
|
public static XamlSourceInfo? GetXamlSourceInfo(object obj)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
|
||||||
|
if (obj is AvaloniaObject avaloniaObject)
|
||||||
|
{
|
||||||
|
return avaloniaObject.GetValue(s_xamlSourceInfo);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s_sourceInfo.TryGetValue(obj, out var info);
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the XAML source information associated with the specified key in the given resource dictionary, if available.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dictionary"> The resource dictionary associated with the XAML source information.</param>
|
||||||
|
/// <param name="key">The key associated with the source info.</param>
|
||||||
|
/// <returns>A <see cref="XamlSourceInfo"/> instance containing the XAML source information for the specified key, or
|
||||||
|
/// <see langword="null"/> if no source information is available.</returns>
|
||||||
|
public static XamlSourceInfo? GetXamlSourceInfo(IResourceDictionary dictionary, object key)
|
||||||
|
{
|
||||||
|
if (dictionary is null)
|
||||||
|
throw new ArgumentNullException(nameof(dictionary));
|
||||||
|
|
||||||
|
if (s_keyedSourceInfo.TryGetValue(dictionary, out var dict)
|
||||||
|
&& dict.TryGetValue(key, out var info))
|
||||||
|
{
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a string that represents the current <see cref="XamlSourceInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// A formatted string in the form <c>"FilePath:Line,Column"</c>,
|
||||||
|
/// or <c>"(unknown):Line,Column"</c> if the file path is not set.
|
||||||
|
/// </returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var filePath = SourceUri?.LocalPath ?? "(unknown)";
|
||||||
|
return $"{filePath}:{LineNumber},{LinePosition}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,12 @@ public class RuntimeXamlLoaderConfiguration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool DesignMode { get; set; } = false;
|
public bool DesignMode { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When enabled, the XAML compiler embeds SourceInfo metadata (file path, line, and column) into generated code.
|
||||||
|
/// Default is 'false'.
|
||||||
|
/// </summary>
|
||||||
|
public bool CreateSourceInfo { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// XAML diagnostics handler.
|
/// XAML diagnostics handler.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ public class RuntimeXamlLoaderDocument
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Uri? BaseUri { get; set; }
|
public Uri? BaseUri { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Path to the XAML document being loaded.
|
||||||
|
/// </summary>
|
||||||
|
public string? Document { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The optional instance into which the XAML should be loaded.
|
/// The optional instance into which the XAML should be loaded.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
|
|||||||
{
|
{
|
||||||
public class ResourceIncludeTests : XamlTestBase
|
public class ResourceIncludeTests : XamlTestBase
|
||||||
{
|
{
|
||||||
[Fact]
|
[Theory]
|
||||||
public void ResourceInclude_Loads_ResourceDictionary()
|
[InlineData(false)]
|
||||||
|
[InlineData(true)]
|
||||||
|
public void ResourceInclude_Loads_ResourceDictionary(bool createSourceInfo)
|
||||||
{
|
{
|
||||||
var documents = new[]
|
var documents = new[]
|
||||||
{
|
{
|
||||||
@@ -37,9 +39,11 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
|
|||||||
</UserControl>")
|
</UserControl>")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var config = new RuntimeXamlLoaderConfiguration { CreateSourceInfo = createSourceInfo };
|
||||||
|
|
||||||
using (StartWithResources())
|
using (StartWithResources())
|
||||||
{
|
{
|
||||||
var compiled = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
|
var compiled = AvaloniaRuntimeXamlLoader.LoadGroup(documents, config);
|
||||||
var userControl = Assert.IsType<UserControl>(compiled[1]);
|
var userControl = Assert.IsType<UserControl>(compiled[1]);
|
||||||
var border = userControl.GetControl<Border>("border");
|
var border = userControl.GetControl<Border>("border");
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,10 @@ public class MergeResourceIncludeTests : XamlTestBase
|
|||||||
RuntimeHelpers.RunClassConstructor(typeof(RelativeSource).TypeHandle);
|
RuntimeHelpers.RunClassConstructor(typeof(RelativeSource).TypeHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Theory]
|
||||||
public void MergeResourceInclude_Works_With_Single_Resource()
|
[InlineData(false)]
|
||||||
|
[InlineData(true)]
|
||||||
|
public void MergeResourceInclude_Works_With_Single_Resource(bool createSourceInfo)
|
||||||
{
|
{
|
||||||
var documents = new[]
|
var documents = new[]
|
||||||
{
|
{
|
||||||
@@ -41,8 +43,9 @@ public class MergeResourceIncludeTests : XamlTestBase
|
|||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
</UserControl>")
|
</UserControl>")
|
||||||
};
|
};
|
||||||
|
|
||||||
var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
|
var config = new RuntimeXamlLoaderConfiguration { CreateSourceInfo = createSourceInfo };
|
||||||
|
var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents, config);
|
||||||
var contentControl = Assert.IsType<UserControl>(objects[1]);
|
var contentControl = Assert.IsType<UserControl>(objects[1]);
|
||||||
|
|
||||||
var resources = Assert.IsType<ResourceDictionary>(contentControl.Resources);
|
var resources = Assert.IsType<ResourceDictionary>(contentControl.Resources);
|
||||||
|
|||||||
@@ -265,6 +265,8 @@ public class StyleIncludeTests : XamlTestBase
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void StyleInclude_Should_Be_Replaced_With_Direct_Call()
|
public void StyleInclude_Should_Be_Replaced_With_Direct_Call()
|
||||||
{
|
{
|
||||||
|
using var _ = UnitTestApplication.Start(TestServices.StyledWindow);
|
||||||
|
|
||||||
var control = (ContentControl)AvaloniaRuntimeXamlLoader.Load(@"
|
var control = (ContentControl)AvaloniaRuntimeXamlLoader.Load(@"
|
||||||
<ContentControl xmlns='https://github.com/avaloniaui'
|
<ContentControl xmlns='https://github.com/avaloniaui'
|
||||||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
|
||||||
@@ -281,6 +283,8 @@ public class StyleIncludeTests : XamlTestBase
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void Style_Inside_Resources_Should_Produce_Warning()
|
public void Style_Inside_Resources_Should_Produce_Warning()
|
||||||
{
|
{
|
||||||
|
using var _ = UnitTestApplication.Start(TestServices.StyledWindow);
|
||||||
|
|
||||||
var diagnostics = new List<RuntimeXamlDiagnostic>();
|
var diagnostics = new List<RuntimeXamlDiagnostic>();
|
||||||
var control = (ContentControl)AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(@"
|
var control = (ContentControl)AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(@"
|
||||||
<ContentControl xmlns='https://github.com/avaloniaui'
|
<ContentControl xmlns='https://github.com/avaloniaui'
|
||||||
|
|||||||
@@ -0,0 +1,583 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Avalonia.Animation;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Shapes;
|
||||||
|
using Avalonia.Controls.Templates;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Input.GestureRecognizers;
|
||||||
|
using Avalonia.Markup.Xaml.Diagnostics;
|
||||||
|
using Avalonia.Markup.Xaml.Templates;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using Avalonia.Styling;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Avalonia.Markup.Xaml.UnitTests.Xaml
|
||||||
|
{
|
||||||
|
public class XamlSourceInfoTests : XamlTestBase
|
||||||
|
{
|
||||||
|
private static readonly RuntimeXamlLoaderConfiguration s_configuration = new RuntimeXamlLoaderConfiguration
|
||||||
|
{
|
||||||
|
CreateSourceInfo = true
|
||||||
|
};
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(@"C:\TestFolder\TestFile.xaml")] // Windows-style path
|
||||||
|
[InlineData("/TestFolder/TestFile.xaml")] // Unix-style path
|
||||||
|
public void Root_UserControl_With_BaseUri_Gets_XamlSourceInfo_SourceUri_Set(string document)
|
||||||
|
{
|
||||||
|
var xamlDocument = new RuntimeXamlLoaderDocument(
|
||||||
|
"""
|
||||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||||
|
</UserControl>
|
||||||
|
""")
|
||||||
|
{
|
||||||
|
Document = document
|
||||||
|
};
|
||||||
|
|
||||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xamlDocument, s_configuration);
|
||||||
|
|
||||||
|
var sourceInfo = XamlSourceInfo.GetXamlSourceInfo(userControl);
|
||||||
|
|
||||||
|
Assert.NotNull(sourceInfo);
|
||||||
|
Assert.Equal("file", sourceInfo.SourceUri!.Scheme);
|
||||||
|
Assert.True(sourceInfo.SourceUri!.IsAbsoluteUri);
|
||||||
|
Assert.Equal(new UriBuilder("file", "") {Path = document}.Uri, sourceInfo.SourceUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Root_UserControl_Gets_XamlSourceInfo_Set()
|
||||||
|
{
|
||||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||||
|
</UserControl>");
|
||||||
|
|
||||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
|
||||||
|
|
||||||
|
var sourceInfo = XamlSourceInfo.GetXamlSourceInfo(userControl);
|
||||||
|
|
||||||
|
Assert.NotNull(sourceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Nested_Controls_All_Get_XamlSourceInfo_Set()
|
||||||
|
{
|
||||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||||
|
<StackPanel>
|
||||||
|
<Button />
|
||||||
|
<TextBlock />
|
||||||
|
</StackPanel>
|
||||||
|
</UserControl>");
|
||||||
|
|
||||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
|
||||||
|
var stackPanel = (StackPanel)userControl.Content!;
|
||||||
|
var button = (Button)stackPanel.Children[0];
|
||||||
|
var textblock = (TextBlock)stackPanel.Children[1];
|
||||||
|
|
||||||
|
var userControlSourceInfo = XamlSourceInfo.GetXamlSourceInfo(userControl);
|
||||||
|
Assert.NotNull(userControlSourceInfo);
|
||||||
|
|
||||||
|
var stackPanelSourceInfo = XamlSourceInfo.GetXamlSourceInfo(stackPanel);
|
||||||
|
Assert.NotNull(stackPanelSourceInfo);
|
||||||
|
|
||||||
|
var buttonSourceInfo = XamlSourceInfo.GetXamlSourceInfo(button);
|
||||||
|
Assert.NotNull(buttonSourceInfo);
|
||||||
|
|
||||||
|
var textblockSourceInfo = XamlSourceInfo.GetXamlSourceInfo(textblock);
|
||||||
|
Assert.NotNull(textblockSourceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Property_Elements_Get_XamlSourceInfo_Set()
|
||||||
|
{
|
||||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||||
|
<Rectangle Fill=""Blue"" Width=""63"" Height=""41"">
|
||||||
|
<Rectangle.OpacityMask>
|
||||||
|
<LinearGradientBrush StartPoint=""0%,0%"" EndPoint=""100%,100%"">
|
||||||
|
<LinearGradientBrush.GradientStops>
|
||||||
|
<GradientStop Offset=""0"" Color=""Black""/>
|
||||||
|
<GradientStop Offset=""1"" Color=""Transparent""/>
|
||||||
|
</LinearGradientBrush.GradientStops>
|
||||||
|
</LinearGradientBrush>
|
||||||
|
</Rectangle.OpacityMask>
|
||||||
|
</Rectangle>
|
||||||
|
</UserControl>");
|
||||||
|
|
||||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
|
||||||
|
var rect = (Rectangle)userControl.Content!;
|
||||||
|
var gradient = (LinearGradientBrush)rect.OpacityMask!;
|
||||||
|
var stopOne = (GradientStop)gradient.GradientStops.First();
|
||||||
|
var stopTwo = (GradientStop)gradient.GradientStops.Last();
|
||||||
|
|
||||||
|
var rectSourceInfo = XamlSourceInfo.GetXamlSourceInfo(rect);
|
||||||
|
Assert.NotNull(rectSourceInfo);
|
||||||
|
|
||||||
|
var gradientSourceInfo = XamlSourceInfo.GetXamlSourceInfo(gradient);
|
||||||
|
Assert.NotNull(gradientSourceInfo);
|
||||||
|
|
||||||
|
var stopOneSourceInfo = XamlSourceInfo.GetXamlSourceInfo(stopOne);
|
||||||
|
Assert.NotNull(stopOneSourceInfo);
|
||||||
|
|
||||||
|
var stopTwoSourceInfo = XamlSourceInfo.GetXamlSourceInfo(stopTwo);
|
||||||
|
Assert.NotNull(stopTwoSourceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Shapes_Get_XamlSourceInfo_Set()
|
||||||
|
{
|
||||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||||
|
<Canvas Name=""TheCanvas"" Background=""Yellow"" Width=""300"" Height=""400"">
|
||||||
|
<Ellipse Fill=""Green"" Width=""58"" Height=""58"" Canvas.Left=""88"" Canvas.Top=""100""/>
|
||||||
|
<Path Fill=""Orange"" Canvas.Left=""30"" Canvas.Top=""250""/>
|
||||||
|
<Path Fill=""OrangeRed"" Canvas.Left=""180"" Canvas.Top=""250"">
|
||||||
|
<Path.Data>
|
||||||
|
<PathGeometry>
|
||||||
|
<PathFigure StartPoint=""0,0"" IsClosed=""True"">
|
||||||
|
<QuadraticBezierSegment Point1=""50,0"" Point2=""50,-50"" />
|
||||||
|
<QuadraticBezierSegment Point1=""100,-50"" Point2=""100,0"" />
|
||||||
|
<LineSegment Point=""50,0"" />
|
||||||
|
<LineSegment Point=""50,50"" />
|
||||||
|
</PathFigure>
|
||||||
|
</PathGeometry>
|
||||||
|
</Path.Data>
|
||||||
|
</Path>
|
||||||
|
<Line StartPoint=""120,185"" EndPoint=""30,115"" Stroke=""Red"" StrokeThickness=""2""/>
|
||||||
|
<Polygon Points=""75,0 120,120 0,45 150,45 30,120"" Stroke=""DarkBlue"" StrokeThickness=""1"" Fill=""Violet"" Canvas.Left=""150"" Canvas.Top=""31""/>
|
||||||
|
</Canvas>
|
||||||
|
</UserControl>");
|
||||||
|
|
||||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
|
||||||
|
var canvas = (Canvas)userControl.Content!;
|
||||||
|
var ellipse = (Ellipse)canvas.Children[0];
|
||||||
|
var path1 = (Path)canvas.Children[1];
|
||||||
|
var path2 = (Path)canvas.Children[2];
|
||||||
|
var geometry = (PathGeometry)path2.Data!;
|
||||||
|
var figure = (PathFigure)geometry.Figures![0];
|
||||||
|
var segment1 = figure.Segments![0];
|
||||||
|
var segment2 = figure.Segments![1];
|
||||||
|
var segment3 = figure.Segments![2];
|
||||||
|
var segment4 = figure.Segments![3];
|
||||||
|
var line = (Line)canvas.Children[3];
|
||||||
|
var polygon = (Polygon)canvas.Children[4];
|
||||||
|
|
||||||
|
var canvasSourceInfo = XamlSourceInfo.GetXamlSourceInfo(canvas);
|
||||||
|
Assert.NotNull(canvasSourceInfo);
|
||||||
|
|
||||||
|
var ellipseSourceInfo = XamlSourceInfo.GetXamlSourceInfo(ellipse);
|
||||||
|
Assert.NotNull(ellipseSourceInfo);
|
||||||
|
|
||||||
|
var path1SourceInfo = XamlSourceInfo.GetXamlSourceInfo(path1);
|
||||||
|
Assert.NotNull(path1SourceInfo);
|
||||||
|
|
||||||
|
var path2SourceInfo = XamlSourceInfo.GetXamlSourceInfo(path2);
|
||||||
|
Assert.NotNull(path2SourceInfo);
|
||||||
|
|
||||||
|
var geometrySourceInfo = XamlSourceInfo.GetXamlSourceInfo(geometry);
|
||||||
|
Assert.NotNull(geometrySourceInfo);
|
||||||
|
|
||||||
|
var figureSourceInfo = XamlSourceInfo.GetXamlSourceInfo(figure);
|
||||||
|
Assert.NotNull(figureSourceInfo);
|
||||||
|
|
||||||
|
var segment1SourceInfo = XamlSourceInfo.GetXamlSourceInfo(segment1);
|
||||||
|
Assert.NotNull(segment1SourceInfo);
|
||||||
|
|
||||||
|
var segment2SourceInfo = XamlSourceInfo.GetXamlSourceInfo(segment2);
|
||||||
|
Assert.NotNull(segment2SourceInfo);
|
||||||
|
|
||||||
|
var segment3SourceInfo = XamlSourceInfo.GetXamlSourceInfo(segment3);
|
||||||
|
Assert.NotNull(segment3SourceInfo);
|
||||||
|
|
||||||
|
var segment4SourceInfo = XamlSourceInfo.GetXamlSourceInfo(segment4);
|
||||||
|
Assert.NotNull(segment4SourceInfo);
|
||||||
|
|
||||||
|
var lineSourceInfo = XamlSourceInfo.GetXamlSourceInfo(line);
|
||||||
|
Assert.NotNull(lineSourceInfo);
|
||||||
|
|
||||||
|
var polygonSourceInfo = XamlSourceInfo.GetXamlSourceInfo(polygon);
|
||||||
|
Assert.NotNull(polygonSourceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Styles_Get_XamlSourceInfo_Set()
|
||||||
|
{
|
||||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||||
|
<UserControl.Styles>
|
||||||
|
<Style Selector=""Button"">
|
||||||
|
<Setter Property=""Margin"" Value=""5"" />
|
||||||
|
</Style>
|
||||||
|
<ContainerQuery Name=""container""
|
||||||
|
Query=""max-width:400"">
|
||||||
|
<Style Selector=""Button"">
|
||||||
|
<Setter Property=""Background""
|
||||||
|
Value=""Red""/>
|
||||||
|
</Style>
|
||||||
|
</ContainerQuery>
|
||||||
|
</UserControl.Styles>
|
||||||
|
</UserControl>");
|
||||||
|
|
||||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
|
||||||
|
var style = (Style)userControl.Styles[0];
|
||||||
|
var query = (ContainerQuery)userControl.Styles[1];
|
||||||
|
|
||||||
|
var styleSourceInfo = XamlSourceInfo.GetXamlSourceInfo(style);
|
||||||
|
Assert.NotNull(styleSourceInfo);
|
||||||
|
|
||||||
|
var querySourceInfo = XamlSourceInfo.GetXamlSourceInfo(query);
|
||||||
|
Assert.NotNull(querySourceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Animations_Get_XamlSourceInfo_Set()
|
||||||
|
{
|
||||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||||
|
<UserControl.Styles>
|
||||||
|
<Style Selector=""Rectangle.red"">
|
||||||
|
<Setter Property=""Fill"" Value=""Red""/>
|
||||||
|
<Style.Animations>
|
||||||
|
<Animation Duration=""0:0:3"">
|
||||||
|
<KeyFrame Cue=""0%"">
|
||||||
|
<Setter Property=""Opacity"" Value=""0.0""/>
|
||||||
|
</KeyFrame>
|
||||||
|
<KeyFrame Cue=""100%"">
|
||||||
|
<Setter Property=""Opacity"" Value=""1.0""/>
|
||||||
|
</KeyFrame>
|
||||||
|
</Animation>
|
||||||
|
</Style.Animations>
|
||||||
|
</Style>
|
||||||
|
</UserControl.Styles>
|
||||||
|
</UserControl>");
|
||||||
|
|
||||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
|
||||||
|
var style = (Style)userControl.Styles[0];
|
||||||
|
var animation = (Animation.Animation)style.Animations[0];
|
||||||
|
var frame1 = animation.Children[0];
|
||||||
|
var frame2 = animation.Children[1];
|
||||||
|
|
||||||
|
var styleSourceInfo = XamlSourceInfo.GetXamlSourceInfo(style);
|
||||||
|
Assert.NotNull(styleSourceInfo);
|
||||||
|
|
||||||
|
var animationSourceInfo = XamlSourceInfo.GetXamlSourceInfo(animation);
|
||||||
|
Assert.NotNull(animationSourceInfo);
|
||||||
|
|
||||||
|
var frameOneSourceInfo = XamlSourceInfo.GetXamlSourceInfo(frame1);
|
||||||
|
Assert.NotNull(frameOneSourceInfo);
|
||||||
|
|
||||||
|
var frameTwoSourceInfo = XamlSourceInfo.GetXamlSourceInfo(frame2);
|
||||||
|
Assert.NotNull(frameTwoSourceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DataTemplates_And_Deferred_Contents_Get_XamlSourceInfo_Set()
|
||||||
|
{
|
||||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
|
||||||
|
xmlns:local='using:Avalonia.Markup.Xaml.UnitTests.Xaml'>
|
||||||
|
<UserControl.DataTemplates>
|
||||||
|
<DataTemplate DataType=""local:SourceInfoTestViewModel"">
|
||||||
|
<Border Background=""Red"" CornerRadius=""8"">
|
||||||
|
<TextBox Text=""{Binding Name}""/>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</UserControl.DataTemplates>
|
||||||
|
</UserControl>");
|
||||||
|
|
||||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
|
||||||
|
var datatemplate = (DataTemplate)userControl.DataTemplates[0];
|
||||||
|
|
||||||
|
Border border;
|
||||||
|
|
||||||
|
// The template and it's content is deferred as not used (yet)
|
||||||
|
if (datatemplate.Content is IDeferredContent deferredContent)
|
||||||
|
{
|
||||||
|
var templateResult = (ITemplateResult)deferredContent.Build(null)!;
|
||||||
|
border = (Border)templateResult.Result!;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
border = (Border)datatemplate.Content!;
|
||||||
|
}
|
||||||
|
|
||||||
|
var textBox = (TextBox)border!.Child!;
|
||||||
|
|
||||||
|
var datatemplateSourceInfo = XamlSourceInfo.GetXamlSourceInfo(datatemplate);
|
||||||
|
Assert.NotNull(datatemplateSourceInfo);
|
||||||
|
|
||||||
|
var borderSourceInfo = XamlSourceInfo.GetXamlSourceInfo(border);
|
||||||
|
Assert.NotNull(borderSourceInfo);
|
||||||
|
|
||||||
|
var textBoxSourceInfo = XamlSourceInfo.GetXamlSourceInfo(textBox);
|
||||||
|
Assert.NotNull(textBoxSourceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Resources_Get_XamlSourceInfo_Set()
|
||||||
|
{
|
||||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||||
|
<UserControl.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.ThemeDictionaries>
|
||||||
|
<ResourceDictionary x:Key='Light'>
|
||||||
|
<SolidColorBrush x:Key='BackgroundBrush' Color='White'/>
|
||||||
|
<SolidColorBrush x:Key='ForegroundBrush' Color='Black'/>
|
||||||
|
</ResourceDictionary>
|
||||||
|
<ResourceDictionary x:Key='Dark'>
|
||||||
|
<SolidColorBrush x:Key='BackgroundBrush' Color='Black'/>
|
||||||
|
<SolidColorBrush x:Key='ForegroundBrush' Color='White'/>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</ResourceDictionary.ThemeDictionaries>
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key=""Background"" Color=""Yellow"" />
|
||||||
|
<SolidColorBrush x:Key='OtherBrush'>Black</SolidColorBrush>
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
|
</UserControl.Resources>
|
||||||
|
</UserControl>");
|
||||||
|
|
||||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
|
||||||
|
var backgroundBrush = userControl.Resources["Background"];
|
||||||
|
var lightDictionary = (ResourceDictionary)userControl.Resources.ThemeDictionaries[ThemeVariant.Light];
|
||||||
|
var darkDictionary = (ResourceDictionary)userControl.Resources.ThemeDictionaries[ThemeVariant.Dark];
|
||||||
|
var lightForeground = lightDictionary["ForegroundBrush"];
|
||||||
|
var darkBackground = lightDictionary["BackgroundBrush"];
|
||||||
|
var otherBrush = userControl.Resources["OtherBrush"];
|
||||||
|
|
||||||
|
var backgroundBrushSourceInfo = XamlSourceInfo.GetXamlSourceInfo(backgroundBrush!);
|
||||||
|
Assert.NotNull(backgroundBrushSourceInfo);
|
||||||
|
|
||||||
|
var lightDictionarySourceInfo = XamlSourceInfo.GetXamlSourceInfo(lightDictionary!);
|
||||||
|
Assert.NotNull(lightDictionarySourceInfo);
|
||||||
|
|
||||||
|
var darkDictionarySourceInfo = XamlSourceInfo.GetXamlSourceInfo(darkDictionary!);
|
||||||
|
Assert.NotNull(darkDictionarySourceInfo);
|
||||||
|
|
||||||
|
var lightForegroundSourceInfo = XamlSourceInfo.GetXamlSourceInfo(lightForeground!);
|
||||||
|
Assert.NotNull(lightForegroundSourceInfo);
|
||||||
|
|
||||||
|
var darkBackgroundSourceInfo = XamlSourceInfo.GetXamlSourceInfo(darkBackground!);
|
||||||
|
Assert.NotNull(darkBackgroundSourceInfo);
|
||||||
|
|
||||||
|
var otherBrushSourceInfo = XamlSourceInfo.GetXamlSourceInfo(otherBrush!);
|
||||||
|
Assert.NotNull(otherBrushSourceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResourceDictionary_Value_Types_Do_Not_Set_XamlSourceInfo()
|
||||||
|
{
|
||||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||||
|
<UserControl.Resources>
|
||||||
|
<x:String x:Key='text'>foobar</x:String>
|
||||||
|
<x:Double x:Key=""A_Double"">123.3</x:Double>
|
||||||
|
<x:Int16 x:Key=""An_Int16"">123</x:Int16>
|
||||||
|
<x:Int32 x:Key=""An_Int32"">37434323</x:Int32>
|
||||||
|
<Thickness x:Key=""PreferredPadding"">10,20,10,0</Thickness>
|
||||||
|
</UserControl.Resources>
|
||||||
|
</UserControl>");
|
||||||
|
|
||||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
|
||||||
|
var foobarString = userControl.Resources["text"];
|
||||||
|
var aDouble = userControl.Resources["A_Double"];
|
||||||
|
var anInt16 = userControl.Resources["An_Int16"];
|
||||||
|
var anInt32 = userControl.Resources["An_Int32"];
|
||||||
|
var padding = userControl.Resources["PreferredPadding"];
|
||||||
|
|
||||||
|
// Value types shouldn't get source info
|
||||||
|
var foobarStringSourceInfo = XamlSourceInfo.GetXamlSourceInfo(foobarString!);
|
||||||
|
Assert.Null(foobarStringSourceInfo);
|
||||||
|
|
||||||
|
var aDoubleSourceInfo = XamlSourceInfo.GetXamlSourceInfo(aDouble!);
|
||||||
|
Assert.Null(aDoubleSourceInfo);
|
||||||
|
|
||||||
|
var anInt16SourceInfo = XamlSourceInfo.GetXamlSourceInfo(anInt16!);
|
||||||
|
Assert.Null(anInt16SourceInfo);
|
||||||
|
|
||||||
|
var anInt32SourceInfo = XamlSourceInfo.GetXamlSourceInfo(anInt32!);
|
||||||
|
Assert.Null(anInt32SourceInfo);
|
||||||
|
|
||||||
|
var paddingSourceInfo = XamlSourceInfo.GetXamlSourceInfo(padding!);
|
||||||
|
Assert.Null(paddingSourceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResourceDictionary_Set_Resource_Source_Info()
|
||||||
|
{
|
||||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||||
|
<UserControl.Resources>
|
||||||
|
<x:String x:Key='text'>foobar</x:String>
|
||||||
|
<x:Double x:Key=""A_Double"">123.3</x:Double>
|
||||||
|
<x:Int16 x:Key=""An_Int16"">123</x:Int16>
|
||||||
|
<x:Int32 x:Key=""An_Int32"">37434323</x:Int32>
|
||||||
|
<Thickness x:Key=""PreferredPadding"">10,20,10,0</Thickness>
|
||||||
|
<x:Uri x:Key='homepage'>http://avaloniaui.net</x:Uri>
|
||||||
|
<SolidColorBrush x:Key='MyBrush' Color='Red'/>
|
||||||
|
</UserControl.Resources>
|
||||||
|
</UserControl>");
|
||||||
|
|
||||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
|
||||||
|
var resources = userControl.Resources;
|
||||||
|
|
||||||
|
var foobarStringSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "text");
|
||||||
|
Assert.NotNull(foobarStringSourceInfo);
|
||||||
|
|
||||||
|
var aDoubleSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "A_Double");
|
||||||
|
Assert.NotNull(aDoubleSourceInfo);
|
||||||
|
|
||||||
|
var anInt16SourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "An_Int16");
|
||||||
|
Assert.NotNull(anInt16SourceInfo);
|
||||||
|
|
||||||
|
var anInt32SourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "An_Int32");
|
||||||
|
Assert.NotNull(anInt32SourceInfo);
|
||||||
|
|
||||||
|
var paddingSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "PreferredPadding");
|
||||||
|
Assert.NotNull(paddingSourceInfo);
|
||||||
|
|
||||||
|
var homepageSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "homepage");
|
||||||
|
Assert.NotNull(homepageSourceInfo);
|
||||||
|
|
||||||
|
var myBrushSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "MyBrush");
|
||||||
|
Assert.NotNull(myBrushSourceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResourceDictionary_Set_Resource_Source_Info_With_Nested_Dictionaries()
|
||||||
|
{
|
||||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||||
|
<UserControl.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<x:String x:Key='text'>foobar</x:String>
|
||||||
|
<x:Double x:Key=""A_Double"">123.3</x:Double>
|
||||||
|
<x:Int16 x:Key=""An_Int16"">123</x:Int16>
|
||||||
|
<x:Int32 x:Key=""An_Int32"">37434323</x:Int32>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<Thickness x:Key=""PreferredPadding"">10,20,10,0</Thickness>
|
||||||
|
<x:Uri x:Key='homepage'>http://avaloniaui.net</x:Uri>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary.ThemeDictionaries>
|
||||||
|
<ResourceDictionary x:Key='Light'>
|
||||||
|
<SolidColorBrush x:Key='MyBrush' Color='Red'/>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</ResourceDictionary.ThemeDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</UserControl.Resources>
|
||||||
|
</UserControl>");
|
||||||
|
|
||||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
|
||||||
|
var resources = userControl.Resources;
|
||||||
|
var innerResources = (IResourceDictionary)resources.MergedDictionaries[0];
|
||||||
|
var themeResources = (IResourceDictionary)resources.ThemeDictionaries[ThemeVariant.Light];
|
||||||
|
|
||||||
|
// Outer define source info
|
||||||
|
var foobarStringSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "text");
|
||||||
|
Assert.NotNull(foobarStringSourceInfo);
|
||||||
|
|
||||||
|
var aDoubleSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "A_Double");
|
||||||
|
Assert.NotNull(aDoubleSourceInfo);
|
||||||
|
|
||||||
|
var anInt16SourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "An_Int16");
|
||||||
|
Assert.NotNull(anInt16SourceInfo);
|
||||||
|
|
||||||
|
var anInt32SourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "An_Int32");
|
||||||
|
Assert.NotNull(anInt32SourceInfo);
|
||||||
|
|
||||||
|
// Outer one should not have source info for inner resources
|
||||||
|
var paddingSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "PreferredPadding");
|
||||||
|
Assert.Null(paddingSourceInfo);
|
||||||
|
|
||||||
|
var homepageSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "homepage");
|
||||||
|
Assert.Null(homepageSourceInfo);
|
||||||
|
|
||||||
|
var myBrushSourceInfo = XamlSourceInfo.GetXamlSourceInfo(resources, "MyBrush");
|
||||||
|
Assert.Null(myBrushSourceInfo);
|
||||||
|
|
||||||
|
// Inner defined source info
|
||||||
|
homepageSourceInfo = XamlSourceInfo.GetXamlSourceInfo(innerResources, "homepage");
|
||||||
|
Assert.NotNull(homepageSourceInfo);
|
||||||
|
|
||||||
|
myBrushSourceInfo = XamlSourceInfo.GetXamlSourceInfo(themeResources, "MyBrush");
|
||||||
|
Assert.NotNull(myBrushSourceInfo);
|
||||||
|
|
||||||
|
// Non-value types should have source info themselves
|
||||||
|
var homepage = XamlSourceInfo.GetXamlSourceInfo(innerResources["homepage"]!);
|
||||||
|
Assert.NotNull(homepage);
|
||||||
|
|
||||||
|
var myBrush = XamlSourceInfo.GetXamlSourceInfo(themeResources["MyBrush"]!);
|
||||||
|
Assert.NotNull(myBrush);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Gestures_Get_XamlSourceInfo_Set()
|
||||||
|
{
|
||||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||||
|
<UserControl.GestureRecognizers>
|
||||||
|
<ScrollGestureRecognizer CanHorizontallyScroll=""True""
|
||||||
|
CanVerticallyScroll=""True""/>
|
||||||
|
<PullGestureRecognizer PullDirection=""TopToBottom""/>
|
||||||
|
</UserControl.GestureRecognizers>
|
||||||
|
</UserControl>");
|
||||||
|
|
||||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
|
||||||
|
var scroll = (ScrollGestureRecognizer)userControl.GestureRecognizers.First();
|
||||||
|
var pull = (PullGestureRecognizer)userControl.GestureRecognizers.Last();
|
||||||
|
|
||||||
|
var scrollSourceInfo = XamlSourceInfo.GetXamlSourceInfo(scroll);
|
||||||
|
Assert.NotNull(scrollSourceInfo);
|
||||||
|
|
||||||
|
var pullSourceInfo = XamlSourceInfo.GetXamlSourceInfo(pull);
|
||||||
|
Assert.NotNull(pullSourceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Transitions_Get_XamlSourceInfo_Set()
|
||||||
|
{
|
||||||
|
var xaml = new RuntimeXamlLoaderDocument(@"
|
||||||
|
<UserControl xmlns='https://github.com/avaloniaui'
|
||||||
|
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||||
|
<UserControl.Transitions>
|
||||||
|
<Transitions>
|
||||||
|
<DoubleTransition Property=""Width"" Duration=""0:0:1.5""/>
|
||||||
|
<DoubleTransition Property=""Height"" Duration=""0:0:1.5""/>
|
||||||
|
</Transitions>
|
||||||
|
</UserControl.Transitions>
|
||||||
|
</UserControl>");
|
||||||
|
|
||||||
|
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml, s_configuration);
|
||||||
|
var width = (DoubleTransition)userControl.Transitions!.First();
|
||||||
|
var height = (DoubleTransition)userControl.Transitions!.Last();
|
||||||
|
|
||||||
|
var widthSourceInfo = XamlSourceInfo.GetXamlSourceInfo(width);
|
||||||
|
Assert.NotNull(widthSourceInfo);
|
||||||
|
|
||||||
|
var heightSourceInfo = XamlSourceInfo.GetXamlSourceInfo(height);
|
||||||
|
Assert.NotNull(heightSourceInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SourceInfoTestViewModel
|
||||||
|
{
|
||||||
|
public string? Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user