File: SetNamescopesAndRegisterNamesVisitor.cs
Web Access
Project: src\src\Controls\src\Build.Tasks\Controls.Build.Tasks.csproj (Microsoft.Maui.Controls.Build.Tasks)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Controls.Xaml;
using Mono.Cecil.Cil;
 
namespace Microsoft.Maui.Controls.Build.Tasks
{
	class SetNamescopesAndRegisterNamesVisitor : IXamlNodeVisitor
	{
		public SetNamescopesAndRegisterNamesVisitor(ILContext context) => Context = context;
 
		ILContext Context { get; }
 
		public TreeVisitingMode VisitingMode => TreeVisitingMode.TopDown;
		public bool StopOnDataTemplate => true;
		public bool StopOnResourceDictionary => false;
		public bool VisitNodeOnDataTemplate => false;
		public bool SkipChildren(INode node, INode parentNode) => false;
 
		public bool IsResourceDictionary(ElementNode node)
		{
			var parentVar = Context.Variables[node];
			return parentVar.VariableType.FullName == "Microsoft.Maui.Controls.ResourceDictionary"
				|| parentVar.VariableType.Resolve().BaseType?.FullName == "Microsoft.Maui.Controls.ResourceDictionary";
		}
 
		public void Visit(ValueNode node, INode parentNode)
		{
			Context.Scopes[node] = Context.Scopes[parentNode];
			if (!IsXNameProperty(node, parentNode))
				return;
			RegisterName((string)node.Value, Context.Scopes[node].Item1, Context.Scopes[node].Item2, Context.Variables[(IElementNode)parentNode], node);
			SetStyleId((string)node.Value, Context.Variables[(IElementNode)parentNode]);
		}
 
		public void Visit(MarkupNode node, INode parentNode)
		{
			Context.Scopes[node] = Context.Scopes[parentNode];
		}
 
		public void Visit(ElementNode node, INode parentNode)
		{
			VariableDefinition namescopeVarDef;
			IList<string> namesInNamescope;
			var setNameScope = false;
			if (parentNode == null || IsDataTemplate(node, parentNode) || IsStyle(node, parentNode) || IsVisualStateGroupList(node))
			{
				namescopeVarDef = CreateNamescope();
				namesInNamescope = new List<string>();
				setNameScope = true;
			}
			else
			{
				namescopeVarDef = Context.Scopes[parentNode].Item1;
				namesInNamescope = Context.Scopes[parentNode].Item2;
			}
			if (setNameScope && Context.Variables[node].VariableType.InheritsFromOrImplements(Context.Cache, Context.Body.Method.Module.ImportReference(Context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "BindableObject"))))
				SetNameScope(node, namescopeVarDef);
			//workaround when VSM tries to apply state before parenting
			else if (Context.Variables[node].VariableType.InheritsFromOrImplements(Context.Cache, Context.Body.Method.Module.ImportReference(Context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "Element"))))
			{
				var module = Context.Body.Method.Module;
				var parameterTypes = new[] {
					("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "Element"),
					("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Internals", "INameScope"),
				};
				Context.IL.Append(Context.Variables[node].LoadAs(Context.Cache, module.GetTypeDefinition(Context.Cache, parameterTypes[0]), module));
				Context.IL.Append(namescopeVarDef.LoadAs(Context.Cache, module.GetTypeDefinition(Context.Cache, parameterTypes[1]), module));
				Context.IL.Emit(OpCodes.Stfld, module.ImportFieldReference(Context.Cache, parameterTypes[0], nameof(Element.transientNamescope)));
			}
			Context.Scopes[node] = new Tuple<VariableDefinition, IList<string>>(namescopeVarDef, namesInNamescope);
		}
 
		public void Visit(RootNode node, INode parentNode)
		{
			var namescopeVarDef = GetOrCreateNameScope(node);
			IList<string> namesInNamescope = new List<string>();
			if (Context.Variables[node].VariableType.InheritsFromOrImplements(Context.Cache, Context.Body.Method.Module.ImportReference(Context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "BindableObject"))))
				SetNameScope(node, namescopeVarDef);
			Context.Scopes[node] = new System.Tuple<VariableDefinition, IList<string>>(namescopeVarDef, namesInNamescope);
		}
 
		public void Visit(ListNode node, INode parentNode)
		{
			Context.Scopes[node] = Context.Scopes[parentNode];
		}
 
		static bool IsDataTemplate(INode node, INode parentNode)
			=> parentNode is IElementNode parentElement && parentElement.Properties.TryGetValue(XmlName._CreateContent, out INode createContent) && createContent == node;
 
		static bool IsStyle(INode node, INode parentNode) => parentNode is ElementNode pnode && pnode.XmlType.Name == "Style";
 
		static bool IsVisualStateGroupList(ElementNode node) => node != null && node.XmlType.Name == "VisualStateGroup" && node.Parent is IListNode;
 
		static bool IsXNameProperty(ValueNode node, INode parentNode)
			=> parentNode is IElementNode parentElement && parentElement.Properties.TryGetValue(XmlName.xName, out INode xNameNode) && xNameNode == node;
 
		VariableDefinition GetOrCreateNameScope(ElementNode node)
		{
			var module = Context.Body.Method.Module;
			var vardef = new VariableDefinition(module.ImportReference(Context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Internals", "NameScope")));
			Context.Body.Variables.Add(vardef);
			var stloc = Instruction.Create(OpCodes.Stloc, vardef);
 
			if (Context.Variables[node].VariableType.InheritsFromOrImplements(Context.Cache, Context.Body.Method.Module.ImportReference(Context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "BindableObject"))))
			{
				var namescoperef = ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "BindableObject");
				Context.IL.Append(Context.Variables[node].LoadAs(Context.Cache, module.GetTypeDefinition(Context.Cache, namescoperef), module));
				Context.IL.Emit(OpCodes.Call, module.ImportMethodReference(Context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Internals", "NameScope"),
																		   methodName: "GetNameScope",
																		   parameterTypes: new[] { namescoperef },
																		   isStatic: true));
				Context.IL.Emit(OpCodes.Dup);
				Context.IL.Emit(OpCodes.Brtrue, stloc);
 
				Context.IL.Emit(OpCodes.Pop);
			}
			Context.IL.Emit(OpCodes.Newobj, module.ImportCtorReference(Context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Internals", "NameScope"), parameterTypes: null));
 
			Context.IL.Append(stloc);
			return vardef;
		}
 
		VariableDefinition CreateNamescope()
		{
			var module = Context.Body.Method.Module;
			var vardef = new VariableDefinition(module.ImportReference(Context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Internals", "NameScope")));
			Context.Body.Variables.Add(vardef);
			Context.IL.Emit(OpCodes.Newobj, module.ImportCtorReference(Context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Internals", "NameScope"), parameterTypes: null));
			Context.IL.Emit(OpCodes.Stloc, vardef);
			return vardef;
		}
 
		void SetNameScope(ElementNode node, VariableDefinition ns)
		{
			var module = Context.Body.Method.Module;
			var parameterTypes = new[] {
				("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "BindableObject"),
				("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Internals", "INameScope"),
			};
			Context.IL.Append(Context.Variables[node].LoadAs(Context.Cache, module.GetTypeDefinition(Context.Cache, parameterTypes[0]), module));
			Context.IL.Append(ns.LoadAs(Context.Cache, module.GetTypeDefinition(Context.Cache, parameterTypes[1]), module));
			Context.IL.Emit(OpCodes.Call, module.ImportMethodReference(Context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Internals", "NameScope"),
																	   methodName: "SetNameScope",
																	   parameterTypes: parameterTypes,
																	   isStatic: true));
		}
 
		void RegisterName(string str, VariableDefinition namescopeVarDef, IList<string> namesInNamescope, VariableDefinition element, INode node)
		{
			if (namesInNamescope.Contains(str))
				throw new BuildException(BuildExceptionCode.NamescopeDuplicate, node as IXmlLineInfo, null, str);
			namesInNamescope.Add(str);
 
			var module = Context.Body.Method.Module;
			var namescopeType = ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Internals", "INameScope");
			Context.IL.Append(namescopeVarDef.LoadAs(Context.Cache, module.GetTypeDefinition(Context.Cache, namescopeType), module));
			Context.IL.Emit(OpCodes.Ldstr, str);
			Context.IL.Append(element.LoadAs(Context.Cache, module.TypeSystem.Object, module));
			Context.IL.Emit(OpCodes.Callvirt, module.ImportMethodReference(Context.Cache,
																		   namescopeType,
																		   methodName: "RegisterName",
																		   parameterTypes: new[] {
																			   ("mscorlib", "System", "String"),
																			   ("mscorlib", "System", "Object"),
																		   }));
		}
 
		void SetStyleId(string str, VariableDefinition element)
		{
			if (!element.VariableType.InheritsFromOrImplements(Context.Cache, Context.Body.Method.Module.ImportReference(Context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "Element"))))
				return;
 
			var module = Context.Body.Method.Module;
			var elementType = ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "Element");
			var elementTypeRef = module.GetTypeDefinition(Context.Cache, elementType);
 
			var nop = Instruction.Create(OpCodes.Nop);
			Context.IL.Append(element.LoadAs(Context.Cache, elementTypeRef, module));
			Context.IL.Emit(OpCodes.Callvirt, module.ImportPropertyGetterReference(Context.Cache, elementType, propertyName: "StyleId"));
			Context.IL.Emit(OpCodes.Brtrue, nop);
			Context.IL.Append(element.LoadAs(Context.Cache, elementTypeRef, module));
			Context.IL.Emit(OpCodes.Ldstr, str);
			Context.IL.Emit(OpCodes.Callvirt, module.ImportPropertySetterReference(Context.Cache, elementType, propertyName: "StyleId"));
			Context.IL.Append(nop);
		}
	}
}