From 6547433fade4e512d8b76d65294f5ab6a646fe6e Mon Sep 17 00:00:00 2001 From: Ken Tucker Date: Fri, 8 Aug 2025 21:42:49 -0400 Subject: [PATCH 1/2] Potential fix for code scanning alert no. 87: String concatenation in loop use string builder to combine strings in a loop Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- src/Caliburn.Micro.Platform/ViewModelBinder.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Caliburn.Micro.Platform/ViewModelBinder.cs b/src/Caliburn.Micro.Platform/ViewModelBinder.cs index 49032030..badf5f0d 100644 --- a/src/Caliburn.Micro.Platform/ViewModelBinder.cs +++ b/src/Caliburn.Micro.Platform/ViewModelBinder.cs @@ -11,6 +11,7 @@ namespace Caliburn.Micro using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; + using System.Text; #if XFORMS using UIElement = global::Xamarin.Forms.Element; using FrameworkElement = global::Xamarin.Forms.VisualElement; @@ -176,11 +177,11 @@ public static bool ShouldApplyConventions(FrameworkElement view) { } #endif - var message = method.Name; var parameters = method.GetParameters(); + var messageBuilder = new StringBuilder(method.Name); if (parameters.Length > 0) { - message += "("; + messageBuilder.Append("("); foreach (var parameter in parameters) { var paramName = parameter.Name; @@ -189,11 +190,15 @@ public static bool ShouldApplyConventions(FrameworkElement view) { if (MessageBinder.SpecialValues.ContainsKey(specialValue)) paramName = specialValue; - message += paramName + ","; + messageBuilder.Append(paramName).Append(","); } - message = message.Remove(message.Length - 1, 1); - message += ")"; + // Remove the trailing comma + if (parameters.Length > 0) + messageBuilder.Length -= 1; + messageBuilder.Append(")"); + var message = messageBuilder.ToString(); + } Log.Info("Action Convention Applied: Action {0} on element {1}.", method.Name, message); From ee1b1e544de3b77cbbffd33e92e2ad7dca222f14 Mon Sep 17 00:00:00 2001 From: Ken Tucker Date: Sat, 9 Aug 2025 08:32:48 -0400 Subject: [PATCH 2/2] Improve code readability in ViewModelBinder.cs Added line breaks and formatting adjustments to enhance clarity. Reformatted comments and logging statements for consistency. No functional changes were made; the code is now more maintainable. --- .../ViewModelBinder.cs | 85 ++++++++++++------- 1 file changed, 55 insertions(+), 30 deletions(-) diff --git a/src/Caliburn.Micro.Platform/ViewModelBinder.cs b/src/Caliburn.Micro.Platform/ViewModelBinder.cs index badf5f0d..3c8ad649 100644 --- a/src/Caliburn.Micro.Platform/ViewModelBinder.cs +++ b/src/Caliburn.Micro.Platform/ViewModelBinder.cs @@ -42,7 +42,8 @@ namespace Caliburn.Micro /// /// Binds a view to a view model. /// - public static class ViewModelBinder { + public static class ViewModelBinder + { const string AsyncSuffix = "Async"; static readonly ILog Log = LogManager.GetLog(typeof(ViewModelBinder)); @@ -76,7 +77,8 @@ public static class ViewModelBinder { /// /// The view to check. /// Whether or not conventions should be applied to the view. - public static bool ShouldApplyConventions(FrameworkElement view) { + public static bool ShouldApplyConventions(FrameworkElement view) + { var overriden = View.GetApplyConventions(view); return overriden.GetValueOrDefault(ApplyConventionsByDefault); } @@ -85,30 +87,35 @@ public static bool ShouldApplyConventions(FrameworkElement view) { /// Creates data bindings on the view's controls based on the provided properties. /// /// Parameters include named Elements to search through and the type of view model to determine conventions for. Returns unmatched elements. - public static Func, Type, IEnumerable> BindProperties = (namedElements, viewModelType) => { + public static Func, Type, IEnumerable> BindProperties = (namedElements, viewModelType) => + { var unmatchedElements = new List(); #if !XFORMS && !MAUI - foreach (var element in namedElements) { + foreach (var element in namedElements) + { var cleanName = element.Name.Trim('_'); var parts = cleanName.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries); var property = viewModelType.GetPropertyCaseInsensitive(parts[0]); var interpretedViewModelType = viewModelType; - for (int i = 1; i < parts.Length && property != null; i++) { + for (int i = 1; i < parts.Length && property != null; i++) + { interpretedViewModelType = property.PropertyType; property = interpretedViewModelType.GetPropertyCaseInsensitive(parts[i]); } - if (property == null) { + if (property == null) + { unmatchedElements.Add(element); Log.Info("Binding Convention Not Applied: Element {0} did not match a property.", element.Name); continue; } var convention = ConventionManager.GetElementConvention(element.GetType()); - if (convention == null) { + if (convention == null) + { unmatchedElements.Add(element); Log.Warn("Binding Convention Not Applied: No conventions configured for {0}.", element.GetType()); continue; @@ -122,10 +129,12 @@ public static bool ShouldApplyConventions(FrameworkElement view) { convention ); - if (applied) { + if (applied) + { Log.Info("Binding Convention Applied: Element {0}.", element.Name); } - else { + else + { Log.Info("Binding Convention Not Applied: Element {0} has existing binding.", element.Name); unmatchedElements.Add(element); } @@ -139,7 +148,8 @@ public static bool ShouldApplyConventions(FrameworkElement view) { /// Attaches instances of to the view's controls based on the provided methods. /// /// Parameters include the named elements to search through and the type of view model to determine conventions for. Returns unmatched elements. - public static Func, Type, IEnumerable> BindActions = (namedElements, viewModelType) => { + public static Func, Type, IEnumerable> BindActions = (namedElements, viewModelType) => + { var unmatchedElements = namedElements.ToList(); #if !XFORMS && !MAUI #if WINDOWS_UWP || XFORMS || MAUI @@ -147,21 +157,24 @@ public static bool ShouldApplyConventions(FrameworkElement view) { #else var methods = viewModelType.GetMethods(); #endif - - foreach (var method in methods) { - Log.Info($"Searching for methods control {method.Name} unmatchedElements count {unmatchedElements.Count}"); + + foreach (var method in methods) + { + Log.Info($"Searching for methods control {method.Name} unmatchedElements count {unmatchedElements.Count}"); var foundControl = unmatchedElements.FindName(method.Name); - if (foundControl == null && IsAsyncMethod(method)) { + if (foundControl == null && IsAsyncMethod(method)) + { var methodNameWithoutAsyncSuffix = method.Name.Substring(0, method.Name.Length - AsyncSuffix.Length); foundControl = unmatchedElements.FindName(methodNameWithoutAsyncSuffix); } - if(foundControl == null) { + if (foundControl == null) + { Log.Info("Action Convention Not Applied: No actionable element for {0}. {1}", method.Name, unmatchedElements.Count); - foreach(var element in unmatchedElements) + foreach (var element in unmatchedElements) { - Log.Info($"Unnamed element {element.Name}"); + Log.Info($"Unnamed element {element.Name}"); } continue; } @@ -180,10 +193,12 @@ public static bool ShouldApplyConventions(FrameworkElement view) { var parameters = method.GetParameters(); var messageBuilder = new StringBuilder(method.Name); - if (parameters.Length > 0) { + if (parameters.Length > 0) + { messageBuilder.Append("("); - foreach (var parameter in parameters) { + foreach (var parameter in parameters) + { var paramName = parameter.Name; var specialValue = "$" + paramName.ToLower(); @@ -197,9 +212,9 @@ public static bool ShouldApplyConventions(FrameworkElement view) { if (parameters.Length > 0) messageBuilder.Length -= 1; messageBuilder.Append(")"); - var message = messageBuilder.ToString(); } + var message = messageBuilder.ToString(); Log.Info("Action Convention Applied: Action {0} on element {1}.", method.Name, message); Message.SetAttach(foundControl, message); @@ -208,7 +223,8 @@ public static bool ShouldApplyConventions(FrameworkElement view) { return unmatchedElements; }; - static bool IsAsyncMethod(MethodInfo method) { + static bool IsAsyncMethod(MethodInfo method) + { return typeof(Task).GetTypeInfo().IsAssignableFrom(method.ReturnType.GetTypeInfo()) && method.Name.EndsWith(AsyncSuffix, StringComparison.OrdinalIgnoreCase); } @@ -222,13 +238,16 @@ static bool IsAsyncMethod(MethodInfo method) { /// Binds the specified viewModel to the view. /// ///Passes the the view model, view and creation context (or null for default) to use in applying binding. - public static Action Bind = (viewModel, view, context) => { + public static Action Bind = (viewModel, view, context) => + { #if !WINDOWS_UWP && !XFORMS && !MAUI // when using d:DesignInstance, Blend tries to assign the DesignInstanceExtension class as the DataContext, // so here we get the actual ViewModel which is in the Instance property of DesignInstanceExtension - if (View.InDesignMode) { + if (View.InDesignMode) + { var vmType = viewModel.GetType(); - if (vmType.FullName == "Microsoft.Expression.DesignModel.InstanceBuilders.DesignInstanceExtension") { + if (vmType.FullName == "Microsoft.Expression.DesignModel.InstanceBuilders.DesignInstanceExtension") + { var propInfo = vmType.GetProperty("Instance", BindingFlags.Instance | BindingFlags.NonPublic); viewModel = propInfo.GetValue(viewModel, null); } @@ -245,29 +264,35 @@ static bool IsAsyncMethod(MethodInfo method) { var noContext = Caliburn.Micro.Bind.NoContextProperty; #endif - if ((bool)view.GetValue(noContext)) { + if ((bool)view.GetValue(noContext)) + { Action.SetTargetWithoutContext(view, viewModel); } - else { + else + { Action.SetTarget(view, viewModel); } var viewAware = viewModel as IViewAware; - if (viewAware != null) { + if (viewAware != null) + { Log.Info("Attaching {0} to {1}.", view, viewAware); viewAware.AttachView(view, context); } - if ((bool)view.GetValue(ConventionsAppliedProperty)) { + if ((bool)view.GetValue(ConventionsAppliedProperty)) + { return; } var element = View.GetFirstNonGeneratedView(view) as FrameworkElement; - if (element == null) { + if (element == null) + { return; } - if (!ShouldApplyConventions(element)) { + if (!ShouldApplyConventions(element)) + { Log.Info("Skipping conventions for {0} and {1}.", element, viewModel); return; }